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