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