Skip to content

Commit c89061e

Browse files
authored
Merge branch 'master' into add_mqtt_broker
2 parents dd341c4 + d728c42 commit c89061e

File tree

6 files changed

+204
-0
lines changed

6 files changed

+204
-0
lines changed

README.md

+4
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,7 @@ $ wget -q -O- --post-data='{"domain": "ffmuc_welt","public_key": "o52Ge+Rpj4CUS
2828
"Message": "OK"
2929
}
3030
```
31+
32+
## Contact
33+
34+
#wgkex - IRCNet

pyproject.toml

+1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ python = "^3.6"
1515
PyYAML = "^5.3.1"
1616
Flask = "^1.1.2"
1717
flask-mqtt = "^1.1.1"
18+
pyroute2 = "^0.5.14"
1819
voluptuous = "^0.12.0"
1920

2021
[tool.poetry.dev-dependencies]

wgkex/worker/__init__.py

Whitespace-only changes.

wgkex/worker/app.py

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
#!/usr/bin/env python3
2+
3+
4+
from wgkex.config import load_config
5+
from wgkex.worker.mqtt import connect as mqtt
6+
7+
config = load_config()
8+
9+
10+
def main():
11+
12+
mqtt(config.get("domains"))
13+
14+
15+
if __name__ == "__main__":
16+
main()

wgkex/worker/mqtt.py

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
#!/usr/bin/env python3
2+
import paho.mqtt.client as mqtt
3+
import socket
4+
import time
5+
import re
6+
from wgkex.worker.netlink import (
7+
find_stale_wireguard_clients,
8+
link_handler,
9+
WireGuardClient,
10+
generate_lladdr,
11+
generate_interface_names,
12+
)
13+
14+
15+
def connect(domains: str):
16+
broker_address = "broker.ov.ffmuc.net"
17+
client = mqtt.Client(socket.gethostname())
18+
client.on_message = on_message
19+
print("connecting to broker " + broker_address)
20+
client.connect(broker_address)
21+
for domain in domains:
22+
print("Subscribing to topic", "wireguard/" + domain + "/+")
23+
client.subscribe("wireguard/" + domain + "/+")
24+
client.loop_forever()
25+
26+
27+
def on_message(client, userdata, message):
28+
29+
domain = re.search("/.*ffmuc_(\w+)/", message.topic).group(1)
30+
31+
client = WireGuardClient(
32+
public_key=str(message.payload.decode("utf-8")),
33+
lladdr=b"",
34+
domain=domain,
35+
wg_interface="",
36+
vx_interface="",
37+
remove=False,
38+
)
39+
40+
client.lladdr = generate_lladdr(client.public_key)
41+
42+
client = generate_interface_names(client)
43+
44+
print("Received node create message for key " + client.public_key)
45+
46+
print(link_handler(client))

wgkex/worker/netlink.py

+137
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
#!/usr/bin/env python3
2+
import re
3+
import hashlib
4+
5+
from salt.utils.network import mac2eui64
6+
from textwrap import wrap
7+
from typing import Dict, List
8+
from pyroute2 import WireGuard, IPRoute
9+
from pyroute2.netlink.rtnl import ndmsg
10+
from typing import Dict, List
11+
from dataclasses import dataclass
12+
from datetime import datetime, timedelta
13+
14+
15+
@dataclass
16+
class WireGuardClient:
17+
"""WireGuardClient describes complete configuration for a specific WireGuard client
18+
19+
Attributes:
20+
public_key: WireGuard Public key
21+
domain: Domain Name of the WireGuard peer
22+
lladdr: IPv6 lladdr of the WireGuard peer
23+
wg_interface: Name of the WireGuard interface this peer will use
24+
vx_interface: Name of the VXLAN interface we set a route for the lladdr to
25+
remove: Are we removing this peer or not?
26+
"""
27+
28+
public_key: str
29+
domain: str
30+
lladdr: str
31+
wg_interface: str
32+
vx_interface: str
33+
remove: bool
34+
35+
36+
# we receive stuff from wgkex-broker
37+
def generate_lladdr(public_key: str) -> str:
38+
m = hashlib.md5()
39+
40+
m.update(public_key.encode("ascii") + b"\n")
41+
hashed_key = m.hexdigest()
42+
hash_as_list = wrap(hashed_key, 2)
43+
temp_mac = ":".join(["02"] + hash_as_list[:5])
44+
45+
lladdr = re.sub(r"/\d+$", "/128", mac2eui64(mac=temp_mac, prefix="fe80::/10"))
46+
return lladdr
47+
48+
49+
def generate_ifname(peer: WireGuardClient) -> WireGuardClient:
50+
peer.wg_interface = "wg-" + peer.domain
51+
peer.vx_interface = "vx-" + peer.domain
52+
53+
return peer
54+
55+
56+
def wg_flush_stale_peers(domain: str) -> List[Dict]:
57+
stale_clients = find_stale_wireguard_clients("wg-" + domain)
58+
result = []
59+
for stale_client in stale_clients:
60+
stale_wireguard_client = WireGuardClient(
61+
public_key=stale_client,
62+
lladdr=generate_lladdr(stale_client),
63+
domain=domain,
64+
wg_interface="",
65+
vx_interface="",
66+
remove=True,
67+
)
68+
stale_wireguard_client = generate_ifname(stale_wireguard_client)
69+
result = link_handler(stale_wireguard_client)
70+
return result
71+
72+
73+
# pyroute2 stuff
74+
def link_handler(client: WireGuardClient) -> Dict:
75+
results = {}
76+
77+
results.update({"Wireguard": wireguard_handler(client)})
78+
try:
79+
results.update({"Route": route_handler(client)})
80+
except Exception as e:
81+
results.update({"Route": e})
82+
results.update({"Bridge FDB": bridge_fdb_handler(client)})
83+
84+
return results
85+
86+
87+
def bridge_fdb_handler(client: WireGuardClient) -> Dict:
88+
89+
action = "append"
90+
if client.remove:
91+
action = "del"
92+
93+
with IPRoute() as ip:
94+
return ip.fdb(
95+
action,
96+
ifindex=ip.link_lookup(ifname=client.vx_interface)[0],
97+
lladdr="00:00:00:00:00:00",
98+
dst=re.sub("\/\d+$", "", client.lladdr),
99+
)
100+
101+
102+
def wireguard_handler(client: WireGuardClient) -> Dict:
103+
wg = WireGuard()
104+
105+
wg_peer = {
106+
"public_key": client.public_key,
107+
"persistent_keepalive": 15,
108+
"allowed_ips": [client.lladdr],
109+
"remove": client.remove,
110+
}
111+
112+
return wg.set(client.wg_interface, peer=wg_peer)
113+
114+
115+
def route_handler(client: WireGuardClient) -> Dict:
116+
with IPRoute() as ip:
117+
return ip.route(
118+
"del" if client.remove else "add",
119+
dst=client.lladdr,
120+
oif=ip.link_lookup(ifname=client.wg_interface)[0],
121+
)
122+
123+
124+
def find_stale_wireguard_clients(wg_interface: str) -> List:
125+
wg = WireGuard()
126+
127+
clients = wg.info(wg_interface)[0].WGDEVICE_A_PEERS.value
128+
129+
three_hours_ago = (datetime.now() - timedelta(hours=3)).timestamp()
130+
131+
stale_clients = []
132+
for client in clients:
133+
latest_handshake = client.WGPEER_A_LAST_HANDSHAKE_TIME["tv_sec"]
134+
if latest_handshake < int(three_hours_ago):
135+
stale_clients.append(client.WGPEER_A_PUBLIC_KEY["value"].decode("utf-8"))
136+
137+
return stale_clients

0 commit comments

Comments
 (0)