mirror of
https://github.com/dadevel/wg-netns
synced 2024-10-30 21:20:12 +00:00
rewrite for v1
Allow multiple interfaces per namespace. Change configuration format to json.
This commit is contained in:
parent
e225ad0bfe
commit
812e027bc0
112
README.md
112
README.md
@ -1,7 +1,7 @@
|
|||||||
# wg-netns
|
# wg-netns
|
||||||
|
|
||||||
[wg-quick](https://git.zx2c4.com/wireguard-tools/about/src/man/wg-quick.8) for linux network namespaces.
|
[wg-quick](https://git.zx2c4.com/wireguard-tools/about/src/man/wg-quick.8) with support for linux network namespaces.
|
||||||
A simple python script that implements the steps described at [wireguard.com/netns](https://www.wireguard.com/netns/#ordinary-containerization).
|
It's a simple python script that implements the steps described at [wireguard.com/netns](https://www.wireguard.com/netns/#ordinary-containerization).
|
||||||
|
|
||||||
## Setup
|
## Setup
|
||||||
|
|
||||||
@ -20,21 +20,105 @@ mkdir -p ~/.local/bin/ && curl -o ~/.local/bin/wg-netns https://raw.githubuserco
|
|||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
Instead of running `wg-quick up my-vpn` run `wg-netns up my-vpn`.
|
First, create a configuration profile.
|
||||||
|
You can find two examples below.
|
||||||
|
|
||||||
Now you can spawn a shell in the new network namespace.
|
`./mini.json`:
|
||||||
|
|
||||||
|
~~~ json
|
||||||
|
{
|
||||||
|
"name": "ns-example",
|
||||||
|
"interfaces": [
|
||||||
|
{
|
||||||
|
"name": "wg-example",
|
||||||
|
"address": ["10.10.10.192/32", "fc00:dead:beef::192/128"],
|
||||||
|
"private-key": "4bvaEZHI...",
|
||||||
|
"peers": [
|
||||||
|
{
|
||||||
|
"public-key": "bELgMXGt...",
|
||||||
|
"endpoint": "vpn.example.com:51820",
|
||||||
|
"allowed-ips": ["0.0.0.0/0", "::/0"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
~~~
|
||||||
|
|
||||||
|
`./maxi.json`:
|
||||||
|
|
||||||
|
~~~ json
|
||||||
|
{
|
||||||
|
"name": "ns-example",
|
||||||
|
"dns-server": ["10.10.10.1", "10.10.10.2"],
|
||||||
|
"pre-up": "some shell command",
|
||||||
|
"post-up": "some shell command",
|
||||||
|
"pred-own": "some shell command",
|
||||||
|
"post-down": "some shell command",
|
||||||
|
"interfaces": [
|
||||||
|
{
|
||||||
|
"name": "wg-site-a",
|
||||||
|
"address": ["10.10.11.172/32", "fc00:dead:beef:1::172/128"],
|
||||||
|
"listen-port": 51821,
|
||||||
|
"fwmark": 51821,
|
||||||
|
"private-key": "nFkQQjN+...",
|
||||||
|
"mtu": 1420,
|
||||||
|
"peers": [
|
||||||
|
{
|
||||||
|
"public-key": "Kx+wpJpj...",
|
||||||
|
"preshared-key": "5daskLoW...",
|
||||||
|
"endpoint": "a.example.com:51821",
|
||||||
|
"persistent-keepalive": 25,
|
||||||
|
"allowed-ips": ["10.10.11.0/24", "fc00:dead:beef:1::/64"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "wg-site-b",
|
||||||
|
"address": ["10.10.12.172/32", "fc00:dead:beef:2::172/128"],
|
||||||
|
"listen-port": 51822,
|
||||||
|
"fwmark": 51822,
|
||||||
|
"private-key": "guYPuE3X...",
|
||||||
|
"mtu": 1420,
|
||||||
|
"peers": [
|
||||||
|
{
|
||||||
|
"public-key": "NvZMoyrg...",
|
||||||
|
"preshared-key": "cFQuyIX/...",
|
||||||
|
"endpoint": "b.example.com:51822",
|
||||||
|
"persistent-keepalive": 25,
|
||||||
|
"allowed-ips": ["10.10.12.0/24", "fc00:dead:beef:2::/64"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
~~~
|
||||||
|
|
||||||
|
Now it's time to setup your new network namespace and all associated wireguard interfaces.
|
||||||
|
|
||||||
~~~ bash
|
~~~ bash
|
||||||
ip netns exec my-vpn bash -i
|
wg-netns up ./example.json
|
||||||
|
~~~
|
||||||
|
|
||||||
|
You can verify the success with a combination of `ip` and `wg`.
|
||||||
|
|
||||||
|
~~~ bash
|
||||||
|
ip netns exec ns-example wg show
|
||||||
|
~~~
|
||||||
|
|
||||||
|
Or you can spawn a shell inside the netns.
|
||||||
|
|
||||||
|
~~~ bash
|
||||||
|
ip netns exec ns-example bash -i
|
||||||
~~~
|
~~~
|
||||||
|
|
||||||
Or connect a container to it.
|
Or connect a container to it.
|
||||||
|
|
||||||
~~~ bash
|
~~~ bash
|
||||||
podman run -it --rm --network ns:/var/run/netns/my-vpn alpine wget -O - https://ipinfo.io
|
podman run -it --rm --network ns:/var/run/netns/ns-example docker.io/alpine wget -O - https://ipinfo.io
|
||||||
~~~
|
~~~
|
||||||
|
|
||||||
Or do whatever you want.
|
Or do whatever else you want.
|
||||||
|
|
||||||
### System Service
|
### System Service
|
||||||
|
|
||||||
@ -42,22 +126,22 @@ You can find a `wg-quick@.service` equivalent at [wg-netns@.service](./wg-netns@
|
|||||||
|
|
||||||
### Port Forwarding
|
### Port Forwarding
|
||||||
|
|
||||||
Forward TCP traffic from outside a network namespace to a port inside a network namespace with `socat`.
|
With `socat` you can forward TCP traffic from outside a network namespace to a port inside a network namespace.
|
||||||
|
|
||||||
~~~ bash
|
~~~ bash
|
||||||
socat tcp-listen:$LHOST,reuseaddr,fork "exec:ip netns exec $NETNS socat stdio 'tcp-connect:$RHOST',nofork"
|
socat tcp-listen:$LHOST,reuseaddr,fork "exec:ip netns exec $NETNS socat stdio 'tcp-connect:$RHOST',nofork"
|
||||||
~~~
|
~~~
|
||||||
|
|
||||||
Example: All connections to port 1234/tcp in the main netns are forwarded into the *my-vpn* netns to port 5678/tcp.
|
Example: All connections to port 1234/tcp in the main netns are forwarded into the *ns-example* namespace to port 5678/tcp.
|
||||||
|
|
||||||
~~~ bash
|
~~~ bash
|
||||||
# terminal 1, create netns and start http server inside
|
# terminal 1, create netns and start http server inside
|
||||||
wg-netns up my-vpn
|
wg-netns up ns-example
|
||||||
echo hello > ./hello.txt
|
hello > ./hello.txt
|
||||||
ip netns exec my-vpn python3 -m http.server 5678
|
ip netns exec ns-example python3 -m http.server 5678
|
||||||
# terminal 2, setup port forwarding
|
# terminal 2, setup port forwarding
|
||||||
socat tcp-listen:1234,reuseaddr,fork "exec:ip netns exec my-vpn socat stdio 'tcp-connect:127.0.0.1:5678',nofork"
|
socat tcp-listen:1234,reuseaddr,fork "exec:ip netns exec ns-example socat stdio 'tcp-connect:127.0.0.1:5678',nofork"
|
||||||
# terminal 3, test
|
# terminal 3, test access
|
||||||
curl http://127.0.0.1:1234/hello.txt
|
curl http://127.0.0.1:1234/hello.txt
|
||||||
~~~
|
~~~
|
||||||
|
|
||||||
|
315
wg-netns.py
315
wg-netns.py
@ -1,223 +1,202 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
from argparse import ArgumentParser, RawDescriptionHelpFormatter
|
from argparse import ArgumentParser, RawDescriptionHelpFormatter
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import itertools
|
import json
|
||||||
import os
|
import os
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
NETNS_CONFIG_DIR = '/etc/netns'
|
||||||
|
DEBUG_LEVEL = 0
|
||||||
|
|
||||||
|
|
||||||
def main(args):
|
def main(args):
|
||||||
main_parser = ArgumentParser(
|
global NETNS_CONFIG_DIR
|
||||||
|
global DEBUG_LEVEL
|
||||||
|
|
||||||
|
entrypoint = ArgumentParser(
|
||||||
formatter_class=RawDescriptionHelpFormatter,
|
formatter_class=RawDescriptionHelpFormatter,
|
||||||
epilog=(
|
epilog=(
|
||||||
'environment variables:\n'
|
'environment variables:\n'
|
||||||
' WGNETNS_WG_DIR wireguard config directory, default: /etc/wireguard\n'
|
f' NETNS_CONFIG_DIR network namespace config directory, default: {NETNS_CONFIG_DIR}\n'
|
||||||
' WGNETNS_NETNS_DIR network namespace config directory, default: /etc/netns\n'
|
f' DEBUG_LEVEL print stack traces, default: {DEBUG_LEVEL}\n'
|
||||||
' WGNETNS_DEBUG print stack traces\n'
|
|
||||||
),
|
),
|
||||||
|
|
||||||
)
|
)
|
||||||
main_parser.add_argument(
|
|
||||||
'--wg-dir',
|
|
||||||
type=lambda x: Path(x).expanduser(),
|
|
||||||
default=os.environ.get('WGNETNS_WG_DIR', '/etc/wireguard'),
|
|
||||||
metavar='DIRECTORY',
|
|
||||||
help='override WGNETNS_WG_DIR',
|
|
||||||
)
|
|
||||||
main_parser.add_argument(
|
|
||||||
'--netns-dir',
|
|
||||||
type=lambda x: Path(x).expanduser(),
|
|
||||||
default=os.environ.get('WGNETNS_NETNS_DIR', '/etc/netns'),
|
|
||||||
metavar='DIRECTORY',
|
|
||||||
help='override WGNETNS_NETNS_DIR',
|
|
||||||
)
|
|
||||||
subparsers = main_parser.add_subparsers(dest='command', required=True)
|
|
||||||
|
|
||||||
parser = subparsers.add_parser('up', help='set up interface')
|
subparsers = entrypoint.add_subparsers(dest='action', required=True)
|
||||||
parser.add_argument('name', help='configuration name')
|
|
||||||
|
|
||||||
parser = subparsers.add_parser('down', help='tear down interface')
|
parser = subparsers.add_parser('up', help='setup namespace and associated interfaces')
|
||||||
parser.add_argument('-f', '--force', help='ignore errors')
|
parser.add_argument('profile', type=lambda x: Path(x).expanduser(), help='path to profile')
|
||||||
parser.add_argument('name', help='configuration name')
|
|
||||||
|
|
||||||
parser = subparsers.add_parser('status', help='show status info')
|
parser = subparsers.add_parser('down', help='teardown namespace and associated interfaces')
|
||||||
parser.add_argument('name', help='configuration name')
|
parser.add_argument('-f', '--force', action='store_true', help='ignore errors')
|
||||||
|
parser.add_argument('profile', type=lambda x: Path(x).expanduser(), help='path to profile')
|
||||||
|
|
||||||
opts = main_parser.parse_args(args)
|
opts = entrypoint.parse_args(args)
|
||||||
commands = dict(
|
|
||||||
up=setup_wrapped,
|
|
||||||
down=teardown,
|
|
||||||
status=print_status,
|
|
||||||
)
|
|
||||||
fn = commands[opts.command]
|
|
||||||
fn(opts.wg_dir, opts.netns_dir, opts.name)
|
|
||||||
|
|
||||||
|
|
||||||
def print_status(wg_dir, netns_dir, name):
|
|
||||||
run('ip', 'netns', 'exec', name, 'wg', 'show', name)
|
|
||||||
|
|
||||||
|
|
||||||
def setup_wrapped(*args):
|
|
||||||
try:
|
try:
|
||||||
setup(*args)
|
NETNS_CONFIG_DIR = Path(os.environ.get('NETNS_CONFIG_DIR', NETNS_CONFIG_DIR))
|
||||||
|
DEBUG_LEVEL = int(os.environ.get('DEBUG_LEVEL', DEBUG_LEVEL))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
teardown(*args, force=True)
|
raise RuntimeError(f'failed to load environment variable: {e} (e.__class__.__name__)') from e
|
||||||
|
|
||||||
|
if opts.action == 'up':
|
||||||
|
setup_action(opts.profile)
|
||||||
|
elif opts.action == 'down':
|
||||||
|
teardown_action(opts.profile, check=not opts.force)
|
||||||
|
else:
|
||||||
|
raise RuntimeError('congratulations, you reached unreachable code')
|
||||||
|
|
||||||
|
|
||||||
|
def setup_action(path):
|
||||||
|
namespace = profile_read(path)
|
||||||
|
try:
|
||||||
|
namespace_setup(namespace)
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
namespace_teardown(namespace, check=False)
|
||||||
|
except Exception as e:
|
||||||
|
namespace_teardown(namespace, check=False)
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
|
||||||
def setup(wg_dir, netns_dir, name):
|
def teardown_action(path, check=True):
|
||||||
interface_config, peer_config = parse_wireguard_config(wg_dir.joinpath(name).with_suffix('.conf'))
|
namespace = profile_read(path)
|
||||||
setup_network_namespace(name)
|
namespace_teardown(namespace, check=check)
|
||||||
setup_wireguard_interface(name, interface_config, peer_config)
|
|
||||||
setup_resolv_conf(netns_dir/name, interface_config)
|
|
||||||
|
|
||||||
|
|
||||||
def setup_network_namespace(name):
|
def profile_read(path):
|
||||||
run('ip', 'netns', 'add', name)
|
with open(path) as file:
|
||||||
run('ip', '-n', name, 'link', 'set', 'dev', 'lo', 'up')
|
return json.load(file)
|
||||||
|
|
||||||
|
|
||||||
def setup_wireguard_interface(name, interface, peers):
|
def namespace_setup(namespace):
|
||||||
run('ip', 'link', 'add', name, 'type', 'wireguard')
|
if namespace.get('pre-up'):
|
||||||
run('ip', 'link', 'set', name, 'netns', name)
|
ip_netns_shell(namespace['pre-up'], netns=namespace)
|
||||||
run(
|
namespace_create(namespace)
|
||||||
'ip', 'netns', 'exec', name,
|
namespace_resolvconf_write(namespace)
|
||||||
'wg', 'set', name, 'listen-port', interface.get('listenport', 0),
|
for interface in namespace['interfaces']:
|
||||||
)
|
interface_setup(interface, namespace)
|
||||||
run(
|
if namespace.get('post-up'):
|
||||||
'ip', 'netns', 'exec', name,
|
ip_netns_shell(namespace['post-up'], netns=namespace)
|
||||||
'wg', 'set', name, 'private-key', '/dev/stdin', stdin=interface['privatekey'],
|
|
||||||
)
|
|
||||||
for peer in peers:
|
|
||||||
run(
|
|
||||||
'ip', 'netns', 'exec', name,
|
|
||||||
'wg', 'set', name,
|
|
||||||
'peer', peer['publickey'],
|
|
||||||
'preshared-key', '/dev/stdin' if peer.get('presharedkey') else '/dev/null',
|
|
||||||
'endpoint', peer['endpoint'],
|
|
||||||
'persistent-keepalive', peer.get('persistentkeepalive', 0),
|
|
||||||
'allowed-ips', '0.0.0.0/0,::/0',
|
|
||||||
stdin=peer.get('presharedkey', ''),
|
|
||||||
)
|
|
||||||
for addr in interface['address']:
|
|
||||||
run('ip', '-n', name, '-6' if ':' in addr else '-4', 'address', 'add', addr, 'dev', name)
|
|
||||||
run('ip', '-n', name, 'link', 'set', name, 'mtu', interface.get('mtu', 1420))
|
|
||||||
run('ip', '-n', name, 'link', 'set', name, 'up')
|
|
||||||
run('ip', '-n', name, 'route', 'add', 'default', 'dev', name)
|
|
||||||
|
|
||||||
|
|
||||||
def setup_resolv_conf(netns_dir, interface_config):
|
def namespace_create(namespace):
|
||||||
if interface_config.get('dns'):
|
ip('netns', 'add', namespace['name'])
|
||||||
netns_dir.mkdir(parents=True, exist_ok=True)
|
ip('-n', namespace['name'], 'link', 'set', 'dev', 'lo', 'up')
|
||||||
text = '\n'.join(f'nameserver {server}' for server in interface_config['dns'])
|
|
||||||
netns_dir.joinpath('resolv.conf').write_text(text)
|
|
||||||
|
|
||||||
|
|
||||||
def teardown(wg_dir, netns_dir, name, force=False):
|
def namespace_resolvconf_write(namespace):
|
||||||
teardown_network_namespace(name, check=not force)
|
content = '\n'.join(f'nameserver {server}' for server in namespace['dns-server'])
|
||||||
teardown_resolv_conf(netns_dir/name)
|
if content:
|
||||||
|
NETNS_CONFIG_DIR.joinpath(namespace['name']).mkdir(parents=True, exist_ok=True)
|
||||||
|
NETNS_CONFIG_DIR.joinpath(namespace['name']).joinpath('resolv.conf').write_text(content)
|
||||||
|
|
||||||
|
|
||||||
def teardown_network_namespace(name, check=True):
|
def namespace_teardown(namespace, check=True):
|
||||||
run('ip', '-n', name, 'route', 'delete', 'default', 'dev', name, check=check)
|
if namespace.get('pre-down'):
|
||||||
run('ip', '-n', name, 'link', 'set', name, 'down', check=check)
|
ip_netns_shell(namespace['pre-down'], netns=namespace)
|
||||||
run('ip', '-n', name, 'link', 'delete', name, check=check)
|
for interface in namespace['interfaces']:
|
||||||
run('ip', 'netns', 'delete', name, check=check)
|
interface_teardown(interface, namespace)
|
||||||
|
namespace_delete(namespace)
|
||||||
|
namespace_resolvconf_delete(namespace)
|
||||||
|
if namespace.get('post-down'):
|
||||||
|
ip_netns_shell(namespace['post-down'], netns=namespace)
|
||||||
|
|
||||||
|
|
||||||
def teardown_resolv_conf(netns_dir):
|
def namespace_delete(namespace, check=True):
|
||||||
resolv_conf = netns_dir/'resolv.conf'
|
ip('netns', 'delete', namespace['name'], check=check)
|
||||||
if resolv_conf.exists():
|
|
||||||
resolv_conf.unlink()
|
|
||||||
|
def namespace_resolvconf_delete(namespace):
|
||||||
|
path = NETNS_CONFIG_DIR/namespace['name']/'resolv.conf'
|
||||||
|
if path.exists():
|
||||||
|
path.unlink()
|
||||||
try:
|
try:
|
||||||
netns_dir.rmdir()
|
NETNS_CONFIG_DIR.rmdir()
|
||||||
except OSError:
|
except OSError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def parse_wireguard_config(path):
|
def interface_setup(interface, namespace):
|
||||||
with open(path) as file:
|
interface_create(interface, namespace)
|
||||||
it = iter(
|
interface_configure_wireguard(interface, namespace)
|
||||||
line.strip()
|
for peer in interface['peers']:
|
||||||
for line in file
|
peer_setup(peer, interface, namespace)
|
||||||
if line.strip() and not line.startswith('#')
|
interface_assign_addresses(interface, namespace)
|
||||||
)
|
interface_bring_up(interface, namespace)
|
||||||
interface = dict()
|
interface_create_routes(interface, namespace)
|
||||||
peers = list()
|
|
||||||
try:
|
|
||||||
while True:
|
|
||||||
line = next(it)
|
|
||||||
if line.lower() == '[interface]':
|
|
||||||
it, result = parse_interface(it)
|
|
||||||
interface.update(result)
|
|
||||||
elif line.lower() == '[peer]':
|
|
||||||
it, result = parse_peer(it)
|
|
||||||
peers.append(result)
|
|
||||||
else:
|
|
||||||
raise ParserError(f'invalid line: {line}')
|
|
||||||
except ParserError as e:
|
|
||||||
raise ParserError(f'failed to parse wireguard configuration: {e}') from e
|
|
||||||
except StopIteration:
|
|
||||||
return interface, peers
|
|
||||||
|
|
||||||
|
|
||||||
def parse_interface(it):
|
def interface_create(interface, namespace):
|
||||||
result = dict()
|
ip('link', 'add', interface['name'], 'type', 'wireguard')
|
||||||
for line in it:
|
ip('link', 'set', interface['name'], 'netns', namespace['name'])
|
||||||
if line.lower() in ('[interface]', '[peer]'):
|
|
||||||
return itertools.chain((line,), it), result
|
|
||||||
key, value = parse_pair(line)
|
|
||||||
if key in ('address', 'dns'):
|
|
||||||
result[key] = parse_items(value)
|
|
||||||
elif key in ('mtu', 'listenport', 'privatekey'):
|
|
||||||
result[key] = value
|
|
||||||
elif key in ('preup', 'postup', 'predown', 'postdown', 'saveconfig', 'table', 'fwmark'):
|
|
||||||
raise ParserError(f'unsupported interface key: {key}')
|
|
||||||
else:
|
|
||||||
raise ParserError(f'unknown interface key: {key}')
|
|
||||||
return iter(()), result
|
|
||||||
|
|
||||||
|
|
||||||
def parse_peer(it):
|
def interface_configure_wireguard(interface, namespace):
|
||||||
result = dict()
|
wg('set', interface['name'], 'listen-port', interface.get('listen-port', 0), netns=namespace)
|
||||||
for line in it:
|
wg('set', interface['name'], 'fwmark', interface.get('fwmark', 0), netns=namespace)
|
||||||
if line.lower() in ('[interface]', '[peer]'):
|
wg('set', interface['name'], 'private-key', '/dev/stdin', stdin=interface['private-key'], netns=namespace)
|
||||||
return itertools.chain((line,), it), result
|
|
||||||
key, value = parse_pair(line)
|
|
||||||
if key == 'allowedips':
|
|
||||||
result[key] = parse_items(value)
|
|
||||||
elif key in ('presharedkey', 'publickey', 'endpoint', 'persistentkeepalive'):
|
|
||||||
result[key] = value
|
|
||||||
else:
|
|
||||||
raise ParserError(f'unknown peer key: {key}')
|
|
||||||
return iter(()), result
|
|
||||||
|
|
||||||
|
|
||||||
def parse_pair(line):
|
def interface_assign_addresses(interface, namespace):
|
||||||
pair = line.split('=', maxsplit=1)
|
for address in interface['address']:
|
||||||
if len(pair) != 2:
|
ip('-n', namespace['name'], '-6' if ':' in address else '-4', 'address', 'add', address, 'dev', interface['name'])
|
||||||
raise ParserError(f'invalid pair: {line}')
|
|
||||||
key, value = pair
|
|
||||||
return key.strip().lower(), value.strip()
|
|
||||||
|
|
||||||
|
|
||||||
def parse_items(text):
|
def interface_bring_up(interface, namespace):
|
||||||
return [item.strip() for item in text.split(',')]
|
ip('-n', namespace['name'], 'link', 'set', 'dev', interface['name'], 'mtu', interface.get('mtu', 1420), 'up')
|
||||||
|
|
||||||
|
|
||||||
def run(*args, stdin=None, check=False):
|
def interface_create_routes(interface, namespace):
|
||||||
args = [str(item) for item in args if item is not None]
|
for peer in interface['peers']:
|
||||||
process = subprocess.run(
|
for network in peer['allowed-ips']:
|
||||||
args,
|
ip('-n', namespace['name'], '-6' if ':' in network else '-4', 'route', 'add', network, 'dev', interface['name'])
|
||||||
input=stdin,
|
|
||||||
text=True,
|
|
||||||
check=check,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class ParserError(Exception):
|
def interface_teardown(interface, namespace, check=True):
|
||||||
pass
|
ip('-n', namespace['name'], 'link', 'set', interface['name'], 'down', check=check)
|
||||||
|
ip('-n', namespace['name'], 'link', 'delete', interface['name'], check=check)
|
||||||
|
|
||||||
|
|
||||||
|
def peer_setup(peer, interface, namespace):
|
||||||
|
options = [
|
||||||
|
'peer', peer['public-key'],
|
||||||
|
'preshared-key', '/dev/stdin' if peer.get('preshared-key') else '/dev/null',
|
||||||
|
]
|
||||||
|
if peer.get('endpoint'):
|
||||||
|
options.extend(('endpoint', peer.get('endpoint')))
|
||||||
|
options += [
|
||||||
|
'persistent-keepalive', peer.get('persistent-keepalive', 0),
|
||||||
|
'allowed-ips', ','.join(peer['allowed-ips']),
|
||||||
|
]
|
||||||
|
wg('set', interface['name'], *options, stdin=peer.get('preshared-key'), netns=namespace)
|
||||||
|
|
||||||
|
|
||||||
|
def wg(*args, **kwargs):
|
||||||
|
return ip_netns_exec('wg', *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
def ip_netns_shell(*args, **kwargs):
|
||||||
|
return ip_netns_exec(SHELL, '-c', *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
def ip_netns_exec(*args, netns=None, **kwargs):
|
||||||
|
return ip('netns', 'exec', netns['name'], *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
def ip(*args, **kwargs):
|
||||||
|
return run('ip', *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
def run(*args, stdin=None, check=True, capture=False):
|
||||||
|
args = [str(item) if item is not None else '' for item in args]
|
||||||
|
if DEBUG_LEVEL:
|
||||||
|
print('>', ' '.join(args), file=sys.stderr)
|
||||||
|
process = subprocess.run(args, input=stdin, text=True, capture_output=capture)
|
||||||
|
if check and process.returncode != 0:
|
||||||
|
error = process.stderr.strip() if process.stderr else f'exit code {process.returncode}'
|
||||||
|
raise RuntimeError(f'subprocess failed: {" ".join(args)}: {error}')
|
||||||
|
return process.stdout
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
@ -225,7 +204,7 @@ if __name__ == '__main__':
|
|||||||
main(sys.argv[1:])
|
main(sys.argv[1:])
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
if os.environ.get('WGNETNS_DEBUG'):
|
if DEBUG_LEVEL:
|
||||||
raise
|
raise
|
||||||
print(f'error: {e} ({e.__class__.__name__})', file=sys.stderr)
|
print(f'error: {e} ({e.__class__.__name__})', file=sys.stderr)
|
||||||
sys.exit(2)
|
sys.exit(2)
|
||||||
|
@ -5,11 +5,11 @@ After=network-online.target nss-lookup.target
|
|||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
Type=oneshot
|
Type=oneshot
|
||||||
RemainAfterExit=yes
|
|
||||||
|
|
||||||
Environment=WG_ENDPOINT_RESOLUTION_RETRIES=infinity
|
Environment=WG_ENDPOINT_RESOLUTION_RETRIES=infinity
|
||||||
ExecStart=wg-netns up %i
|
ExecStart=wg-netns up ./%i.json
|
||||||
ExecStop=wg-netns down %i
|
ExecStop=wg-netns down ./%i.json
|
||||||
|
WorkingDirectory=%E/wg-netns
|
||||||
|
RemainAfterExit=yes
|
||||||
|
|
||||||
[Install]
|
[Install]
|
||||||
WantedBy=multi-user.target
|
WantedBy=multi-user.target
|
||||||
|
Loading…
Reference in New Issue
Block a user