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