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