FIB table add/delete API
[vpp.git] / test / test_dhcp.py
1 #!/usr/bin/env python
2
3 import unittest
4 import socket
5 import struct
6
7 from framework import VppTestCase, VppTestRunner
8 from vpp_neighbor import VppNeighbor
9 from vpp_ip_route import find_route, VppIpTable
10 from util import mk_ll_addr
11
12 from scapy.layers.l2 import Ether, getmacbyip, ARP
13 from scapy.layers.inet import IP, UDP, ICMP
14 from scapy.layers.inet6 import IPv6, in6_getnsmac, in6_mactoifaceid
15 from scapy.layers.dhcp import DHCP, BOOTP, DHCPTypes
16 from scapy.layers.dhcp6 import DHCP6, DHCP6_Solicit, DHCP6_RelayForward, \
17     DHCP6_RelayReply, DHCP6_Advertise, DHCP6OptRelayMsg, DHCP6OptIfaceId, \
18     DHCP6OptStatusCode, DHCP6OptVSS, DHCP6OptClientLinkLayerAddr, DHCP6_Request
19 from socket import AF_INET, AF_INET6
20 from scapy.utils import inet_pton, inet_ntop
21 from scapy.utils6 import in6_ptop
22
23 DHCP4_CLIENT_PORT = 68
24 DHCP4_SERVER_PORT = 67
25 DHCP6_CLIENT_PORT = 547
26 DHCP6_SERVER_PORT = 546
27
28
29 class TestDHCP(VppTestCase):
30     """ DHCP Test Case """
31
32     def setUp(self):
33         super(TestDHCP, self).setUp()
34
35         # create 3 pg interfaces
36         self.create_pg_interfaces(range(4))
37         self.tables = []
38
39         # pg0 and 1 are IP configured in VRF 0 and 1.
40         # pg2 and 3 are non IP-configured in VRF 0 and 1
41         table_id = 0
42         for table_id in range(1, 4):
43             tbl4 = VppIpTable(self, table_id)
44             tbl4.add_vpp_config()
45             self.tables.append(tbl4)
46             tbl6 = VppIpTable(self, table_id, is_ip6=1)
47             tbl6.add_vpp_config()
48             self.tables.append(tbl6)
49
50         table_id = 0
51         for i in self.pg_interfaces[:2]:
52             i.admin_up()
53             i.set_table_ip4(table_id)
54             i.set_table_ip6(table_id)
55             i.config_ip4()
56             i.resolve_arp()
57             i.config_ip6()
58             i.resolve_ndp()
59             table_id += 1
60
61         table_id = 0
62         for i in self.pg_interfaces[2:]:
63             i.admin_up()
64             i.set_table_ip4(table_id)
65             i.set_table_ip6(table_id)
66             table_id += 1
67
68     def tearDown(self):
69         for i in self.pg_interfaces[:2]:
70             i.unconfig_ip4()
71             i.unconfig_ip6()
72
73         for i in self.pg_interfaces:
74             i.set_table_ip4(0)
75             i.set_table_ip6(0)
76             i.admin_down()
77         super(TestDHCP, self).tearDown()
78
79     def send_and_assert_no_replies(self, intf, pkts, remark):
80         intf.add_stream(pkts)
81         self.pg_enable_capture(self.pg_interfaces)
82         self.pg_start()
83         for i in self.pg_interfaces:
84             i.assert_nothing_captured(remark=remark)
85
86     def verify_dhcp_has_option(self, pkt, option, value):
87         dhcp = pkt[DHCP]
88         found = False
89
90         for i in dhcp.options:
91             if type(i) is tuple:
92                 if i[0] == option:
93                     self.assertEqual(i[1], value)
94                     found = True
95
96         self.assertTrue(found)
97
98     def validate_relay_options(self, pkt, intf, ip_addr, fib_id, oui):
99         dhcp = pkt[DHCP]
100         found = 0
101         data = []
102
103         for i in dhcp.options:
104             if type(i) is tuple:
105                 if i[0] == "relay_agent_Information":
106                     #
107                     # There are two sb-options present - each of length 6.
108                     #
109                     data = i[1]
110                     if oui != 0:
111                         self.assertEqual(len(data), 24)
112                     else:
113                         self.assertEqual(len(data), 12)
114
115                     #
116                     # First sub-option is ID 1, len 4, then encoded
117                     #  sw_if_index. This test uses low valued indicies
118                     # so [2:4] are 0.
119                     # The ID space is VPP internal - so no matching value
120                     # scapy
121                     #
122                     self.assertEqual(ord(data[0]), 1)
123                     self.assertEqual(ord(data[1]), 4)
124                     self.assertEqual(ord(data[2]), 0)
125                     self.assertEqual(ord(data[3]), 0)
126                     self.assertEqual(ord(data[4]), 0)
127                     self.assertEqual(ord(data[5]), intf._sw_if_index)
128
129                     #
130                     # next sub-option is the IP address of the client side
131                     # interface.
132                     # sub-option ID=5, length (of a v4 address)=4
133                     #
134                     claddr = socket.inet_pton(AF_INET, ip_addr)
135
136                     self.assertEqual(ord(data[6]), 5)
137                     self.assertEqual(ord(data[7]), 4)
138                     self.assertEqual(data[8], claddr[0])
139                     self.assertEqual(data[9], claddr[1])
140                     self.assertEqual(data[10], claddr[2])
141                     self.assertEqual(data[11], claddr[3])
142
143                     if oui != 0:
144                         # sub-option 151 encodes the 3 byte oui
145                         # and the 4 byte fib_id
146                         self.assertEqual(ord(data[12]), 151)
147                         self.assertEqual(ord(data[13]), 8)
148                         self.assertEqual(ord(data[14]), 1)
149                         self.assertEqual(ord(data[15]), 0)
150                         self.assertEqual(ord(data[16]), 0)
151                         self.assertEqual(ord(data[17]), oui)
152                         self.assertEqual(ord(data[18]), 0)
153                         self.assertEqual(ord(data[19]), 0)
154                         self.assertEqual(ord(data[20]), 0)
155                         self.assertEqual(ord(data[21]), fib_id)
156
157                         # VSS control sub-option
158                         self.assertEqual(ord(data[22]), 152)
159                         self.assertEqual(ord(data[23]), 0)
160
161                     found = 1
162         self.assertTrue(found)
163
164         return data
165
166     def verify_dhcp_msg_type(self, pkt, name):
167         dhcp = pkt[DHCP]
168         found = False
169         for o in dhcp.options:
170             if type(o) is tuple:
171                 if o[0] == "message-type" \
172                    and DHCPTypes[o[1]] == name:
173                     found = True
174         self.assertTrue(found)
175
176     def verify_dhcp_offer(self, pkt, intf, fib_id=0, oui=0):
177         ether = pkt[Ether]
178         self.assertEqual(ether.dst, "ff:ff:ff:ff:ff:ff")
179         self.assertEqual(ether.src, intf.local_mac)
180
181         ip = pkt[IP]
182         self.assertEqual(ip.dst, "255.255.255.255")
183         self.assertEqual(ip.src, intf.local_ip4)
184
185         udp = pkt[UDP]
186         self.assertEqual(udp.dport, DHCP4_CLIENT_PORT)
187         self.assertEqual(udp.sport, DHCP4_SERVER_PORT)
188
189         self.verify_dhcp_msg_type(pkt, "offer")
190         data = self.validate_relay_options(pkt, intf, intf.local_ip4,
191                                            fib_id, oui)
192
193     def verify_orig_dhcp_pkt(self, pkt, intf):
194         ether = pkt[Ether]
195         self.assertEqual(ether.dst, "ff:ff:ff:ff:ff:ff")
196         self.assertEqual(ether.src, intf.local_mac)
197
198         ip = pkt[IP]
199         self.assertEqual(ip.dst, "255.255.255.255")
200         self.assertEqual(ip.src, "0.0.0.0")
201
202         udp = pkt[UDP]
203         self.assertEqual(udp.dport, DHCP4_SERVER_PORT)
204         self.assertEqual(udp.sport, DHCP4_CLIENT_PORT)
205
206     def verify_orig_dhcp_discover(self, pkt, intf, hostname, client_id=None):
207         self.verify_orig_dhcp_pkt(pkt, intf)
208
209         self.verify_dhcp_msg_type(pkt, "discover")
210         self.verify_dhcp_has_option(pkt, "hostname", hostname)
211         if client_id:
212             self.verify_dhcp_has_option(pkt, "client_id", client_id)
213         bootp = pkt[BOOTP]
214         self.assertEqual(bootp.ciaddr, "0.0.0.0")
215         self.assertEqual(bootp.giaddr, "0.0.0.0")
216         self.assertEqual(bootp.flags, 0x8000)
217
218     def verify_orig_dhcp_request(self, pkt, intf, hostname, ip):
219         self.verify_orig_dhcp_pkt(pkt, intf)
220
221         self.verify_dhcp_msg_type(pkt, "request")
222         self.verify_dhcp_has_option(pkt, "hostname", hostname)
223         self.verify_dhcp_has_option(pkt, "requested_addr", ip)
224         bootp = pkt[BOOTP]
225         self.assertEqual(bootp.ciaddr, "0.0.0.0")
226         self.assertEqual(bootp.giaddr, "0.0.0.0")
227         self.assertEqual(bootp.flags, 0x8000)
228
229     def verify_relayed_dhcp_discover(self, pkt, intf, src_intf=None,
230                                      fib_id=0, oui=0,
231                                      dst_mac=None, dst_ip=None):
232         if not dst_mac:
233             dst_mac = intf.remote_mac
234         if not dst_ip:
235             dst_ip = intf.remote_ip4
236
237         ether = pkt[Ether]
238         self.assertEqual(ether.dst, dst_mac)
239         self.assertEqual(ether.src, intf.local_mac)
240
241         ip = pkt[IP]
242         self.assertEqual(ip.dst, dst_ip)
243         self.assertEqual(ip.src, intf.local_ip4)
244
245         udp = pkt[UDP]
246         self.assertEqual(udp.dport, DHCP4_SERVER_PORT)
247         self.assertEqual(udp.sport, DHCP4_CLIENT_PORT)
248
249         dhcp = pkt[DHCP]
250
251         is_discover = False
252         for o in dhcp.options:
253             if type(o) is tuple:
254                 if o[0] == "message-type" \
255                    and DHCPTypes[o[1]] == "discover":
256                     is_discover = True
257         self.assertTrue(is_discover)
258
259         data = self.validate_relay_options(pkt, src_intf,
260                                            src_intf.local_ip4,
261                                            fib_id, oui)
262         return data
263
264     def verify_dhcp6_solicit(self, pkt, intf,
265                              peer_ip, peer_mac,
266                              fib_id=0,
267                              oui=0,
268                              dst_mac=None,
269                              dst_ip=None):
270         if not dst_mac:
271             dst_mac = intf.remote_mac
272         if not dst_ip:
273             dst_ip = in6_ptop(intf.remote_ip6)
274
275         ether = pkt[Ether]
276         self.assertEqual(ether.dst, dst_mac)
277         self.assertEqual(ether.src, intf.local_mac)
278
279         ip = pkt[IPv6]
280         self.assertEqual(in6_ptop(ip.dst), dst_ip)
281         self.assertEqual(in6_ptop(ip.src), in6_ptop(intf.local_ip6))
282
283         udp = pkt[UDP]
284         self.assertEqual(udp.dport, DHCP6_CLIENT_PORT)
285         self.assertEqual(udp.sport, DHCP6_SERVER_PORT)
286
287         relay = pkt[DHCP6_RelayForward]
288         self.assertEqual(in6_ptop(relay.peeraddr), in6_ptop(peer_ip))
289         oid = pkt[DHCP6OptIfaceId]
290         cll = pkt[DHCP6OptClientLinkLayerAddr]
291         self.assertEqual(cll.optlen, 8)
292         self.assertEqual(cll.lltype, 1)
293         self.assertEqual(cll.clladdr, peer_mac)
294
295         if fib_id != 0:
296             vss = pkt[DHCP6OptVSS]
297             self.assertEqual(vss.optlen, 8)
298             self.assertEqual(vss.type, 1)
299             # the OUI and FIB-id are really 3 and 4 bytes resp.
300             # but the tested range is small
301             self.assertEqual(ord(vss.data[0]), 0)
302             self.assertEqual(ord(vss.data[1]), 0)
303             self.assertEqual(ord(vss.data[2]), oui)
304             self.assertEqual(ord(vss.data[3]), 0)
305             self.assertEqual(ord(vss.data[4]), 0)
306             self.assertEqual(ord(vss.data[5]), 0)
307             self.assertEqual(ord(vss.data[6]), fib_id)
308
309         # the relay message should be an encoded Solicit
310         msg = pkt[DHCP6OptRelayMsg]
311         sol = DHCP6_Solicit()
312         self.assertEqual(msg.optlen, len(str(sol)))
313         self.assertEqual(str(sol), (str(msg[1]))[:msg.optlen])
314
315     def verify_dhcp6_advert(self, pkt, intf, peer):
316         ether = pkt[Ether]
317         self.assertEqual(ether.dst, "ff:ff:ff:ff:ff:ff")
318         self.assertEqual(ether.src, intf.local_mac)
319
320         ip = pkt[IPv6]
321         self.assertEqual(in6_ptop(ip.dst), in6_ptop(peer))
322         self.assertEqual(in6_ptop(ip.src), in6_ptop(intf.local_ip6))
323
324         udp = pkt[UDP]
325         self.assertEqual(udp.dport, DHCP6_SERVER_PORT)
326         self.assertEqual(udp.sport, DHCP6_CLIENT_PORT)
327
328         # not sure why this is not decoding
329         # adv = pkt[DHCP6_Advertise]
330
331     def test_dhcp_proxy(self):
332         """ DHCPv4 Proxy """
333
334         #
335         # Verify no response to DHCP request without DHCP config
336         #
337         p_disc_vrf0 = (Ether(dst="ff:ff:ff:ff:ff:ff",
338                              src=self.pg2.remote_mac) /
339                        IP(src="0.0.0.0", dst="255.255.255.255") /
340                        UDP(sport=DHCP4_CLIENT_PORT,
341                            dport=DHCP4_SERVER_PORT) /
342                        BOOTP(op=1) /
343                        DHCP(options=[('message-type', 'discover'), ('end')]))
344         pkts_disc_vrf0 = [p_disc_vrf0]
345         p_disc_vrf1 = (Ether(dst="ff:ff:ff:ff:ff:ff",
346                              src=self.pg3.remote_mac) /
347                        IP(src="0.0.0.0", dst="255.255.255.255") /
348                        UDP(sport=DHCP4_CLIENT_PORT,
349                            dport=DHCP4_SERVER_PORT) /
350                        BOOTP(op=1) /
351                        DHCP(options=[('message-type', 'discover'), ('end')]))
352         pkts_disc_vrf1 = [p_disc_vrf0]
353
354         self.send_and_assert_no_replies(self.pg2, pkts_disc_vrf0,
355                                         "DHCP with no configuration")
356         self.send_and_assert_no_replies(self.pg3, pkts_disc_vrf1,
357                                         "DHCP with no configuration")
358
359         #
360         # Enable DHCP proxy in VRF 0
361         #
362         server_addr = self.pg0.remote_ip4n
363         src_addr = self.pg0.local_ip4n
364
365         self.vapi.dhcp_proxy_config(server_addr,
366                                     src_addr,
367                                     rx_table_id=0)
368
369         #
370         # Discover packets from the client are dropped because there is no
371         # IP address configured on the client facing interface
372         #
373         self.send_and_assert_no_replies(self.pg2, pkts_disc_vrf0,
374                                         "Discover DHCP no relay address")
375
376         #
377         # Inject a response from the server
378         #  dropped, because there is no IP addrees on the
379         #  client interfce to fill in the option.
380         #
381         p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
382              IP(src=self.pg0.remote_ip4, dst=self.pg0.local_ip4) /
383              UDP(sport=DHCP4_SERVER_PORT, dport=DHCP4_SERVER_PORT) /
384              BOOTP(op=1) /
385              DHCP(options=[('message-type', 'offer'), ('end')]))
386         pkts = [p]
387
388         self.send_and_assert_no_replies(self.pg2, pkts,
389                                         "Offer DHCP no relay address")
390
391         #
392         # configure an IP address on the client facing interface
393         #
394         self.pg2.config_ip4()
395
396         #
397         # Try again with a discover packet
398         # Rx'd packet should be to the server address and from the configured
399         # source address
400         # UDP source ports are unchanged
401         # we've no option 82 config so that should be absent
402         #
403         self.pg2.add_stream(pkts_disc_vrf0)
404         self.pg_enable_capture(self.pg_interfaces)
405         self.pg_start()
406
407         rx = self.pg0.get_capture(1)
408         rx = rx[0]
409
410         option_82 = self.verify_relayed_dhcp_discover(rx, self.pg0,
411                                                       src_intf=self.pg2)
412
413         #
414         # Create an DHCP offer reply from the server with a correctly formatted
415         # option 82. i.e. send back what we just captured
416         # The offer, sent mcast to the client, still has option 82.
417         #
418         p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
419              IP(src=self.pg0.remote_ip4, dst=self.pg0.local_ip4) /
420              UDP(sport=DHCP4_SERVER_PORT, dport=DHCP4_SERVER_PORT) /
421              BOOTP(op=1) /
422              DHCP(options=[('message-type', 'offer'),
423                            ('relay_agent_Information', option_82),
424                            ('end')]))
425         pkts = [p]
426
427         self.pg0.add_stream(pkts)
428         self.pg_enable_capture(self.pg_interfaces)
429         self.pg_start()
430
431         rx = self.pg2.get_capture(1)
432         rx = rx[0]
433
434         self.verify_dhcp_offer(rx, self.pg2)
435
436         #
437         # Bogus Option 82:
438         #
439         # 1. not our IP address = not checked by VPP? so offer is replayed
440         #    to client
441         bad_ip = option_82[0:8] + chr(33) + option_82[9:]
442
443         p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
444              IP(src=self.pg0.remote_ip4, dst=self.pg0.local_ip4) /
445              UDP(sport=DHCP4_SERVER_PORT, dport=DHCP4_SERVER_PORT) /
446              BOOTP(op=1) /
447              DHCP(options=[('message-type', 'offer'),
448                            ('relay_agent_Information', bad_ip),
449                            ('end')]))
450         pkts = [p]
451         self.send_and_assert_no_replies(self.pg0, pkts,
452                                         "DHCP offer option 82 bad address")
453
454         # 2. Not a sw_if_index VPP knows
455         bad_if_index = option_82[0:2] + chr(33) + option_82[3:]
456
457         p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
458              IP(src=self.pg0.remote_ip4, dst=self.pg0.local_ip4) /
459              UDP(sport=DHCP4_SERVER_PORT, dport=DHCP4_SERVER_PORT) /
460              BOOTP(op=1) /
461              DHCP(options=[('message-type', 'offer'),
462                            ('relay_agent_Information', bad_if_index),
463                            ('end')]))
464         pkts = [p]
465         self.send_and_assert_no_replies(self.pg0, pkts,
466                                         "DHCP offer option 82 bad if index")
467
468         #
469         # Send a DHCP request in VRF 1. should be dropped.
470         #
471         self.send_and_assert_no_replies(self.pg3, pkts_disc_vrf1,
472                                         "DHCP with no configuration VRF 1")
473
474         #
475         # Delete the DHCP config in VRF 0
476         # Should now drop requests.
477         #
478         self.vapi.dhcp_proxy_config(server_addr,
479                                     src_addr,
480                                     rx_table_id=0,
481                                     is_add=0)
482
483         self.send_and_assert_no_replies(self.pg2, pkts_disc_vrf0,
484                                         "DHCP config removed VRF 0")
485         self.send_and_assert_no_replies(self.pg3, pkts_disc_vrf1,
486                                         "DHCP config removed VRF 1")
487
488         #
489         # Add DHCP config for VRF 1
490         #
491         server_addr = self.pg1.remote_ip4n
492         src_addr = self.pg1.local_ip4n
493         self.vapi.dhcp_proxy_config(server_addr,
494                                     src_addr,
495                                     rx_table_id=1,
496                                     server_table_id=1)
497
498         #
499         # Confim DHCP requests ok in VRF 1.
500         #  - dropped on IP config on client interface
501         #
502         self.send_and_assert_no_replies(self.pg3, pkts_disc_vrf1,
503                                         "DHCP config removed VRF 1")
504
505         #
506         # configure an IP address on the client facing interface
507         #
508         self.pg3.config_ip4()
509
510         self.pg3.add_stream(pkts_disc_vrf1)
511         self.pg_enable_capture(self.pg_interfaces)
512         self.pg_start()
513
514         rx = self.pg1.get_capture(1)
515         rx = rx[0]
516         self.verify_relayed_dhcp_discover(rx, self.pg1, src_intf=self.pg3)
517
518         #
519         # Add VSS config
520         #  table=1, fib=id=1, oui=4
521         self.vapi.dhcp_proxy_set_vss(1, 1, 4)
522
523         self.pg3.add_stream(pkts_disc_vrf1)
524         self.pg_enable_capture(self.pg_interfaces)
525         self.pg_start()
526
527         rx = self.pg1.get_capture(1)
528         rx = rx[0]
529         self.verify_relayed_dhcp_discover(rx, self.pg1,
530                                           src_intf=self.pg3,
531                                           fib_id=1, oui=4)
532
533         #
534         # Add a second DHCP server in VRF 1
535         #  expect clients messages to be relay to both configured servers
536         #
537         self.pg1.generate_remote_hosts(2)
538         server_addr2 = socket.inet_pton(AF_INET, self.pg1.remote_hosts[1].ip4)
539
540         self.vapi.dhcp_proxy_config(server_addr2,
541                                     src_addr,
542                                     rx_table_id=1,
543                                     server_table_id=1,
544                                     is_add=1)
545
546         #
547         # We'll need an ARP entry for the server to send it packets
548         #
549         arp_entry = VppNeighbor(self,
550                                 self.pg1.sw_if_index,
551                                 self.pg1.remote_hosts[1].mac,
552                                 self.pg1.remote_hosts[1].ip4)
553         arp_entry.add_vpp_config()
554
555         #
556         # Send a discover from the client. expect two relayed messages
557         # The frist packet is sent to the second server
558         # We're not enforcing that here, it's just the way it is.
559         #
560         self.pg3.add_stream(pkts_disc_vrf1)
561         self.pg_enable_capture(self.pg_interfaces)
562         self.pg_start()
563
564         rx = self.pg1.get_capture(2)
565
566         option_82 = self.verify_relayed_dhcp_discover(
567             rx[0], self.pg1,
568             src_intf=self.pg3,
569             dst_mac=self.pg1.remote_hosts[1].mac,
570             dst_ip=self.pg1.remote_hosts[1].ip4,
571             fib_id=1, oui=4)
572         self.verify_relayed_dhcp_discover(rx[1], self.pg1,
573                                           src_intf=self.pg3,
574                                           fib_id=1, oui=4)
575
576         #
577         # Send both packets back. Client gets both.
578         #
579         p1 = (Ether(dst=self.pg1.local_mac, src=self.pg1.remote_mac) /
580               IP(src=self.pg1.remote_ip4, dst=self.pg1.local_ip4) /
581               UDP(sport=DHCP4_SERVER_PORT, dport=DHCP4_SERVER_PORT) /
582               BOOTP(op=1) /
583               DHCP(options=[('message-type', 'offer'),
584                             ('relay_agent_Information', option_82),
585                             ('end')]))
586         p2 = (Ether(dst=self.pg1.local_mac, src=self.pg1.remote_mac) /
587               IP(src=self.pg1.remote_hosts[1].ip4, dst=self.pg1.local_ip4) /
588               UDP(sport=DHCP4_SERVER_PORT, dport=DHCP4_SERVER_PORT) /
589               BOOTP(op=1) /
590               DHCP(options=[('message-type', 'offer'),
591                             ('relay_agent_Information', option_82),
592                             ('end')]))
593         pkts = [p1, p2]
594
595         self.pg1.add_stream(pkts)
596         self.pg_enable_capture(self.pg_interfaces)
597         self.pg_start()
598
599         rx = self.pg3.get_capture(2)
600
601         self.verify_dhcp_offer(rx[0], self.pg3, fib_id=1, oui=4)
602         self.verify_dhcp_offer(rx[1], self.pg3, fib_id=1, oui=4)
603
604         #
605         # Ensure offers from non-servers are dropeed
606         #
607         p2 = (Ether(dst=self.pg1.local_mac, src=self.pg1.remote_mac) /
608               IP(src="8.8.8.8", dst=self.pg1.local_ip4) /
609               UDP(sport=DHCP4_SERVER_PORT, dport=DHCP4_SERVER_PORT) /
610               BOOTP(op=1) /
611               DHCP(options=[('message-type', 'offer'),
612                             ('relay_agent_Information', option_82),
613                             ('end')]))
614         self.send_and_assert_no_replies(self.pg1, p2,
615                                         "DHCP offer from non-server")
616
617         #
618         # Ensure only the discover is sent to multiple servers
619         #
620         p_req_vrf1 = (Ether(dst="ff:ff:ff:ff:ff:ff",
621                             src=self.pg3.remote_mac) /
622                       IP(src="0.0.0.0", dst="255.255.255.255") /
623                       UDP(sport=DHCP4_CLIENT_PORT,
624                           dport=DHCP4_SERVER_PORT) /
625                       BOOTP(op=1) /
626                       DHCP(options=[('message-type', 'request'),
627                                     ('end')]))
628
629         self.pg3.add_stream(p_req_vrf1)
630         self.pg_enable_capture(self.pg_interfaces)
631         self.pg_start()
632
633         rx = self.pg1.get_capture(1)
634
635         #
636         # Remove the second DHCP server
637         #
638         self.vapi.dhcp_proxy_config(server_addr2,
639                                     src_addr,
640                                     rx_table_id=1,
641                                     server_table_id=1,
642                                     is_add=0)
643
644         #
645         # Test we can still relay with the first
646         #
647         self.pg3.add_stream(pkts_disc_vrf1)
648         self.pg_enable_capture(self.pg_interfaces)
649         self.pg_start()
650
651         rx = self.pg1.get_capture(1)
652         rx = rx[0]
653         self.verify_relayed_dhcp_discover(rx, self.pg1,
654                                           src_intf=self.pg3,
655                                           fib_id=1, oui=4)
656
657         #
658         # Remove the VSS config
659         #  relayed DHCP has default vlaues in the option.
660         #
661         self.vapi.dhcp_proxy_set_vss(1, 1, 4, is_add=0)
662
663         self.pg3.add_stream(pkts_disc_vrf1)
664         self.pg_enable_capture(self.pg_interfaces)
665         self.pg_start()
666
667         rx = self.pg1.get_capture(1)
668         rx = rx[0]
669         self.verify_relayed_dhcp_discover(rx, self.pg1, src_intf=self.pg3)
670
671         #
672         # remove DHCP config to cleanup
673         #
674         self.vapi.dhcp_proxy_config(server_addr,
675                                     src_addr,
676                                     rx_table_id=1,
677                                     server_table_id=1,
678                                     is_add=0)
679
680         self.send_and_assert_no_replies(self.pg2, pkts_disc_vrf0,
681                                         "DHCP cleanup VRF 0")
682         self.send_and_assert_no_replies(self.pg3, pkts_disc_vrf1,
683                                         "DHCP cleanup VRF 1")
684         self.pg2.unconfig_ip4()
685         self.pg3.unconfig_ip4()
686
687     def test_dhcp6_proxy(self):
688         """ DHCPv6 Proxy"""
689         #
690         # Verify no response to DHCP request without DHCP config
691         #
692         dhcp_solicit_dst = "ff02::1:2"
693         dhcp_solicit_src_vrf0 = mk_ll_addr(self.pg2.remote_mac)
694         dhcp_solicit_src_vrf1 = mk_ll_addr(self.pg3.remote_mac)
695         server_addr_vrf0 = self.pg0.remote_ip6n
696         src_addr_vrf0 = self.pg0.local_ip6n
697         server_addr_vrf1 = self.pg1.remote_ip6n
698         src_addr_vrf1 = self.pg1.local_ip6n
699
700         dmac = in6_getnsmac(inet_pton(socket.AF_INET6, dhcp_solicit_dst))
701         p_solicit_vrf0 = (Ether(dst=dmac, src=self.pg2.remote_mac) /
702                           IPv6(src=dhcp_solicit_src_vrf0,
703                                dst=dhcp_solicit_dst) /
704                           UDP(sport=DHCP6_SERVER_PORT,
705                               dport=DHCP6_CLIENT_PORT) /
706                           DHCP6_Solicit())
707         p_solicit_vrf1 = (Ether(dst=dmac, src=self.pg3.remote_mac) /
708                           IPv6(src=dhcp_solicit_src_vrf1,
709                                dst=dhcp_solicit_dst) /
710                           UDP(sport=DHCP6_SERVER_PORT,
711                               dport=DHCP6_CLIENT_PORT) /
712                           DHCP6_Solicit())
713
714         self.send_and_assert_no_replies(self.pg2, p_solicit_vrf0,
715                                         "DHCP with no configuration")
716         self.send_and_assert_no_replies(self.pg3, p_solicit_vrf1,
717                                         "DHCP with no configuration")
718
719         #
720         # DHCPv6 config in VRF 0.
721         # Packets still dropped because the client facing interface has no
722         # IPv6 config
723         #
724         self.vapi.dhcp_proxy_config(server_addr_vrf0,
725                                     src_addr_vrf0,
726                                     rx_table_id=0,
727                                     server_table_id=0,
728                                     is_ipv6=1)
729
730         self.send_and_assert_no_replies(self.pg2, p_solicit_vrf0,
731                                         "DHCP with no configuration")
732         self.send_and_assert_no_replies(self.pg3, p_solicit_vrf1,
733                                         "DHCP with no configuration")
734
735         #
736         # configure an IP address on the client facing interface
737         #
738         self.pg2.config_ip6()
739
740         #
741         # Now the DHCP requests are relayed to the server
742         #
743         self.pg2.add_stream(p_solicit_vrf0)
744         self.pg_enable_capture(self.pg_interfaces)
745         self.pg_start()
746
747         rx = self.pg0.get_capture(1)
748
749         self.verify_dhcp6_solicit(rx[0], self.pg0,
750                                   dhcp_solicit_src_vrf0,
751                                   self.pg2.remote_mac)
752
753         #
754         # Exception cases for rejected relay responses
755         #
756
757         # 1 - not a relay reply
758         p_adv_vrf0 = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
759                       IPv6(dst=self.pg0.local_ip6, src=self.pg0.remote_ip6) /
760                       UDP(sport=DHCP6_SERVER_PORT, dport=DHCP6_SERVER_PORT) /
761                       DHCP6_Advertise())
762         self.send_and_assert_no_replies(self.pg2, p_adv_vrf0,
763                                         "DHCP6 not a relay reply")
764
765         # 2 - no relay message option
766         p_adv_vrf0 = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
767                       IPv6(dst=self.pg0.local_ip6, src=self.pg0.remote_ip6) /
768                       UDP(sport=DHCP6_SERVER_PORT, dport=DHCP6_SERVER_PORT) /
769                       DHCP6_RelayReply() /
770                       DHCP6_Advertise())
771         self.send_and_assert_no_replies(self.pg2, p_adv_vrf0,
772                                         "DHCP not a relay message")
773
774         # 3 - no circuit ID
775         p_adv_vrf0 = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
776                       IPv6(dst=self.pg0.local_ip6, src=self.pg0.remote_ip6) /
777                       UDP(sport=DHCP6_SERVER_PORT, dport=DHCP6_SERVER_PORT) /
778                       DHCP6_RelayReply() /
779                       DHCP6OptRelayMsg(optlen=0) /
780                       DHCP6_Advertise())
781         self.send_and_assert_no_replies(self.pg2, p_adv_vrf0,
782                                         "DHCP6 no circuit ID")
783         # 4 - wrong circuit ID
784         p_adv_vrf0 = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
785                       IPv6(dst=self.pg0.local_ip6, src=self.pg0.remote_ip6) /
786                       UDP(sport=DHCP6_SERVER_PORT, dport=DHCP6_SERVER_PORT) /
787                       DHCP6_RelayReply() /
788                       DHCP6OptIfaceId(optlen=4, ifaceid='\x00\x00\x00\x05') /
789                       DHCP6OptRelayMsg(optlen=0) /
790                       DHCP6_Advertise())
791         self.send_and_assert_no_replies(self.pg2, p_adv_vrf0,
792                                         "DHCP6 wrong circuit ID")
793
794         #
795         # Send the relay response (the advertisement)
796         #   - no peer address
797         p_adv_vrf0 = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
798                       IPv6(dst=self.pg0.local_ip6, src=self.pg0.remote_ip6) /
799                       UDP(sport=DHCP6_SERVER_PORT, dport=DHCP6_SERVER_PORT) /
800                       DHCP6_RelayReply() /
801                       DHCP6OptIfaceId(optlen=4, ifaceid='\x00\x00\x00\x03') /
802                       DHCP6OptRelayMsg(optlen=0) /
803                       DHCP6_Advertise(trid=1) /
804                       DHCP6OptStatusCode(statuscode=0))
805         pkts_adv_vrf0 = [p_adv_vrf0]
806
807         self.pg0.add_stream(pkts_adv_vrf0)
808         self.pg_enable_capture(self.pg_interfaces)
809         self.pg_start()
810
811         rx = self.pg2.get_capture(1)
812
813         self.verify_dhcp6_advert(rx[0], self.pg2, "::")
814
815         #
816         # Send the relay response (the advertisement)
817         #   - with peer address
818         p_adv_vrf0 = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
819                       IPv6(dst=self.pg0.local_ip6, src=self.pg0.remote_ip6) /
820                       UDP(sport=DHCP6_SERVER_PORT, dport=DHCP6_SERVER_PORT) /
821                       DHCP6_RelayReply(peeraddr=dhcp_solicit_src_vrf0) /
822                       DHCP6OptIfaceId(optlen=4, ifaceid='\x00\x00\x00\x03') /
823                       DHCP6OptRelayMsg(optlen=0) /
824                       DHCP6_Advertise(trid=1) /
825                       DHCP6OptStatusCode(statuscode=0))
826         pkts_adv_vrf0 = [p_adv_vrf0]
827
828         self.pg0.add_stream(pkts_adv_vrf0)
829         self.pg_enable_capture(self.pg_interfaces)
830         self.pg_start()
831
832         rx = self.pg2.get_capture(1)
833
834         self.verify_dhcp6_advert(rx[0], self.pg2, dhcp_solicit_src_vrf0)
835
836         #
837         # Add all the config for VRF 1
838         #
839         self.vapi.dhcp_proxy_config(server_addr_vrf1,
840                                     src_addr_vrf1,
841                                     rx_table_id=1,
842                                     server_table_id=1,
843                                     is_ipv6=1)
844         self.pg3.config_ip6()
845
846         #
847         # VRF 1 solicit
848         #
849         self.pg3.add_stream(p_solicit_vrf1)
850         self.pg_enable_capture(self.pg_interfaces)
851         self.pg_start()
852
853         rx = self.pg1.get_capture(1)
854
855         self.verify_dhcp6_solicit(rx[0], self.pg1,
856                                   dhcp_solicit_src_vrf1,
857                                   self.pg3.remote_mac)
858
859         #
860         # VRF 1 Advert
861         #
862         p_adv_vrf1 = (Ether(dst=self.pg1.local_mac, src=self.pg1.remote_mac) /
863                       IPv6(dst=self.pg1.local_ip6, src=self.pg1.remote_ip6) /
864                       UDP(sport=DHCP6_SERVER_PORT, dport=DHCP6_SERVER_PORT) /
865                       DHCP6_RelayReply(peeraddr=dhcp_solicit_src_vrf1) /
866                       DHCP6OptIfaceId(optlen=4, ifaceid='\x00\x00\x00\x04') /
867                       DHCP6OptRelayMsg(optlen=0) /
868                       DHCP6_Advertise(trid=1) /
869                       DHCP6OptStatusCode(statuscode=0))
870         pkts_adv_vrf1 = [p_adv_vrf1]
871
872         self.pg1.add_stream(pkts_adv_vrf1)
873         self.pg_enable_capture(self.pg_interfaces)
874         self.pg_start()
875
876         rx = self.pg3.get_capture(1)
877
878         self.verify_dhcp6_advert(rx[0], self.pg3, dhcp_solicit_src_vrf1)
879
880         #
881         # Add VSS config
882         #  table=1, fib=id=1, oui=4
883         self.vapi.dhcp_proxy_set_vss(1, 1, 4, is_ip6=1)
884
885         self.pg3.add_stream(p_solicit_vrf1)
886         self.pg_enable_capture(self.pg_interfaces)
887         self.pg_start()
888
889         rx = self.pg1.get_capture(1)
890
891         self.verify_dhcp6_solicit(rx[0], self.pg1,
892                                   dhcp_solicit_src_vrf1,
893                                   self.pg3.remote_mac,
894                                   fib_id=1,
895                                   oui=4)
896
897         #
898         # Remove the VSS config
899         #  relayed DHCP has default vlaues in the option.
900         #
901         self.vapi.dhcp_proxy_set_vss(1, 1, 4, is_ip6=1, is_add=0)
902
903         self.pg3.add_stream(p_solicit_vrf1)
904         self.pg_enable_capture(self.pg_interfaces)
905         self.pg_start()
906
907         rx = self.pg1.get_capture(1)
908
909         self.verify_dhcp6_solicit(rx[0], self.pg1,
910                                   dhcp_solicit_src_vrf1,
911                                   self.pg3.remote_mac)
912
913         #
914         # Add a second DHCP server in VRF 1
915         #  expect clients messages to be relay to both configured servers
916         #
917         self.pg1.generate_remote_hosts(2)
918         server_addr2 = socket.inet_pton(AF_INET6, self.pg1.remote_hosts[1].ip6)
919
920         self.vapi.dhcp_proxy_config(server_addr2,
921                                     src_addr_vrf1,
922                                     rx_table_id=1,
923                                     server_table_id=1,
924                                     is_ipv6=1)
925
926         #
927         # We'll need an ND entry for the server to send it packets
928         #
929         nd_entry = VppNeighbor(self,
930                                self.pg1.sw_if_index,
931                                self.pg1.remote_hosts[1].mac,
932                                self.pg1.remote_hosts[1].ip6,
933                                af=AF_INET6)
934         nd_entry.add_vpp_config()
935
936         #
937         # Send a discover from the client. expect two relayed messages
938         # The frist packet is sent to the second server
939         # We're not enforcing that here, it's just the way it is.
940         #
941         self.pg3.add_stream(p_solicit_vrf1)
942         self.pg_enable_capture(self.pg_interfaces)
943         self.pg_start()
944
945         rx = self.pg1.get_capture(2)
946
947         self.verify_dhcp6_solicit(rx[0], self.pg1,
948                                   dhcp_solicit_src_vrf1,
949                                   self.pg3.remote_mac)
950         self.verify_dhcp6_solicit(rx[1], self.pg1,
951                                   dhcp_solicit_src_vrf1,
952                                   self.pg3.remote_mac,
953                                   dst_mac=self.pg1.remote_hosts[1].mac,
954                                   dst_ip=self.pg1.remote_hosts[1].ip6)
955
956         #
957         # Send both packets back. Client gets both.
958         #
959         p1 = (Ether(dst=self.pg1.local_mac, src=self.pg1.remote_mac) /
960               IPv6(dst=self.pg1.local_ip6, src=self.pg1.remote_ip6) /
961               UDP(sport=DHCP6_SERVER_PORT, dport=DHCP6_SERVER_PORT) /
962               DHCP6_RelayReply(peeraddr=dhcp_solicit_src_vrf1) /
963               DHCP6OptIfaceId(optlen=4, ifaceid='\x00\x00\x00\x04') /
964               DHCP6OptRelayMsg(optlen=0) /
965               DHCP6_Advertise(trid=1) /
966               DHCP6OptStatusCode(statuscode=0))
967         p2 = (Ether(dst=self.pg1.local_mac, src=self.pg1.remote_hosts[1].mac) /
968               IPv6(dst=self.pg1.local_ip6, src=self.pg1._remote_hosts[1].ip6) /
969               UDP(sport=DHCP6_SERVER_PORT, dport=DHCP6_SERVER_PORT) /
970               DHCP6_RelayReply(peeraddr=dhcp_solicit_src_vrf1) /
971               DHCP6OptIfaceId(optlen=4, ifaceid='\x00\x00\x00\x04') /
972               DHCP6OptRelayMsg(optlen=0) /
973               DHCP6_Advertise(trid=1) /
974               DHCP6OptStatusCode(statuscode=0))
975
976         pkts = [p1, p2]
977
978         self.pg1.add_stream(pkts)
979         self.pg_enable_capture(self.pg_interfaces)
980         self.pg_start()
981
982         rx = self.pg3.get_capture(2)
983
984         self.verify_dhcp6_advert(rx[0], self.pg3, dhcp_solicit_src_vrf1)
985         self.verify_dhcp6_advert(rx[1], self.pg3, dhcp_solicit_src_vrf1)
986
987         #
988         # Ensure only solicit messages are duplicated
989         #
990         p_request_vrf1 = (Ether(dst=dmac, src=self.pg3.remote_mac) /
991                           IPv6(src=dhcp_solicit_src_vrf1,
992                                dst=dhcp_solicit_dst) /
993                           UDP(sport=DHCP6_SERVER_PORT,
994                               dport=DHCP6_CLIENT_PORT) /
995                           DHCP6_Request())
996
997         self.pg3.add_stream(p_request_vrf1)
998         self.pg_enable_capture(self.pg_interfaces)
999         self.pg_start()
1000
1001         rx = self.pg1.get_capture(1)
1002
1003         #
1004         # Test we drop DHCP packets from addresses that are not configured as
1005         # DHCP servers
1006         #
1007         p2 = (Ether(dst=self.pg1.local_mac, src=self.pg1.remote_hosts[1].mac) /
1008               IPv6(dst=self.pg1.local_ip6, src="3001::1") /
1009               UDP(sport=DHCP6_SERVER_PORT, dport=DHCP6_SERVER_PORT) /
1010               DHCP6_RelayReply(peeraddr=dhcp_solicit_src_vrf1) /
1011               DHCP6OptIfaceId(optlen=4, ifaceid='\x00\x00\x00\x04') /
1012               DHCP6OptRelayMsg(optlen=0) /
1013               DHCP6_Advertise(trid=1) /
1014               DHCP6OptStatusCode(statuscode=0))
1015         self.send_and_assert_no_replies(self.pg1, p2,
1016                                         "DHCP6 not from server")
1017
1018         #
1019         # Remove the second DHCP server
1020         #
1021         self.vapi.dhcp_proxy_config(server_addr2,
1022                                     src_addr_vrf1,
1023                                     rx_table_id=1,
1024                                     server_table_id=1,
1025                                     is_ipv6=1,
1026                                     is_add=0)
1027
1028         #
1029         # Test we can still relay with the first
1030         #
1031         self.pg3.add_stream(p_solicit_vrf1)
1032         self.pg_enable_capture(self.pg_interfaces)
1033         self.pg_start()
1034
1035         rx = self.pg1.get_capture(1)
1036
1037         self.verify_dhcp6_solicit(rx[0], self.pg1,
1038                                   dhcp_solicit_src_vrf1,
1039                                   self.pg3.remote_mac)
1040
1041         #
1042         # Cleanup
1043         #
1044         self.vapi.dhcp_proxy_config(server_addr_vrf1,
1045                                     src_addr_vrf1,
1046                                     rx_table_id=1,
1047                                     server_table_id=1,
1048                                     is_ipv6=1,
1049                                     is_add=0)
1050         self.vapi.dhcp_proxy_config(server_addr_vrf0,
1051                                     src_addr_vrf0,
1052                                     rx_table_id=0,
1053                                     server_table_id=0,
1054                                     is_ipv6=1,
1055                                     is_add=0)
1056
1057         # duplicate delete
1058         self.vapi.dhcp_proxy_config(server_addr_vrf0,
1059                                     src_addr_vrf0,
1060                                     rx_table_id=0,
1061                                     server_table_id=0,
1062                                     is_ipv6=1,
1063                                     is_add=0)
1064         self.pg2.unconfig_ip6()
1065         self.pg3.unconfig_ip6()
1066
1067     def test_dhcp_client(self):
1068         """ DHCP Client"""
1069
1070         hostname = 'universal-dp'
1071
1072         self.pg_enable_capture(self.pg_interfaces)
1073
1074         #
1075         # Configure DHCP client on PG2 and capture the discover sent
1076         #
1077         self.vapi.dhcp_client(self.pg2.sw_if_index, hostname)
1078
1079         rx = self.pg2.get_capture(1)
1080
1081         self.verify_orig_dhcp_discover(rx[0], self.pg2, hostname)
1082
1083         #
1084         # Sned back on offer, expect the request
1085         #
1086         p_offer = (Ether(dst=self.pg2.local_mac, src=self.pg2.remote_mac) /
1087                    IP(src=self.pg2.remote_ip4, dst="255.255.255.255") /
1088                    UDP(sport=DHCP4_SERVER_PORT, dport=DHCP4_CLIENT_PORT) /
1089                    BOOTP(op=1, yiaddr=self.pg2.local_ip4) /
1090                    DHCP(options=[('message-type', 'offer'),
1091                                  ('server_id', self.pg2.remote_ip4),
1092                                  ('end')]))
1093
1094         self.pg2.add_stream(p_offer)
1095         self.pg_enable_capture(self.pg_interfaces)
1096         self.pg_start()
1097
1098         rx = self.pg2.get_capture(1)
1099         self.verify_orig_dhcp_request(rx[0], self.pg2, hostname,
1100                                       self.pg2.local_ip4)
1101
1102         #
1103         # Send an acknowloedgement
1104         #
1105         p_ack = (Ether(dst=self.pg2.local_mac, src=self.pg2.remote_mac) /
1106                  IP(src=self.pg2.remote_ip4, dst="255.255.255.255") /
1107                  UDP(sport=DHCP4_SERVER_PORT, dport=DHCP4_CLIENT_PORT) /
1108                  BOOTP(op=1, yiaddr=self.pg2.local_ip4) /
1109                  DHCP(options=[('message-type', 'ack'),
1110                                ('subnet_mask', "255.255.255.0"),
1111                                ('router', self.pg2.remote_ip4),
1112                                ('server_id', self.pg2.remote_ip4),
1113                                ('lease_time', 43200),
1114                                ('end')]))
1115
1116         self.pg2.add_stream(p_ack)
1117         self.pg_enable_capture(self.pg_interfaces)
1118         self.pg_start()
1119
1120         #
1121         # We'll get an ARP request for the router address
1122         #
1123         rx = self.pg2.get_capture(1)
1124
1125         self.assertEqual(rx[0][ARP].pdst, self.pg2.remote_ip4)
1126         self.pg_enable_capture(self.pg_interfaces)
1127
1128         #
1129         # At the end of this procedure there should be a connected route
1130         # in the FIB
1131         #
1132         self.assertTrue(find_route(self, self.pg2.local_ip4, 24))
1133         self.assertTrue(find_route(self, self.pg2.local_ip4, 32))
1134
1135         # remove the left over ARP entry
1136         self.vapi.ip_neighbor_add_del(self.pg2.sw_if_index,
1137                                       self.pg2.remote_mac,
1138                                       self.pg2.remote_ip4,
1139                                       is_add=0)
1140         #
1141         # remove the DHCP config
1142         #
1143         self.vapi.dhcp_client(self.pg2.sw_if_index, hostname, is_add=0)
1144
1145         #
1146         # and now the route should be gone
1147         #
1148         self.assertFalse(find_route(self, self.pg2.local_ip4, 32))
1149         self.assertFalse(find_route(self, self.pg2.local_ip4, 24))
1150
1151         #
1152         # Start the procedure again. this time have VPP send the client-ID
1153         #
1154         self.pg2.admin_down()
1155         self.sleep(1)
1156         self.pg2.admin_up()
1157         self.vapi.dhcp_client(self.pg2.sw_if_index, hostname,
1158                               client_id=self.pg2.local_mac)
1159
1160         rx = self.pg2.get_capture(1)
1161
1162         self.verify_orig_dhcp_discover(rx[0], self.pg2, hostname,
1163                                        self.pg2.local_mac)
1164
1165         self.pg2.add_stream(p_offer)
1166         self.pg_enable_capture(self.pg_interfaces)
1167         self.pg_start()
1168
1169         rx = self.pg2.get_capture(1)
1170         self.verify_orig_dhcp_request(rx[0], self.pg2, hostname,
1171                                       self.pg2.local_ip4)
1172
1173         #
1174         # unicast the ack to the offered address
1175         #
1176         p_ack = (Ether(dst=self.pg2.local_mac, src=self.pg2.remote_mac) /
1177                  IP(src=self.pg2.remote_ip4, dst=self.pg2.local_ip4) /
1178                  UDP(sport=DHCP4_SERVER_PORT, dport=DHCP4_CLIENT_PORT) /
1179                  BOOTP(op=1, yiaddr=self.pg2.local_ip4) /
1180                  DHCP(options=[('message-type', 'ack'),
1181                                ('subnet_mask', "255.255.255.0"),
1182                                ('router', self.pg2.remote_ip4),
1183                                ('server_id', self.pg2.remote_ip4),
1184                                ('lease_time', 43200),
1185                                ('end')]))
1186
1187         self.pg2.add_stream(p_ack)
1188         self.pg_enable_capture(self.pg_interfaces)
1189         self.pg_start()
1190
1191         #
1192         # At the end of this procedure there should be a connected route
1193         # in the FIB
1194         #
1195         self.assertTrue(find_route(self, self.pg2.local_ip4, 32))
1196         self.assertTrue(find_route(self, self.pg2.local_ip4, 24))
1197
1198         #
1199         # remove the DHCP config
1200         #
1201         self.vapi.dhcp_client(self.pg2.sw_if_index, hostname, is_add=0)
1202
1203         self.assertFalse(find_route(self, self.pg2.local_ip4, 32))
1204         self.assertFalse(find_route(self, self.pg2.local_ip4, 24))
1205
1206
1207 if __name__ == '__main__':
1208     unittest.main(testRunner=VppTestRunner)