Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[dhcp_relay] More detailed crafting and strict testing of OFFER and ACK packets #924

Merged
merged 1 commit into from
Jun 5, 2019
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
146 changes: 121 additions & 25 deletions ansible/roles/test/files/ptftests/dhcp_relay_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,46 @@ def create_dhcp_offer_packet(self):
dhcp_lease=self.LEASE_TIME,
padding_bytes=0)

def create_dhcp_offer_relayed_packet(self):
my_chaddr = ''.join([chr(int(octet, 16)) for octet in self.client_mac.split(':')])

# Relay modifies the DHCPOFFER message in the following ways:
# 1.) Replaces the source MAC with the MAC of the interface it received it on
# 2.) Replaces the destination MAC with boradcast (ff:ff:ff:ff:ff:ff)
# 3.) Replaces the source IP with the IP of the interface which the relay
# received it on
# 4.) Replaces the destination IP with broadcast (255.255.255.255)
# 5.) Replaces the destination port with the DHCP client port (68)
ether = scapy.Ether(dst=self.BROADCAST_MAC, src=self.relay_iface_mac, type=0x0800)
ip = scapy.IP(src=self.relay_iface_ip, dst=self.BROADCAST_IP, len=290, ttl=64)
udp = scapy.UDP(sport=self.DHCP_SERVER_PORT, dport=self.DHCP_CLIENT_PORT, len=262)
bootp = scapy.BOOTP(op=2,
htype=1,
hlen=6,
hops=0,
xid=0,
secs=0,
flags=0,
ciaddr=self.DEFAULT_ROUTE_IP,
yiaddr=self.client_ip,
siaddr=self.server_ip,
giaddr=self.relay_iface_ip,
chaddr=my_chaddr)
bootp /= scapy.DHCP(options=[('message-type', 'offer'),
('server_id', self.server_ip),
('lease_time', self.LEASE_TIME),
('subnet_mask', self.client_subnet),
('end')])

# TODO: Need to add this to the packet creation functions in PTF code first!
# If our bootp layer is too small, pad it
#pad_bytes = self.DHCP_PKT_BOOTP_MIN_LEN - len(bootp)
#if pad_bytes > 0:
# bootp /= scapy.PADDING('\x00' * pad_bytes)

pkt = ether / ip / udp / bootp
return pkt

def create_dhcp_request_packet(self):
return testutils.dhcp_request_packet(eth_client=self.client_mac,
ip_server=self.server_ip,
Expand Down Expand Up @@ -272,6 +312,47 @@ def create_dhcp_ack_packet(self):
dhcp_lease=self.LEASE_TIME,
padding_bytes=0)

def create_dhcp_ack_relayed_packet(self):
my_chaddr = ''.join([chr(int(octet, 16)) for octet in self.client_mac.split(':')])

# Relay modifies the DHCPACK message in the following ways:
# 1.) Replaces the source MAC with the MAC of the interface it received it on
# 2.) Replaces the destination MAC with boradcast (ff:ff:ff:ff:ff:ff)
# 3.) Replaces the source IP with the IP of the interface which the relay
# received it on
# 4.) Replaces the destination IP with broadcast (255.255.255.255)
# 5.) Replaces the destination port with the DHCP client port (68)
ether = scapy.Ether(dst=self.BROADCAST_MAC, src=self.relay_iface_mac, type=0x0800)
ip = scapy.IP(src=self.relay_iface_ip, dst=self.BROADCAST_IP, len=290, ttl=64)
udp = scapy.UDP(sport=self.DHCP_SERVER_PORT, dport=self.DHCP_CLIENT_PORT, len=262)
bootp = scapy.BOOTP(op=2,
htype=1,
hlen=6,
hops=0,
xid=0,
secs=0,
flags=0,
ciaddr=self.DEFAULT_ROUTE_IP,
yiaddr=self.client_ip,
siaddr=self.server_ip,
giaddr=self.relay_iface_ip,
chaddr=my_chaddr)
bootp /= scapy.DHCP(options=[('message-type', 'ack'),
('server_id', self.server_ip),
('lease_time', self.LEASE_TIME),
('subnet_mask', self.client_subnet),
('end')])

# TODO: Need to add this to the packet creation functions in PTF code first!
# If our bootp layer is too small, pad it
#pad_bytes = self.DHCP_PKT_BOOTP_MIN_LEN - len(bootp)
#if pad_bytes > 0:
# bootp /= scapy.PADDING('\x00' * pad_bytes)

pkt = ether / ip / udp / bootp
return pkt



"""
Send/receive functions
Expand Down Expand Up @@ -318,9 +399,10 @@ def verify_relayed_discover(self):
masked_discover.set_do_not_care_scapy(scapy.PADDING, "load")

# Count the number of these packets received on the ports connected to our leaves
num_expected_packets = self.num_dhcp_servers
discover_count = testutils.count_matched_packets_all_ports(self, masked_discover, self.server_port_indices)
self.assertTrue(discover_count == self.num_dhcp_servers,
"Failed: Discover count of %d != %d (num_dhcp_servers)" % (discover_count, self.num_dhcp_servers))
self.assertTrue(discover_count == num_expected_packets,
"Failed: Discover count of %d != %d" % (discover_count, num_expected_packets))

# Simulate a DHCP server sending a DHCPOFFER message to client.
# We do this by injecting a DHCPOFFER message on the link connected to one
Expand All @@ -331,24 +413,31 @@ def server_send_offer(self):

# Verify that the DHCPOFFER would be received by our simulated client
def verify_offer_received(self):
dhcp_offer = self.create_dhcp_offer_packet()
dhcp_offer = self.create_dhcp_offer_relayed_packet()

masked_offer = Mask(dhcp_offer)
masked_offer.set_do_not_care_scapy(scapy.Ether, "src")
masked_offer.set_do_not_care_scapy(scapy.Ether, "dst")

masked_offer.set_do_not_care_scapy(scapy.IP, "version")
masked_offer.set_do_not_care_scapy(scapy.IP, "ihl")
masked_offer.set_do_not_care_scapy(scapy.IP, "tos")
masked_offer.set_do_not_care_scapy(scapy.IP, "len")
masked_offer.set_do_not_care_scapy(scapy.IP, "id")
masked_offer.set_do_not_care_scapy(scapy.IP, "flags")
masked_offer.set_do_not_care_scapy(scapy.IP, "frag")
masked_offer.set_do_not_care_scapy(scapy.IP, "ttl")
masked_offer.set_do_not_care_scapy(scapy.IP, "proto")
masked_offer.set_do_not_care_scapy(scapy.IP, "chksum")
masked_offer.set_do_not_care_scapy(scapy.IP, "src")
masked_offer.set_do_not_care_scapy(scapy.IP, "dst")
masked_offer.set_do_not_care_scapy(scapy.IP, "options")

masked_offer.set_do_not_care_scapy(scapy.UDP, "len")
masked_offer.set_do_not_care_scapy(scapy.UDP, "chksum")
masked_offer.set_do_not_care_scapy(scapy.UDP, "dport")

# Mask out lease time since it can change depending on when the server receives the request
# Lease time in ack can be slightly different than in offer, since lease time varies slightly
# We also want to ignore the checksums since they will vary a bit depending on the timestamp
# Offset is byte 292, 6 byte field, set_do_not_care() expects values in bits
masked_offer.set_do_not_care((self.DHCP_LEASE_TIME_OFFSET * 8), (self.DHCP_LEASE_TIME_LEN * 8))
masked_offer.set_do_not_care_scapy(scapy.BOOTP, "sname")
masked_offer.set_do_not_care_scapy(scapy.BOOTP, "file")

masked_offer.set_do_not_care_scapy(scapy.DHCP, "lease_time")

#masked_offer.set_do_not_care_scapy(scapy.PADDING, "load")

# NOTE: verify_packet() will fail for us via an assert, so no need to check a return value here
testutils.verify_packet(self, masked_offer, self.client_port_index)
Expand Down Expand Up @@ -390,9 +479,10 @@ def verify_relayed_request(self):
masked_request.set_do_not_care_scapy(scapy.BOOTP, "file")

# Count the number of these packets received on the ports connected to our leaves
num_expected_packets = self.num_dhcp_servers
request_count = testutils.count_matched_packets_all_ports(self, masked_request, self.server_port_indices)
self.assertTrue(request_count == self.num_dhcp_servers,
"Failed: Request count of %d != %d (num_dhcp_servers)" % (request_count, self.num_dhcp_servers))
self.assertTrue(request_count == num_expected_packets,
"Failed: Request count of %d != %d" % (request_count, num_expected_packets))

# Simulate a DHCP server sending a DHCPOFFER message to client from one of our leaves
def server_send_ack(self):
Expand All @@ -401,23 +491,29 @@ def server_send_ack(self):

# Verify that the DHCPACK would be received by our simulated client
def verify_ack_received(self):
dhcp_ack = self.create_dhcp_ack_packet()
dhcp_ack = self.create_dhcp_ack_relayed_packet()

# Mask out lease time, ip checksum, udp checksum (explanation above)
masked_ack = Mask(dhcp_ack)

masked_ack.set_do_not_care_scapy(scapy.Ether, "src")
masked_ack.set_do_not_care_scapy(scapy.Ether, "dst")

masked_ack.set_do_not_care_scapy(scapy.IP, "version")
masked_ack.set_do_not_care_scapy(scapy.IP, "ihl")
masked_ack.set_do_not_care_scapy(scapy.IP, "tos")
masked_ack.set_do_not_care_scapy(scapy.IP, "len")
masked_ack.set_do_not_care_scapy(scapy.IP, "id")
masked_ack.set_do_not_care_scapy(scapy.IP, "flags")
masked_ack.set_do_not_care_scapy(scapy.IP, "frag")
masked_ack.set_do_not_care_scapy(scapy.IP, "ttl")
masked_ack.set_do_not_care_scapy(scapy.IP, "proto")
masked_ack.set_do_not_care_scapy(scapy.IP, "chksum")
masked_ack.set_do_not_care_scapy(scapy.IP, "src")
masked_ack.set_do_not_care_scapy(scapy.IP, "dst")
masked_ack.set_do_not_care_scapy(scapy.IP, "options")

masked_ack.set_do_not_care_scapy(scapy.UDP, "len")
masked_ack.set_do_not_care_scapy(scapy.UDP, "chksum")
masked_ack.set_do_not_care_scapy(scapy.UDP, "dport")

# Also mask out lease time (see comment in verify_offer_received() above)
masked_ack.set_do_not_care((self.DHCP_LEASE_TIME_OFFSET * 8), (self.DHCP_LEASE_TIME_LEN * 8))
masked_ack.set_do_not_care_scapy(scapy.BOOTP, "sname")
masked_ack.set_do_not_care_scapy(scapy.BOOTP, "file")

masked_ack.set_do_not_care_scapy(scapy.DHCP, "lease_time")

# NOTE: verify_packet() will fail for us via an assert, so no need to check a return value here
testutils.verify_packet(self, masked_ack, self.client_port_index)
Expand Down