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