wireguard: add dos mitigation support
[vpp.git] / test / test_wireguard.py
1 #!/usr/bin/env python3
2 """ Wg tests """
3
4 import datetime
5 import base64
6 import os
7
8 from hashlib import blake2s
9 from scapy.packet import Packet
10 from scapy.packet import Raw
11 from scapy.layers.l2 import Ether, ARP
12 from scapy.layers.inet import IP, UDP
13 from scapy.layers.inet6 import IPv6
14 from scapy.contrib.wireguard import (
15     Wireguard,
16     WireguardResponse,
17     WireguardInitiation,
18     WireguardTransport,
19     WireguardCookieReply,
20 )
21 from cryptography.hazmat.primitives.asymmetric.x25519 import (
22     X25519PrivateKey,
23     X25519PublicKey,
24 )
25 from cryptography.hazmat.primitives.serialization import (
26     Encoding,
27     PrivateFormat,
28     PublicFormat,
29     NoEncryption,
30 )
31 from cryptography.hazmat.primitives.hashes import BLAKE2s, Hash
32 from cryptography.hazmat.primitives.hmac import HMAC
33 from cryptography.hazmat.backends import default_backend
34 from noise.connection import NoiseConnection, Keypair
35
36 from Crypto.Cipher import ChaCha20_Poly1305
37 from Crypto.Random import get_random_bytes
38
39 from vpp_ipip_tun_interface import VppIpIpTunInterface
40 from vpp_interface import VppInterface
41 from vpp_ip_route import VppIpRoute, VppRoutePath
42 from vpp_object import VppObject
43 from vpp_papi import VppEnum
44 from framework import VppTestCase
45 from re import compile
46 import unittest
47
48 """ TestWg is a subclass of  VPPTestCase classes.
49
50 Wg test.
51
52 """
53
54
55 def private_key_bytes(k):
56     return k.private_bytes(Encoding.Raw, PrivateFormat.Raw, NoEncryption())
57
58
59 def public_key_bytes(k):
60     return k.public_bytes(Encoding.Raw, PublicFormat.Raw)
61
62
63 def get_field_bytes(pkt, name):
64     fld, val = pkt.getfield_and_val(name)
65     return fld.i2m(pkt, val)
66
67
68 class VppWgInterface(VppInterface):
69     """
70     VPP WireGuard interface
71     """
72
73     def __init__(self, test, src, port):
74         super(VppWgInterface, self).__init__(test)
75
76         self.port = port
77         self.src = src
78         self.private_key = X25519PrivateKey.generate()
79         self.public_key = self.private_key.public_key()
80
81         # cookie related params
82         self.cookie_key = blake2s(b"cookie--" + self.public_key_bytes()).digest()
83
84     def public_key_bytes(self):
85         return public_key_bytes(self.public_key)
86
87     def private_key_bytes(self):
88         return private_key_bytes(self.private_key)
89
90     def add_vpp_config(self):
91         r = self.test.vapi.wireguard_interface_create(
92             interface={
93                 "user_instance": 0xFFFFFFFF,
94                 "port": self.port,
95                 "src_ip": self.src,
96                 "private_key": private_key_bytes(self.private_key),
97                 "generate_key": False,
98             }
99         )
100         self.set_sw_if_index(r.sw_if_index)
101         self.test.registry.register(self, self.test.logger)
102         return self
103
104     def remove_vpp_config(self):
105         self.test.vapi.wireguard_interface_delete(sw_if_index=self._sw_if_index)
106
107     def query_vpp_config(self):
108         ts = self.test.vapi.wireguard_interface_dump(sw_if_index=0xFFFFFFFF)
109         for t in ts:
110             if (
111                 t.interface.sw_if_index == self._sw_if_index
112                 and str(t.interface.src_ip) == self.src
113                 and t.interface.port == self.port
114                 and t.interface.private_key == private_key_bytes(self.private_key)
115             ):
116                 return True
117         return False
118
119     def want_events(self, peer_index=0xFFFFFFFF):
120         self.test.vapi.want_wireguard_peer_events(
121             enable_disable=1,
122             pid=os.getpid(),
123             sw_if_index=self._sw_if_index,
124             peer_index=peer_index,
125         )
126
127     def wait_events(self, expect, peers, timeout=5):
128         for i in range(len(peers)):
129             rv = self.test.vapi.wait_for_event(timeout, "wireguard_peer_event")
130             self.test.assertEqual(rv.peer_index, peers[i])
131             self.test.assertEqual(rv.flags, expect)
132
133     def __str__(self):
134         return self.object_id()
135
136     def object_id(self):
137         return "wireguard-%d" % self._sw_if_index
138
139
140 def find_route(test, prefix, is_ip6, table_id=0):
141     routes = test.vapi.ip_route_dump(table_id, is_ip6)
142
143     for e in routes:
144         if table_id == e.route.table_id and str(e.route.prefix) == str(prefix):
145             return True
146     return False
147
148
149 NOISE_HANDSHAKE_NAME = b"Noise_IKpsk2_25519_ChaChaPoly_BLAKE2s"
150 NOISE_IDENTIFIER_NAME = b"WireGuard v1 zx2c4 Jason@zx2c4.com"
151
152 HANDSHAKE_COUNTING_INTERVAL = 0.5
153 UNDER_LOAD_INTERVAL = 1.0
154 HANDSHAKE_NUM_PER_PEER_UNTIL_UNDER_LOAD = 40
155
156
157 class VppWgPeer(VppObject):
158     def __init__(self, test, itf, endpoint, port, allowed_ips, persistent_keepalive=15):
159         self._test = test
160         self.itf = itf
161         self.endpoint = endpoint
162         self.port = port
163         self.allowed_ips = allowed_ips
164         self.persistent_keepalive = persistent_keepalive
165
166         # remote peer's public
167         self.private_key = X25519PrivateKey.generate()
168         self.public_key = self.private_key.public_key()
169
170         # cookie related params
171         self.cookie_key = blake2s(b"cookie--" + self.public_key_bytes()).digest()
172         self.last_sent_cookie = None
173         self.last_mac1 = None
174         self.last_received_cookie = None
175
176         self.noise = NoiseConnection.from_name(NOISE_HANDSHAKE_NAME)
177
178     def add_vpp_config(self, is_ip6=False):
179         rv = self._test.vapi.wireguard_peer_add(
180             peer={
181                 "public_key": self.public_key_bytes(),
182                 "port": self.port,
183                 "endpoint": self.endpoint,
184                 "n_allowed_ips": len(self.allowed_ips),
185                 "allowed_ips": self.allowed_ips,
186                 "sw_if_index": self.itf.sw_if_index,
187                 "persistent_keepalive": self.persistent_keepalive,
188             }
189         )
190         self.index = rv.peer_index
191         self.receiver_index = self.index + 1
192         self._test.registry.register(self, self._test.logger)
193         return self
194
195     def remove_vpp_config(self):
196         self._test.vapi.wireguard_peer_remove(peer_index=self.index)
197
198     def object_id(self):
199         return "wireguard-peer-%s" % self.index
200
201     def public_key_bytes(self):
202         return public_key_bytes(self.public_key)
203
204     def query_vpp_config(self):
205         peers = self._test.vapi.wireguard_peers_dump()
206
207         for p in peers:
208             if (
209                 p.peer.public_key == self.public_key_bytes()
210                 and p.peer.port == self.port
211                 and str(p.peer.endpoint) == self.endpoint
212                 and p.peer.sw_if_index == self.itf.sw_if_index
213                 and len(self.allowed_ips) == p.peer.n_allowed_ips
214             ):
215                 self.allowed_ips.sort()
216                 p.peer.allowed_ips.sort()
217
218                 for (a1, a2) in zip(self.allowed_ips, p.peer.allowed_ips):
219                     if str(a1) != str(a2):
220                         return False
221                 return True
222         return False
223
224     def mk_tunnel_header(self, tx_itf, is_ip6=False):
225         if is_ip6 is False:
226             return (
227                 Ether(dst=tx_itf.local_mac, src=tx_itf.remote_mac)
228                 / IP(src=self.endpoint, dst=self.itf.src)
229                 / UDP(sport=self.port, dport=self.itf.port)
230             )
231         else:
232             return (
233                 Ether(dst=tx_itf.local_mac, src=tx_itf.remote_mac)
234                 / IPv6(src=self.endpoint, dst=self.itf.src)
235                 / UDP(sport=self.port, dport=self.itf.port)
236             )
237
238     def noise_reset(self):
239         self.noise = NoiseConnection.from_name(NOISE_HANDSHAKE_NAME)
240
241     def noise_init(self, public_key=None):
242         self.noise.set_prologue(NOISE_IDENTIFIER_NAME)
243         self.noise.set_psks(psk=bytes(bytearray(32)))
244
245         if not public_key:
246             public_key = self.itf.public_key
247
248         # local/this private
249         self.noise.set_keypair_from_private_bytes(
250             Keypair.STATIC, private_key_bytes(self.private_key)
251         )
252         # remote's public
253         self.noise.set_keypair_from_public_bytes(
254             Keypair.REMOTE_STATIC, public_key_bytes(public_key)
255         )
256
257         self.noise.start_handshake()
258
259     def mk_cookie(self, p, tx_itf, is_resp=False, is_ip6=False):
260         self.verify_header(p, is_ip6)
261
262         wg_pkt = Wireguard(p[Raw])
263
264         if is_resp:
265             self._test.assertEqual(wg_pkt[Wireguard].message_type, 2)
266             self._test.assertEqual(wg_pkt[Wireguard].reserved_zero, 0)
267             self._test.assertEqual(wg_pkt[WireguardResponse].mac2, bytes([0] * 16))
268         else:
269             self._test.assertEqual(wg_pkt[Wireguard].message_type, 1)
270             self._test.assertEqual(wg_pkt[Wireguard].reserved_zero, 0)
271             self._test.assertEqual(wg_pkt[WireguardInitiation].mac2, bytes([0] * 16))
272
273         # collect info from wg packet (initiation or response)
274         src = get_field_bytes(p[IPv6 if is_ip6 else IP], "src")
275         sport = p[UDP].sport.to_bytes(2, byteorder="big")
276         if is_resp:
277             mac1 = wg_pkt[WireguardResponse].mac1
278             sender_index = wg_pkt[WireguardResponse].sender_index
279         else:
280             mac1 = wg_pkt[WireguardInitiation].mac1
281             sender_index = wg_pkt[WireguardInitiation].sender_index
282
283         # make cookie reply
284         cookie_reply = Wireguard() / WireguardCookieReply()
285         cookie_reply[Wireguard].message_type = 3
286         cookie_reply[Wireguard].reserved_zero = 0
287         cookie_reply[WireguardCookieReply].receiver_index = sender_index
288         nonce = get_random_bytes(24)
289         cookie_reply[WireguardCookieReply].nonce = nonce
290
291         # generate cookie data
292         changing_secret = get_random_bytes(32)
293         self.last_sent_cookie = blake2s(
294             src + sport, digest_size=16, key=changing_secret
295         ).digest()
296
297         # encrypt cookie data
298         cipher = ChaCha20_Poly1305.new(key=self.cookie_key, nonce=nonce)
299         cipher.update(mac1)
300         ciphertext, tag = cipher.encrypt_and_digest(self.last_sent_cookie)
301         cookie_reply[WireguardCookieReply].encrypted_cookie = ciphertext + tag
302
303         # prepare cookie reply to be sent
304         cookie_reply = self.mk_tunnel_header(tx_itf, is_ip6) / cookie_reply
305
306         return cookie_reply
307
308     def consume_cookie(self, p, is_ip6=False):
309         self.verify_header(p, is_ip6)
310
311         cookie_reply = Wireguard(p[Raw])
312
313         self._test.assertEqual(cookie_reply[Wireguard].message_type, 3)
314         self._test.assertEqual(cookie_reply[Wireguard].reserved_zero, 0)
315         self._test.assertEqual(
316             cookie_reply[WireguardCookieReply].receiver_index, self.receiver_index
317         )
318
319         # collect info from cookie reply
320         nonce = cookie_reply[WireguardCookieReply].nonce
321         encrypted_cookie = cookie_reply[WireguardCookieReply].encrypted_cookie
322         ciphertext, tag = encrypted_cookie[:16], encrypted_cookie[16:]
323
324         # decrypt cookie data
325         cipher = ChaCha20_Poly1305.new(key=self.itf.cookie_key, nonce=nonce)
326         cipher.update(self.last_mac1)
327         self.last_received_cookie = cipher.decrypt_and_verify(ciphertext, tag)
328
329     def mk_handshake(self, tx_itf, is_ip6=False, public_key=None):
330         self.noise.set_as_initiator()
331         self.noise_init(public_key)
332
333         p = Wireguard() / WireguardInitiation()
334
335         p[Wireguard].message_type = 1
336         p[Wireguard].reserved_zero = 0
337         p[WireguardInitiation].sender_index = self.receiver_index
338
339         # some random data for the message
340         #  lifted from the noise protocol's wireguard example
341         now = datetime.datetime.now()
342         tai = struct.pack(
343             "!qi",
344             4611686018427387914 + int(now.timestamp()),
345             int(now.microsecond * 1e3),
346         )
347         b = self.noise.write_message(payload=tai)
348
349         # load noise into init message
350         p[WireguardInitiation].unencrypted_ephemeral = b[0:32]
351         p[WireguardInitiation].encrypted_static = b[32:80]
352         p[WireguardInitiation].encrypted_timestamp = b[80:108]
353
354         # generate the mac1 hash
355         mac_key = blake2s(b"mac1----" + self.itf.public_key_bytes()).digest()
356         mac1 = blake2s(bytes(p)[0:116], digest_size=16, key=mac_key).digest()
357         p[WireguardInitiation].mac1 = mac1
358         self.last_mac1 = mac1
359
360         # generate the mac2 hash
361         if self.last_received_cookie:
362             mac2 = blake2s(
363                 bytes(p)[0:132], digest_size=16, key=self.last_received_cookie
364             ).digest()
365             p[WireguardInitiation].mac2 = mac2
366             self.last_received_cookie = None
367         else:
368             p[WireguardInitiation].mac2 = bytearray(16)
369
370         p = self.mk_tunnel_header(tx_itf, is_ip6) / p
371
372         return p
373
374     def verify_header(self, p, is_ip6=False):
375         if is_ip6 is False:
376             self._test.assertEqual(p[IP].src, self.itf.src)
377             self._test.assertEqual(p[IP].dst, self.endpoint)
378         else:
379             self._test.assertEqual(p[IPv6].src, self.itf.src)
380             self._test.assertEqual(p[IPv6].dst, self.endpoint)
381         self._test.assertEqual(p[UDP].sport, self.itf.port)
382         self._test.assertEqual(p[UDP].dport, self.port)
383         self._test.assert_packet_checksums_valid(p)
384
385     def consume_init(self, p, tx_itf, is_ip6=False, is_mac2=False):
386         self.noise.set_as_responder()
387         self.noise_init(self.itf.public_key)
388         self.verify_header(p, is_ip6)
389
390         init = Wireguard(p[Raw])
391
392         self._test.assertEqual(init[Wireguard].message_type, 1)
393         self._test.assertEqual(init[Wireguard].reserved_zero, 0)
394
395         self.sender = init[WireguardInitiation].sender_index
396
397         # validate the mac1 hash
398         mac_key = blake2s(b"mac1----" + public_key_bytes(self.public_key)).digest()
399         mac1 = blake2s(bytes(init)[0:-32], digest_size=16, key=mac_key).digest()
400         self._test.assertEqual(init[WireguardInitiation].mac1, mac1)
401
402         # validate the mac2 hash
403         if is_mac2:
404             self._test.assertNotEqual(init[WireguardInitiation].mac2, bytes([0] * 16))
405             self._test.assertNotEqual(self.last_sent_cookie, None)
406             mac2 = blake2s(
407                 bytes(init)[0:-16], digest_size=16, key=self.last_sent_cookie
408             ).digest()
409             self._test.assertEqual(init[WireguardInitiation].mac2, mac2)
410             self.last_sent_cookie = None
411         else:
412             self._test.assertEqual(init[WireguardInitiation].mac2, bytes([0] * 16))
413
414         # this passes only unencrypted_ephemeral, encrypted_static,
415         # encrypted_timestamp fields of the init
416         payload = self.noise.read_message(bytes(init)[8:-32])
417
418         # build the response
419         b = self.noise.write_message()
420         mac_key = blake2s(b"mac1----" + public_key_bytes(self.itf.public_key)).digest()
421         resp = Wireguard(message_type=2, reserved_zero=0) / WireguardResponse(
422             sender_index=self.receiver_index,
423             receiver_index=self.sender,
424             unencrypted_ephemeral=b[0:32],
425             encrypted_nothing=b[32:],
426         )
427         mac1 = blake2s(bytes(resp)[:-32], digest_size=16, key=mac_key).digest()
428         resp[WireguardResponse].mac1 = mac1
429         self.last_mac1 = mac1
430
431         resp = self.mk_tunnel_header(tx_itf, is_ip6) / resp
432         self._test.assertTrue(self.noise.handshake_finished)
433
434         return resp
435
436     def consume_response(self, p, is_ip6=False):
437         self.verify_header(p, is_ip6)
438
439         resp = Wireguard(p[Raw])
440
441         self._test.assertEqual(resp[Wireguard].message_type, 2)
442         self._test.assertEqual(resp[Wireguard].reserved_zero, 0)
443         self._test.assertEqual(
444             resp[WireguardResponse].receiver_index, self.receiver_index
445         )
446
447         self.sender = resp[Wireguard].sender_index
448
449         payload = self.noise.read_message(bytes(resp)[12:60])
450         self._test.assertEqual(payload, b"")
451         self._test.assertTrue(self.noise.handshake_finished)
452
453     def decrypt_transport(self, p, is_ip6=False):
454         self.verify_header(p, is_ip6)
455
456         p = Wireguard(p[Raw])
457         self._test.assertEqual(p[Wireguard].message_type, 4)
458         self._test.assertEqual(p[Wireguard].reserved_zero, 0)
459         self._test.assertEqual(
460             p[WireguardTransport].receiver_index, self.receiver_index
461         )
462
463         d = self.noise.decrypt(p[WireguardTransport].encrypted_encapsulated_packet)
464         return d
465
466     def encrypt_transport(self, p):
467         return self.noise.encrypt(bytes(p))
468
469     def validate_encapped(self, rxs, tx, is_ip6=False):
470         for rx in rxs:
471             if is_ip6 is False:
472                 rx = IP(self.decrypt_transport(rx))
473
474                 # chech the oringial packet is present
475                 self._test.assertEqual(rx[IP].dst, tx[IP].dst)
476                 self._test.assertEqual(rx[IP].ttl, tx[IP].ttl - 1)
477             else:
478                 rx = IPv6(self.decrypt_transport(rx))
479
480                 # chech the oringial packet is present
481                 self._test.assertEqual(rx[IPv6].dst, tx[IPv6].dst)
482                 self._test.assertEqual(rx[IPv6].ttl, tx[IPv6].ttl - 1)
483
484     def want_events(self):
485         self._test.vapi.want_wireguard_peer_events(
486             enable_disable=1,
487             pid=os.getpid(),
488             peer_index=self.index,
489             sw_if_index=self.itf.sw_if_index,
490         )
491
492     def wait_event(self, expect, timeout=5):
493         rv = self._test.vapi.wait_for_event(timeout, "wireguard_peer_event")
494         self._test.assertEqual(rv.flags, expect)
495         self._test.assertEqual(rv.peer_index, self.index)
496
497
498 class TestWg(VppTestCase):
499     """Wireguard Test Case"""
500
501     error_str = compile(r"Error")
502
503     wg4_output_node_name = "/err/wg4-output-tun/"
504     wg4_input_node_name = "/err/wg4-input/"
505     wg6_output_node_name = "/err/wg6-output-tun/"
506     wg6_input_node_name = "/err/wg6-input/"
507     kp4_error = wg4_output_node_name + "Keypair error"
508     mac4_error = wg4_input_node_name + "Invalid MAC handshake"
509     peer4_in_err = wg4_input_node_name + "Peer error"
510     peer4_out_err = wg4_output_node_name + "Peer error"
511     kp6_error = wg6_output_node_name + "Keypair error"
512     mac6_error = wg6_input_node_name + "Invalid MAC handshake"
513     peer6_in_err = wg6_input_node_name + "Peer error"
514     peer6_out_err = wg6_output_node_name + "Peer error"
515     cookie_dec4_err = wg4_input_node_name + "Failed during Cookie decryption"
516     cookie_dec6_err = wg6_input_node_name + "Failed during Cookie decryption"
517
518     @classmethod
519     def setUpClass(cls):
520         super(TestWg, cls).setUpClass()
521         try:
522             cls.create_pg_interfaces(range(3))
523             for i in cls.pg_interfaces:
524                 i.admin_up()
525                 i.config_ip4()
526                 i.config_ip6()
527                 i.resolve_arp()
528                 i.resolve_ndp()
529
530         except Exception:
531             super(TestWg, cls).tearDownClass()
532             raise
533
534     @classmethod
535     def tearDownClass(cls):
536         super(TestWg, cls).tearDownClass()
537
538     def setUp(self):
539         super(VppTestCase, self).setUp()
540         self.base_kp4_err = self.statistics.get_err_counter(self.kp4_error)
541         self.base_mac4_err = self.statistics.get_err_counter(self.mac4_error)
542         self.base_peer4_in_err = self.statistics.get_err_counter(self.peer4_in_err)
543         self.base_peer4_out_err = self.statistics.get_err_counter(self.peer4_out_err)
544         self.base_kp6_err = self.statistics.get_err_counter(self.kp6_error)
545         self.base_mac6_err = self.statistics.get_err_counter(self.mac6_error)
546         self.base_peer6_in_err = self.statistics.get_err_counter(self.peer6_in_err)
547         self.base_peer6_out_err = self.statistics.get_err_counter(self.peer6_out_err)
548         self.base_cookie_dec4_err = self.statistics.get_err_counter(
549             self.cookie_dec4_err
550         )
551         self.base_cookie_dec6_err = self.statistics.get_err_counter(
552             self.cookie_dec6_err
553         )
554
555     def test_wg_interface(self):
556         """Simple interface creation"""
557         port = 12312
558
559         # Create interface
560         wg0 = VppWgInterface(self, self.pg1.local_ip4, port).add_vpp_config()
561
562         self.logger.info(self.vapi.cli("sh int"))
563
564         # delete interface
565         wg0.remove_vpp_config()
566
567     def test_handshake_hash(self):
568         """test hashing an init message"""
569         # a init packet generated by linux given the key below
570         h = (
571             "0100000098b9032b"
572             "55cc4b39e73c3d24"
573             "a2a1ab884b524a81"
574             "1808bb86640fb70d"
575             "e93154fec1879125"
576             "ab012624a27f0b75"
577             "c0a2582f438ddb5f"
578             "8e768af40b4ab444"
579             "02f9ff473e1b797e"
580             "80d39d93c5480c82"
581             "a3d4510f70396976"
582             "586fb67300a5167b"
583             "ae6ca3ff3dfd00eb"
584             "59be198810f5aa03"
585             "6abc243d2155ee4f"
586             "2336483900aef801"
587             "08752cd700000000"
588             "0000000000000000"
589             "00000000"
590         )
591
592         b = bytearray.fromhex(h)
593         tgt = Wireguard(b)
594
595         pubb = base64.b64decode("aRuHFTTxICIQNefp05oKWlJv3zgKxb8+WW7JJMh0jyM=")
596         pub = X25519PublicKey.from_public_bytes(pubb)
597
598         self.assertEqual(pubb, public_key_bytes(pub))
599
600         # strip the macs and build a new packet
601         init = b[0:-32]
602         mac_key = blake2s(b"mac1----" + public_key_bytes(pub)).digest()
603         init += blake2s(init, digest_size=16, key=mac_key).digest()
604         init += b"\x00" * 16
605
606         act = Wireguard(init)
607
608         self.assertEqual(tgt, act)
609
610     def _test_wg_send_cookie_tmpl(self, is_resp, is_ip6):
611         port = 12323
612
613         # create wg interface
614         if is_ip6:
615             wg0 = VppWgInterface(self, self.pg1.local_ip6, port).add_vpp_config()
616             wg0.admin_up()
617             wg0.config_ip6()
618         else:
619             wg0 = VppWgInterface(self, self.pg1.local_ip4, port).add_vpp_config()
620             wg0.admin_up()
621             wg0.config_ip4()
622
623         self.pg_enable_capture(self.pg_interfaces)
624         self.pg_start()
625
626         # create a peer
627         if is_ip6:
628             peer_1 = VppWgPeer(
629                 self, wg0, self.pg1.remote_ip6, port + 1, ["1::3:0/112"]
630             ).add_vpp_config()
631         else:
632             peer_1 = VppWgPeer(
633                 self, wg0, self.pg1.remote_ip4, port + 1, ["10.11.3.0/24"]
634             ).add_vpp_config()
635         self.assertEqual(len(self.vapi.wireguard_peers_dump()), 1)
636
637         if is_resp:
638             # prepare and send a handshake initiation
639             # expect the peer to send a handshake response
640             init = peer_1.mk_handshake(self.pg1, is_ip6=is_ip6)
641             rxs = self.send_and_expect(self.pg1, [init], self.pg1)
642         else:
643             # wait for the peer to send a handshake initiation
644             rxs = self.pg1.get_capture(1, timeout=2)
645
646         # prepare and send a wrong cookie reply
647         # expect no replies and the cookie error incremented
648         cookie = peer_1.mk_cookie(rxs[0], self.pg1, is_resp=is_resp, is_ip6=is_ip6)
649         cookie.nonce = b"1234567890"
650         self.send_and_assert_no_replies(self.pg1, [cookie], timeout=0.1)
651         if is_ip6:
652             self.assertEqual(
653                 self.base_cookie_dec6_err + 1,
654                 self.statistics.get_err_counter(self.cookie_dec6_err),
655             )
656         else:
657             self.assertEqual(
658                 self.base_cookie_dec4_err + 1,
659                 self.statistics.get_err_counter(self.cookie_dec4_err),
660             )
661
662         # prepare and send a correct cookie reply
663         cookie = peer_1.mk_cookie(rxs[0], self.pg1, is_resp=is_resp, is_ip6=is_ip6)
664         self.pg_send(self.pg1, [cookie])
665
666         # wait for the peer to send a handshake initiation with mac2 set
667         rxs = self.pg1.get_capture(1, timeout=6)
668
669         # verify the initiation and its mac2
670         peer_1.consume_init(rxs[0], self.pg1, is_ip6=is_ip6, is_mac2=True)
671
672         # remove configs
673         peer_1.remove_vpp_config()
674         wg0.remove_vpp_config()
675
676     def test_wg_send_cookie_on_init_v4(self):
677         """Send cookie on handshake initiation (v4)"""
678         self._test_wg_send_cookie_tmpl(is_resp=False, is_ip6=False)
679
680     def test_wg_send_cookie_on_init_v6(self):
681         """Send cookie on handshake initiation (v6)"""
682         self._test_wg_send_cookie_tmpl(is_resp=False, is_ip6=True)
683
684     def test_wg_send_cookie_on_resp_v4(self):
685         """Send cookie on handshake response (v4)"""
686         self._test_wg_send_cookie_tmpl(is_resp=True, is_ip6=False)
687
688     def test_wg_send_cookie_on_resp_v6(self):
689         """Send cookie on handshake response (v6)"""
690         self._test_wg_send_cookie_tmpl(is_resp=True, is_ip6=True)
691
692     def _test_wg_receive_cookie_tmpl(self, is_resp, is_ip6):
693         port = 12323
694
695         # create wg interface
696         if is_ip6:
697             wg0 = VppWgInterface(self, self.pg1.local_ip6, port).add_vpp_config()
698             wg0.admin_up()
699             wg0.config_ip6()
700         else:
701             wg0 = VppWgInterface(self, self.pg1.local_ip4, port).add_vpp_config()
702             wg0.admin_up()
703             wg0.config_ip4()
704
705         self.pg_enable_capture(self.pg_interfaces)
706         self.pg_start()
707
708         # create a peer
709         if is_ip6:
710             peer_1 = VppWgPeer(
711                 self, wg0, self.pg1.remote_ip6, port + 1, ["1::3:0/112"]
712             ).add_vpp_config()
713         else:
714             peer_1 = VppWgPeer(
715                 self, wg0, self.pg1.remote_ip4, port + 1, ["10.11.3.0/24"]
716             ).add_vpp_config()
717         self.assertEqual(len(self.vapi.wireguard_peers_dump()), 1)
718
719         if is_resp:
720             # wait for the peer to send a handshake initiation
721             rxs = self.pg1.get_capture(1, timeout=2)
722             # prepare and send a bunch of handshake responses
723             # expect to switch to under load state
724             resp = peer_1.consume_init(rxs[0], self.pg1, is_ip6=is_ip6)
725             txs = [resp] * HANDSHAKE_NUM_PER_PEER_UNTIL_UNDER_LOAD
726             rxs = self.send_and_expect_some(self.pg1, txs, self.pg1)
727             # reset noise to be able to turn into initiator later
728             peer_1.noise_reset()
729         else:
730             # prepare and send a bunch of handshake initiations
731             # expect to switch to under load state
732             init = peer_1.mk_handshake(self.pg1, is_ip6=is_ip6)
733             txs = [init] * HANDSHAKE_NUM_PER_PEER_UNTIL_UNDER_LOAD
734             rxs = self.send_and_expect_some(self.pg1, txs, self.pg1)
735
736         # expect the peer to send a cookie reply
737         peer_1.consume_cookie(rxs[-1], is_ip6=is_ip6)
738
739         # prepare and send a handshake initiation with wrong mac2
740         # expect a cookie reply
741         init = peer_1.mk_handshake(self.pg1, is_ip6=is_ip6)
742         init.mac2 = b"1234567890"
743         rxs = self.send_and_expect(self.pg1, [init], self.pg1)
744         peer_1.consume_cookie(rxs[0], is_ip6=is_ip6)
745
746         # prepare and send a handshake initiation with correct mac2
747         # expect a handshake response
748         init = peer_1.mk_handshake(self.pg1, is_ip6=is_ip6)
749         rxs = self.send_and_expect(self.pg1, [init], self.pg1)
750
751         # verify the response
752         peer_1.consume_response(rxs[0], is_ip6=is_ip6)
753
754         # clear up under load state
755         self.sleep(UNDER_LOAD_INTERVAL)
756
757         # remove configs
758         peer_1.remove_vpp_config()
759         wg0.remove_vpp_config()
760
761     def test_wg_receive_cookie_on_init_v4(self):
762         """Receive cookie on handshake initiation (v4)"""
763         self._test_wg_receive_cookie_tmpl(is_resp=False, is_ip6=False)
764
765     def test_wg_receive_cookie_on_init_v6(self):
766         """Receive cookie on handshake initiation (v6)"""
767         self._test_wg_receive_cookie_tmpl(is_resp=False, is_ip6=True)
768
769     def test_wg_receive_cookie_on_resp_v4(self):
770         """Receive cookie on handshake response (v4)"""
771         self._test_wg_receive_cookie_tmpl(is_resp=True, is_ip6=False)
772
773     def test_wg_receive_cookie_on_resp_v6(self):
774         """Receive cookie on handshake response (v6)"""
775         self._test_wg_receive_cookie_tmpl(is_resp=True, is_ip6=True)
776
777     def test_wg_under_load_interval(self):
778         """Under load interval"""
779         port = 12323
780
781         # create wg interface
782         wg0 = VppWgInterface(self, self.pg1.local_ip4, port).add_vpp_config()
783         wg0.admin_up()
784         wg0.config_ip4()
785
786         self.pg_enable_capture(self.pg_interfaces)
787         self.pg_start()
788
789         # create a peer
790         peer_1 = VppWgPeer(
791             self, wg0, self.pg1.remote_ip4, port + 1, ["10.11.3.0/24"]
792         ).add_vpp_config()
793         self.assertEqual(len(self.vapi.wireguard_peers_dump()), 1)
794
795         # prepare and send a bunch of handshake initiations
796         # expect to switch to under load state
797         init = peer_1.mk_handshake(self.pg1)
798         txs = [init] * HANDSHAKE_NUM_PER_PEER_UNTIL_UNDER_LOAD
799         rxs = self.send_and_expect_some(self.pg1, txs, self.pg1)
800
801         # expect the peer to send a cookie reply
802         peer_1.consume_cookie(rxs[-1])
803
804         # sleep till the next counting interval
805         # expect under load state is still active
806         self.sleep(HANDSHAKE_COUNTING_INTERVAL)
807
808         # prepare and send a handshake initiation with wrong mac2
809         # expect a cookie reply
810         init = peer_1.mk_handshake(self.pg1)
811         init.mac2 = b"1234567890"
812         rxs = self.send_and_expect(self.pg1, [init], self.pg1)
813         peer_1.consume_cookie(rxs[0])
814
815         # sleep till the end of being under load
816         # expect under load state is over
817         self.sleep(UNDER_LOAD_INTERVAL - HANDSHAKE_COUNTING_INTERVAL)
818
819         # prepare and send a handshake initiation with wrong mac2
820         # expect a handshake response
821         init = peer_1.mk_handshake(self.pg1)
822         init.mac2 = b"1234567890"
823         rxs = self.send_and_expect(self.pg1, [init], self.pg1)
824
825         # verify the response
826         peer_1.consume_response(rxs[0])
827
828         # remove configs
829         peer_1.remove_vpp_config()
830         wg0.remove_vpp_config()
831
832     def test_wg_peer_resp(self):
833         """Send handshake response"""
834         port = 12323
835
836         # Create interfaces
837         wg0 = VppWgInterface(self, self.pg1.local_ip4, port).add_vpp_config()
838         wg0.admin_up()
839         wg0.config_ip4()
840
841         self.pg_enable_capture(self.pg_interfaces)
842         self.pg_start()
843
844         peer_1 = VppWgPeer(
845             self, wg0, self.pg1.remote_ip4, port + 1, ["10.11.3.0/24"]
846         ).add_vpp_config()
847         self.assertEqual(len(self.vapi.wireguard_peers_dump()), 1)
848
849         r1 = VppIpRoute(
850             self, "10.11.3.0", 24, [VppRoutePath("10.11.3.1", wg0.sw_if_index)]
851         ).add_vpp_config()
852
853         # wait for the peer to send a handshake
854         rx = self.pg1.get_capture(1, timeout=2)
855
856         # consume the handshake in the noise protocol and
857         # generate the response
858         resp = peer_1.consume_init(rx[0], self.pg1)
859
860         # send the response, get keepalive
861         rxs = self.send_and_expect(self.pg1, [resp], self.pg1)
862
863         for rx in rxs:
864             b = peer_1.decrypt_transport(rx)
865             self.assertEqual(0, len(b))
866
867         # send a packets that are routed into the tunnel
868         p = (
869             Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac)
870             / IP(src=self.pg0.remote_ip4, dst="10.11.3.2")
871             / UDP(sport=555, dport=556)
872             / Raw(b"\x00" * 80)
873         )
874
875         rxs = self.send_and_expect(self.pg0, p * 255, self.pg1)
876
877         peer_1.validate_encapped(rxs, p)
878
879         # send packets into the tunnel, expect to receive them on
880         # the other side
881         p = [
882             (
883                 peer_1.mk_tunnel_header(self.pg1)
884                 / Wireguard(message_type=4, reserved_zero=0)
885                 / WireguardTransport(
886                     receiver_index=peer_1.sender,
887                     counter=ii,
888                     encrypted_encapsulated_packet=peer_1.encrypt_transport(
889                         (
890                             IP(src="10.11.3.1", dst=self.pg0.remote_ip4, ttl=20)
891                             / UDP(sport=222, dport=223)
892                             / Raw()
893                         )
894                     ),
895                 )
896             )
897             for ii in range(255)
898         ]
899
900         rxs = self.send_and_expect(self.pg1, p, self.pg0)
901
902         for rx in rxs:
903             self.assertEqual(rx[IP].dst, self.pg0.remote_ip4)
904             self.assertEqual(rx[IP].ttl, 19)
905
906         r1.remove_vpp_config()
907         peer_1.remove_vpp_config()
908         wg0.remove_vpp_config()
909
910     def test_wg_peer_v4o4(self):
911         """Test v4o4"""
912
913         port = 12333
914
915         # Create interfaces
916         wg0 = VppWgInterface(self, self.pg1.local_ip4, port).add_vpp_config()
917         wg0.admin_up()
918         wg0.config_ip4()
919
920         peer_1 = VppWgPeer(
921             self, wg0, self.pg1.remote_ip4, port + 1, ["10.11.3.0/24"]
922         ).add_vpp_config()
923         self.assertEqual(len(self.vapi.wireguard_peers_dump()), 1)
924
925         r1 = VppIpRoute(
926             self, "10.11.3.0", 24, [VppRoutePath("10.11.3.1", wg0.sw_if_index)]
927         ).add_vpp_config()
928         r2 = VppIpRoute(
929             self, "20.22.3.0", 24, [VppRoutePath("20.22.3.1", wg0.sw_if_index)]
930         ).add_vpp_config()
931
932         # route a packet into the wg interface
933         #  use the allowed-ip prefix
934         #  this is dropped because the peer is not initiated
935         p = (
936             Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac)
937             / IP(src=self.pg0.remote_ip4, dst="10.11.3.2")
938             / UDP(sport=555, dport=556)
939             / Raw()
940         )
941         self.send_and_assert_no_replies(self.pg0, [p])
942         self.assertEqual(
943             self.base_kp4_err + 1, self.statistics.get_err_counter(self.kp4_error)
944         )
945
946         # route a packet into the wg interface
947         #  use a not allowed-ip prefix
948         #  this is dropped because there is no matching peer
949         p = (
950             Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac)
951             / IP(src=self.pg0.remote_ip4, dst="20.22.3.2")
952             / UDP(sport=555, dport=556)
953             / Raw()
954         )
955         self.send_and_assert_no_replies(self.pg0, [p])
956         self.assertEqual(
957             self.base_peer4_out_err + 1,
958             self.statistics.get_err_counter(self.peer4_out_err),
959         )
960
961         # send a handsake from the peer with an invalid MAC
962         p = peer_1.mk_handshake(self.pg1)
963         p[WireguardInitiation].mac1 = b"foobar"
964         self.send_and_assert_no_replies(self.pg1, [p])
965         self.assertEqual(
966             self.base_mac4_err + 1, self.statistics.get_err_counter(self.mac4_error)
967         )
968
969         # send a handsake from the peer but signed by the wrong key.
970         p = peer_1.mk_handshake(
971             self.pg1, False, X25519PrivateKey.generate().public_key()
972         )
973         self.send_and_assert_no_replies(self.pg1, [p])
974         self.assertEqual(
975             self.base_peer4_in_err + 1,
976             self.statistics.get_err_counter(self.peer4_in_err),
977         )
978
979         # send a valid handsake init for which we expect a response
980         p = peer_1.mk_handshake(self.pg1)
981
982         rx = self.send_and_expect(self.pg1, [p], self.pg1)
983
984         peer_1.consume_response(rx[0])
985
986         # route a packet into the wg interface
987         #  this is dropped because the peer is still not initiated
988         p = (
989             Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac)
990             / IP(src=self.pg0.remote_ip4, dst="10.11.3.2")
991             / UDP(sport=555, dport=556)
992             / Raw()
993         )
994         self.send_and_assert_no_replies(self.pg0, [p])
995         self.assertEqual(
996             self.base_kp4_err + 2, self.statistics.get_err_counter(self.kp4_error)
997         )
998
999         # send a data packet from the peer through the tunnel
1000         # this completes the handshake
1001         p = (
1002             IP(src="10.11.3.1", dst=self.pg0.remote_ip4, ttl=20)
1003             / UDP(sport=222, dport=223)
1004             / Raw()
1005         )
1006         d = peer_1.encrypt_transport(p)
1007         p = peer_1.mk_tunnel_header(self.pg1) / (
1008             Wireguard(message_type=4, reserved_zero=0)
1009             / WireguardTransport(
1010                 receiver_index=peer_1.sender, counter=0, encrypted_encapsulated_packet=d
1011             )
1012         )
1013         rxs = self.send_and_expect(self.pg1, [p], self.pg0)
1014
1015         for rx in rxs:
1016             self.assertEqual(rx[IP].dst, self.pg0.remote_ip4)
1017             self.assertEqual(rx[IP].ttl, 19)
1018
1019         # send a packets that are routed into the tunnel
1020         p = (
1021             Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac)
1022             / IP(src=self.pg0.remote_ip4, dst="10.11.3.2")
1023             / UDP(sport=555, dport=556)
1024             / Raw(b"\x00" * 80)
1025         )
1026
1027         rxs = self.send_and_expect(self.pg0, p * 255, self.pg1)
1028
1029         for rx in rxs:
1030             rx = IP(peer_1.decrypt_transport(rx))
1031
1032             # chech the oringial packet is present
1033             self.assertEqual(rx[IP].dst, p[IP].dst)
1034             self.assertEqual(rx[IP].ttl, p[IP].ttl - 1)
1035
1036         # send packets into the tunnel, expect to receive them on
1037         # the other side
1038         p = [
1039             (
1040                 peer_1.mk_tunnel_header(self.pg1)
1041                 / Wireguard(message_type=4, reserved_zero=0)
1042                 / WireguardTransport(
1043                     receiver_index=peer_1.sender,
1044                     counter=ii + 1,
1045                     encrypted_encapsulated_packet=peer_1.encrypt_transport(
1046                         (
1047                             IP(src="10.11.3.1", dst=self.pg0.remote_ip4, ttl=20)
1048                             / UDP(sport=222, dport=223)
1049                             / Raw()
1050                         )
1051                     ),
1052                 )
1053             )
1054             for ii in range(255)
1055         ]
1056
1057         rxs = self.send_and_expect(self.pg1, p, self.pg0)
1058
1059         for rx in rxs:
1060             self.assertEqual(rx[IP].dst, self.pg0.remote_ip4)
1061             self.assertEqual(rx[IP].ttl, 19)
1062
1063         r1.remove_vpp_config()
1064         r2.remove_vpp_config()
1065         peer_1.remove_vpp_config()
1066         wg0.remove_vpp_config()
1067
1068     def test_wg_peer_v6o6(self):
1069         """Test v6o6"""
1070
1071         port = 12343
1072
1073         # Create interfaces
1074         wg0 = VppWgInterface(self, self.pg1.local_ip6, port).add_vpp_config()
1075         wg0.admin_up()
1076         wg0.config_ip6()
1077
1078         peer_1 = VppWgPeer(
1079             self, wg0, self.pg1.remote_ip6, port + 1, ["1::3:0/112"]
1080         ).add_vpp_config(True)
1081         self.assertEqual(len(self.vapi.wireguard_peers_dump()), 1)
1082
1083         r1 = VppIpRoute(
1084             self, "1::3:0", 112, [VppRoutePath("1::3:1", wg0.sw_if_index)]
1085         ).add_vpp_config()
1086         r2 = VppIpRoute(
1087             self, "22::3:0", 112, [VppRoutePath("22::3:1", wg0.sw_if_index)]
1088         ).add_vpp_config()
1089
1090         # route a packet into the wg interface
1091         #  use the allowed-ip prefix
1092         #  this is dropped because the peer is not initiated
1093
1094         p = (
1095             Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac)
1096             / IPv6(src=self.pg0.remote_ip6, dst="1::3:2")
1097             / UDP(sport=555, dport=556)
1098             / Raw()
1099         )
1100         self.send_and_assert_no_replies(self.pg0, [p])
1101
1102         self.assertEqual(
1103             self.base_kp6_err + 1, self.statistics.get_err_counter(self.kp6_error)
1104         )
1105
1106         # route a packet into the wg interface
1107         #  use a not allowed-ip prefix
1108         #  this is dropped because there is no matching peer
1109         p = (
1110             Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac)
1111             / IPv6(src=self.pg0.remote_ip6, dst="22::3:2")
1112             / UDP(sport=555, dport=556)
1113             / Raw()
1114         )
1115         self.send_and_assert_no_replies(self.pg0, [p])
1116         self.assertEqual(
1117             self.base_peer6_out_err + 1,
1118             self.statistics.get_err_counter(self.peer6_out_err),
1119         )
1120
1121         # send a handsake from the peer with an invalid MAC
1122         p = peer_1.mk_handshake(self.pg1, True)
1123         p[WireguardInitiation].mac1 = b"foobar"
1124         self.send_and_assert_no_replies(self.pg1, [p])
1125
1126         self.assertEqual(
1127             self.base_mac6_err + 1, self.statistics.get_err_counter(self.mac6_error)
1128         )
1129
1130         # send a handsake from the peer but signed by the wrong key.
1131         p = peer_1.mk_handshake(
1132             self.pg1, True, X25519PrivateKey.generate().public_key()
1133         )
1134         self.send_and_assert_no_replies(self.pg1, [p])
1135         self.assertEqual(
1136             self.base_peer6_in_err + 1,
1137             self.statistics.get_err_counter(self.peer6_in_err),
1138         )
1139
1140         # send a valid handsake init for which we expect a response
1141         p = peer_1.mk_handshake(self.pg1, True)
1142
1143         rx = self.send_and_expect(self.pg1, [p], self.pg1)
1144
1145         peer_1.consume_response(rx[0], True)
1146
1147         # route a packet into the wg interface
1148         #  this is dropped because the peer is still not initiated
1149         p = (
1150             Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac)
1151             / IPv6(src=self.pg0.remote_ip6, dst="1::3:2")
1152             / UDP(sport=555, dport=556)
1153             / Raw()
1154         )
1155         self.send_and_assert_no_replies(self.pg0, [p])
1156         self.assertEqual(
1157             self.base_kp6_err + 2, self.statistics.get_err_counter(self.kp6_error)
1158         )
1159
1160         # send a data packet from the peer through the tunnel
1161         # this completes the handshake
1162         p = (
1163             IPv6(src="1::3:1", dst=self.pg0.remote_ip6, hlim=20)
1164             / UDP(sport=222, dport=223)
1165             / Raw()
1166         )
1167         d = peer_1.encrypt_transport(p)
1168         p = peer_1.mk_tunnel_header(self.pg1, True) / (
1169             Wireguard(message_type=4, reserved_zero=0)
1170             / WireguardTransport(
1171                 receiver_index=peer_1.sender, counter=0, encrypted_encapsulated_packet=d
1172             )
1173         )
1174         rxs = self.send_and_expect(self.pg1, [p], self.pg0)
1175
1176         for rx in rxs:
1177             self.assertEqual(rx[IPv6].dst, self.pg0.remote_ip6)
1178             self.assertEqual(rx[IPv6].hlim, 19)
1179
1180         # send a packets that are routed into the tunnel
1181         p = (
1182             Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac)
1183             / IPv6(src=self.pg0.remote_ip6, dst="1::3:2")
1184             / UDP(sport=555, dport=556)
1185             / Raw(b"\x00" * 80)
1186         )
1187
1188         rxs = self.send_and_expect(self.pg0, p * 255, self.pg1)
1189
1190         for rx in rxs:
1191             rx = IPv6(peer_1.decrypt_transport(rx, True))
1192
1193             # chech the oringial packet is present
1194             self.assertEqual(rx[IPv6].dst, p[IPv6].dst)
1195             self.assertEqual(rx[IPv6].hlim, p[IPv6].hlim - 1)
1196
1197         # send packets into the tunnel, expect to receive them on
1198         # the other side
1199         p = [
1200             (
1201                 peer_1.mk_tunnel_header(self.pg1, True)
1202                 / Wireguard(message_type=4, reserved_zero=0)
1203                 / WireguardTransport(
1204                     receiver_index=peer_1.sender,
1205                     counter=ii + 1,
1206                     encrypted_encapsulated_packet=peer_1.encrypt_transport(
1207                         (
1208                             IPv6(src="1::3:1", dst=self.pg0.remote_ip6, hlim=20)
1209                             / UDP(sport=222, dport=223)
1210                             / Raw()
1211                         )
1212                     ),
1213                 )
1214             )
1215             for ii in range(255)
1216         ]
1217
1218         rxs = self.send_and_expect(self.pg1, p, self.pg0)
1219
1220         for rx in rxs:
1221             self.assertEqual(rx[IPv6].dst, self.pg0.remote_ip6)
1222             self.assertEqual(rx[IPv6].hlim, 19)
1223
1224         r1.remove_vpp_config()
1225         r2.remove_vpp_config()
1226         peer_1.remove_vpp_config()
1227         wg0.remove_vpp_config()
1228
1229     def test_wg_peer_v6o4(self):
1230         """Test v6o4"""
1231
1232         port = 12353
1233
1234         # Create interfaces
1235         wg0 = VppWgInterface(self, self.pg1.local_ip4, port).add_vpp_config()
1236         wg0.admin_up()
1237         wg0.config_ip6()
1238
1239         peer_1 = VppWgPeer(
1240             self, wg0, self.pg1.remote_ip4, port + 1, ["1::3:0/112"]
1241         ).add_vpp_config(True)
1242         self.assertEqual(len(self.vapi.wireguard_peers_dump()), 1)
1243
1244         r1 = VppIpRoute(
1245             self, "1::3:0", 112, [VppRoutePath("1::3:1", wg0.sw_if_index)]
1246         ).add_vpp_config()
1247
1248         # route a packet into the wg interface
1249         #  use the allowed-ip prefix
1250         #  this is dropped because the peer is not initiated
1251         p = (
1252             Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac)
1253             / IPv6(src=self.pg0.remote_ip6, dst="1::3:2")
1254             / UDP(sport=555, dport=556)
1255             / Raw()
1256         )
1257         self.send_and_assert_no_replies(self.pg0, [p])
1258         self.assertEqual(
1259             self.base_kp6_err + 1, self.statistics.get_err_counter(self.kp6_error)
1260         )
1261
1262         # send a handsake from the peer with an invalid MAC
1263         p = peer_1.mk_handshake(self.pg1)
1264         p[WireguardInitiation].mac1 = b"foobar"
1265         self.send_and_assert_no_replies(self.pg1, [p])
1266
1267         self.assertEqual(
1268             self.base_mac4_err + 1, self.statistics.get_err_counter(self.mac4_error)
1269         )
1270
1271         # send a handsake from the peer but signed by the wrong key.
1272         p = peer_1.mk_handshake(
1273             self.pg1, False, X25519PrivateKey.generate().public_key()
1274         )
1275         self.send_and_assert_no_replies(self.pg1, [p])
1276         self.assertEqual(
1277             self.base_peer4_in_err + 1,
1278             self.statistics.get_err_counter(self.peer4_in_err),
1279         )
1280
1281         # send a valid handsake init for which we expect a response
1282         p = peer_1.mk_handshake(self.pg1)
1283
1284         rx = self.send_and_expect(self.pg1, [p], self.pg1)
1285
1286         peer_1.consume_response(rx[0])
1287
1288         # route a packet into the wg interface
1289         #  this is dropped because the peer is still not initiated
1290         p = (
1291             Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac)
1292             / IPv6(src=self.pg0.remote_ip6, dst="1::3:2")
1293             / UDP(sport=555, dport=556)
1294             / Raw()
1295         )
1296         self.send_and_assert_no_replies(self.pg0, [p])
1297         self.assertEqual(
1298             self.base_kp6_err + 2, self.statistics.get_err_counter(self.kp6_error)
1299         )
1300
1301         # send a data packet from the peer through the tunnel
1302         # this completes the handshake
1303         p = (
1304             IPv6(src="1::3:1", dst=self.pg0.remote_ip6, hlim=20)
1305             / UDP(sport=222, dport=223)
1306             / Raw()
1307         )
1308         d = peer_1.encrypt_transport(p)
1309         p = peer_1.mk_tunnel_header(self.pg1) / (
1310             Wireguard(message_type=4, reserved_zero=0)
1311             / WireguardTransport(
1312                 receiver_index=peer_1.sender, counter=0, encrypted_encapsulated_packet=d
1313             )
1314         )
1315         rxs = self.send_and_expect(self.pg1, [p], self.pg0)
1316
1317         for rx in rxs:
1318             self.assertEqual(rx[IPv6].dst, self.pg0.remote_ip6)
1319             self.assertEqual(rx[IPv6].hlim, 19)
1320
1321         # send a packets that are routed into the tunnel
1322         p = (
1323             Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac)
1324             / IPv6(src=self.pg0.remote_ip6, dst="1::3:2")
1325             / UDP(sport=555, dport=556)
1326             / Raw(b"\x00" * 80)
1327         )
1328
1329         rxs = self.send_and_expect(self.pg0, p * 255, self.pg1)
1330
1331         for rx in rxs:
1332             rx = IPv6(peer_1.decrypt_transport(rx))
1333
1334             # chech the oringial packet is present
1335             self.assertEqual(rx[IPv6].dst, p[IPv6].dst)
1336             self.assertEqual(rx[IPv6].hlim, p[IPv6].hlim - 1)
1337
1338         # send packets into the tunnel, expect to receive them on
1339         # the other side
1340         p = [
1341             (
1342                 peer_1.mk_tunnel_header(self.pg1)
1343                 / Wireguard(message_type=4, reserved_zero=0)
1344                 / WireguardTransport(
1345                     receiver_index=peer_1.sender,
1346                     counter=ii + 1,
1347                     encrypted_encapsulated_packet=peer_1.encrypt_transport(
1348                         (
1349                             IPv6(src="1::3:1", dst=self.pg0.remote_ip6, hlim=20)
1350                             / UDP(sport=222, dport=223)
1351                             / Raw()
1352                         )
1353                     ),
1354                 )
1355             )
1356             for ii in range(255)
1357         ]
1358
1359         rxs = self.send_and_expect(self.pg1, p, self.pg0)
1360
1361         for rx in rxs:
1362             self.assertEqual(rx[IPv6].dst, self.pg0.remote_ip6)
1363             self.assertEqual(rx[IPv6].hlim, 19)
1364
1365         r1.remove_vpp_config()
1366         peer_1.remove_vpp_config()
1367         wg0.remove_vpp_config()
1368
1369     def test_wg_peer_v4o6(self):
1370         """Test v4o6"""
1371
1372         port = 12363
1373
1374         # Create interfaces
1375         wg0 = VppWgInterface(self, self.pg1.local_ip6, port).add_vpp_config()
1376         wg0.admin_up()
1377         wg0.config_ip4()
1378
1379         peer_1 = VppWgPeer(
1380             self, wg0, self.pg1.remote_ip6, port + 1, ["10.11.3.0/24"]
1381         ).add_vpp_config()
1382         self.assertEqual(len(self.vapi.wireguard_peers_dump()), 1)
1383
1384         r1 = VppIpRoute(
1385             self, "10.11.3.0", 24, [VppRoutePath("10.11.3.1", wg0.sw_if_index)]
1386         ).add_vpp_config()
1387
1388         # route a packet into the wg interface
1389         #  use the allowed-ip prefix
1390         #  this is dropped because the peer is not initiated
1391         p = (
1392             Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac)
1393             / IP(src=self.pg0.remote_ip4, dst="10.11.3.2")
1394             / UDP(sport=555, dport=556)
1395             / Raw()
1396         )
1397         self.send_and_assert_no_replies(self.pg0, [p])
1398         self.assertEqual(
1399             self.base_kp4_err + 1, self.statistics.get_err_counter(self.kp4_error)
1400         )
1401
1402         # send a handsake from the peer with an invalid MAC
1403         p = peer_1.mk_handshake(self.pg1, True)
1404         p[WireguardInitiation].mac1 = b"foobar"
1405         self.send_and_assert_no_replies(self.pg1, [p])
1406         self.assertEqual(
1407             self.base_mac6_err + 1, self.statistics.get_err_counter(self.mac6_error)
1408         )
1409
1410         # send a handsake from the peer but signed by the wrong key.
1411         p = peer_1.mk_handshake(
1412             self.pg1, True, X25519PrivateKey.generate().public_key()
1413         )
1414         self.send_and_assert_no_replies(self.pg1, [p])
1415         self.assertEqual(
1416             self.base_peer6_in_err + 1,
1417             self.statistics.get_err_counter(self.peer6_in_err),
1418         )
1419
1420         # send a valid handsake init for which we expect a response
1421         p = peer_1.mk_handshake(self.pg1, True)
1422
1423         rx = self.send_and_expect(self.pg1, [p], self.pg1)
1424
1425         peer_1.consume_response(rx[0], True)
1426
1427         # route a packet into the wg interface
1428         #  this is dropped because the peer is still not initiated
1429         p = (
1430             Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac)
1431             / IP(src=self.pg0.remote_ip4, dst="10.11.3.2")
1432             / UDP(sport=555, dport=556)
1433             / Raw()
1434         )
1435         self.send_and_assert_no_replies(self.pg0, [p])
1436         self.assertEqual(
1437             self.base_kp4_err + 2, self.statistics.get_err_counter(self.kp4_error)
1438         )
1439
1440         # send a data packet from the peer through the tunnel
1441         # this completes the handshake
1442         p = (
1443             IP(src="10.11.3.1", dst=self.pg0.remote_ip4, ttl=20)
1444             / UDP(sport=222, dport=223)
1445             / Raw()
1446         )
1447         d = peer_1.encrypt_transport(p)
1448         p = peer_1.mk_tunnel_header(self.pg1, True) / (
1449             Wireguard(message_type=4, reserved_zero=0)
1450             / WireguardTransport(
1451                 receiver_index=peer_1.sender, counter=0, encrypted_encapsulated_packet=d
1452             )
1453         )
1454         rxs = self.send_and_expect(self.pg1, [p], self.pg0)
1455
1456         for rx in rxs:
1457             self.assertEqual(rx[IP].dst, self.pg0.remote_ip4)
1458             self.assertEqual(rx[IP].ttl, 19)
1459
1460         # send a packets that are routed into the tunnel
1461         p = (
1462             Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac)
1463             / IP(src=self.pg0.remote_ip4, dst="10.11.3.2")
1464             / UDP(sport=555, dport=556)
1465             / Raw(b"\x00" * 80)
1466         )
1467
1468         rxs = self.send_and_expect(self.pg0, p * 255, self.pg1)
1469
1470         for rx in rxs:
1471             rx = IP(peer_1.decrypt_transport(rx, True))
1472
1473             # chech the oringial packet is present
1474             self.assertEqual(rx[IP].dst, p[IP].dst)
1475             self.assertEqual(rx[IP].ttl, p[IP].ttl - 1)
1476
1477         # send packets into the tunnel, expect to receive them on
1478         # the other side
1479         p = [
1480             (
1481                 peer_1.mk_tunnel_header(self.pg1, True)
1482                 / Wireguard(message_type=4, reserved_zero=0)
1483                 / WireguardTransport(
1484                     receiver_index=peer_1.sender,
1485                     counter=ii + 1,
1486                     encrypted_encapsulated_packet=peer_1.encrypt_transport(
1487                         (
1488                             IP(src="10.11.3.1", dst=self.pg0.remote_ip4, ttl=20)
1489                             / UDP(sport=222, dport=223)
1490                             / Raw()
1491                         )
1492                     ),
1493                 )
1494             )
1495             for ii in range(255)
1496         ]
1497
1498         rxs = self.send_and_expect(self.pg1, p, self.pg0)
1499
1500         for rx in rxs:
1501             self.assertEqual(rx[IP].dst, self.pg0.remote_ip4)
1502             self.assertEqual(rx[IP].ttl, 19)
1503
1504         r1.remove_vpp_config()
1505         peer_1.remove_vpp_config()
1506         wg0.remove_vpp_config()
1507
1508     def test_wg_multi_peer(self):
1509         """multiple peer setup"""
1510         port = 12373
1511
1512         # Create interfaces
1513         wg0 = VppWgInterface(self, self.pg1.local_ip4, port).add_vpp_config()
1514         wg1 = VppWgInterface(self, self.pg2.local_ip4, port + 1).add_vpp_config()
1515         wg0.admin_up()
1516         wg1.admin_up()
1517
1518         # Check peer counter
1519         self.assertEqual(len(self.vapi.wireguard_peers_dump()), 0)
1520
1521         self.pg_enable_capture(self.pg_interfaces)
1522         self.pg_start()
1523
1524         # Create many peers on sencond interface
1525         NUM_PEERS = 16
1526         self.pg2.generate_remote_hosts(NUM_PEERS)
1527         self.pg2.configure_ipv4_neighbors()
1528         self.pg1.generate_remote_hosts(NUM_PEERS)
1529         self.pg1.configure_ipv4_neighbors()
1530
1531         peers_1 = []
1532         peers_2 = []
1533         routes_1 = []
1534         routes_2 = []
1535         for i in range(NUM_PEERS):
1536             peers_1.append(
1537                 VppWgPeer(
1538                     self,
1539                     wg0,
1540                     self.pg1.remote_hosts[i].ip4,
1541                     port + 1 + i,
1542                     ["10.0.%d.4/32" % i],
1543                 ).add_vpp_config()
1544             )
1545             routes_1.append(
1546                 VppIpRoute(
1547                     self,
1548                     "10.0.%d.4" % i,
1549                     32,
1550                     [VppRoutePath(self.pg1.remote_hosts[i].ip4, wg0.sw_if_index)],
1551                 ).add_vpp_config()
1552             )
1553
1554             peers_2.append(
1555                 VppWgPeer(
1556                     self,
1557                     wg1,
1558                     self.pg2.remote_hosts[i].ip4,
1559                     port + 100 + i,
1560                     ["10.100.%d.4/32" % i],
1561                 ).add_vpp_config()
1562             )
1563             routes_2.append(
1564                 VppIpRoute(
1565                     self,
1566                     "10.100.%d.4" % i,
1567                     32,
1568                     [VppRoutePath(self.pg2.remote_hosts[i].ip4, wg1.sw_if_index)],
1569                 ).add_vpp_config()
1570             )
1571
1572         self.assertEqual(len(self.vapi.wireguard_peers_dump()), NUM_PEERS * 2)
1573
1574         self.logger.info(self.vapi.cli("show wireguard peer"))
1575         self.logger.info(self.vapi.cli("show wireguard interface"))
1576         self.logger.info(self.vapi.cli("show adj 37"))
1577         self.logger.info(self.vapi.cli("sh ip fib 172.16.3.17"))
1578         self.logger.info(self.vapi.cli("sh ip fib 10.11.3.0"))
1579
1580         # remove routes
1581         for r in routes_1:
1582             r.remove_vpp_config()
1583         for r in routes_2:
1584             r.remove_vpp_config()
1585
1586         # remove peers
1587         for p in peers_1:
1588             self.assertTrue(p.query_vpp_config())
1589             p.remove_vpp_config()
1590         for p in peers_2:
1591             self.assertTrue(p.query_vpp_config())
1592             p.remove_vpp_config()
1593
1594         wg0.remove_vpp_config()
1595         wg1.remove_vpp_config()
1596
1597     def test_wg_multi_interface(self):
1598         """Multi-tunnel on the same port"""
1599         port = 12500
1600
1601         # Create many wireguard interfaces
1602         NUM_IFS = 4
1603         self.pg1.generate_remote_hosts(NUM_IFS)
1604         self.pg1.configure_ipv4_neighbors()
1605         self.pg0.generate_remote_hosts(NUM_IFS)
1606         self.pg0.configure_ipv4_neighbors()
1607
1608         # Create interfaces with a peer on each
1609         peers = []
1610         routes = []
1611         wg_ifs = []
1612         for i in range(NUM_IFS):
1613             # Use the same port for each interface
1614             wg0 = VppWgInterface(self, self.pg1.local_ip4, port).add_vpp_config()
1615             wg0.admin_up()
1616             wg0.config_ip4()
1617             wg_ifs.append(wg0)
1618             peers.append(
1619                 VppWgPeer(
1620                     self,
1621                     wg0,
1622                     self.pg1.remote_hosts[i].ip4,
1623                     port + 1 + i,
1624                     ["10.0.%d.0/24" % i],
1625                 ).add_vpp_config()
1626             )
1627
1628             routes.append(
1629                 VppIpRoute(
1630                     self,
1631                     "10.0.%d.0" % i,
1632                     24,
1633                     [VppRoutePath("10.0.%d.4" % i, wg0.sw_if_index)],
1634                 ).add_vpp_config()
1635             )
1636
1637         self.assertEqual(len(self.vapi.wireguard_peers_dump()), NUM_IFS)
1638
1639         for i in range(NUM_IFS):
1640             # send a valid handsake init for which we expect a response
1641             p = peers[i].mk_handshake(self.pg1)
1642             rx = self.send_and_expect(self.pg1, [p], self.pg1)
1643             peers[i].consume_response(rx[0])
1644
1645             # send a data packet from the peer through the tunnel
1646             # this completes the handshake
1647             p = (
1648                 IP(src="10.0.%d.4" % i, dst=self.pg0.remote_hosts[i].ip4, ttl=20)
1649                 / UDP(sport=222, dport=223)
1650                 / Raw()
1651             )
1652             d = peers[i].encrypt_transport(p)
1653             p = peers[i].mk_tunnel_header(self.pg1) / (
1654                 Wireguard(message_type=4, reserved_zero=0)
1655                 / WireguardTransport(
1656                     receiver_index=peers[i].sender,
1657                     counter=0,
1658                     encrypted_encapsulated_packet=d,
1659                 )
1660             )
1661             rxs = self.send_and_expect(self.pg1, [p], self.pg0)
1662             for rx in rxs:
1663                 self.assertEqual(rx[IP].dst, self.pg0.remote_hosts[i].ip4)
1664                 self.assertEqual(rx[IP].ttl, 19)
1665
1666         # send a packets that are routed into the tunnel
1667         for i in range(NUM_IFS):
1668             p = (
1669                 Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac)
1670                 / IP(src=self.pg0.remote_hosts[i].ip4, dst="10.0.%d.4" % i)
1671                 / UDP(sport=555, dport=556)
1672                 / Raw(b"\x00" * 80)
1673             )
1674
1675             rxs = self.send_and_expect(self.pg0, p * 64, self.pg1)
1676
1677             for rx in rxs:
1678                 rx = IP(peers[i].decrypt_transport(rx))
1679
1680                 # check the oringial packet is present
1681                 self.assertEqual(rx[IP].dst, p[IP].dst)
1682                 self.assertEqual(rx[IP].ttl, p[IP].ttl - 1)
1683
1684         # send packets into the tunnel
1685         for i in range(NUM_IFS):
1686             p = [
1687                 (
1688                     peers[i].mk_tunnel_header(self.pg1)
1689                     / Wireguard(message_type=4, reserved_zero=0)
1690                     / WireguardTransport(
1691                         receiver_index=peers[i].sender,
1692                         counter=ii + 1,
1693                         encrypted_encapsulated_packet=peers[i].encrypt_transport(
1694                             (
1695                                 IP(
1696                                     src="10.0.%d.4" % i,
1697                                     dst=self.pg0.remote_hosts[i].ip4,
1698                                     ttl=20,
1699                                 )
1700                                 / UDP(sport=222, dport=223)
1701                                 / Raw()
1702                             )
1703                         ),
1704                     )
1705                 )
1706                 for ii in range(64)
1707             ]
1708
1709             rxs = self.send_and_expect(self.pg1, p, self.pg0)
1710
1711             for rx in rxs:
1712                 self.assertEqual(rx[IP].dst, self.pg0.remote_hosts[i].ip4)
1713                 self.assertEqual(rx[IP].ttl, 19)
1714
1715         for r in routes:
1716             r.remove_vpp_config()
1717         for p in peers:
1718             p.remove_vpp_config()
1719         for i in wg_ifs:
1720             i.remove_vpp_config()
1721
1722     def test_wg_event(self):
1723         """Test events"""
1724         port = 12600
1725         ESTABLISHED_FLAG = (
1726             VppEnum.vl_api_wireguard_peer_flags_t.WIREGUARD_PEER_ESTABLISHED
1727         )
1728         DEAD_FLAG = VppEnum.vl_api_wireguard_peer_flags_t.WIREGUARD_PEER_STATUS_DEAD
1729
1730         # Create interfaces
1731         wg0 = VppWgInterface(self, self.pg1.local_ip4, port).add_vpp_config()
1732         wg1 = VppWgInterface(self, self.pg2.local_ip4, port + 1).add_vpp_config()
1733         wg0.admin_up()
1734         wg1.admin_up()
1735
1736         # Check peer counter
1737         self.assertEqual(len(self.vapi.wireguard_peers_dump()), 0)
1738
1739         self.pg_enable_capture(self.pg_interfaces)
1740         self.pg_start()
1741
1742         # Create peers
1743         NUM_PEERS = 2
1744         self.pg2.generate_remote_hosts(NUM_PEERS)
1745         self.pg2.configure_ipv4_neighbors()
1746         self.pg1.generate_remote_hosts(NUM_PEERS)
1747         self.pg1.configure_ipv4_neighbors()
1748
1749         peers_0 = []
1750         peers_1 = []
1751         routes_0 = []
1752         routes_1 = []
1753         for i in range(NUM_PEERS):
1754             peers_0.append(
1755                 VppWgPeer(
1756                     self,
1757                     wg0,
1758                     self.pg1.remote_hosts[i].ip4,
1759                     port + 1 + i,
1760                     ["10.0.%d.4/32" % i],
1761                 ).add_vpp_config()
1762             )
1763             routes_0.append(
1764                 VppIpRoute(
1765                     self,
1766                     "10.0.%d.4" % i,
1767                     32,
1768                     [VppRoutePath(self.pg1.remote_hosts[i].ip4, wg0.sw_if_index)],
1769                 ).add_vpp_config()
1770             )
1771
1772             peers_1.append(
1773                 VppWgPeer(
1774                     self,
1775                     wg1,
1776                     self.pg2.remote_hosts[i].ip4,
1777                     port + 100 + i,
1778                     ["10.100.%d.4/32" % i],
1779                 ).add_vpp_config()
1780             )
1781             routes_1.append(
1782                 VppIpRoute(
1783                     self,
1784                     "10.100.%d.4" % i,
1785                     32,
1786                     [VppRoutePath(self.pg2.remote_hosts[i].ip4, wg1.sw_if_index)],
1787                 ).add_vpp_config()
1788             )
1789
1790         self.assertEqual(len(self.vapi.wireguard_peers_dump()), NUM_PEERS * 2)
1791
1792         # Want events from the first perr of wg0
1793         # and from all wg1 peers
1794         peers_0[0].want_events()
1795         wg1.want_events()
1796
1797         for i in range(NUM_PEERS):
1798             # send a valid handsake init for which we expect a response
1799             p = peers_0[i].mk_handshake(self.pg1)
1800             rx = self.send_and_expect(self.pg1, [p], self.pg1)
1801             peers_0[i].consume_response(rx[0])
1802             if i == 0:
1803                 peers_0[0].wait_event(ESTABLISHED_FLAG)
1804
1805             p = peers_1[i].mk_handshake(self.pg2)
1806             rx = self.send_and_expect(self.pg2, [p], self.pg2)
1807             peers_1[i].consume_response(rx[0])
1808
1809         wg1.wait_events(ESTABLISHED_FLAG, [peers_1[0].index, peers_1[1].index])
1810
1811         # remove routes
1812         for r in routes_0:
1813             r.remove_vpp_config()
1814         for r in routes_1:
1815             r.remove_vpp_config()
1816
1817         # remove peers
1818         for i in range(NUM_PEERS):
1819             self.assertTrue(peers_0[i].query_vpp_config())
1820             peers_0[i].remove_vpp_config()
1821             if i == 0:
1822                 peers_0[i].wait_event(0)
1823                 peers_0[i].wait_event(DEAD_FLAG)
1824         for p in peers_1:
1825             self.assertTrue(p.query_vpp_config())
1826             p.remove_vpp_config()
1827             p.wait_event(0)
1828             p.wait_event(DEAD_FLAG)
1829
1830         wg0.remove_vpp_config()
1831         wg1.remove_vpp_config()
1832
1833
1834 class WireguardHandoffTests(TestWg):
1835     """Wireguard Tests in multi worker setup"""
1836
1837     vpp_worker_count = 2
1838
1839     def test_wg_peer_init(self):
1840         """Handoff"""
1841
1842         port = 12383
1843
1844         # Create interfaces
1845         wg0 = VppWgInterface(self, self.pg1.local_ip4, port).add_vpp_config()
1846         wg0.admin_up()
1847         wg0.config_ip4()
1848
1849         peer_1 = VppWgPeer(
1850             self, wg0, self.pg1.remote_ip4, port + 1, ["10.11.2.0/24", "10.11.3.0/24"]
1851         ).add_vpp_config()
1852         self.assertEqual(len(self.vapi.wireguard_peers_dump()), 1)
1853
1854         r1 = VppIpRoute(
1855             self, "10.11.3.0", 24, [VppRoutePath("10.11.3.1", wg0.sw_if_index)]
1856         ).add_vpp_config()
1857
1858         # send a valid handsake init for which we expect a response
1859         p = peer_1.mk_handshake(self.pg1)
1860
1861         rx = self.send_and_expect(self.pg1, [p], self.pg1)
1862
1863         peer_1.consume_response(rx[0])
1864
1865         # send a data packet from the peer through the tunnel
1866         # this completes the handshake and pins the peer to worker 0
1867         p = (
1868             IP(src="10.11.3.1", dst=self.pg0.remote_ip4, ttl=20)
1869             / UDP(sport=222, dport=223)
1870             / Raw()
1871         )
1872         d = peer_1.encrypt_transport(p)
1873         p = peer_1.mk_tunnel_header(self.pg1) / (
1874             Wireguard(message_type=4, reserved_zero=0)
1875             / WireguardTransport(
1876                 receiver_index=peer_1.sender, counter=0, encrypted_encapsulated_packet=d
1877             )
1878         )
1879         rxs = self.send_and_expect(self.pg1, [p], self.pg0, worker=0)
1880
1881         for rx in rxs:
1882             self.assertEqual(rx[IP].dst, self.pg0.remote_ip4)
1883             self.assertEqual(rx[IP].ttl, 19)
1884
1885         # send a packets that are routed into the tunnel
1886         # and pins the peer tp worker 1
1887         pe = (
1888             Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac)
1889             / IP(src=self.pg0.remote_ip4, dst="10.11.3.2")
1890             / UDP(sport=555, dport=556)
1891             / Raw(b"\x00" * 80)
1892         )
1893         rxs = self.send_and_expect(self.pg0, pe * 255, self.pg1, worker=1)
1894         peer_1.validate_encapped(rxs, pe)
1895
1896         # send packets into the tunnel, from the other worker
1897         p = [
1898             (
1899                 peer_1.mk_tunnel_header(self.pg1)
1900                 / Wireguard(message_type=4, reserved_zero=0)
1901                 / WireguardTransport(
1902                     receiver_index=peer_1.sender,
1903                     counter=ii + 1,
1904                     encrypted_encapsulated_packet=peer_1.encrypt_transport(
1905                         (
1906                             IP(src="10.11.3.1", dst=self.pg0.remote_ip4, ttl=20)
1907                             / UDP(sport=222, dport=223)
1908                             / Raw()
1909                         )
1910                     ),
1911                 )
1912             )
1913             for ii in range(255)
1914         ]
1915
1916         rxs = self.send_and_expect(self.pg1, p, self.pg0, worker=1)
1917
1918         for rx in rxs:
1919             self.assertEqual(rx[IP].dst, self.pg0.remote_ip4)
1920             self.assertEqual(rx[IP].ttl, 19)
1921
1922         # send a packets that are routed into the tunnel
1923         # from owrker 0
1924         rxs = self.send_and_expect(self.pg0, pe * 255, self.pg1, worker=0)
1925
1926         peer_1.validate_encapped(rxs, pe)
1927
1928         r1.remove_vpp_config()
1929         peer_1.remove_vpp_config()
1930         wg0.remove_vpp_config()
1931
1932     @unittest.skip("test disabled")
1933     def test_wg_multi_interface(self):
1934         """Multi-tunnel on the same port"""