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