blob: 96c1bc0f5fdb7f64d746b38b9abede5821c06b8d [file] [log] [blame]
Artem Glazychevedca1322020-08-31 17:12:30 +07001#!/usr/bin/env python3
2""" Wg tests """
3
Neale Rannsd75a2d12020-09-10 08:49:10 +00004import datetime
5import base64
6
7from hashlib import blake2s
Artem Glazychevedca1322020-08-31 17:12:30 +07008from scapy.packet import Packet
9from scapy.packet import Raw
Neale Rannsd75a2d12020-09-10 08:49:10 +000010from scapy.layers.l2 import Ether, ARP
Artem Glazychevedca1322020-08-31 17:12:30 +070011from scapy.layers.inet import IP, UDP
12from scapy.contrib.wireguard import Wireguard, WireguardResponse, \
Neale Rannsd75a2d12020-09-10 08:49:10 +000013 WireguardInitiation, WireguardTransport
14from cryptography.hazmat.primitives.asymmetric.x25519 import \
15 X25519PrivateKey, X25519PublicKey
Artem Glazychevedca1322020-08-31 17:12:30 +070016from cryptography.hazmat.primitives.serialization import Encoding, \
17 PrivateFormat, PublicFormat, NoEncryption
Neale Rannsd75a2d12020-09-10 08:49:10 +000018from cryptography.hazmat.primitives.hashes import BLAKE2s, Hash
19from cryptography.hazmat.primitives.hmac import HMAC
20from cryptography.hazmat.backends import default_backend
21from noise.connection import NoiseConnection, Keypair
Artem Glazychevedca1322020-08-31 17:12:30 +070022
23from vpp_ipip_tun_interface import VppIpIpTunInterface
24from vpp_interface import VppInterface
Artem Glazychevde3caf32021-05-20 12:33:52 +070025from vpp_ip_route import VppIpRoute, VppRoutePath
Artem Glazychevedca1322020-08-31 17:12:30 +070026from vpp_object import VppObject
27from framework import VppTestCase
28from re import compile
29import unittest
30
31""" TestWg is a subclass of VPPTestCase classes.
32
33Wg test.
34
35"""
36
37
Neale Rannsd75a2d12020-09-10 08:49:10 +000038def private_key_bytes(k):
39 return k.private_bytes(Encoding.Raw,
40 PrivateFormat.Raw,
41 NoEncryption())
42
43
44def public_key_bytes(k):
45 return k.public_bytes(Encoding.Raw,
46 PublicFormat.Raw)
47
48
Artem Glazychevedca1322020-08-31 17:12:30 +070049class VppWgInterface(VppInterface):
50 """
51 VPP WireGuard interface
52 """
53
Neale Rannsd75a2d12020-09-10 08:49:10 +000054 def __init__(self, test, src, port):
Artem Glazychevedca1322020-08-31 17:12:30 +070055 super(VppWgInterface, self).__init__(test)
56
Artem Glazychevedca1322020-08-31 17:12:30 +070057 self.port = port
58 self.src = src
Neale Rannsd75a2d12020-09-10 08:49:10 +000059 self.private_key = X25519PrivateKey.generate()
60 self.public_key = self.private_key.public_key()
61
62 def public_key_bytes(self):
63 return public_key_bytes(self.public_key)
64
65 def private_key_bytes(self):
66 return private_key_bytes(self.private_key)
Artem Glazychevedca1322020-08-31 17:12:30 +070067
68 def add_vpp_config(self):
69 r = self.test.vapi.wireguard_interface_create(interface={
70 'user_instance': 0xffffffff,
71 'port': self.port,
72 'src_ip': self.src,
Neale Rannsd75a2d12020-09-10 08:49:10 +000073 'private_key': private_key_bytes(self.private_key),
74 'generate_key': False
Artem Glazychevedca1322020-08-31 17:12:30 +070075 })
76 self.set_sw_if_index(r.sw_if_index)
77 self.test.registry.register(self, self.test.logger)
78 return self
79
Artem Glazychevedca1322020-08-31 17:12:30 +070080 def remove_vpp_config(self):
81 self.test.vapi.wireguard_interface_delete(
82 sw_if_index=self._sw_if_index)
83
84 def query_vpp_config(self):
85 ts = self.test.vapi.wireguard_interface_dump(sw_if_index=0xffffffff)
86 for t in ts:
87 if t.interface.sw_if_index == self._sw_if_index and \
88 str(t.interface.src_ip) == self.src and \
89 t.interface.port == self.port and \
Neale Rannsd75a2d12020-09-10 08:49:10 +000090 t.interface.private_key == private_key_bytes(self.private_key):
Artem Glazychevedca1322020-08-31 17:12:30 +070091 return True
92 return False
93
94 def __str__(self):
95 return self.object_id()
96
97 def object_id(self):
98 return "wireguard-%d" % self._sw_if_index
99
100
101def find_route(test, prefix, table_id=0):
102 routes = test.vapi.ip_route_dump(table_id, False)
103
104 for e in routes:
105 if table_id == e.route.table_id \
106 and str(e.route.prefix) == str(prefix):
107 return True
108 return False
109
110
Neale Rannsd75a2d12020-09-10 08:49:10 +0000111NOISE_HANDSHAKE_NAME = b"Noise_IKpsk2_25519_ChaChaPoly_BLAKE2s"
112NOISE_IDENTIFIER_NAME = b"WireGuard v1 zx2c4 Jason@zx2c4.com"
113
114
Artem Glazychevedca1322020-08-31 17:12:30 +0700115class VppWgPeer(VppObject):
116
117 def __init__(self,
118 test,
119 itf,
120 endpoint,
121 port,
122 allowed_ips,
123 persistent_keepalive=15):
124 self._test = test
125 self.itf = itf
126 self.endpoint = endpoint
127 self.port = port
128 self.allowed_ips = allowed_ips
129 self.persistent_keepalive = persistent_keepalive
Neale Rannsd75a2d12020-09-10 08:49:10 +0000130
131 # remote peer's public
Artem Glazychevedca1322020-08-31 17:12:30 +0700132 self.private_key = X25519PrivateKey.generate()
133 self.public_key = self.private_key.public_key()
Neale Rannsd75a2d12020-09-10 08:49:10 +0000134
135 self.noise = NoiseConnection.from_name(NOISE_HANDSHAKE_NAME)
Artem Glazychevedca1322020-08-31 17:12:30 +0700136
Artem Glazychevedca1322020-08-31 17:12:30 +0700137 def add_vpp_config(self):
138 rv = self._test.vapi.wireguard_peer_add(
139 peer={
140 'public_key': self.public_key_bytes(),
141 'port': self.port,
142 'endpoint': self.endpoint,
143 'n_allowed_ips': len(self.allowed_ips),
144 'allowed_ips': self.allowed_ips,
145 'sw_if_index': self.itf.sw_if_index,
146 'persistent_keepalive': self.persistent_keepalive})
147 self.index = rv.peer_index
Neale Rannsd75a2d12020-09-10 08:49:10 +0000148 self.receiver_index = self.index + 1
Artem Glazychevedca1322020-08-31 17:12:30 +0700149 self._test.registry.register(self, self._test.logger)
Artem Glazychevedca1322020-08-31 17:12:30 +0700150 return self
151
152 def remove_vpp_config(self):
153 self._test.vapi.wireguard_peer_remove(peer_index=self.index)
Artem Glazychevedca1322020-08-31 17:12:30 +0700154
155 def object_id(self):
156 return ("wireguard-peer-%s" % self.index)
157
158 def public_key_bytes(self):
Neale Rannsd75a2d12020-09-10 08:49:10 +0000159 return public_key_bytes(self.public_key)
Artem Glazychevedca1322020-08-31 17:12:30 +0700160
161 def query_vpp_config(self):
162 peers = self._test.vapi.wireguard_peers_dump()
163
164 for p in peers:
165 if p.peer.public_key == self.public_key_bytes() and \
166 p.peer.port == self.port and \
167 str(p.peer.endpoint) == self.endpoint and \
168 p.peer.sw_if_index == self.itf.sw_if_index and \
169 len(self.allowed_ips) == p.peer.n_allowed_ips:
170 self.allowed_ips.sort()
171 p.peer.allowed_ips.sort()
172
173 for (a1, a2) in zip(self.allowed_ips, p.peer.allowed_ips):
174 if str(a1) != str(a2):
175 return False
176 return True
177 return False
178
Neale Rannsd75a2d12020-09-10 08:49:10 +0000179 def set_responder(self):
180 self.noise.set_as_responder()
181
182 def mk_tunnel_header(self, tx_itf):
183 return (Ether(dst=tx_itf.local_mac, src=tx_itf.remote_mac) /
184 IP(src=self.endpoint, dst=self.itf.src) /
185 UDP(sport=self.port, dport=self.itf.port))
186
187 def noise_init(self, public_key=None):
188 self.noise.set_prologue(NOISE_IDENTIFIER_NAME)
189 self.noise.set_psks(psk=bytes(bytearray(32)))
190
191 if not public_key:
192 public_key = self.itf.public_key
193
194 # local/this private
195 self.noise.set_keypair_from_private_bytes(
196 Keypair.STATIC,
197 private_key_bytes(self.private_key))
198 # remote's public
199 self.noise.set_keypair_from_public_bytes(
200 Keypair.REMOTE_STATIC,
201 public_key_bytes(public_key))
202
203 self.noise.start_handshake()
204
205 def mk_handshake(self, tx_itf, public_key=None):
206 self.noise.set_as_initiator()
207 self.noise_init(public_key)
208
209 p = (Wireguard() / WireguardInitiation())
210
211 p[Wireguard].message_type = 1
212 p[Wireguard].reserved_zero = 0
213 p[WireguardInitiation].sender_index = self.receiver_index
214
215 # some random data for the message
216 # lifted from the noise protocol's wireguard example
217 now = datetime.datetime.now()
218 tai = struct.pack('!qi', 4611686018427387914 + int(now.timestamp()),
219 int(now.microsecond * 1e3))
220 b = self.noise.write_message(payload=tai)
221
222 # load noise into init message
223 p[WireguardInitiation].unencrypted_ephemeral = b[0:32]
224 p[WireguardInitiation].encrypted_static = b[32:80]
225 p[WireguardInitiation].encrypted_timestamp = b[80:108]
226
227 # generate the mac1 hash
228 mac_key = blake2s(b'mac1----' +
229 self.itf.public_key_bytes()).digest()
230 p[WireguardInitiation].mac1 = blake2s(bytes(p)[0:116],
231 digest_size=16,
232 key=mac_key).digest()
233 p[WireguardInitiation].mac2 = bytearray(16)
234
235 p = (self.mk_tunnel_header(tx_itf) / p)
236
237 return p
238
239 def verify_header(self, p):
240 self._test.assertEqual(p[IP].src, self.itf.src)
241 self._test.assertEqual(p[IP].dst, self.endpoint)
242 self._test.assertEqual(p[UDP].sport, self.itf.port)
243 self._test.assertEqual(p[UDP].dport, self.port)
244 self._test.assert_packet_checksums_valid(p)
245
246 def consume_init(self, p, tx_itf):
247 self.noise.set_as_responder()
248 self.noise_init(self.itf.public_key)
249 self.verify_header(p)
250
251 init = Wireguard(p[Raw])
252
253 self._test.assertEqual(init[Wireguard].message_type, 1)
254 self._test.assertEqual(init[Wireguard].reserved_zero, 0)
255
256 self.sender = init[WireguardInitiation].sender_index
257
258 # validate the hash
259 mac_key = blake2s(b'mac1----' +
260 public_key_bytes(self.public_key)).digest()
261 mac1 = blake2s(bytes(init)[0:-32],
262 digest_size=16,
263 key=mac_key).digest()
264 self._test.assertEqual(init[WireguardInitiation].mac1, mac1)
265
266 # this passes only unencrypted_ephemeral, encrypted_static,
267 # encrypted_timestamp fields of the init
268 payload = self.noise.read_message(bytes(init)[8:-32])
269
270 # build the response
271 b = self.noise.write_message()
272 mac_key = blake2s(b'mac1----' +
273 public_key_bytes(self.itf.public_key)).digest()
274 resp = (Wireguard(message_type=2, reserved_zero=0) /
275 WireguardResponse(sender_index=self.receiver_index,
276 receiver_index=self.sender,
277 unencrypted_ephemeral=b[0:32],
278 encrypted_nothing=b[32:]))
279 mac1 = blake2s(bytes(resp)[:-32],
280 digest_size=16,
281 key=mac_key).digest()
282 resp[WireguardResponse].mac1 = mac1
283
284 resp = (self.mk_tunnel_header(tx_itf) / resp)
285 self._test.assertTrue(self.noise.handshake_finished)
286
287 return resp
288
289 def consume_response(self, p):
290 self.verify_header(p)
291
292 resp = Wireguard(p[Raw])
293
294 self._test.assertEqual(resp[Wireguard].message_type, 2)
295 self._test.assertEqual(resp[Wireguard].reserved_zero, 0)
296 self._test.assertEqual(resp[WireguardResponse].receiver_index,
297 self.receiver_index)
298
299 self.sender = resp[Wireguard].sender_index
300
301 payload = self.noise.read_message(bytes(resp)[12:60])
302 self._test.assertEqual(payload, b'')
303 self._test.assertTrue(self.noise.handshake_finished)
304
305 def decrypt_transport(self, p):
306 self.verify_header(p)
307
308 p = Wireguard(p[Raw])
309 self._test.assertEqual(p[Wireguard].message_type, 4)
310 self._test.assertEqual(p[Wireguard].reserved_zero, 0)
311 self._test.assertEqual(p[WireguardTransport].receiver_index,
312 self.receiver_index)
313
314 d = self.noise.decrypt(
315 p[WireguardTransport].encrypted_encapsulated_packet)
316 return d
317
318 def encrypt_transport(self, p):
319 return self.noise.encrypt(bytes(p))
320
Artem Glazychev8eb69402020-09-14 11:36:01 +0700321 def validate_encapped(self, rxs, tx):
322 for rx in rxs:
323 rx = IP(self.decrypt_transport(rx))
324
325 # chech the oringial packet is present
326 self._test.assertEqual(rx[IP].dst, tx[IP].dst)
327 self._test.assertEqual(rx[IP].ttl, tx[IP].ttl-1)
328
Artem Glazychevedca1322020-08-31 17:12:30 +0700329
330class TestWg(VppTestCase):
331 """ Wireguard Test Case """
332
333 error_str = compile(r"Error")
334
335 @classmethod
336 def setUpClass(cls):
337 super(TestWg, cls).setUpClass()
338 try:
339 cls.create_pg_interfaces(range(3))
340 for i in cls.pg_interfaces:
341 i.admin_up()
342 i.config_ip4()
343 i.resolve_arp()
344
345 except Exception:
346 super(TestWg, cls).tearDownClass()
347 raise
348
349 @classmethod
350 def tearDownClass(cls):
351 super(TestWg, cls).tearDownClass()
352
353 def test_wg_interface(self):
Neale Rannsd75a2d12020-09-10 08:49:10 +0000354 """ Simple interface creation """
Artem Glazychevedca1322020-08-31 17:12:30 +0700355 port = 12312
356
357 # Create interface
358 wg0 = VppWgInterface(self,
359 self.pg1.local_ip4,
360 port).add_vpp_config()
361
362 self.logger.info(self.vapi.cli("sh int"))
363
364 # delete interface
365 wg0.remove_vpp_config()
366
Neale Rannsd75a2d12020-09-10 08:49:10 +0000367 def test_handshake_hash(self):
368 """ test hashing an init message """
369 # a init packet generated by linux given the key below
370 h = "0100000098b9032b" \
371 "55cc4b39e73c3d24" \
372 "a2a1ab884b524a81" \
373 "1808bb86640fb70d" \
374 "e93154fec1879125" \
375 "ab012624a27f0b75" \
376 "c0a2582f438ddb5f" \
377 "8e768af40b4ab444" \
378 "02f9ff473e1b797e" \
379 "80d39d93c5480c82" \
380 "a3d4510f70396976" \
381 "586fb67300a5167b" \
382 "ae6ca3ff3dfd00eb" \
383 "59be198810f5aa03" \
384 "6abc243d2155ee4f" \
385 "2336483900aef801" \
386 "08752cd700000000" \
387 "0000000000000000" \
388 "00000000"
389
390 b = bytearray.fromhex(h)
391 tgt = Wireguard(b)
392
393 pubb = base64.b64decode("aRuHFTTxICIQNefp05oKWlJv3zgKxb8+WW7JJMh0jyM=")
394 pub = X25519PublicKey.from_public_bytes(pubb)
395
396 self.assertEqual(pubb, public_key_bytes(pub))
397
398 # strip the macs and build a new packet
399 init = b[0:-32]
400 mac_key = blake2s(b'mac1----' + public_key_bytes(pub)).digest()
401 init += blake2s(init,
402 digest_size=16,
403 key=mac_key).digest()
404 init += b'\x00' * 16
405
406 act = Wireguard(init)
407
408 self.assertEqual(tgt, act)
409
410 def test_wg_peer_resp(self):
411 """ Send handshake response """
Artem Glazychevedca1322020-08-31 17:12:30 +0700412 wg_output_node_name = '/err/wg-output-tun/'
413 wg_input_node_name = '/err/wg-input/'
414
415 port = 12323
416
417 # Create interfaces
418 wg0 = VppWgInterface(self,
419 self.pg1.local_ip4,
Neale Rannsd75a2d12020-09-10 08:49:10 +0000420 port).add_vpp_config()
Artem Glazychevedca1322020-08-31 17:12:30 +0700421 wg0.admin_up()
Neale Rannsd75a2d12020-09-10 08:49:10 +0000422 wg0.config_ip4()
Artem Glazychevedca1322020-08-31 17:12:30 +0700423
424 self.pg_enable_capture(self.pg_interfaces)
425 self.pg_start()
426
427 peer_1 = VppWgPeer(self,
428 wg0,
429 self.pg1.remote_ip4,
430 port+1,
Artem Glazychevde3caf32021-05-20 12:33:52 +0700431 ["10.11.3.0/24"]).add_vpp_config()
Artem Glazychevedca1322020-08-31 17:12:30 +0700432 self.assertEqual(len(self.vapi.wireguard_peers_dump()), 1)
433
Artem Glazychevde3caf32021-05-20 12:33:52 +0700434 r1 = VppIpRoute(self, "10.11.3.0", 24,
435 [VppRoutePath("10.11.3.1",
436 wg0.sw_if_index)]).add_vpp_config()
437
Artem Glazychevedca1322020-08-31 17:12:30 +0700438 # wait for the peer to send a handshake
Neale Rannsd75a2d12020-09-10 08:49:10 +0000439 rx = self.pg1.get_capture(1, timeout=2)
Artem Glazychevedca1322020-08-31 17:12:30 +0700440
Neale Rannsd75a2d12020-09-10 08:49:10 +0000441 # consume the handshake in the noise protocol and
442 # generate the response
443 resp = peer_1.consume_init(rx[0], self.pg1)
444
445 # send the response, get keepalive
446 rxs = self.send_and_expect(self.pg1, [resp], self.pg1)
447
448 for rx in rxs:
449 b = peer_1.decrypt_transport(rx)
450 self.assertEqual(0, len(b))
451
452 # send a packets that are routed into the tunnel
453 p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
454 IP(src=self.pg0.remote_ip4, dst="10.11.3.2") /
455 UDP(sport=555, dport=556) /
456 Raw(b'\x00' * 80))
457
458 rxs = self.send_and_expect(self.pg0, p * 255, self.pg1)
459
Artem Glazychev8eb69402020-09-14 11:36:01 +0700460 peer_1.validate_encapped(rxs, p)
Neale Rannsd75a2d12020-09-10 08:49:10 +0000461
462 # send packets into the tunnel, expect to receive them on
463 # the other side
464 p = [(peer_1.mk_tunnel_header(self.pg1) /
465 Wireguard(message_type=4, reserved_zero=0) /
466 WireguardTransport(
467 receiver_index=peer_1.sender,
468 counter=ii,
469 encrypted_encapsulated_packet=peer_1.encrypt_transport(
470 (IP(src="10.11.3.1", dst=self.pg0.remote_ip4, ttl=20) /
471 UDP(sport=222, dport=223) /
472 Raw())))) for ii in range(255)]
473
474 rxs = self.send_and_expect(self.pg1, p, self.pg0)
475
476 for rx in rxs:
477 self.assertEqual(rx[IP].dst, self.pg0.remote_ip4)
478 self.assertEqual(rx[IP].ttl, 19)
479
Artem Glazychevde3caf32021-05-20 12:33:52 +0700480 r1.remove_vpp_config()
481 peer_1.remove_vpp_config()
482 wg0.remove_vpp_config()
483
Neale Rannsd75a2d12020-09-10 08:49:10 +0000484 def test_wg_peer_init(self):
485 """ Send handshake init """
486 wg_output_node_name = '/err/wg-output-tun/'
487 wg_input_node_name = '/err/wg-input/'
488
Artem Glazychev124d5e02020-09-30 01:07:46 +0700489 port = 12333
Neale Rannsd75a2d12020-09-10 08:49:10 +0000490
491 # Create interfaces
492 wg0 = VppWgInterface(self,
493 self.pg1.local_ip4,
494 port).add_vpp_config()
495 wg0.admin_up()
496 wg0.config_ip4()
497
498 peer_1 = VppWgPeer(self,
499 wg0,
500 self.pg1.remote_ip4,
501 port+1,
Artem Glazychevde3caf32021-05-20 12:33:52 +0700502 ["10.11.3.0/24"]).add_vpp_config()
Neale Rannsd75a2d12020-09-10 08:49:10 +0000503 self.assertEqual(len(self.vapi.wireguard_peers_dump()), 1)
Artem Glazychevedca1322020-08-31 17:12:30 +0700504
Artem Glazychevde3caf32021-05-20 12:33:52 +0700505 r1 = VppIpRoute(self, "10.11.3.0", 24,
506 [VppRoutePath("10.11.3.1",
507 wg0.sw_if_index)]).add_vpp_config()
508
Artem Glazychevedca1322020-08-31 17:12:30 +0700509 # route a packet into the wg interface
510 # use the allowed-ip prefix
Neale Rannsd75a2d12020-09-10 08:49:10 +0000511 # this is dropped because the peer is not initiated
Artem Glazychevedca1322020-08-31 17:12:30 +0700512 p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
513 IP(src=self.pg0.remote_ip4, dst="10.11.3.2") /
514 UDP(sport=555, dport=556) /
515 Raw())
Neale Rannsd75a2d12020-09-10 08:49:10 +0000516 self.send_and_assert_no_replies(self.pg0, [p])
Artem Glazychevedca1322020-08-31 17:12:30 +0700517
Neale Rannsd75a2d12020-09-10 08:49:10 +0000518 kp_error = wg_output_node_name + "Keypair error"
519 self.assertEqual(1, self.statistics.get_err_counter(kp_error))
520
521 # send a handsake from the peer with an invalid MAC
522 p = peer_1.mk_handshake(self.pg1)
523 p[WireguardInitiation].mac1 = b'foobar'
524 self.send_and_assert_no_replies(self.pg1, [p])
525 self.assertEqual(1, self.statistics.get_err_counter(
526 wg_input_node_name + "Invalid MAC handshake"))
527
528 # send a handsake from the peer but signed by the wrong key.
529 p = peer_1.mk_handshake(self.pg1,
530 X25519PrivateKey.generate().public_key())
531 self.send_and_assert_no_replies(self.pg1, [p])
532 self.assertEqual(1, self.statistics.get_err_counter(
533 wg_input_node_name + "Peer error"))
534
535 # send a valid handsake init for which we expect a response
536 p = peer_1.mk_handshake(self.pg1)
537
538 rx = self.send_and_expect(self.pg1, [p], self.pg1)
539
540 peer_1.consume_response(rx[0])
541
542 # route a packet into the wg interface
543 # this is dropped because the peer is still not initiated
544 p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
545 IP(src=self.pg0.remote_ip4, dst="10.11.3.2") /
546 UDP(sport=555, dport=556) /
547 Raw())
548 self.send_and_assert_no_replies(self.pg0, [p])
549 self.assertEqual(2, self.statistics.get_err_counter(kp_error))
550
551 # send a data packet from the peer through the tunnel
552 # this completes the handshake
553 p = (IP(src="10.11.3.1", dst=self.pg0.remote_ip4, ttl=20) /
554 UDP(sport=222, dport=223) /
555 Raw())
556 d = peer_1.encrypt_transport(p)
557 p = (peer_1.mk_tunnel_header(self.pg1) /
558 (Wireguard(message_type=4, reserved_zero=0) /
559 WireguardTransport(receiver_index=peer_1.sender,
560 counter=0,
561 encrypted_encapsulated_packet=d)))
562 rxs = self.send_and_expect(self.pg1, [p], self.pg0)
563
564 for rx in rxs:
565 self.assertEqual(rx[IP].dst, self.pg0.remote_ip4)
566 self.assertEqual(rx[IP].ttl, 19)
567
568 # send a packets that are routed into the tunnel
569 p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
570 IP(src=self.pg0.remote_ip4, dst="10.11.3.2") /
571 UDP(sport=555, dport=556) /
572 Raw(b'\x00' * 80))
573
574 rxs = self.send_and_expect(self.pg0, p * 255, self.pg1)
575
576 for rx in rxs:
577 rx = IP(peer_1.decrypt_transport(rx))
578
579 # chech the oringial packet is present
580 self.assertEqual(rx[IP].dst, p[IP].dst)
581 self.assertEqual(rx[IP].ttl, p[IP].ttl-1)
582
583 # send packets into the tunnel, expect to receive them on
584 # the other side
585 p = [(peer_1.mk_tunnel_header(self.pg1) /
586 Wireguard(message_type=4, reserved_zero=0) /
587 WireguardTransport(
588 receiver_index=peer_1.sender,
589 counter=ii+1,
590 encrypted_encapsulated_packet=peer_1.encrypt_transport(
591 (IP(src="10.11.3.1", dst=self.pg0.remote_ip4, ttl=20) /
592 UDP(sport=222, dport=223) /
593 Raw())))) for ii in range(255)]
594
595 rxs = self.send_and_expect(self.pg1, p, self.pg0)
596
597 for rx in rxs:
598 self.assertEqual(rx[IP].dst, self.pg0.remote_ip4)
599 self.assertEqual(rx[IP].ttl, 19)
600
Artem Glazychevde3caf32021-05-20 12:33:52 +0700601 r1.remove_vpp_config()
Neale Rannsd75a2d12020-09-10 08:49:10 +0000602 peer_1.remove_vpp_config()
603 wg0.remove_vpp_config()
604
605 def test_wg_multi_peer(self):
606 """ multiple peer setup """
Artem Glazychev124d5e02020-09-30 01:07:46 +0700607 port = 12343
Neale Rannsd75a2d12020-09-10 08:49:10 +0000608
609 # Create interfaces
610 wg0 = VppWgInterface(self,
611 self.pg1.local_ip4,
612 port).add_vpp_config()
613 wg1 = VppWgInterface(self,
614 self.pg2.local_ip4,
615 port+1).add_vpp_config()
616 wg0.admin_up()
617 wg1.admin_up()
618
619 # Check peer counter
620 self.assertEqual(len(self.vapi.wireguard_peers_dump()), 0)
621
622 self.pg_enable_capture(self.pg_interfaces)
623 self.pg_start()
Artem Glazychevedca1322020-08-31 17:12:30 +0700624
625 # Create many peers on sencond interface
626 NUM_PEERS = 16
627 self.pg2.generate_remote_hosts(NUM_PEERS)
628 self.pg2.configure_ipv4_neighbors()
Neale Rannsd75a2d12020-09-10 08:49:10 +0000629 self.pg1.generate_remote_hosts(NUM_PEERS)
630 self.pg1.configure_ipv4_neighbors()
Artem Glazychevedca1322020-08-31 17:12:30 +0700631
Neale Rannsd75a2d12020-09-10 08:49:10 +0000632 peers_1 = []
633 peers_2 = []
Artem Glazychevde3caf32021-05-20 12:33:52 +0700634 routes_1 = []
635 routes_2 = []
Artem Glazychevedca1322020-08-31 17:12:30 +0700636 for i in range(NUM_PEERS):
Neale Rannsd75a2d12020-09-10 08:49:10 +0000637 peers_1.append(VppWgPeer(self,
638 wg0,
639 self.pg1.remote_hosts[i].ip4,
640 port+1+i,
641 ["10.0.%d.4/32" % i]).add_vpp_config())
Artem Glazychevde3caf32021-05-20 12:33:52 +0700642 routes_1.append(VppIpRoute(self, "10.0.%d.4" % i, 32,
643 [VppRoutePath(self.pg1.remote_hosts[i].ip4,
644 wg0.sw_if_index)]).add_vpp_config())
645
Neale Rannsd75a2d12020-09-10 08:49:10 +0000646 peers_2.append(VppWgPeer(self,
647 wg1,
648 self.pg2.remote_hosts[i].ip4,
649 port+100+i,
650 ["10.100.%d.4/32" % i]).add_vpp_config())
Artem Glazychevde3caf32021-05-20 12:33:52 +0700651 routes_2.append(VppIpRoute(self, "10.100.%d.4" % i, 32,
652 [VppRoutePath(self.pg2.remote_hosts[i].ip4,
653 wg1.sw_if_index)]).add_vpp_config())
Neale Rannsd75a2d12020-09-10 08:49:10 +0000654
655 self.assertEqual(len(self.vapi.wireguard_peers_dump()), NUM_PEERS*2)
Artem Glazychevedca1322020-08-31 17:12:30 +0700656
657 self.logger.info(self.vapi.cli("show wireguard peer"))
658 self.logger.info(self.vapi.cli("show wireguard interface"))
659 self.logger.info(self.vapi.cli("show adj 37"))
660 self.logger.info(self.vapi.cli("sh ip fib 172.16.3.17"))
661 self.logger.info(self.vapi.cli("sh ip fib 10.11.3.0"))
662
Artem Glazychevde3caf32021-05-20 12:33:52 +0700663 # remove routes
664 for r in routes_1:
665 r.remove_vpp_config()
666 for r in routes_2:
667 r.remove_vpp_config()
668
Artem Glazychevedca1322020-08-31 17:12:30 +0700669 # remove peers
Neale Rannsd75a2d12020-09-10 08:49:10 +0000670 for p in peers_1:
Artem Glazychevedca1322020-08-31 17:12:30 +0700671 self.assertTrue(p.query_vpp_config())
672 p.remove_vpp_config()
Neale Rannsd75a2d12020-09-10 08:49:10 +0000673 for p in peers_2:
674 self.assertTrue(p.query_vpp_config())
675 p.remove_vpp_config()
Artem Glazychevedca1322020-08-31 17:12:30 +0700676
677 wg0.remove_vpp_config()
Neale Rannsd75a2d12020-09-10 08:49:10 +0000678 wg1.remove_vpp_config()
Artem Glazychev8eb69402020-09-14 11:36:01 +0700679
Artem Glazychev9e24f7e2021-05-25 12:06:42 +0700680 def test_wg_multi_interface(self):
681 """ Multi-tunnel on the same port """
682 port = 12500
683
684 # Create many wireguard interfaces
685 NUM_IFS = 4
686 self.pg1.generate_remote_hosts(NUM_IFS)
687 self.pg1.configure_ipv4_neighbors()
688 self.pg0.generate_remote_hosts(NUM_IFS)
689 self.pg0.configure_ipv4_neighbors()
690
691 # Create interfaces with a peer on each
692 peers = []
693 routes = []
694 wg_ifs = []
695 for i in range(NUM_IFS):
696 # Use the same port for each interface
697 wg0 = VppWgInterface(self,
698 self.pg1.local_ip4,
699 port).add_vpp_config()
700 wg0.admin_up()
701 wg0.config_ip4()
702 wg_ifs.append(wg0)
703 peers.append(VppWgPeer(self,
704 wg0,
705 self.pg1.remote_hosts[i].ip4,
706 port+1+i,
707 ["10.0.%d.0/24" % i]).add_vpp_config())
708
709 routes.append(VppIpRoute(self, "10.0.%d.0" % i, 24,
710 [VppRoutePath("10.0.%d.4" % i,
711 wg0.sw_if_index)]).add_vpp_config())
712
713 self.assertEqual(len(self.vapi.wireguard_peers_dump()), NUM_IFS)
714
715 for i in range(NUM_IFS):
716 # send a valid handsake init for which we expect a response
717 p = peers[i].mk_handshake(self.pg1)
718 rx = self.send_and_expect(self.pg1, [p], self.pg1)
719 peers[i].consume_response(rx[0])
720
721 # send a data packet from the peer through the tunnel
722 # this completes the handshake
723 p = (IP(src="10.0.%d.4" % i,
724 dst=self.pg0.remote_hosts[i].ip4, ttl=20) /
725 UDP(sport=222, dport=223) /
726 Raw())
727 d = peers[i].encrypt_transport(p)
728 p = (peers[i].mk_tunnel_header(self.pg1) /
729 (Wireguard(message_type=4, reserved_zero=0) /
730 WireguardTransport(receiver_index=peers[i].sender,
731 counter=0,
732 encrypted_encapsulated_packet=d)))
733 rxs = self.send_and_expect(self.pg1, [p], self.pg0)
734 for rx in rxs:
735 self.assertEqual(rx[IP].dst, self.pg0.remote_hosts[i].ip4)
736 self.assertEqual(rx[IP].ttl, 19)
737
738 # send a packets that are routed into the tunnel
739 for i in range(NUM_IFS):
740 p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
741 IP(src=self.pg0.remote_hosts[i].ip4, dst="10.0.%d.4" % i) /
742 UDP(sport=555, dport=556) /
743 Raw(b'\x00' * 80))
744
745 rxs = self.send_and_expect(self.pg0, p * 64, self.pg1)
746
747 for rx in rxs:
748 rx = IP(peers[i].decrypt_transport(rx))
749
750 # check the oringial packet is present
751 self.assertEqual(rx[IP].dst, p[IP].dst)
752 self.assertEqual(rx[IP].ttl, p[IP].ttl-1)
753
754 # send packets into the tunnel
755 for i in range(NUM_IFS):
756 p = [(peers[i].mk_tunnel_header(self.pg1) /
757 Wireguard(message_type=4, reserved_zero=0) /
758 WireguardTransport(
759 receiver_index=peers[i].sender,
760 counter=ii+1,
761 encrypted_encapsulated_packet=peers[i].encrypt_transport(
762 (IP(src="10.0.%d.4" % i,
763 dst=self.pg0.remote_hosts[i].ip4, ttl=20) /
764 UDP(sport=222, dport=223) /
765 Raw())))) for ii in range(64)]
766
767 rxs = self.send_and_expect(self.pg1, p, self.pg0)
768
769 for rx in rxs:
770 self.assertEqual(rx[IP].dst, self.pg0.remote_hosts[i].ip4)
771 self.assertEqual(rx[IP].ttl, 19)
772
773 for r in routes:
774 r.remove_vpp_config()
775 for p in peers:
776 p.remove_vpp_config()
777 for i in wg_ifs:
778 i.remove_vpp_config()
779
Artem Glazychev8eb69402020-09-14 11:36:01 +0700780
781class WireguardHandoffTests(TestWg):
782 """ Wireguard Tests in multi worker setup """
Klement Sekera8d815022021-03-15 16:58:10 +0100783 vpp_worker_count = 2
Artem Glazychev8eb69402020-09-14 11:36:01 +0700784
785 def test_wg_peer_init(self):
786 """ Handoff """
787 wg_output_node_name = '/err/wg-output-tun/'
788 wg_input_node_name = '/err/wg-input/'
789
Artem Glazychev124d5e02020-09-30 01:07:46 +0700790 port = 12353
Artem Glazychev8eb69402020-09-14 11:36:01 +0700791
792 # Create interfaces
793 wg0 = VppWgInterface(self,
794 self.pg1.local_ip4,
795 port).add_vpp_config()
796 wg0.admin_up()
797 wg0.config_ip4()
798
799 peer_1 = VppWgPeer(self,
800 wg0,
801 self.pg1.remote_ip4,
802 port+1,
803 ["10.11.2.0/24",
804 "10.11.3.0/24"]).add_vpp_config()
805 self.assertEqual(len(self.vapi.wireguard_peers_dump()), 1)
806
Artem Glazychevde3caf32021-05-20 12:33:52 +0700807 r1 = VppIpRoute(self, "10.11.3.0", 24,
808 [VppRoutePath("10.11.3.1",
809 wg0.sw_if_index)]).add_vpp_config()
810
Artem Glazychev8eb69402020-09-14 11:36:01 +0700811 # send a valid handsake init for which we expect a response
812 p = peer_1.mk_handshake(self.pg1)
813
814 rx = self.send_and_expect(self.pg1, [p], self.pg1)
815
816 peer_1.consume_response(rx[0])
817
818 # send a data packet from the peer through the tunnel
819 # this completes the handshake and pins the peer to worker 0
820 p = (IP(src="10.11.3.1", dst=self.pg0.remote_ip4, ttl=20) /
821 UDP(sport=222, dport=223) /
822 Raw())
823 d = peer_1.encrypt_transport(p)
824 p = (peer_1.mk_tunnel_header(self.pg1) /
825 (Wireguard(message_type=4, reserved_zero=0) /
826 WireguardTransport(receiver_index=peer_1.sender,
827 counter=0,
828 encrypted_encapsulated_packet=d)))
829 rxs = self.send_and_expect(self.pg1, [p], self.pg0,
830 worker=0)
831
832 for rx in rxs:
833 self.assertEqual(rx[IP].dst, self.pg0.remote_ip4)
834 self.assertEqual(rx[IP].ttl, 19)
835
836 # send a packets that are routed into the tunnel
837 # and pins the peer tp worker 1
838 pe = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
839 IP(src=self.pg0.remote_ip4, dst="10.11.3.2") /
840 UDP(sport=555, dport=556) /
841 Raw(b'\x00' * 80))
842 rxs = self.send_and_expect(self.pg0, pe * 255, self.pg1, worker=1)
843 peer_1.validate_encapped(rxs, pe)
844
845 # send packets into the tunnel, from the other worker
846 p = [(peer_1.mk_tunnel_header(self.pg1) /
847 Wireguard(message_type=4, reserved_zero=0) /
848 WireguardTransport(
849 receiver_index=peer_1.sender,
850 counter=ii+1,
851 encrypted_encapsulated_packet=peer_1.encrypt_transport(
852 (IP(src="10.11.3.1", dst=self.pg0.remote_ip4, ttl=20) /
853 UDP(sport=222, dport=223) /
854 Raw())))) for ii in range(255)]
855
856 rxs = self.send_and_expect(self.pg1, p, self.pg0, worker=1)
857
858 for rx in rxs:
859 self.assertEqual(rx[IP].dst, self.pg0.remote_ip4)
860 self.assertEqual(rx[IP].ttl, 19)
861
862 # send a packets that are routed into the tunnel
863 # from owrker 0
864 rxs = self.send_and_expect(self.pg0, pe * 255, self.pg1, worker=0)
865
866 peer_1.validate_encapped(rxs, pe)
867
Artem Glazychevde3caf32021-05-20 12:33:52 +0700868 r1.remove_vpp_config()
Artem Glazychev8eb69402020-09-14 11:36:01 +0700869 peer_1.remove_vpp_config()
870 wg0.remove_vpp_config()
Artem Glazychev9e24f7e2021-05-25 12:06:42 +0700871
872 @unittest.skip("test disabled")
873 def test_wg_multi_interface(self):
874 """ Multi-tunnel on the same port """