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