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