]> asedeno.scripts.mit.edu Git - wg_llv6.git/blob - wg_llv6.py
initial commit
[wg_llv6.git] / wg_llv6.py
1 #!/usr/bin/env python
2
3 import base64
4 import ipaddress
5 import os
6 import sys
7
8 from pyroute2 import IPDB, WireGuard
9
10 from wg_pubkey_to_llv6 import pubkey_to_lladdr
11
12
13 def fix_addr(addr_str):
14     """This is awful. It fixes wrongly-formatted IPv6 addresses that come back from pyroute2.
15     All 16 bytes are returned, with a ':' between each.
16     e.g., fe:80:00:00:.../128 instead of fe80::.../128"""
17     if '.' in addr_str:
18         # IPv4 is rendered fine.
19         return addr_str
20
21     # Rebuild IPv6 address in proper form.
22     addr, mask = addr_str.split('/')
23     addr = str(ipaddress.IPv6Address(b''.join(bytes.fromhex(x) for x in addr.split(':'))))
24     return '/'.join([addr, mask])
25
26
27 def peer_to_spec(peer):
28     """Partially reconstruct the peer definition. We only care about the
29     public key [identity] and the allowed ipaddresses [to be updated]."""
30     return {
31         'public_key': peer.WGPEER_A_PUBLIC_KEY['value'],
32         'allowed_ips': [fix_addr(x['addr']) for x in peer.WGPEER_A_ALLOWEDIPS['value']],
33     }
34
35
36 def add_llv6_addresses_to_interface(ifname):
37     # Local state.
38     addresses = set()
39     collisions = set()
40     address_map = {}
41     wg = WireGuard()
42     wg_iface = wg.info(ifname)[0]
43
44     # Calculate interface IPv6 link-local address.
45     dev_pk = wg_iface.WGDEVICE_A_PUBLIC_KEY['value']
46     dev_lla = pubkey_to_lladdr(dev_pk)
47     addresses.add(dev_lla)
48     address_map[dev_pk] = dev_lla
49
50     # Calculate peer IPv6 link-local addresses.
51     peers = [peer_to_spec(peer) for peer in wg_iface.WGDEVICE_A_PEERS.value]
52     for peer in peers:
53         peer_pk = peer['public_key']
54         peer_lla = pubkey_to_lladdr(peer_pk)
55         if peer_lla in addresses:
56             collisions.add(peer_lla)
57         addresses.add(peer_lla)
58         address_map[peer_pk] = peer_lla
59
60     # Did we find collisions?
61     if collisions:
62         print('Peer link local address collisions:')
63         for pk, lla in addresses.items:
64             if lla in collision:
65                 print(pk, lla)
66         if dev_lla in collisions:
67             raise Exception('Our link local address collides with a peer. Giving up.')
68         else:
69             print('Proceeding with link-local assignments, skipping colliding peers.')
70
71     # Add IPv6 link-local address and subnet to interface.
72     with IPDB() as ip:
73         iface = ip.interfaces[ifname]
74         iface.add_ip(str(dev_lla)+'/64')
75         iface.commit()
76
77     # Add IPv6 link-local addresses to peers without address collisions.
78     for peer in peers:
79         peer_pk = peer['public_key']
80         peer_lla = str(address_map[peer_pk])+'/128'
81         if peer_lla not in peer['allowed_ips'] and peer_lla not in collisions:
82             peer['allowed_ips'].append(peer_lla)
83             wg.set(ifname, peer=peer)
84
85
86 def main():
87     ifname = os.getenv('IFACE')
88     if not ifname:
89         try:
90             ifname = sys.argv[1]
91         except IndexError:
92             pass
93     if not ifname:
94         print('No interface specified; quitting.')
95         return
96     add_llv6_addresses_to_interface(ifname)
97
98
99 if __name__ == '__main__':
100     main()