#!/usr/bin/env python import base64 import ipaddress import os import sys from pyroute2 import IPDB, WireGuard from wg_pubkey_to_llv6 import pubkey_to_lladdr def fix_addr(addr_str): """This is awful. It fixes wrongly-formatted IPv6 addresses that come back from pyroute2. All 16 bytes are returned, with a ':' between each. e.g., fe:80:00:00:.../128 instead of fe80::.../128""" if '.' in addr_str: # IPv4 is rendered fine. return addr_str # Rebuild IPv6 address in proper form. addr, mask = addr_str.split('/') addr = str(ipaddress.IPv6Address(b''.join(bytes.fromhex(x) for x in addr.split(':')))) return '/'.join([addr, mask]) def peer_to_spec(peer): """Partially reconstruct the peer definition. We only care about the public key [identity] and the allowed ipaddresses [to be updated].""" return { 'public_key': peer.WGPEER_A_PUBLIC_KEY['value'], 'allowed_ips': [fix_addr(x['addr']) for x in peer.WGPEER_A_ALLOWEDIPS['value']], } def add_llv6_addresses_to_interface(ifname): # Local state. addresses = set() collisions = set() address_map = {} wg = WireGuard() wg_iface = wg.info(ifname)[0] # Calculate interface IPv6 link-local address. dev_pk = wg_iface.WGDEVICE_A_PUBLIC_KEY['value'] dev_lla = pubkey_to_lladdr(dev_pk) addresses.add(dev_lla) address_map[dev_pk] = dev_lla # Calculate peer IPv6 link-local addresses. peers = [peer_to_spec(peer) for peer in wg_iface.WGDEVICE_A_PEERS.value] for peer in peers: peer_pk = peer['public_key'] peer_lla = pubkey_to_lladdr(peer_pk) if peer_lla in addresses: collisions.add(peer_lla) addresses.add(peer_lla) address_map[peer_pk] = peer_lla # Did we find collisions? if collisions: print('Peer link local address collisions:') for pk, lla in addresses.items: if lla in collision: print(pk, lla) if dev_lla in collisions: raise Exception('Our link local address collides with a peer. Giving up.') else: print('Proceeding with link-local assignments, skipping colliding peers.') # Add IPv6 link-local address and subnet to interface. with IPDB() as ip: iface = ip.interfaces[ifname] iface.add_ip(str(dev_lla)+'/64') iface.commit() # Add IPv6 link-local addresses to peers without address collisions. for peer in peers: peer_pk = peer['public_key'] peer_lla = str(address_map[peer_pk])+'/128' if peer_lla not in peer['allowed_ips'] and peer_lla not in collisions: peer['allowed_ips'].append(peer_lla) wg.set(ifname, peer=peer) def main(): ifname = os.getenv('IFACE') if not ifname: try: ifname = sys.argv[1] except IndexError: pass if not ifname: print('No interface specified; quitting.') return add_llv6_addresses_to_interface(ifname) if __name__ == '__main__': main()