VPP-1508: Use scapy.compat to manage packet level library differences.
[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, running_extended_tests
8 from vpp_neighbor import VppNeighbor
9 from vpp_ip_route import find_route, VppIpTable
10 from util import mk_ll_addr
11 import scapy.compat
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
15 from scapy.utils6 import in6_mactoifaceid
16 from scapy.layers.dhcp import DHCP, BOOTP, DHCPTypes
17 from scapy.layers.dhcp6 import DHCP6, DHCP6_Solicit, DHCP6_RelayForward, \
18     DHCP6_RelayReply, DHCP6_Advertise, DHCP6OptRelayMsg, DHCP6OptIfaceId, \
19     DHCP6OptStatusCode, DHCP6OptVSS, DHCP6OptClientLinkLayerAddr, DHCP6_Request
20 from socket import AF_INET, AF_INET6
21 from scapy.utils import inet_pton, inet_ntop
22 from scapy.utils6 import in6_ptop
23 from vpp_papi import mac_pton
24
25 DHCP4_CLIENT_PORT = 68
26 DHCP4_SERVER_PORT = 67
27 DHCP6_CLIENT_PORT = 547
28 DHCP6_SERVER_PORT = 546
29
30
31 class TestDHCP(VppTestCase):
32     """ DHCP Test Case """
33
34     def setUp(self):
35         super(TestDHCP, self).setUp()
36
37         # create 6 pg interfaces for pg0 to pg5
38         self.create_pg_interfaces(range(6))
39         self.tables = []
40
41         # pg0 to 2 are IP configured in VRF 0, 1 and 2.
42         # pg3 to 5 are non IP-configured in VRF 0, 1 and 2.
43         table_id = 0
44         for table_id in range(1, 4):
45             tbl4 = VppIpTable(self, table_id)
46             tbl4.add_vpp_config()
47             self.tables.append(tbl4)
48             tbl6 = VppIpTable(self, table_id, is_ip6=1)
49             tbl6.add_vpp_config()
50             self.tables.append(tbl6)
51
52         table_id = 0
53         for i in self.pg_interfaces[:3]:
54             i.admin_up()
55             i.set_table_ip4(table_id)
56             i.set_table_ip6(table_id)
57             i.config_ip4()
58             i.resolve_arp()
59             i.config_ip6()
60             i.resolve_ndp()
61             table_id += 1
62
63         table_id = 0
64         for i in self.pg_interfaces[3:]:
65             i.admin_up()
66             i.set_table_ip4(table_id)
67             i.set_table_ip6(table_id)
68             table_id += 1
69
70     def tearDown(self):
71         for i in self.pg_interfaces[:3]:
72             i.unconfig_ip4()
73             i.unconfig_ip6()
74
75         for i in self.pg_interfaces:
76             i.set_table_ip4(0)
77             i.set_table_ip6(0)
78             i.admin_down()
79         super(TestDHCP, self).tearDown()
80
81     def verify_dhcp_has_option(self, pkt, option, value):
82         dhcp = pkt[DHCP]
83         found = False
84
85         for i in dhcp.options:
86             if type(i) is tuple:
87                 if i[0] == option:
88                     self.assertEqual(i[1], value)
89                     found = True
90
91         self.assertTrue(found)
92
93     def validate_relay_options(self, pkt, intf, ip_addr, vpn_id, fib_id, oui):
94         dhcp = pkt[DHCP]
95         found = 0
96         data = []
97         id_len = len(vpn_id)
98
99         for i in dhcp.options:
100             if type(i) is tuple:
101                 if i[0] == "relay_agent_Information":
102                     #
103                     # There are two sb-options present - each of length 6.
104                     #
105                     data = i[1]
106                     if oui != 0:
107                         self.assertEqual(len(data), 24)
108                     elif len(vpn_id) > 0:
109                         self.assertEqual(len(data), len(vpn_id)+17)
110                     else:
111                         self.assertEqual(len(data), 12)
112
113                     #
114                     # First sub-option is ID 1, len 4, then encoded
115                     #  sw_if_index. This test uses low valued indicies
116                     # so [2:4] are 0.
117                     # The ID space is VPP internal - so no matching value
118                     # scapy
119                     #
120                     self.assertEqual(ord(data[0]), 1)
121                     self.assertEqual(ord(data[1]), 4)
122                     self.assertEqual(ord(data[2]), 0)
123                     self.assertEqual(ord(data[3]), 0)
124                     self.assertEqual(ord(data[4]), 0)
125                     self.assertEqual(ord(data[5]), intf._sw_if_index)
126
127                     #
128                     # next sub-option is the IP address of the client side
129                     # interface.
130                     # sub-option ID=5, length (of a v4 address)=4
131                     #
132                     claddr = socket.inet_pton(AF_INET, ip_addr)
133
134                     self.assertEqual(ord(data[6]), 5)
135                     self.assertEqual(ord(data[7]), 4)
136                     self.assertEqual(data[8], claddr[0])
137                     self.assertEqual(data[9], claddr[1])
138                     self.assertEqual(data[10], claddr[2])
139                     self.assertEqual(data[11], claddr[3])
140
141                     if oui != 0:
142                         # sub-option 151 encodes vss_type 1,
143                         # the 3 byte oui and the 4 byte fib_id
144                         self.assertEqual(id_len, 0)
145                         self.assertEqual(ord(data[12]), 151)
146                         self.assertEqual(ord(data[13]), 8)
147                         self.assertEqual(ord(data[14]), 1)
148                         self.assertEqual(ord(data[15]), 0)
149                         self.assertEqual(ord(data[16]), 0)
150                         self.assertEqual(ord(data[17]), oui)
151                         self.assertEqual(ord(data[18]), 0)
152                         self.assertEqual(ord(data[19]), 0)
153                         self.assertEqual(ord(data[20]), 0)
154                         self.assertEqual(ord(data[21]), fib_id)
155
156                         # VSS control sub-option
157                         self.assertEqual(ord(data[22]), 152)
158                         self.assertEqual(ord(data[23]), 0)
159
160                     if id_len > 0:
161                         # sub-option 151 encode vss_type of 0
162                         # followerd by vpn_id in ascii
163                         self.assertEqual(oui, 0)
164                         self.assertEqual(ord(data[12]), 151)
165                         self.assertEqual(ord(data[13]), id_len+1)
166                         self.assertEqual(ord(data[14]), 0)
167                         self.assertEqual(data[15:15+id_len], vpn_id)
168
169                         # VSS control sub-option
170                         self.assertEqual(ord(data[15+len(vpn_id)]), 152)
171                         self.assertEqual(ord(data[16+len(vpn_id)]), 0)
172
173                     found = 1
174         self.assertTrue(found)
175
176         return data
177
178     def verify_dhcp_msg_type(self, pkt, name):
179         dhcp = pkt[DHCP]
180         found = False
181         for o in dhcp.options:
182             if type(o) is tuple:
183                 if o[0] == "message-type" \
184                    and DHCPTypes[o[1]] == name:
185                     found = True
186         self.assertTrue(found)
187
188     def verify_dhcp_offer(self, pkt, intf, vpn_id="", fib_id=0, oui=0):
189         ether = pkt[Ether]
190         self.assertEqual(ether.dst, "ff:ff:ff:ff:ff:ff")
191         self.assertEqual(ether.src, intf.local_mac)
192
193         ip = pkt[IP]
194         self.assertEqual(ip.dst, "255.255.255.255")
195         self.assertEqual(ip.src, intf.local_ip4)
196
197         udp = pkt[UDP]
198         self.assertEqual(udp.dport, DHCP4_CLIENT_PORT)
199         self.assertEqual(udp.sport, DHCP4_SERVER_PORT)
200
201         self.verify_dhcp_msg_type(pkt, "offer")
202         data = self.validate_relay_options(pkt, intf, intf.local_ip4,
203                                            vpn_id, fib_id, oui)
204
205     def verify_orig_dhcp_pkt(self, pkt, intf):
206         ether = pkt[Ether]
207         self.assertEqual(ether.dst, "ff:ff:ff:ff:ff:ff")
208         self.assertEqual(ether.src, intf.local_mac)
209
210         ip = pkt[IP]
211         self.assertEqual(ip.dst, "255.255.255.255")
212         self.assertEqual(ip.src, "0.0.0.0")
213
214         udp = pkt[UDP]
215         self.assertEqual(udp.dport, DHCP4_SERVER_PORT)
216         self.assertEqual(udp.sport, DHCP4_CLIENT_PORT)
217
218     def verify_orig_dhcp_discover(self, pkt, intf, hostname, client_id=None,
219                                   broadcast=1):
220         self.verify_orig_dhcp_pkt(pkt, intf)
221
222         self.verify_dhcp_msg_type(pkt, "discover")
223         self.verify_dhcp_has_option(pkt, "hostname", hostname)
224         if client_id:
225             self.verify_dhcp_has_option(pkt, "client_id", client_id)
226         bootp = pkt[BOOTP]
227         self.assertEqual(bootp.ciaddr, "0.0.0.0")
228         self.assertEqual(bootp.giaddr, "0.0.0.0")
229         if broadcast:
230             self.assertEqual(bootp.flags, 0x8000)
231         else:
232             self.assertEqual(bootp.flags, 0x0000)
233
234     def verify_orig_dhcp_request(self, pkt, intf, hostname, ip,
235                                  broadcast=1):
236         self.verify_orig_dhcp_pkt(pkt, intf)
237
238         self.verify_dhcp_msg_type(pkt, "request")
239         self.verify_dhcp_has_option(pkt, "hostname", hostname)
240         self.verify_dhcp_has_option(pkt, "requested_addr", ip)
241         bootp = pkt[BOOTP]
242         self.assertEqual(bootp.ciaddr, "0.0.0.0")
243         self.assertEqual(bootp.giaddr, "0.0.0.0")
244         if broadcast:
245             self.assertEqual(bootp.flags, 0x8000)
246         else:
247             self.assertEqual(bootp.flags, 0x0000)
248
249     def verify_relayed_dhcp_discover(self, pkt, intf, src_intf=None,
250                                      fib_id=0, oui=0,
251                                      vpn_id="",
252                                      dst_mac=None, dst_ip=None):
253         if not dst_mac:
254             dst_mac = intf.remote_mac
255         if not dst_ip:
256             dst_ip = intf.remote_ip4
257
258         ether = pkt[Ether]
259         self.assertEqual(ether.dst, dst_mac)
260         self.assertEqual(ether.src, intf.local_mac)
261
262         ip = pkt[IP]
263         self.assertEqual(ip.dst, dst_ip)
264         self.assertEqual(ip.src, intf.local_ip4)
265
266         udp = pkt[UDP]
267         self.assertEqual(udp.dport, DHCP4_SERVER_PORT)
268         self.assertEqual(udp.sport, DHCP4_CLIENT_PORT)
269
270         dhcp = pkt[DHCP]
271
272         is_discover = False
273         for o in dhcp.options:
274             if type(o) is tuple:
275                 if o[0] == "message-type" \
276                    and DHCPTypes[o[1]] == "discover":
277                     is_discover = True
278         self.assertTrue(is_discover)
279
280         data = self.validate_relay_options(pkt, src_intf,
281                                            src_intf.local_ip4,
282                                            vpn_id,
283                                            fib_id, oui)
284         return data
285
286     def verify_dhcp6_solicit(self, pkt, intf,
287                              peer_ip, peer_mac,
288                              vpn_id="",
289                              fib_id=0,
290                              oui=0,
291                              dst_mac=None,
292                              dst_ip=None):
293         if not dst_mac:
294             dst_mac = intf.remote_mac
295         if not dst_ip:
296             dst_ip = in6_ptop(intf.remote_ip6)
297
298         ether = pkt[Ether]
299         self.assertEqual(ether.dst, dst_mac)
300         self.assertEqual(ether.src, intf.local_mac)
301
302         ip = pkt[IPv6]
303         self.assertEqual(in6_ptop(ip.dst), dst_ip)
304         self.assertEqual(in6_ptop(ip.src), in6_ptop(intf.local_ip6))
305
306         udp = pkt[UDP]
307         self.assertEqual(udp.dport, DHCP6_CLIENT_PORT)
308         self.assertEqual(udp.sport, DHCP6_SERVER_PORT)
309
310         relay = pkt[DHCP6_RelayForward]
311         self.assertEqual(in6_ptop(relay.peeraddr), in6_ptop(peer_ip))
312         oid = pkt[DHCP6OptIfaceId]
313         cll = pkt[DHCP6OptClientLinkLayerAddr]
314         self.assertEqual(cll.optlen, 8)
315         self.assertEqual(cll.lltype, 1)
316         self.assertEqual(cll.clladdr, peer_mac)
317
318         id_len = len(vpn_id)
319
320         if fib_id != 0:
321             self.assertEqual(id_len, 0)
322             vss = pkt[DHCP6OptVSS]
323             self.assertEqual(vss.optlen, 8)
324             self.assertEqual(vss.type, 1)
325             # the OUI and FIB-id are really 3 and 4 bytes resp.
326             # but the tested range is small
327             self.assertEqual(ord(vss.data[0]), 0)
328             self.assertEqual(ord(vss.data[1]), 0)
329             self.assertEqual(ord(vss.data[2]), oui)
330             self.assertEqual(ord(vss.data[3]), 0)
331             self.assertEqual(ord(vss.data[4]), 0)
332             self.assertEqual(ord(vss.data[5]), 0)
333             self.assertEqual(ord(vss.data[6]), fib_id)
334
335         if id_len > 0:
336             self.assertEqual(oui, 0)
337             vss = pkt[DHCP6OptVSS]
338             self.assertEqual(vss.optlen, id_len+1)
339             self.assertEqual(vss.type, 0)
340             self.assertEqual(vss.data[0:id_len], vpn_id)
341
342         # the relay message should be an encoded Solicit
343         msg = pkt[DHCP6OptRelayMsg]
344         sol = DHCP6_Solicit()
345         self.assertEqual(msg.optlen, len(str(sol)))
346         self.assertEqual(str(sol), (str(msg[1]))[:msg.optlen])
347
348     def verify_dhcp6_advert(self, pkt, intf, peer):
349         ether = pkt[Ether]
350         self.assertEqual(ether.dst, "ff:ff:ff:ff:ff:ff")
351         self.assertEqual(ether.src, intf.local_mac)
352
353         ip = pkt[IPv6]
354         self.assertEqual(in6_ptop(ip.dst), in6_ptop(peer))
355         self.assertEqual(in6_ptop(ip.src), in6_ptop(intf.local_ip6))
356
357         udp = pkt[UDP]
358         self.assertEqual(udp.dport, DHCP6_SERVER_PORT)
359         self.assertEqual(udp.sport, DHCP6_CLIENT_PORT)
360
361         # not sure why this is not decoding
362         # adv = pkt[DHCP6_Advertise]
363
364     def wait_for_no_route(self, address, length,
365                           n_tries=50, s_time=1):
366         while (n_tries):
367             if not find_route(self, address, length):
368                 return True
369             n_tries = n_tries - 1
370             self.sleep(s_time)
371
372         return False
373
374     def test_dhcp_proxy(self):
375         """ DHCPv4 Proxy """
376
377         #
378         # Verify no response to DHCP request without DHCP config
379         #
380         p_disc_vrf0 = (Ether(dst="ff:ff:ff:ff:ff:ff",
381                              src=self.pg3.remote_mac) /
382                        IP(src="0.0.0.0", dst="255.255.255.255") /
383                        UDP(sport=DHCP4_CLIENT_PORT,
384                            dport=DHCP4_SERVER_PORT) /
385                        BOOTP(op=1) /
386                        DHCP(options=[('message-type', 'discover'), ('end')]))
387         pkts_disc_vrf0 = [p_disc_vrf0]
388         p_disc_vrf1 = (Ether(dst="ff:ff:ff:ff:ff:ff",
389                              src=self.pg4.remote_mac) /
390                        IP(src="0.0.0.0", dst="255.255.255.255") /
391                        UDP(sport=DHCP4_CLIENT_PORT,
392                            dport=DHCP4_SERVER_PORT) /
393                        BOOTP(op=1) /
394                        DHCP(options=[('message-type', 'discover'), ('end')]))
395         pkts_disc_vrf1 = [p_disc_vrf1]
396         p_disc_vrf2 = (Ether(dst="ff:ff:ff:ff:ff:ff",
397                              src=self.pg5.remote_mac) /
398                        IP(src="0.0.0.0", dst="255.255.255.255") /
399                        UDP(sport=DHCP4_CLIENT_PORT,
400                            dport=DHCP4_SERVER_PORT) /
401                        BOOTP(op=1) /
402                        DHCP(options=[('message-type', 'discover'), ('end')]))
403         pkts_disc_vrf2 = [p_disc_vrf2]
404
405         self.send_and_assert_no_replies(self.pg3, pkts_disc_vrf0,
406                                         "DHCP with no configuration")
407         self.send_and_assert_no_replies(self.pg4, pkts_disc_vrf1,
408                                         "DHCP with no configuration")
409         self.send_and_assert_no_replies(self.pg5, pkts_disc_vrf2,
410                                         "DHCP with no configuration")
411
412         #
413         # Enable DHCP proxy in VRF 0
414         #
415         server_addr = self.pg0.remote_ip4n
416         src_addr = self.pg0.local_ip4n
417
418         self.vapi.dhcp_proxy_config(server_addr,
419                                     src_addr,
420                                     rx_table_id=0)
421
422         #
423         # Discover packets from the client are dropped because there is no
424         # IP address configured on the client facing interface
425         #
426         self.send_and_assert_no_replies(self.pg3, pkts_disc_vrf0,
427                                         "Discover DHCP no relay address")
428
429         #
430         # Inject a response from the server
431         #  dropped, because there is no IP addrees on the
432         #  client interfce to fill in the option.
433         #
434         p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
435              IP(src=self.pg0.remote_ip4, dst=self.pg0.local_ip4) /
436              UDP(sport=DHCP4_SERVER_PORT, dport=DHCP4_SERVER_PORT) /
437              BOOTP(op=1) /
438              DHCP(options=[('message-type', 'offer'), ('end')]))
439         pkts = [p]
440
441         self.send_and_assert_no_replies(self.pg3, pkts,
442                                         "Offer DHCP no relay address")
443
444         #
445         # configure an IP address on the client facing interface
446         #
447         self.pg3.config_ip4()
448
449         #
450         # Try again with a discover packet
451         # Rx'd packet should be to the server address and from the configured
452         # source address
453         # UDP source ports are unchanged
454         # we've no option 82 config so that should be absent
455         #
456         self.pg3.add_stream(pkts_disc_vrf0)
457         self.pg_enable_capture(self.pg_interfaces)
458         self.pg_start()
459
460         rx = self.pg0.get_capture(1)
461         rx = rx[0]
462
463         option_82 = self.verify_relayed_dhcp_discover(rx, self.pg0,
464                                                       src_intf=self.pg3)
465
466         #
467         # Create an DHCP offer reply from the server with a correctly formatted
468         # option 82. i.e. send back what we just captured
469         # The offer, sent mcast to the client, still has option 82.
470         #
471         p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
472              IP(src=self.pg0.remote_ip4, dst=self.pg0.local_ip4) /
473              UDP(sport=DHCP4_SERVER_PORT, dport=DHCP4_SERVER_PORT) /
474              BOOTP(op=1) /
475              DHCP(options=[('message-type', 'offer'),
476                            ('relay_agent_Information', option_82),
477                            ('end')]))
478         pkts = [p]
479
480         self.pg0.add_stream(pkts)
481         self.pg_enable_capture(self.pg_interfaces)
482         self.pg_start()
483
484         rx = self.pg3.get_capture(1)
485         rx = rx[0]
486
487         self.verify_dhcp_offer(rx, self.pg3)
488
489         #
490         # Bogus Option 82:
491         #
492         # 1. not our IP address = not checked by VPP? so offer is replayed
493         #    to client
494         bad_ip = option_82[0:8] + scapy.compat.chb(33) + option_82[9:]
495
496         p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
497              IP(src=self.pg0.remote_ip4, dst=self.pg0.local_ip4) /
498              UDP(sport=DHCP4_SERVER_PORT, dport=DHCP4_SERVER_PORT) /
499              BOOTP(op=1) /
500              DHCP(options=[('message-type', 'offer'),
501                            ('relay_agent_Information', bad_ip),
502                            ('end')]))
503         pkts = [p]
504         self.send_and_assert_no_replies(self.pg0, pkts,
505                                         "DHCP offer option 82 bad address")
506
507         # 2. Not a sw_if_index VPP knows
508         bad_if_index = option_82[0:2] + scapy.compat.chb(33) + option_82[3:]
509
510         p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
511              IP(src=self.pg0.remote_ip4, dst=self.pg0.local_ip4) /
512              UDP(sport=DHCP4_SERVER_PORT, dport=DHCP4_SERVER_PORT) /
513              BOOTP(op=1) /
514              DHCP(options=[('message-type', 'offer'),
515                            ('relay_agent_Information', bad_if_index),
516                            ('end')]))
517         pkts = [p]
518         self.send_and_assert_no_replies(self.pg0, pkts,
519                                         "DHCP offer option 82 bad if index")
520
521         #
522         # Send a DHCP request in VRF 1. should be dropped.
523         #
524         self.send_and_assert_no_replies(self.pg4, pkts_disc_vrf1,
525                                         "DHCP with no configuration VRF 1")
526
527         #
528         # Delete the DHCP config in VRF 0
529         # Should now drop requests.
530         #
531         self.vapi.dhcp_proxy_config(server_addr,
532                                     src_addr,
533                                     rx_table_id=0,
534                                     is_add=0)
535
536         self.send_and_assert_no_replies(self.pg3, pkts_disc_vrf0,
537                                         "DHCP config removed VRF 0")
538         self.send_and_assert_no_replies(self.pg4, pkts_disc_vrf1,
539                                         "DHCP config removed VRF 1")
540
541         #
542         # Add DHCP config for VRF 1 & 2
543         #
544         server_addr1 = self.pg1.remote_ip4n
545         src_addr1 = self.pg1.local_ip4n
546         self.vapi.dhcp_proxy_config(server_addr1,
547                                     src_addr1,
548                                     rx_table_id=1,
549                                     server_table_id=1)
550         server_addr2 = self.pg2.remote_ip4n
551         src_addr2 = self.pg2.local_ip4n
552         self.vapi.dhcp_proxy_config(server_addr2,
553                                     src_addr2,
554                                     rx_table_id=2,
555                                     server_table_id=2)
556
557         #
558         # Confim DHCP requests ok in VRF 1 & 2.
559         #  - dropped on IP config on client interface
560         #
561         self.send_and_assert_no_replies(self.pg4, pkts_disc_vrf1,
562                                         "DHCP config removed VRF 1")
563         self.send_and_assert_no_replies(self.pg5, pkts_disc_vrf2,
564                                         "DHCP config removed VRF 2")
565
566         #
567         # configure an IP address on the client facing interface
568         #
569         self.pg4.config_ip4()
570         self.pg4.add_stream(pkts_disc_vrf1)
571         self.pg_enable_capture(self.pg_interfaces)
572         self.pg_start()
573         rx = self.pg1.get_capture(1)
574         rx = rx[0]
575         self.verify_relayed_dhcp_discover(rx, self.pg1, src_intf=self.pg4)
576
577         self.pg5.config_ip4()
578         self.pg5.add_stream(pkts_disc_vrf2)
579         self.pg_enable_capture(self.pg_interfaces)
580         self.pg_start()
581         rx = self.pg2.get_capture(1)
582         rx = rx[0]
583         self.verify_relayed_dhcp_discover(rx, self.pg2, src_intf=self.pg5)
584
585         #
586         # Add VSS config
587         #  table=1, vss_type=1, vpn_index=1, oui=4
588         #  table=2, vss_type=0, vpn_id = "ip4-table-2"
589         self.vapi.dhcp_proxy_set_vss(1, 1, vpn_index=1, oui=4, is_add=1)
590         self.vapi.dhcp_proxy_set_vss(2, 0, "ip4-table-2", is_add=1)
591
592         self.pg4.add_stream(pkts_disc_vrf1)
593         self.pg_enable_capture(self.pg_interfaces)
594         self.pg_start()
595
596         rx = self.pg1.get_capture(1)
597         rx = rx[0]
598         self.verify_relayed_dhcp_discover(rx, self.pg1,
599                                           src_intf=self.pg4,
600                                           fib_id=1, oui=4)
601
602         self.pg5.add_stream(pkts_disc_vrf2)
603         self.pg_enable_capture(self.pg_interfaces)
604         self.pg_start()
605
606         rx = self.pg2.get_capture(1)
607         rx = rx[0]
608         self.verify_relayed_dhcp_discover(rx, self.pg2,
609                                           src_intf=self.pg5,
610                                           vpn_id="ip4-table-2")
611
612         #
613         # Add a second DHCP server in VRF 1
614         #  expect clients messages to be relay to both configured servers
615         #
616         self.pg1.generate_remote_hosts(2)
617         server_addr12 = socket.inet_pton(AF_INET, self.pg1.remote_hosts[1].ip4)
618
619         self.vapi.dhcp_proxy_config(server_addr12,
620                                     src_addr1,
621                                     rx_table_id=1,
622                                     server_table_id=1,
623                                     is_add=1)
624
625         #
626         # We'll need an ARP entry for the server to send it packets
627         #
628         arp_entry = VppNeighbor(self,
629                                 self.pg1.sw_if_index,
630                                 self.pg1.remote_hosts[1].mac,
631                                 self.pg1.remote_hosts[1].ip4)
632         arp_entry.add_vpp_config()
633
634         #
635         # Send a discover from the client. expect two relayed messages
636         # The frist packet is sent to the second server
637         # We're not enforcing that here, it's just the way it is.
638         #
639         self.pg4.add_stream(pkts_disc_vrf1)
640         self.pg_enable_capture(self.pg_interfaces)
641         self.pg_start()
642
643         rx = self.pg1.get_capture(2)
644
645         option_82 = self.verify_relayed_dhcp_discover(
646             rx[0], self.pg1,
647             src_intf=self.pg4,
648             dst_mac=self.pg1.remote_hosts[1].mac,
649             dst_ip=self.pg1.remote_hosts[1].ip4,
650             fib_id=1, oui=4)
651         self.verify_relayed_dhcp_discover(rx[1], self.pg1,
652                                           src_intf=self.pg4,
653                                           fib_id=1, oui=4)
654
655         #
656         # Send both packets back. Client gets both.
657         #
658         p1 = (Ether(dst=self.pg1.local_mac, src=self.pg1.remote_mac) /
659               IP(src=self.pg1.remote_ip4, dst=self.pg1.local_ip4) /
660               UDP(sport=DHCP4_SERVER_PORT, dport=DHCP4_SERVER_PORT) /
661               BOOTP(op=1) /
662               DHCP(options=[('message-type', 'offer'),
663                             ('relay_agent_Information', option_82),
664                             ('end')]))
665         p2 = (Ether(dst=self.pg1.local_mac, src=self.pg1.remote_mac) /
666               IP(src=self.pg1.remote_hosts[1].ip4, dst=self.pg1.local_ip4) /
667               UDP(sport=DHCP4_SERVER_PORT, dport=DHCP4_SERVER_PORT) /
668               BOOTP(op=1) /
669               DHCP(options=[('message-type', 'offer'),
670                             ('relay_agent_Information', option_82),
671                             ('end')]))
672         pkts = [p1, p2]
673
674         self.pg1.add_stream(pkts)
675         self.pg_enable_capture(self.pg_interfaces)
676         self.pg_start()
677
678         rx = self.pg4.get_capture(2)
679
680         self.verify_dhcp_offer(rx[0], self.pg4, fib_id=1, oui=4)
681         self.verify_dhcp_offer(rx[1], self.pg4, fib_id=1, oui=4)
682
683         #
684         # Ensure offers from non-servers are dropeed
685         #
686         p2 = (Ether(dst=self.pg1.local_mac, src=self.pg1.remote_mac) /
687               IP(src="8.8.8.8", dst=self.pg1.local_ip4) /
688               UDP(sport=DHCP4_SERVER_PORT, dport=DHCP4_SERVER_PORT) /
689               BOOTP(op=1) /
690               DHCP(options=[('message-type', 'offer'),
691                             ('relay_agent_Information', option_82),
692                             ('end')]))
693         self.send_and_assert_no_replies(self.pg1, p2,
694                                         "DHCP offer from non-server")
695
696         #
697         # Ensure only the discover is sent to multiple servers
698         #
699         p_req_vrf1 = (Ether(dst="ff:ff:ff:ff:ff:ff",
700                             src=self.pg4.remote_mac) /
701                       IP(src="0.0.0.0", dst="255.255.255.255") /
702                       UDP(sport=DHCP4_CLIENT_PORT,
703                           dport=DHCP4_SERVER_PORT) /
704                       BOOTP(op=1) /
705                       DHCP(options=[('message-type', 'request'),
706                                     ('end')]))
707
708         self.pg4.add_stream(p_req_vrf1)
709         self.pg_enable_capture(self.pg_interfaces)
710         self.pg_start()
711
712         rx = self.pg1.get_capture(1)
713
714         #
715         # Remove the second DHCP server
716         #
717         self.vapi.dhcp_proxy_config(server_addr12,
718                                     src_addr1,
719                                     rx_table_id=1,
720                                     server_table_id=1,
721                                     is_add=0)
722
723         #
724         # Test we can still relay with the first
725         #
726         self.pg4.add_stream(pkts_disc_vrf1)
727         self.pg_enable_capture(self.pg_interfaces)
728         self.pg_start()
729
730         rx = self.pg1.get_capture(1)
731         rx = rx[0]
732         self.verify_relayed_dhcp_discover(rx, self.pg1,
733                                           src_intf=self.pg4,
734                                           fib_id=1, oui=4)
735
736         #
737         # Remove the VSS config
738         #  relayed DHCP has default vlaues in the option.
739         #
740         self.vapi.dhcp_proxy_set_vss(1, is_add=0)
741         self.vapi.dhcp_proxy_set_vss(2, is_add=0)
742
743         self.pg4.add_stream(pkts_disc_vrf1)
744         self.pg_enable_capture(self.pg_interfaces)
745         self.pg_start()
746
747         rx = self.pg1.get_capture(1)
748         rx = rx[0]
749         self.verify_relayed_dhcp_discover(rx, self.pg1, src_intf=self.pg4)
750
751         #
752         # remove DHCP config to cleanup
753         #
754         self.vapi.dhcp_proxy_config(server_addr1,
755                                     src_addr1,
756                                     rx_table_id=1,
757                                     server_table_id=1,
758                                     is_add=0)
759         self.vapi.dhcp_proxy_config(server_addr2,
760                                     src_addr2,
761                                     rx_table_id=2,
762                                     server_table_id=2,
763                                     is_add=0)
764
765         self.send_and_assert_no_replies(self.pg3, pkts_disc_vrf0,
766                                         "DHCP cleanup VRF 0")
767         self.send_and_assert_no_replies(self.pg4, pkts_disc_vrf1,
768                                         "DHCP cleanup VRF 1")
769         self.send_and_assert_no_replies(self.pg5, pkts_disc_vrf2,
770                                         "DHCP cleanup VRF 2")
771
772         self.pg3.unconfig_ip4()
773         self.pg4.unconfig_ip4()
774         self.pg5.unconfig_ip4()
775
776     def test_dhcp6_proxy(self):
777         """ DHCPv6 Proxy"""
778         #
779         # Verify no response to DHCP request without DHCP config
780         #
781         dhcp_solicit_dst = "ff02::1:2"
782         dhcp_solicit_src_vrf0 = mk_ll_addr(self.pg3.remote_mac)
783         dhcp_solicit_src_vrf1 = mk_ll_addr(self.pg4.remote_mac)
784         dhcp_solicit_src_vrf2 = mk_ll_addr(self.pg5.remote_mac)
785         server_addr_vrf0 = self.pg0.remote_ip6n
786         src_addr_vrf0 = self.pg0.local_ip6n
787         server_addr_vrf1 = self.pg1.remote_ip6n
788         src_addr_vrf1 = self.pg1.local_ip6n
789         server_addr_vrf2 = self.pg2.remote_ip6n
790         src_addr_vrf2 = self.pg2.local_ip6n
791
792         dmac = in6_getnsmac(inet_pton(socket.AF_INET6, dhcp_solicit_dst))
793         p_solicit_vrf0 = (Ether(dst=dmac, src=self.pg3.remote_mac) /
794                           IPv6(src=dhcp_solicit_src_vrf0,
795                                dst=dhcp_solicit_dst) /
796                           UDP(sport=DHCP6_SERVER_PORT,
797                               dport=DHCP6_CLIENT_PORT) /
798                           DHCP6_Solicit())
799         p_solicit_vrf1 = (Ether(dst=dmac, src=self.pg4.remote_mac) /
800                           IPv6(src=dhcp_solicit_src_vrf1,
801                                dst=dhcp_solicit_dst) /
802                           UDP(sport=DHCP6_SERVER_PORT,
803                               dport=DHCP6_CLIENT_PORT) /
804                           DHCP6_Solicit())
805         p_solicit_vrf2 = (Ether(dst=dmac, src=self.pg5.remote_mac) /
806                           IPv6(src=dhcp_solicit_src_vrf2,
807                                dst=dhcp_solicit_dst) /
808                           UDP(sport=DHCP6_SERVER_PORT,
809                               dport=DHCP6_CLIENT_PORT) /
810                           DHCP6_Solicit())
811
812         self.send_and_assert_no_replies(self.pg3, p_solicit_vrf0,
813                                         "DHCP with no configuration")
814         self.send_and_assert_no_replies(self.pg4, p_solicit_vrf1,
815                                         "DHCP with no configuration")
816         self.send_and_assert_no_replies(self.pg5, p_solicit_vrf2,
817                                         "DHCP with no configuration")
818
819         #
820         # DHCPv6 config in VRF 0.
821         # Packets still dropped because the client facing interface has no
822         # IPv6 config
823         #
824         self.vapi.dhcp_proxy_config(server_addr_vrf0,
825                                     src_addr_vrf0,
826                                     rx_table_id=0,
827                                     server_table_id=0,
828                                     is_ipv6=1)
829
830         self.send_and_assert_no_replies(self.pg3, p_solicit_vrf0,
831                                         "DHCP with no configuration")
832         self.send_and_assert_no_replies(self.pg4, p_solicit_vrf1,
833                                         "DHCP with no configuration")
834
835         #
836         # configure an IP address on the client facing interface
837         #
838         self.pg3.config_ip6()
839
840         #
841         # Now the DHCP requests are relayed to the server
842         #
843         self.pg3.add_stream(p_solicit_vrf0)
844         self.pg_enable_capture(self.pg_interfaces)
845         self.pg_start()
846
847         rx = self.pg0.get_capture(1)
848
849         self.verify_dhcp6_solicit(rx[0], self.pg0,
850                                   dhcp_solicit_src_vrf0,
851                                   self.pg3.remote_mac)
852
853         #
854         # Exception cases for rejected relay responses
855         #
856
857         # 1 - not a relay reply
858         p_adv_vrf0 = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
859                       IPv6(dst=self.pg0.local_ip6, src=self.pg0.remote_ip6) /
860                       UDP(sport=DHCP6_SERVER_PORT, dport=DHCP6_SERVER_PORT) /
861                       DHCP6_Advertise())
862         self.send_and_assert_no_replies(self.pg3, p_adv_vrf0,
863                                         "DHCP6 not a relay reply")
864
865         # 2 - no relay message option
866         p_adv_vrf0 = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
867                       IPv6(dst=self.pg0.local_ip6, src=self.pg0.remote_ip6) /
868                       UDP(sport=DHCP6_SERVER_PORT, dport=DHCP6_SERVER_PORT) /
869                       DHCP6_RelayReply() /
870                       DHCP6_Advertise())
871         self.send_and_assert_no_replies(self.pg3, p_adv_vrf0,
872                                         "DHCP not a relay message")
873
874         # 3 - no circuit ID
875         p_adv_vrf0 = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
876                       IPv6(dst=self.pg0.local_ip6, src=self.pg0.remote_ip6) /
877                       UDP(sport=DHCP6_SERVER_PORT, dport=DHCP6_SERVER_PORT) /
878                       DHCP6_RelayReply() /
879                       DHCP6OptRelayMsg(optlen=0) /
880                       DHCP6_Advertise())
881         self.send_and_assert_no_replies(self.pg3, p_adv_vrf0,
882                                         "DHCP6 no circuit ID")
883         # 4 - wrong circuit ID
884         p_adv_vrf0 = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
885                       IPv6(dst=self.pg0.local_ip6, src=self.pg0.remote_ip6) /
886                       UDP(sport=DHCP6_SERVER_PORT, dport=DHCP6_SERVER_PORT) /
887                       DHCP6_RelayReply() /
888                       DHCP6OptIfaceId(optlen=4, ifaceid='\x00\x00\x00\x05') /
889                       DHCP6OptRelayMsg(optlen=0) /
890                       DHCP6_Advertise())
891         self.send_and_assert_no_replies(self.pg3, p_adv_vrf0,
892                                         "DHCP6 wrong circuit ID")
893
894         #
895         # Send the relay response (the advertisement)
896         #   - no peer address
897         p_adv_vrf0 = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
898                       IPv6(dst=self.pg0.local_ip6, src=self.pg0.remote_ip6) /
899                       UDP(sport=DHCP6_SERVER_PORT, dport=DHCP6_SERVER_PORT) /
900                       DHCP6_RelayReply() /
901                       DHCP6OptIfaceId(optlen=4, ifaceid='\x00\x00\x00\x04') /
902                       DHCP6OptRelayMsg(optlen=0) /
903                       DHCP6_Advertise(trid=1) /
904                       DHCP6OptStatusCode(statuscode=0))
905         pkts_adv_vrf0 = [p_adv_vrf0]
906
907         self.pg0.add_stream(pkts_adv_vrf0)
908         self.pg_enable_capture(self.pg_interfaces)
909         self.pg_start()
910
911         rx = self.pg3.get_capture(1)
912
913         self.verify_dhcp6_advert(rx[0], self.pg3, "::")
914
915         #
916         # Send the relay response (the advertisement)
917         #   - with peer address
918         p_adv_vrf0 = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
919                       IPv6(dst=self.pg0.local_ip6, src=self.pg0.remote_ip6) /
920                       UDP(sport=DHCP6_SERVER_PORT, dport=DHCP6_SERVER_PORT) /
921                       DHCP6_RelayReply(peeraddr=dhcp_solicit_src_vrf0) /
922                       DHCP6OptIfaceId(optlen=4, ifaceid='\x00\x00\x00\x04') /
923                       DHCP6OptRelayMsg(optlen=0) /
924                       DHCP6_Advertise(trid=1) /
925                       DHCP6OptStatusCode(statuscode=0))
926         pkts_adv_vrf0 = [p_adv_vrf0]
927
928         self.pg0.add_stream(pkts_adv_vrf0)
929         self.pg_enable_capture(self.pg_interfaces)
930         self.pg_start()
931
932         rx = self.pg3.get_capture(1)
933
934         self.verify_dhcp6_advert(rx[0], self.pg3, dhcp_solicit_src_vrf0)
935
936         #
937         # Add all the config for VRF 1 & 2
938         #
939         self.vapi.dhcp_proxy_config(server_addr_vrf1,
940                                     src_addr_vrf1,
941                                     rx_table_id=1,
942                                     server_table_id=1,
943                                     is_ipv6=1)
944         self.pg4.config_ip6()
945
946         self.vapi.dhcp_proxy_config(server_addr_vrf2,
947                                     src_addr_vrf2,
948                                     rx_table_id=2,
949                                     server_table_id=2,
950                                     is_ipv6=1)
951         self.pg5.config_ip6()
952
953         #
954         # VRF 1 solicit
955         #
956         self.pg4.add_stream(p_solicit_vrf1)
957         self.pg_enable_capture(self.pg_interfaces)
958         self.pg_start()
959
960         rx = self.pg1.get_capture(1)
961
962         self.verify_dhcp6_solicit(rx[0], self.pg1,
963                                   dhcp_solicit_src_vrf1,
964                                   self.pg4.remote_mac)
965
966         #
967         # VRF 2 solicit
968         #
969         self.pg5.add_stream(p_solicit_vrf2)
970         self.pg_enable_capture(self.pg_interfaces)
971         self.pg_start()
972
973         rx = self.pg2.get_capture(1)
974
975         self.verify_dhcp6_solicit(rx[0], self.pg2,
976                                   dhcp_solicit_src_vrf2,
977                                   self.pg5.remote_mac)
978
979         #
980         # VRF 1 Advert
981         #
982         p_adv_vrf1 = (Ether(dst=self.pg1.local_mac, src=self.pg1.remote_mac) /
983                       IPv6(dst=self.pg1.local_ip6, src=self.pg1.remote_ip6) /
984                       UDP(sport=DHCP6_SERVER_PORT, dport=DHCP6_SERVER_PORT) /
985                       DHCP6_RelayReply(peeraddr=dhcp_solicit_src_vrf1) /
986                       DHCP6OptIfaceId(optlen=4, ifaceid='\x00\x00\x00\x05') /
987                       DHCP6OptRelayMsg(optlen=0) /
988                       DHCP6_Advertise(trid=1) /
989                       DHCP6OptStatusCode(statuscode=0))
990         pkts_adv_vrf1 = [p_adv_vrf1]
991
992         self.pg1.add_stream(pkts_adv_vrf1)
993         self.pg_enable_capture(self.pg_interfaces)
994         self.pg_start()
995
996         rx = self.pg4.get_capture(1)
997
998         self.verify_dhcp6_advert(rx[0], self.pg4, dhcp_solicit_src_vrf1)
999
1000         #
1001         # Add VSS config
1002         #  table=1, vss_type=1, vpn_index=1, oui=4
1003         #  table=2, vss_type=0, vpn_id = "ip6-table-2"
1004         self.vapi.dhcp_proxy_set_vss(1, 1, oui=4, vpn_index=1, is_ip6=1)
1005         self.vapi.dhcp_proxy_set_vss(2, 0, "IPv6-table-2", is_ip6=1)
1006
1007         self.pg4.add_stream(p_solicit_vrf1)
1008         self.pg_enable_capture(self.pg_interfaces)
1009         self.pg_start()
1010
1011         rx = self.pg1.get_capture(1)
1012
1013         self.verify_dhcp6_solicit(rx[0], self.pg1,
1014                                   dhcp_solicit_src_vrf1,
1015                                   self.pg4.remote_mac,
1016                                   fib_id=1,
1017                                   oui=4)
1018
1019         self.pg5.add_stream(p_solicit_vrf2)
1020         self.pg_enable_capture(self.pg_interfaces)
1021         self.pg_start()
1022
1023         rx = self.pg2.get_capture(1)
1024
1025         self.verify_dhcp6_solicit(rx[0], self.pg2,
1026                                   dhcp_solicit_src_vrf2,
1027                                   self.pg5.remote_mac,
1028                                   vpn_id="IPv6-table-2")
1029
1030         #
1031         # Remove the VSS config
1032         #  relayed DHCP has default vlaues in the option.
1033         #
1034         self.vapi.dhcp_proxy_set_vss(1, is_ip6=1, is_add=0)
1035
1036         self.pg4.add_stream(p_solicit_vrf1)
1037         self.pg_enable_capture(self.pg_interfaces)
1038         self.pg_start()
1039
1040         rx = self.pg1.get_capture(1)
1041
1042         self.verify_dhcp6_solicit(rx[0], self.pg1,
1043                                   dhcp_solicit_src_vrf1,
1044                                   self.pg4.remote_mac)
1045
1046         #
1047         # Add a second DHCP server in VRF 1
1048         #  expect clients messages to be relay to both configured servers
1049         #
1050         self.pg1.generate_remote_hosts(2)
1051         server_addr12 = socket.inet_pton(AF_INET6,
1052                                          self.pg1.remote_hosts[1].ip6)
1053
1054         self.vapi.dhcp_proxy_config(server_addr12,
1055                                     src_addr_vrf1,
1056                                     rx_table_id=1,
1057                                     server_table_id=1,
1058                                     is_ipv6=1)
1059
1060         #
1061         # We'll need an ND entry for the server to send it packets
1062         #
1063         nd_entry = VppNeighbor(self,
1064                                self.pg1.sw_if_index,
1065                                self.pg1.remote_hosts[1].mac,
1066                                self.pg1.remote_hosts[1].ip6)
1067         nd_entry.add_vpp_config()
1068
1069         #
1070         # Send a discover from the client. expect two relayed messages
1071         # The frist packet is sent to the second server
1072         # We're not enforcing that here, it's just the way it is.
1073         #
1074         self.pg4.add_stream(p_solicit_vrf1)
1075         self.pg_enable_capture(self.pg_interfaces)
1076         self.pg_start()
1077
1078         rx = self.pg1.get_capture(2)
1079
1080         self.verify_dhcp6_solicit(rx[0], self.pg1,
1081                                   dhcp_solicit_src_vrf1,
1082                                   self.pg4.remote_mac)
1083         self.verify_dhcp6_solicit(rx[1], self.pg1,
1084                                   dhcp_solicit_src_vrf1,
1085                                   self.pg4.remote_mac,
1086                                   dst_mac=self.pg1.remote_hosts[1].mac,
1087                                   dst_ip=self.pg1.remote_hosts[1].ip6)
1088
1089         #
1090         # Send both packets back. Client gets both.
1091         #
1092         p1 = (Ether(dst=self.pg1.local_mac, src=self.pg1.remote_mac) /
1093               IPv6(dst=self.pg1.local_ip6, src=self.pg1.remote_ip6) /
1094               UDP(sport=DHCP6_SERVER_PORT, dport=DHCP6_SERVER_PORT) /
1095               DHCP6_RelayReply(peeraddr=dhcp_solicit_src_vrf1) /
1096               DHCP6OptIfaceId(optlen=4, ifaceid='\x00\x00\x00\x05') /
1097               DHCP6OptRelayMsg(optlen=0) /
1098               DHCP6_Advertise(trid=1) /
1099               DHCP6OptStatusCode(statuscode=0))
1100         p2 = (Ether(dst=self.pg1.local_mac, src=self.pg1.remote_hosts[1].mac) /
1101               IPv6(dst=self.pg1.local_ip6, src=self.pg1._remote_hosts[1].ip6) /
1102               UDP(sport=DHCP6_SERVER_PORT, dport=DHCP6_SERVER_PORT) /
1103               DHCP6_RelayReply(peeraddr=dhcp_solicit_src_vrf1) /
1104               DHCP6OptIfaceId(optlen=4, ifaceid='\x00\x00\x00\x05') /
1105               DHCP6OptRelayMsg(optlen=0) /
1106               DHCP6_Advertise(trid=1) /
1107               DHCP6OptStatusCode(statuscode=0))
1108
1109         pkts = [p1, p2]
1110
1111         self.pg1.add_stream(pkts)
1112         self.pg_enable_capture(self.pg_interfaces)
1113         self.pg_start()
1114
1115         rx = self.pg4.get_capture(2)
1116
1117         self.verify_dhcp6_advert(rx[0], self.pg4, dhcp_solicit_src_vrf1)
1118         self.verify_dhcp6_advert(rx[1], self.pg4, dhcp_solicit_src_vrf1)
1119
1120         #
1121         # Ensure only solicit messages are duplicated
1122         #
1123         p_request_vrf1 = (Ether(dst=dmac, src=self.pg4.remote_mac) /
1124                           IPv6(src=dhcp_solicit_src_vrf1,
1125                                dst=dhcp_solicit_dst) /
1126                           UDP(sport=DHCP6_SERVER_PORT,
1127                               dport=DHCP6_CLIENT_PORT) /
1128                           DHCP6_Request())
1129
1130         self.pg4.add_stream(p_request_vrf1)
1131         self.pg_enable_capture(self.pg_interfaces)
1132         self.pg_start()
1133
1134         rx = self.pg1.get_capture(1)
1135
1136         #
1137         # Test we drop DHCP packets from addresses that are not configured as
1138         # DHCP servers
1139         #
1140         p2 = (Ether(dst=self.pg1.local_mac, src=self.pg1.remote_hosts[1].mac) /
1141               IPv6(dst=self.pg1.local_ip6, src="3001::1") /
1142               UDP(sport=DHCP6_SERVER_PORT, dport=DHCP6_SERVER_PORT) /
1143               DHCP6_RelayReply(peeraddr=dhcp_solicit_src_vrf1) /
1144               DHCP6OptIfaceId(optlen=4, ifaceid='\x00\x00\x00\x05') /
1145               DHCP6OptRelayMsg(optlen=0) /
1146               DHCP6_Advertise(trid=1) /
1147               DHCP6OptStatusCode(statuscode=0))
1148         self.send_and_assert_no_replies(self.pg1, p2,
1149                                         "DHCP6 not from server")
1150
1151         #
1152         # Remove the second DHCP server
1153         #
1154         self.vapi.dhcp_proxy_config(server_addr12,
1155                                     src_addr_vrf1,
1156                                     rx_table_id=1,
1157                                     server_table_id=1,
1158                                     is_ipv6=1,
1159                                     is_add=0)
1160
1161         #
1162         # Test we can still relay with the first
1163         #
1164         self.pg4.add_stream(p_solicit_vrf1)
1165         self.pg_enable_capture(self.pg_interfaces)
1166         self.pg_start()
1167
1168         rx = self.pg1.get_capture(1)
1169
1170         self.verify_dhcp6_solicit(rx[0], self.pg1,
1171                                   dhcp_solicit_src_vrf1,
1172                                   self.pg4.remote_mac)
1173
1174         #
1175         # Cleanup
1176         #
1177         self.vapi.dhcp_proxy_config(server_addr_vrf2,
1178                                     src_addr_vrf2,
1179                                     rx_table_id=2,
1180                                     server_table_id=2,
1181                                     is_ipv6=1,
1182                                     is_add=0)
1183         self.vapi.dhcp_proxy_config(server_addr_vrf1,
1184                                     src_addr_vrf1,
1185                                     rx_table_id=1,
1186                                     server_table_id=1,
1187                                     is_ipv6=1,
1188                                     is_add=0)
1189         self.vapi.dhcp_proxy_config(server_addr_vrf0,
1190                                     src_addr_vrf0,
1191                                     rx_table_id=0,
1192                                     server_table_id=0,
1193                                     is_ipv6=1,
1194                                     is_add=0)
1195
1196         # duplicate delete
1197         self.vapi.dhcp_proxy_config(server_addr_vrf0,
1198                                     src_addr_vrf0,
1199                                     rx_table_id=0,
1200                                     server_table_id=0,
1201                                     is_ipv6=1,
1202                                     is_add=0)
1203         self.pg3.unconfig_ip6()
1204         self.pg4.unconfig_ip6()
1205         self.pg5.unconfig_ip6()
1206
1207     def test_dhcp_client(self):
1208         """ DHCP Client"""
1209
1210         hostname = 'universal-dp'
1211
1212         self.pg_enable_capture(self.pg_interfaces)
1213
1214         #
1215         # Configure DHCP client on PG3 and capture the discover sent
1216         #
1217         self.vapi.dhcp_client_config(self.pg3.sw_if_index, hostname)
1218
1219         rx = self.pg3.get_capture(1)
1220
1221         self.verify_orig_dhcp_discover(rx[0], self.pg3, hostname)
1222
1223         #
1224         # Send back on offer, expect the request
1225         #
1226         p_offer = (Ether(dst=self.pg3.local_mac, src=self.pg3.remote_mac) /
1227                    IP(src=self.pg3.remote_ip4, dst="255.255.255.255") /
1228                    UDP(sport=DHCP4_SERVER_PORT, dport=DHCP4_CLIENT_PORT) /
1229                    BOOTP(op=1,
1230                          yiaddr=self.pg3.local_ip4,
1231                          chaddr=mac_pton(self.pg3.local_mac)) /
1232                    DHCP(options=[('message-type', 'offer'),
1233                                  ('server_id', self.pg3.remote_ip4),
1234                                  'end']))
1235
1236         self.pg3.add_stream(p_offer)
1237         self.pg_enable_capture(self.pg_interfaces)
1238         self.pg_start()
1239
1240         rx = self.pg3.get_capture(1)
1241         self.verify_orig_dhcp_request(rx[0], self.pg3, hostname,
1242                                       self.pg3.local_ip4)
1243
1244         #
1245         # Send an acknowledgment
1246         #
1247         p_ack = (Ether(dst=self.pg3.local_mac, src=self.pg3.remote_mac) /
1248                  IP(src=self.pg3.remote_ip4, dst="255.255.255.255") /
1249                  UDP(sport=DHCP4_SERVER_PORT, dport=DHCP4_CLIENT_PORT) /
1250                  BOOTP(op=1, yiaddr=self.pg3.local_ip4,
1251                        chaddr=mac_pton(self.pg3.local_mac)) /
1252                  DHCP(options=[('message-type', 'ack'),
1253                                ('subnet_mask', "255.255.255.0"),
1254                                ('router', self.pg3.remote_ip4),
1255                                ('server_id', self.pg3.remote_ip4),
1256                                ('lease_time', 43200),
1257                                'end']))
1258
1259         self.pg3.add_stream(p_ack)
1260         self.pg_enable_capture(self.pg_interfaces)
1261         self.pg_start()
1262
1263         #
1264         # We'll get an ARP request for the router address
1265         #
1266         rx = self.pg3.get_capture(1)
1267
1268         self.assertEqual(rx[0][ARP].pdst, self.pg3.remote_ip4)
1269         self.pg_enable_capture(self.pg_interfaces)
1270
1271         #
1272         # At the end of this procedure there should be a connected route
1273         # in the FIB
1274         #
1275         self.assertTrue(find_route(self, self.pg3.local_ip4, 24))
1276         self.assertTrue(find_route(self, self.pg3.local_ip4, 32))
1277
1278         # remove the left over ARP entry
1279         self.vapi.ip_neighbor_add_del(self.pg3.sw_if_index,
1280                                       self.pg3.remote_mac,
1281                                       self.pg3.remote_ip4,
1282                                       is_add=0)
1283
1284         #
1285         # remove the DHCP config
1286         #
1287         self.vapi.dhcp_client_config(self.pg3.sw_if_index, hostname, is_add=0)
1288
1289         #
1290         # and now the route should be gone
1291         #
1292         self.assertFalse(find_route(self, self.pg3.local_ip4, 32))
1293         self.assertFalse(find_route(self, self.pg3.local_ip4, 24))
1294
1295         #
1296         # Start the procedure again. this time have VPP send the client-ID
1297         #
1298         self.pg3.admin_down()
1299         self.sleep(1)
1300         self.pg3.admin_up()
1301         self.vapi.dhcp_client_config(self.pg3.sw_if_index, hostname,
1302                                      client_id=self.pg3.local_mac)
1303
1304         rx = self.pg3.get_capture(1)
1305
1306         self.verify_orig_dhcp_discover(rx[0], self.pg3, hostname,
1307                                        self.pg3.local_mac)
1308
1309         # TODO: VPP DHCP client should not accept DHCP OFFER message with
1310         # the XID (Transaction ID) not matching the XID of the most recent
1311         # DHCP DISCOVERY message.
1312         # Such DHCP OFFER message must be silently discarded - RFC2131.
1313         # Reported in Jira ticket: VPP-99
1314         self.pg3.add_stream(p_offer)
1315         self.pg_enable_capture(self.pg_interfaces)
1316         self.pg_start()
1317
1318         rx = self.pg3.get_capture(1)
1319         self.verify_orig_dhcp_request(rx[0], self.pg3, hostname,
1320                                       self.pg3.local_ip4)
1321
1322         #
1323         # unicast the ack to the offered address
1324         #
1325         p_ack = (Ether(dst=self.pg3.local_mac, src=self.pg3.remote_mac) /
1326                  IP(src=self.pg3.remote_ip4, dst=self.pg3.local_ip4) /
1327                  UDP(sport=DHCP4_SERVER_PORT, dport=DHCP4_CLIENT_PORT) /
1328                  BOOTP(op=1, yiaddr=self.pg3.local_ip4,
1329                        chaddr=mac_pton(self.pg3.local_mac)) /
1330                  DHCP(options=[('message-type', 'ack'),
1331                                ('subnet_mask', "255.255.255.0"),
1332                                ('router', self.pg3.remote_ip4),
1333                                ('server_id', self.pg3.remote_ip4),
1334                                ('lease_time', 43200),
1335                                'end']))
1336
1337         self.pg3.add_stream(p_ack)
1338         self.pg_enable_capture(self.pg_interfaces)
1339         self.pg_start()
1340
1341         #
1342         # We'll get an ARP request for the router address
1343         #
1344         rx = self.pg3.get_capture(1)
1345
1346         self.assertEqual(rx[0][ARP].pdst, self.pg3.remote_ip4)
1347         self.pg_enable_capture(self.pg_interfaces)
1348
1349         #
1350         # At the end of this procedure there should be a connected route
1351         # in the FIB
1352         #
1353         self.assertTrue(find_route(self, self.pg3.local_ip4, 32))
1354         self.assertTrue(find_route(self, self.pg3.local_ip4, 24))
1355
1356         #
1357         # remove the DHCP config
1358         #
1359         self.vapi.dhcp_client_config(self.pg3.sw_if_index, hostname, is_add=0)
1360
1361         self.assertFalse(find_route(self, self.pg3.local_ip4, 32))
1362         self.assertFalse(find_route(self, self.pg3.local_ip4, 24))
1363
1364         #
1365         # Rince and repeat, this time with VPP configured not to set
1366         # the braodcast flag in the discover and request messages,
1367         # and for the server to unicast the responses.
1368         #
1369         # Configure DHCP client on PG3 and capture the discover sent
1370         #
1371         self.vapi.dhcp_client_config(self.pg3.sw_if_index, hostname,
1372                                      set_broadcast_flag=0)
1373
1374         rx = self.pg3.get_capture(1)
1375
1376         self.verify_orig_dhcp_discover(rx[0], self.pg3, hostname,
1377                                        broadcast=0)
1378
1379         #
1380         # Send back on offer, unicasted to the offered address.
1381         # Expect the request.
1382         #
1383         p_offer = (Ether(dst=self.pg3.local_mac, src=self.pg3.remote_mac) /
1384                    IP(src=self.pg3.remote_ip4, dst=self.pg3.local_ip4) /
1385                    UDP(sport=DHCP4_SERVER_PORT, dport=DHCP4_CLIENT_PORT) /
1386                    BOOTP(op=1, yiaddr=self.pg3.local_ip4,
1387                          chaddr=mac_pton(self.pg3.local_mac)) /
1388                    DHCP(options=[('message-type', 'offer'),
1389                                  ('server_id', self.pg3.remote_ip4),
1390                                  'end']))
1391
1392         self.pg3.add_stream(p_offer)
1393         self.pg_enable_capture(self.pg_interfaces)
1394         self.pg_start()
1395
1396         rx = self.pg3.get_capture(1)
1397         self.verify_orig_dhcp_request(rx[0], self.pg3, hostname,
1398                                       self.pg3.local_ip4,
1399                                       broadcast=0)
1400
1401         #
1402         # Send an acknowledgment
1403         #
1404         p_ack = (Ether(dst=self.pg3.local_mac, src=self.pg3.remote_mac) /
1405                  IP(src=self.pg3.remote_ip4, dst=self.pg3.local_ip4) /
1406                  UDP(sport=DHCP4_SERVER_PORT, dport=DHCP4_CLIENT_PORT) /
1407                  BOOTP(op=1, yiaddr=self.pg3.local_ip4,
1408                        chaddr=mac_pton(self.pg3.local_mac)) /
1409                  DHCP(options=[('message-type', 'ack'),
1410                                ('subnet_mask', "255.255.255.0"),
1411                                ('router', self.pg3.remote_ip4),
1412                                ('server_id', self.pg3.remote_ip4),
1413                                ('lease_time', 43200),
1414                                'end']))
1415
1416         self.pg3.add_stream(p_ack)
1417         self.pg_enable_capture(self.pg_interfaces)
1418         self.pg_start()
1419
1420         #
1421         # We'll get an ARP request for the router address
1422         #
1423         rx = self.pg3.get_capture(1)
1424
1425         self.assertEqual(rx[0][ARP].pdst, self.pg3.remote_ip4)
1426         self.pg_enable_capture(self.pg_interfaces)
1427
1428         #
1429         # At the end of this procedure there should be a connected route
1430         # in the FIB
1431         #
1432         self.assertTrue(find_route(self, self.pg3.local_ip4, 24))
1433         self.assertTrue(find_route(self, self.pg3.local_ip4, 32))
1434
1435         # remove the left over ARP entry
1436         self.vapi.ip_neighbor_add_del(self.pg3.sw_if_index,
1437                                       self.pg3.remote_mac,
1438                                       self.pg3.remote_ip4,
1439                                       is_add=0)
1440
1441         #
1442         # read the DHCP client details from a dump
1443         #
1444         clients = self.vapi.dhcp_client_dump()
1445
1446         self.assertEqual(clients[0].client.sw_if_index,
1447                          self.pg3.sw_if_index)
1448         self.assertEqual(clients[0].lease.sw_if_index,
1449                          self.pg3.sw_if_index)
1450         self.assertEqual(clients[0].client.hostname.rstrip('\0'),
1451                          hostname)
1452         self.assertEqual(clients[0].lease.hostname.rstrip('\0'),
1453                          hostname)
1454         self.assertEqual(clients[0].lease.is_ipv6, 0)
1455         # 0 = DISCOVER, 1 = REQUEST, 2 = BOUND
1456         self.assertEqual(clients[0].lease.state, 2)
1457         self.assertEqual(clients[0].lease.mask_width, 24)
1458         self.assertEqual(clients[0].lease.router_address.rstrip('\0'),
1459                          self.pg3.remote_ip4n)
1460         self.assertEqual(clients[0].lease.host_address.rstrip('\0'),
1461                          self.pg3.local_ip4n)
1462
1463         #
1464         # remove the DHCP config
1465         #
1466         self.vapi.dhcp_client_config(self.pg3.sw_if_index, hostname, is_add=0)
1467
1468         #
1469         # and now the route should be gone
1470         #
1471         self.assertFalse(find_route(self, self.pg3.local_ip4, 32))
1472         self.assertFalse(find_route(self, self.pg3.local_ip4, 24))
1473
1474         #
1475         # Start the procedure again. Use requested lease time option.
1476         #
1477         self.pg3.admin_down()
1478         self.sleep(1)
1479         self.pg3.admin_up()
1480         self.vapi.dhcp_client_config(self.pg3.sw_if_index, hostname)
1481
1482         rx = self.pg3.get_capture(1)
1483
1484         self.verify_orig_dhcp_discover(rx[0], self.pg3, hostname)
1485
1486         #
1487         # Send back on offer with requested lease time, expect the request
1488         #
1489         lease_time = 1
1490         p_offer = (Ether(dst=self.pg3.local_mac, src=self.pg3.remote_mac) /
1491                    IP(src=self.pg3.remote_ip4, dst='255.255.255.255') /
1492                    UDP(sport=DHCP4_SERVER_PORT, dport=DHCP4_CLIENT_PORT) /
1493                    BOOTP(op=1,
1494                          yiaddr=self.pg3.local_ip4,
1495                          chaddr=mac_pton(self.pg3.local_mac)) /
1496                    DHCP(options=[('message-type', 'offer'),
1497                                  ('server_id', self.pg3.remote_ip4),
1498                                  ('lease_time', lease_time),
1499                                  'end']))
1500
1501         self.pg3.add_stream(p_offer)
1502         self.pg_enable_capture(self.pg_interfaces)
1503         self.pg_start()
1504
1505         rx = self.pg3.get_capture(1)
1506         self.verify_orig_dhcp_request(rx[0], self.pg3, hostname,
1507                                       self.pg3.local_ip4)
1508
1509         #
1510         # Send an acknowledgment
1511         #
1512         p_ack = (Ether(dst=self.pg3.local_mac, src=self.pg3.remote_mac) /
1513                  IP(src=self.pg3.remote_ip4, dst='255.255.255.255') /
1514                  UDP(sport=DHCP4_SERVER_PORT, dport=DHCP4_CLIENT_PORT) /
1515                  BOOTP(op=1, yiaddr=self.pg3.local_ip4,
1516                        chaddr=mac_pton(self.pg3.local_mac)) /
1517                  DHCP(options=[('message-type', 'ack'),
1518                                ('subnet_mask', '255.255.255.0'),
1519                                ('router', self.pg3.remote_ip4),
1520                                ('server_id', self.pg3.remote_ip4),
1521                                ('lease_time', lease_time),
1522                                'end']))
1523
1524         self.pg3.add_stream(p_ack)
1525         self.pg_enable_capture(self.pg_interfaces)
1526         self.pg_start()
1527
1528         #
1529         # We'll get an ARP request for the router address
1530         #
1531         rx = self.pg3.get_capture(1)
1532
1533         self.assertEqual(rx[0][ARP].pdst, self.pg3.remote_ip4)
1534
1535         #
1536         # At the end of this procedure there should be a connected route
1537         # in the FIB
1538         #
1539         self.assertTrue(find_route(self, self.pg3.local_ip4, 32))
1540         self.assertTrue(find_route(self, self.pg3.local_ip4, 24))
1541
1542         # remove the left over ARP entry
1543         self.vapi.ip_neighbor_add_del(self.pg3.sw_if_index,
1544                                       self.pg3.remote_mac,
1545                                       self.pg3.remote_ip4,
1546                                       is_add=0)
1547
1548         #
1549         # the route should be gone after the lease expires
1550         #
1551         self.assertTrue(self.wait_for_no_route(self.pg3.local_ip4, 32))
1552         self.assertTrue(self.wait_for_no_route(self.pg3.local_ip4, 24))
1553
1554         #
1555         # remove the DHCP config
1556         #
1557         self.vapi.dhcp_client_config(self.pg3.sw_if_index, hostname, is_add=0)
1558
1559
1560 if __name__ == '__main__':
1561     unittest.main(testRunner=VppTestRunner)