2022-03-12 04:11:50 +08:00
|
|
|
import os
|
|
|
|
import copy
|
|
|
|
import sys
|
|
|
|
import time
|
|
|
|
import uuid
|
|
|
|
import json
|
|
|
|
import base64
|
|
|
|
import traceback
|
|
|
|
import subprocess
|
|
|
|
from hashlib import sha256
|
|
|
|
|
|
|
|
import requests
|
|
|
|
from cryptography import x509
|
|
|
|
from cryptography.hazmat.primitives import serialization, hashes
|
|
|
|
from cryptography.hazmat.primitives.asymmetric import rsa, padding
|
|
|
|
|
|
|
|
|
|
|
|
def errprint(msg):
|
|
|
|
sys.stderr.write("{}\n".format(msg))
|
|
|
|
|
|
|
|
|
|
|
|
def get_subject_name_from_cert(cert_path):
|
|
|
|
try:
|
|
|
|
with open(cert_path, 'rb') as f:
|
|
|
|
cert = x509.load_pem_x509_certificate(f.read())
|
|
|
|
return cert.subject.get_attributes_for_oid(x509.oid.NameOID.COMMON_NAME)[0].value
|
|
|
|
except Exception:
|
|
|
|
errprint(traceback.format_exc())
|
|
|
|
return ""
|
|
|
|
|
|
|
|
|
|
|
|
def generate_rsa_keypair():
|
|
|
|
private_key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
|
|
|
|
public_key = private_key.public_key()
|
|
|
|
return private_key, public_key
|
|
|
|
|
|
|
|
|
|
|
|
def get_pem_from_rsa_keypair(private_key, public_key):
|
|
|
|
if private_key:
|
|
|
|
pripem = private_key.private_bytes(
|
|
|
|
encoding=serialization.Encoding.PEM,
|
|
|
|
format=serialization.PrivateFormat.PKCS8,
|
|
|
|
encryption_algorithm=serialization.NoEncryption()).decode()
|
|
|
|
else:
|
|
|
|
pripem = None
|
|
|
|
|
|
|
|
if public_key:
|
|
|
|
pubpem = public_key.public_bytes(
|
|
|
|
encoding=serialization.Encoding.PEM,
|
|
|
|
format=serialization.PublicFormat.SubjectPublicKeyInfo,
|
|
|
|
).decode()
|
|
|
|
else:
|
|
|
|
pubpem = private_key.public_key().public_bytes(
|
|
|
|
encoding=serialization.Encoding.PEM,
|
|
|
|
format=serialization.PublicFormat.SubjectPublicKeyInfo,
|
|
|
|
).decode()
|
|
|
|
|
|
|
|
return pripem, pubpem
|
|
|
|
|
|
|
|
|
|
|
|
def get_rsa_keypair_from_private_pem(private_pem):
|
|
|
|
private_key = serialization.load_pem_private_key(private_pem.encode(), password=None)
|
|
|
|
public_key = private_key.public_key()
|
|
|
|
return private_key, public_key
|
|
|
|
|
|
|
|
|
|
|
|
def get_rsa_pubkey_from_public_pem(public_pem):
|
|
|
|
public_key = serialization.load_pem_public_key(public_pem.encode())
|
|
|
|
return public_key
|
|
|
|
|
|
|
|
|
|
|
|
def rsa_sign_base64(private_key, bytes_data):
|
|
|
|
signature = private_key.sign(bytes_data, padding.PSS(
|
|
|
|
mgf=padding.MGF1(hashes.SHA256()),
|
|
|
|
salt_length=padding.PSS.MAX_LENGTH,
|
|
|
|
), hashes.SHA256())
|
|
|
|
|
|
|
|
return base64.b64encode(signature).decode()
|
|
|
|
|
|
|
|
|
|
|
|
def rsa_encrypt_base64(public_key, bytes_data):
|
|
|
|
return base64.b64encode(public_key.encrypt(bytes_data, padding.OAEP(
|
|
|
|
mgf=padding.MGF1(algorithm=hashes.SHA256()),
|
|
|
|
algorithm=hashes.SHA256(),
|
|
|
|
label=None
|
|
|
|
))).decode()
|
|
|
|
|
|
|
|
|
|
|
|
def rsa_decrypt_base64(private_key, str_data):
|
|
|
|
return private_key.decrypt(base64.b64decode(str_data), padding.OAEP(
|
|
|
|
mgf=padding.MGF1(algorithm=hashes.SHA256()),
|
|
|
|
algorithm=hashes.SHA256(),
|
|
|
|
label=None
|
|
|
|
))
|
|
|
|
|
|
|
|
|
|
|
|
class Parser:
|
|
|
|
def __init__(self, wgop_basepath):
|
|
|
|
# paths
|
|
|
|
self.path_get_gateway = os.path.join(wgop_basepath, 'tools/get-gateway.py')
|
|
|
|
self.path_get_ip = os.path.join(wgop_basepath, 'tools/get-ip.py')
|
|
|
|
self.path_get_lan_ip = os.path.join(wgop_basepath, 'tools/get-lan-ip.py')
|
2022-03-16 21:22:21 +08:00
|
|
|
self.path_reload_dns = os.path.join(wgop_basepath, 'tools/reload-dns.py')
|
2022-03-12 04:11:50 +08:00
|
|
|
self.path_bin_dir = os.path.join(wgop_basepath, 'bin')
|
|
|
|
self.path_app_dir = os.path.join(wgop_basepath, 'app')
|
2022-03-17 18:02:29 +08:00
|
|
|
self.path_bin_mux = os.path.join(wgop_basepath, 'bin/mux')
|
|
|
|
self.path_bin_udp2raw = os.path.join(wgop_basepath, 'bin/udp2raw_amd64')
|
|
|
|
self.path_bin_gost = os.path.join(wgop_basepath, 'bin/gost')
|
|
|
|
self.path_bin_trojan = os.path.join(wgop_basepath, 'bin/trojan-go')
|
2022-03-12 04:11:50 +08:00
|
|
|
|
2022-03-16 19:47:52 +08:00
|
|
|
# opts
|
|
|
|
self.opt_source_path = ''
|
|
|
|
self.opt_allow_modify = False
|
|
|
|
self.opt_use_tmux = False
|
2022-03-19 04:46:17 +08:00
|
|
|
self.opt_use_systemd = False
|
2022-03-16 19:47:52 +08:00
|
|
|
|
2022-03-12 04:11:50 +08:00
|
|
|
# input parts
|
|
|
|
self.input_interface = []
|
|
|
|
self.input_peer = []
|
|
|
|
|
|
|
|
# output
|
|
|
|
self.result_interface = []
|
|
|
|
self.result_postup = []
|
|
|
|
self.result_postdown = []
|
|
|
|
self.result_peers = []
|
|
|
|
|
|
|
|
# container related output
|
|
|
|
self.result_container_prebootstrap = []
|
|
|
|
self.result_container_postbootstrap = []
|
|
|
|
|
|
|
|
# flags
|
|
|
|
self.flag_is_route_forward = False
|
|
|
|
self.flag_is_route_lookup = False
|
|
|
|
self.flag_container_must_host = False
|
|
|
|
self.flag_require_registry = False
|
2022-03-16 21:22:21 +08:00
|
|
|
self.flag_enable_dns_reload = False
|
|
|
|
self.flag_require_systemd_clean = False
|
2022-03-19 04:46:17 +08:00
|
|
|
self.flag_require_tmpfile_clean = False
|
2022-03-17 18:02:29 +08:00
|
|
|
self.flag_has_open_tmux = False
|
2022-03-12 04:11:50 +08:00
|
|
|
|
|
|
|
# vars
|
|
|
|
self.wg_name = '%i'
|
|
|
|
self.wg_port = 0
|
|
|
|
self.wg_mtu = 0
|
|
|
|
self.wg_pubkey = ''
|
|
|
|
self.wg_hash = ''
|
|
|
|
self.registry_domain = ''
|
|
|
|
self.registry_client_name = ''
|
|
|
|
self.local_private_key = None
|
|
|
|
self.local_public_key = None
|
|
|
|
self.local_autogen_nextport = 29100
|
|
|
|
self.pending_accepts = []
|
|
|
|
self.tunnel_local_endpoint = {}
|
|
|
|
self.tunnel_server_reports = {}
|
|
|
|
self.lookup_table = ''
|
|
|
|
self.container_expose_port = []
|
|
|
|
self.container_bootstrap = []
|
|
|
|
self.podman_user = ''
|
2022-03-19 04:46:17 +08:00
|
|
|
self.systemd_user = ''
|
2022-03-12 04:11:50 +08:00
|
|
|
|
|
|
|
def get_container_network_name(self):
|
|
|
|
if self.flag_container_must_host:
|
|
|
|
return "host"
|
|
|
|
else:
|
|
|
|
return "wgop-net-{}".format(self.wg_name)
|
|
|
|
|
|
|
|
def get_container_name(self):
|
|
|
|
return "wgop-runner-{}".format(self.wg_name)
|
|
|
|
|
|
|
|
def get_podman_cmd_with(self, command):
|
|
|
|
if self.podman_user:
|
|
|
|
return "su - {} -c '{}'".format(self.podman_user, command)
|
|
|
|
else:
|
|
|
|
return command
|
|
|
|
|
2022-03-19 04:46:17 +08:00
|
|
|
def get_systemd_run_cmd_with(self, command):
|
|
|
|
if self.systemd_user:
|
|
|
|
return command.replace('systemd-run', 'systemd-run --property User={}'.format(self.systemd_user), 1)
|
|
|
|
else:
|
|
|
|
return command
|
|
|
|
|
|
|
|
def new_tmp_filepath(self, suffix=None):
|
|
|
|
self.flag_require_tmpfile_clean = True
|
|
|
|
return "/tmp/wg-ops-tmpfile-{}-{}{}".format(self.wg_name, str(uuid.uuid4()), suffix if suffix else "")
|
|
|
|
|
|
|
|
def add_write_tmpfile_bytes(self, data_bytes, suffix=None):
|
|
|
|
raw_code_str = base64.b64encode(data_bytes).decode()
|
|
|
|
raw_parts = []
|
|
|
|
|
|
|
|
while len(raw_code_str) > 1024:
|
|
|
|
raw_parts.append(raw_code_str[:1024])
|
|
|
|
raw_code_str = raw_code_str[1024:]
|
|
|
|
|
|
|
|
if raw_code_str:
|
|
|
|
raw_parts.append(raw_code_str)
|
|
|
|
|
|
|
|
temp_data_path = self.new_tmp_filepath('.data')
|
|
|
|
temp_output_path = self.new_tmp_filepath(suffix)
|
|
|
|
|
|
|
|
for this_line in raw_parts:
|
|
|
|
self.result_postup.append('echo {} >> {}'.format(this_line, temp_data_path))
|
|
|
|
self.result_postup.append('base64 -d {} > {}'.format(temp_data_path, temp_output_path))
|
|
|
|
|
|
|
|
return temp_output_path
|
|
|
|
|
2022-03-12 04:11:50 +08:00
|
|
|
def registry_resolve(self, client_name):
|
|
|
|
if not self.registry_domain:
|
|
|
|
errprint('[ERROR] Cannot query from registry, domain not specified')
|
|
|
|
exit(1)
|
|
|
|
if not self.registry_client_name:
|
|
|
|
errprint('[ERROR] No registry client name found.')
|
|
|
|
exit(1)
|
|
|
|
|
|
|
|
errprint('[REGISTRY] Resolving client {} from registry ({})...'.format(client_name, self.registry_domain))
|
|
|
|
try:
|
|
|
|
res = requests.get('{}/query'.format(self.registry_domain), params={
|
|
|
|
"name": client_name,
|
|
|
|
})
|
|
|
|
res = res.json()
|
|
|
|
errprint('[REGISTRY-SERVER] {}'.format(res['message']))
|
|
|
|
|
|
|
|
if res['code'] < 0:
|
|
|
|
return {}
|
|
|
|
|
|
|
|
remote_result = res['data']
|
|
|
|
remote_peers = remote_result['peers']
|
|
|
|
if self.registry_client_name not in remote_peers:
|
|
|
|
errprint('[REGISTRY-REMOTE] This client ({}) is not accepted by {}'.format(self.registry_client_name, client_name))
|
|
|
|
return {}
|
|
|
|
|
|
|
|
remote_config = rsa_decrypt_base64(self.local_private_key, remote_peers[self.registry_client_name])
|
|
|
|
remote_config = json.loads(remote_config)
|
|
|
|
|
|
|
|
remote_peers[self.registry_client_name] = remote_config
|
|
|
|
|
|
|
|
return remote_result
|
|
|
|
except Exception:
|
|
|
|
errprint(traceback.format_exc())
|
|
|
|
errprint('[REGISTRY] Exception happened during registry client resolve')
|
|
|
|
return {}
|
|
|
|
|
|
|
|
def registry_query(self, client_name):
|
|
|
|
if not self.registry_domain:
|
|
|
|
errprint('[ERROR] Cannot query from registry, domain not specified')
|
|
|
|
exit(1)
|
|
|
|
if not self.registry_client_name:
|
|
|
|
errprint('[ERROR] No registry client name found.')
|
|
|
|
exit(1)
|
|
|
|
|
|
|
|
errprint('[REGISTRY] Querying client {}...'.format(client_name))
|
|
|
|
try:
|
|
|
|
res = requests.get('{}/query'.format(self.registry_domain), params={
|
|
|
|
"name": client_name,
|
|
|
|
})
|
|
|
|
res = res.json()
|
|
|
|
errprint('[REGISTRY-SERVER] {}'.format(res['message']))
|
|
|
|
|
|
|
|
if res['code'] < 0:
|
|
|
|
return {}
|
|
|
|
|
|
|
|
remote_result = res['data']
|
|
|
|
return remote_result
|
|
|
|
except Exception:
|
|
|
|
errprint(traceback.format_exc())
|
|
|
|
errprint('[REGISTRY] Exception happened during registry client query')
|
|
|
|
return {}
|
|
|
|
|
|
|
|
def registry_upload(self, content):
|
|
|
|
if not self.registry_domain:
|
|
|
|
errprint('[ERROR] Cannot query from registry, domain not specified')
|
|
|
|
exit(1)
|
|
|
|
if not self.registry_client_name:
|
|
|
|
errprint('[ERROR] No registry client name found.')
|
|
|
|
exit(1)
|
|
|
|
|
|
|
|
errprint('[REGISTRY] Registering this client ({}) with registry ({})...'.format(self.registry_client_name, self.registry_domain))
|
|
|
|
try:
|
|
|
|
res = requests.post('{}/register'.format(self.registry_domain), json=content)
|
|
|
|
res = res.json()
|
|
|
|
|
|
|
|
errprint('[REGISTRY-SERVER] {}'.format(res['message']))
|
|
|
|
if res['code'] < 0:
|
|
|
|
return False
|
|
|
|
else:
|
|
|
|
return True
|
|
|
|
except Exception:
|
|
|
|
errprint(traceback.format_exc())
|
|
|
|
errprint('[REGISTRY] Exception happened during registry register')
|
|
|
|
return False
|
|
|
|
|
|
|
|
def registry_ensure(self, peers=None):
|
2022-03-17 18:02:29 +08:00
|
|
|
_, public_pem = get_pem_from_rsa_keypair(None, self.local_public_key)
|
2022-03-12 04:11:50 +08:00
|
|
|
can_ensure = self.registry_upload({
|
|
|
|
"name": self.registry_client_name,
|
|
|
|
"pubkey": public_pem,
|
|
|
|
"wgkey": self.wg_pubkey,
|
|
|
|
"peers": peers or {},
|
|
|
|
"sig": rsa_sign_base64(self.local_private_key, self.wg_pubkey.encode())
|
|
|
|
})
|
|
|
|
if not can_ensure:
|
|
|
|
errprint('[ERROR] registry ensure failed, please check your network.')
|
|
|
|
exit(1)
|
|
|
|
|
|
|
|
def add_expose(self, expose_port, mode='udp'):
|
|
|
|
self.container_expose_port.append({
|
|
|
|
"port": int(expose_port),
|
|
|
|
"mode": mode,
|
|
|
|
})
|
|
|
|
|
|
|
|
def append_input_peer_clientside(self, peer_wgkey, allowed_ip, tunnel_name):
|
|
|
|
this_peer = []
|
|
|
|
this_peer.append("PublicKey = {}".format(peer_wgkey))
|
|
|
|
this_peer.append("AllowedIPs = {}".format(allowed_ip))
|
|
|
|
this_peer.append("PersistentKeepalive = 5")
|
|
|
|
this_peer.append("#use-tunnel {}".format(tunnel_name))
|
|
|
|
self.input_peer.append(this_peer)
|
|
|
|
|
|
|
|
def append_input_peer_serverside(self, peer_wgkey, allowed_ip):
|
|
|
|
this_peer = []
|
|
|
|
this_peer.append("PublicKey = {}".format(peer_wgkey))
|
|
|
|
this_peer.append("AllowedIPs = {}".format(allowed_ip))
|
|
|
|
self.input_peer.append(this_peer)
|
|
|
|
|
2022-03-17 18:02:29 +08:00
|
|
|
def _ensure_open_tmux(self):
|
|
|
|
if not self.flag_has_open_tmux:
|
|
|
|
self.flag_has_open_tmux = True
|
|
|
|
self.result_postup.append('''tmux new-session -s tunnel-{} -d 'watch -n 1 --color WG_COLOR_MODE=always wg show {}' '''.format(self.wg_name, self.wg_name))
|
2022-03-19 04:46:17 +08:00
|
|
|
|
2022-03-17 18:02:29 +08:00
|
|
|
def _add_tunnel_local_endpoint(self, tunnel_name, listen_port):
|
2022-03-19 04:46:17 +08:00
|
|
|
if self.opt_use_tmux or self.opt_use_systemd:
|
2022-03-17 18:02:29 +08:00
|
|
|
self.tunnel_local_endpoint[tunnel_name] = "127.0.0.1:{}".format(listen_port)
|
|
|
|
elif self.podman_user:
|
|
|
|
self.add_expose(listen_port)
|
|
|
|
self.tunnel_local_endpoint[tunnel_name] = "127.0.0.1:{}".format(listen_port)
|
|
|
|
else:
|
|
|
|
self.tunnel_local_endpoint[tunnel_name] = "gateway:{}".format(listen_port)
|
|
|
|
|
2022-03-12 04:11:50 +08:00
|
|
|
def add_muxer(self, listen_port, forward_start, forward_size):
|
2022-03-17 18:02:29 +08:00
|
|
|
if self.opt_use_tmux:
|
|
|
|
self._ensure_open_tmux()
|
|
|
|
self.result_postup.append('''tmux new-window -t tunnel-{} -d '{} -l {} -t {} -s {}' '''.format(
|
|
|
|
self.wg_name, self.path_bin_mux, listen_port, forward_start, forward_size,
|
|
|
|
))
|
|
|
|
return
|
|
|
|
|
2022-03-19 04:46:17 +08:00
|
|
|
if self.opt_use_systemd:
|
|
|
|
self.result_postup.append('systemd-run --unit wg-ops-tasks-{}-muxer-{} --collect --property Restart=always {} -l {} -t {} -s {}'.format(
|
|
|
|
self.wg_name, str(uuid.uuid4()), self.path_bin_mux, listen_port, forward_start, forward_size,
|
|
|
|
))
|
|
|
|
return
|
|
|
|
|
2022-03-12 04:11:50 +08:00
|
|
|
self.container_bootstrap.append({
|
|
|
|
"type": "mux",
|
|
|
|
"listen": int(listen_port),
|
|
|
|
"forward": int(forward_start),
|
|
|
|
"size": int(forward_size),
|
|
|
|
})
|
|
|
|
|
|
|
|
def add_gost_server(self, tunnel_name, listen_port):
|
|
|
|
self.tunnel_server_reports[tunnel_name] = {
|
|
|
|
"type": "gost",
|
|
|
|
"listen": int(listen_port),
|
|
|
|
}
|
|
|
|
|
2022-03-17 18:02:29 +08:00
|
|
|
if self.opt_use_tmux:
|
|
|
|
self._ensure_open_tmux()
|
|
|
|
self.result_postup.append('''tmux new-window -t tunnel-{} -d '{} -L=relay+tls://:{}/127.0.0.1:{}' '''.format(
|
|
|
|
self.wg_name, self.path_bin_gost, listen_port, self.wg_port,
|
|
|
|
))
|
|
|
|
return
|
2022-03-19 04:46:17 +08:00
|
|
|
|
|
|
|
if self.opt_use_systemd:
|
|
|
|
self.result_postup.append('systemd-run --unit wg-ops-tasks-{}-gost-server-{} --collect --property Restart=always {} -L=relay+tls://:{}/127.0.0.1:{}'.format(
|
|
|
|
self.wg_name, str(uuid.uuid4()), self.path_bin_gost, listen_port, self.wg_port,
|
|
|
|
))
|
|
|
|
return
|
2022-03-17 18:02:29 +08:00
|
|
|
|
|
|
|
self.container_bootstrap.append({
|
|
|
|
"type": "gost-server",
|
|
|
|
"listen": int(listen_port),
|
|
|
|
})
|
|
|
|
|
2022-03-12 04:11:50 +08:00
|
|
|
def add_gost_client_with(self, remote_config, remote_peer_config):
|
|
|
|
self.local_autogen_nextport += 1
|
|
|
|
tunnel_name = "gen{}{}".format(self.wg_hash[:8], self.local_autogen_nextport)
|
|
|
|
self.add_gost_client(tunnel_name, self.local_autogen_nextport, "{}:{}".format(remote_config['ip'], remote_peer_config['listen']))
|
|
|
|
self.append_input_peer_clientside(remote_config["wgkey"], remote_peer_config["allowed"], tunnel_name)
|
|
|
|
|
|
|
|
def add_gost_client_mux(self, tunnel_name, mux_size, listen_port, tunnel_remote):
|
2022-03-17 18:02:29 +08:00
|
|
|
self._add_tunnel_local_endpoint(tunnel_name, listen_port)
|
|
|
|
|
|
|
|
self.add_muxer(listen_port, listen_port + 1, mux_size)
|
2022-03-12 04:11:50 +08:00
|
|
|
for mux_idx in range(mux_size):
|
|
|
|
self._do_add_gost_client(listen_port + 1 + mux_idx, tunnel_remote)
|
|
|
|
|
|
|
|
def add_gost_client(self, tunnel_name, listen_port, tunnel_remote):
|
2022-03-17 18:02:29 +08:00
|
|
|
self._add_tunnel_local_endpoint(tunnel_name, listen_port)
|
2022-03-12 04:11:50 +08:00
|
|
|
self._do_add_gost_client(listen_port, tunnel_remote)
|
|
|
|
|
|
|
|
def _do_add_gost_client(self, listen_port, tunnel_remote):
|
2022-03-17 18:02:29 +08:00
|
|
|
if self.opt_use_tmux:
|
|
|
|
self._ensure_open_tmux()
|
|
|
|
self.result_postup.append('''tmux new -t tunnel-{} -d '{} -L udp://:{} -F relay+tls://{}' '''.format(
|
|
|
|
self.wg_name, self.path_bin_gost, listen_port, tunnel_remote,
|
|
|
|
))
|
|
|
|
return
|
2022-03-19 04:46:17 +08:00
|
|
|
|
|
|
|
if self.opt_use_systemd:
|
|
|
|
self.result_postup.append('systemd-run --unit wg-ops-tasks-{}-gost-client-{} --collect --property Restart=always {} -L udp://:{} -F relay+tls://{}'.format(
|
|
|
|
self.wg_name, str(uuid.uuid4()), self.path_bin_gost, listen_port, tunnel_remote,
|
|
|
|
))
|
|
|
|
return
|
2022-03-17 18:02:29 +08:00
|
|
|
|
2022-03-12 04:11:50 +08:00
|
|
|
self.container_bootstrap.append({
|
|
|
|
"type": "gost-client",
|
|
|
|
"listen": int(listen_port),
|
|
|
|
"remote": tunnel_remote,
|
|
|
|
})
|
|
|
|
|
|
|
|
def add_udp2raw_server(self, tunnel_name, listen_port, tunnel_password):
|
2022-03-17 18:02:29 +08:00
|
|
|
self.tunnel_server_reports[tunnel_name] = {
|
|
|
|
"type": "udp2raw",
|
|
|
|
"listen": int(listen_port),
|
|
|
|
"password": tunnel_password,
|
|
|
|
}
|
|
|
|
|
2022-03-19 04:46:17 +08:00
|
|
|
if self.opt_use_tmux or self.opt_use_systemd:
|
|
|
|
temp_config_path = self.new_tmp_filepath('.conf')
|
|
|
|
self.result_postup.append('''echo -e '-s\\n-l 0.0.0.0:{}\\n-r 127.0.0.1:{}\\n-k {}\\n--raw-mode faketcp\\n-a' > {}'''.format(
|
|
|
|
listen_port, self.wg_port, tunnel_password, temp_config_path
|
|
|
|
))
|
|
|
|
|
2022-03-17 18:02:29 +08:00
|
|
|
if self.opt_use_tmux:
|
|
|
|
self._ensure_open_tmux()
|
2022-03-19 04:46:17 +08:00
|
|
|
self.result_postup.append('''tmux new-window -t tunnel-{} -n win-{} -d '{} --conf-file {}'; sleep 2 '''.format(
|
|
|
|
self.wg_name, tunnel_name, self.path_bin_udp2raw, temp_config_path,
|
2022-03-17 18:02:29 +08:00
|
|
|
))
|
|
|
|
self.result_postdown.append('''sleep 1; tmux send-keys -t tunnel-{}:win-{} C-c'''.format(self.wg_name, tunnel_name))
|
|
|
|
return
|
2022-03-12 04:11:50 +08:00
|
|
|
|
2022-03-19 04:46:17 +08:00
|
|
|
if self.opt_use_systemd:
|
|
|
|
self.result_postup.append('systemd-run --unit wg-ops-tasks-{}-udp2raw-server-{} --collect --property Restart=always --property KillSignal=SIGINT {} --conf-file {}; sleep 2'.format(
|
|
|
|
self.wg_name, str(uuid.uuid4()), self.path_bin_udp2raw, temp_config_path,
|
|
|
|
))
|
|
|
|
return
|
|
|
|
|
2022-03-17 18:02:29 +08:00
|
|
|
conf_uuid = str(uuid.uuid4())
|
2022-03-12 04:11:50 +08:00
|
|
|
self.container_bootstrap.append({
|
|
|
|
"type": "udp2raw-server",
|
|
|
|
"listen": int(listen_port),
|
|
|
|
"password": tunnel_password,
|
|
|
|
"id": conf_uuid,
|
|
|
|
})
|
|
|
|
|
|
|
|
ipt_filename_inside = "/root/conf/{}-ipt.conf".format(conf_uuid)
|
|
|
|
self.result_container_postbootstrap.append('IPT_COMMANDS=$({}); echo $IPT_COMMANDS; $IPT_COMMANDS'.format(
|
|
|
|
self.get_podman_cmd_with("podman exec {} /root/bin/udp2raw_amd64 --conf-file {} | grep ^iptables".format(self.get_container_name(), ipt_filename_inside))
|
|
|
|
))
|
|
|
|
self.result_postdown.append("IPT_COMMANDS=$({}); IPT_COMMANDS=$(echo $IPT_COMMANDS | sed -e 's/-I /-D /g'); echo $IPT_COMMANDS; $IPT_COMMANDS".format(
|
|
|
|
self.get_podman_cmd_with("podman exec {} /root/bin/udp2raw_amd64 --conf-file {} | grep ^iptables".format(self.get_container_name(), ipt_filename_inside))
|
|
|
|
))
|
|
|
|
|
|
|
|
def add_udp2raw_client_with(self, remote_config, remote_peer_config):
|
|
|
|
self.local_autogen_nextport += 1
|
|
|
|
tunnel_name = "gen{}{}".format(self.wg_hash[:8], self.local_autogen_nextport)
|
|
|
|
self.add_udp2raw_client(tunnel_name, self.local_autogen_nextport, remote_peer_config["password"], "{}:{}".format(remote_config['ip'], remote_peer_config['listen']))
|
|
|
|
self.append_input_peer_clientside(remote_config["wgkey"], remote_peer_config["allowed"], tunnel_name)
|
|
|
|
|
|
|
|
def add_udp2raw_client_mux(self, tunnel_name, mux_size, listen_port, tunnel_password, remote_addr):
|
|
|
|
self.tunnel_local_endpoint[tunnel_name] = "127.0.0.1:{}".format(listen_port)
|
|
|
|
self.flag_container_must_host = True
|
2022-03-17 18:02:29 +08:00
|
|
|
|
2022-03-12 04:11:50 +08:00
|
|
|
self.add_muxer(listen_port, listen_port+1, mux_size)
|
|
|
|
for mux_idx in range(mux_size):
|
2022-03-17 18:02:29 +08:00
|
|
|
self._do_add_udp2raw_client(tunnel_name, listen_port + 1 + mux_idx, tunnel_password, remote_addr)
|
2022-03-12 04:11:50 +08:00
|
|
|
|
|
|
|
def add_udp2raw_client(self, tunnel_name, listen_port, tunnel_password, remote_addr):
|
|
|
|
self.tunnel_local_endpoint[tunnel_name] = "127.0.0.1:{}".format(listen_port)
|
|
|
|
self.flag_container_must_host = True
|
|
|
|
|
2022-03-17 18:02:29 +08:00
|
|
|
self._do_add_udp2raw_client(tunnel_name, listen_port, tunnel_password, remote_addr)
|
|
|
|
|
|
|
|
def _do_add_udp2raw_client(self, tunnel_name, listen_port, tunnel_password, remote_addr):
|
2022-03-19 04:46:17 +08:00
|
|
|
if self.opt_use_tmux or self.opt_use_systemd:
|
|
|
|
temp_config_path = self.new_tmp_filepath('.conf')
|
|
|
|
self.result_postup.append('''echo -e '-c\\n-l 127.0.0.1:{}\\n-r {}\\n-k {}\\n--raw-mode faketcp\\n-a' > {}'''.format(
|
|
|
|
listen_port, remote_addr, tunnel_password, temp_config_path,
|
2022-03-17 18:02:29 +08:00
|
|
|
))
|
2022-03-19 04:46:17 +08:00
|
|
|
|
|
|
|
if self.opt_use_tmux:
|
|
|
|
self._ensure_open_tmux()
|
|
|
|
self.result_postup.append('''tmux new-window -t tunnel-{} -n win-{} -d '{} --conf-file {}'; sleep 2 '''.format(
|
|
|
|
self.wg_name, tunnel_name, self.path_bin_udp2raw, temp_config_path,
|
2022-03-17 18:02:29 +08:00
|
|
|
))
|
|
|
|
self.result_postdown.append('''sleep 1; tmux send-keys -t tunnel-{}:win-{} C-c'''.format(self.wg_name, tunnel_name))
|
|
|
|
return
|
2022-03-12 04:11:50 +08:00
|
|
|
|
2022-03-19 04:46:17 +08:00
|
|
|
if self.opt_use_systemd:
|
|
|
|
self.result_postup.append('systemd-run --unit wg-ops-tasks-{}-udp2raw-client-{} --collect --property Restart=always --property KillSignal=SIGINT {} --conf-file {}; sleep 2'.format(
|
|
|
|
self.wg_name, str(uuid.uuid4()), self.path_bin_udp2raw, temp_config_path,
|
|
|
|
))
|
|
|
|
return
|
|
|
|
|
2022-03-17 18:02:29 +08:00
|
|
|
conf_uuid = str(uuid.uuid4())
|
2022-03-12 04:11:50 +08:00
|
|
|
self.container_bootstrap.append({
|
|
|
|
"type": "udp2raw-client",
|
|
|
|
"listen": int(listen_port),
|
|
|
|
"password": tunnel_password,
|
|
|
|
"remote": remote_addr,
|
|
|
|
"id": conf_uuid,
|
|
|
|
})
|
|
|
|
|
|
|
|
ipt_filename_inside = "/root/conf/{}-ipt.conf".format(conf_uuid)
|
|
|
|
self.result_container_postbootstrap.append('IPT_COMMANDS=$({}); echo $IPT_COMMANDS; $IPT_COMMANDS'.format(
|
|
|
|
self.get_podman_cmd_with("podman exec {} /root/bin/udp2raw_amd64 --conf-file {} | grep ^iptables".format(self.get_container_name(), ipt_filename_inside))
|
|
|
|
))
|
|
|
|
self.result_postdown.append("IPT_COMMANDS=$({}); IPT_COMMANDS=$(echo $IPT_COMMANDS | sed -e 's/-I /-D /g'); echo $IPT_COMMANDS; $IPT_COMMANDS".format(
|
|
|
|
self.get_podman_cmd_with("podman exec {} /root/bin/udp2raw_amd64 --conf-file {} | grep ^iptables".format(self.get_container_name(), ipt_filename_inside))
|
|
|
|
))
|
|
|
|
|
|
|
|
def add_trojan_server(self, tunnel_name, listen_port, tunnel_password, ssl_cert_path, ssl_key_path):
|
2022-03-17 18:02:29 +08:00
|
|
|
self.tunnel_server_reports[tunnel_name] = {
|
|
|
|
"type": "trojan",
|
|
|
|
"listen": int(listen_port),
|
|
|
|
"password": tunnel_password,
|
|
|
|
"target": int(self.wg_port),
|
|
|
|
"sni": get_subject_name_from_cert(ssl_cert_path),
|
|
|
|
}
|
|
|
|
|
2022-03-19 04:46:17 +08:00
|
|
|
if self.opt_use_tmux or self.opt_use_systemd:
|
|
|
|
errprint('[ERROR] Unable to create trojan-go server in tmux or systemd mode. Please use container mode.')
|
2022-03-17 18:02:29 +08:00
|
|
|
exit(1)
|
|
|
|
|
2022-03-12 04:11:50 +08:00
|
|
|
cert_uuid = str(uuid.uuid4())
|
|
|
|
cert_filepath = "/root/ssl/{}.cert".format(cert_uuid)
|
|
|
|
key_filepath = "/root/ssl/{}.key".format(cert_uuid)
|
|
|
|
|
|
|
|
self.result_container_prebootstrap.append(self.get_podman_cmd_with(
|
|
|
|
'podman cp {} {}:{}'.format(ssl_cert_path, self.get_container_name(), cert_filepath)
|
|
|
|
))
|
|
|
|
self.result_container_prebootstrap.append(self.get_podman_cmd_with(
|
|
|
|
'podman cp {} {}:{}'.format(ssl_key_path, self.get_container_name(), key_filepath)
|
|
|
|
))
|
|
|
|
|
|
|
|
self.container_bootstrap.append({
|
|
|
|
"type": "trojan-server",
|
|
|
|
"listen": int(listen_port),
|
|
|
|
"password": tunnel_password,
|
|
|
|
"cert": cert_uuid,
|
|
|
|
})
|
2022-03-17 18:02:29 +08:00
|
|
|
|
2022-03-12 04:11:50 +08:00
|
|
|
|
|
|
|
def add_trojan_client_with(self, remote_config, remote_peer_config):
|
|
|
|
self.local_autogen_nextport += 1
|
|
|
|
tunnel_name = "gen{}{}".format(self.wg_hash[:8], self.local_autogen_nextport)
|
|
|
|
self.add_trojan_client(tunnel_name, self.local_autogen_nextport, remote_peer_config["password"],
|
|
|
|
"{}:{}".format(remote_config["ip"], remote_peer_config["listen"]), remote_peer_config["target"], ssl_sni=remote_peer_config["sni"])
|
|
|
|
self.append_input_peer_clientside(remote_config["wgkey"], remote_peer_config["allowed"], tunnel_name)
|
|
|
|
|
|
|
|
def add_trojan_client_mux(self, tunnel_name, mux_size, listen_port, tunnel_password, remote_addr, target_port, ssl_sni=None):
|
2022-03-17 18:02:29 +08:00
|
|
|
self._add_tunnel_local_endpoint(tunnel_name, listen_port)
|
2022-03-12 04:11:50 +08:00
|
|
|
self.add_muxer(listen_port, listen_port+1, mux_size)
|
|
|
|
for mux_idx in range(mux_size):
|
2022-03-17 18:02:29 +08:00
|
|
|
self._do_add_trojan_client(tunnel_name, listen_port + 1 + mux_idx, tunnel_password, remote_addr, target_port, ssl_sni)
|
2022-03-12 04:11:50 +08:00
|
|
|
|
|
|
|
def add_trojan_client(self, tunnel_name, listen_port, tunnel_password, remote_addr, target_port, ssl_sni=None):
|
2022-03-17 18:02:29 +08:00
|
|
|
self._add_tunnel_local_endpoint(tunnel_name, listen_port)
|
|
|
|
self._do_add_trojan_client(tunnel_name, listen_port, tunnel_password, remote_addr, target_port, ssl_sni)
|
|
|
|
|
|
|
|
def _do_add_trojan_client(self, tunnel_name, listen_port, tunnel_password, remote_addr, target_port, ssl_sni):
|
2022-03-19 04:46:17 +08:00
|
|
|
if self.opt_use_tmux or self.opt_use_systemd:
|
2022-03-17 18:02:29 +08:00
|
|
|
if ':' in remote_addr:
|
|
|
|
remote_parts = remote_addr.split(':')
|
|
|
|
remote_host = remote_parts[0]
|
|
|
|
remote_port = int(remote_parts[1])
|
|
|
|
else:
|
|
|
|
remote_host = remote_addr
|
|
|
|
remote_port = 443
|
|
|
|
|
|
|
|
troj_config = {
|
|
|
|
"run_type": "forward",
|
|
|
|
"local_addr": "0.0.0.0",
|
|
|
|
"local_port": int(listen_port),
|
|
|
|
"remote_addr": remote_host,
|
|
|
|
"remote_port": remote_port,
|
|
|
|
"target_addr": "127.0.0.1",
|
|
|
|
"target_port": target_port,
|
|
|
|
"password": [tunnel_password],
|
|
|
|
"ssl": {
|
|
|
|
"sni": ssl_sni if ssl_sni else remote_host,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-19 04:46:17 +08:00
|
|
|
temp_config_path = self.add_write_tmpfile_bytes(json.dumps(troj_config, ensure_ascii=False).encode(), '.json')
|
|
|
|
|
|
|
|
if self.opt_use_tmux:
|
|
|
|
self.result_postup.append('''tmux new-window -t tunnel-{} -d '{} -config {}' '''.format(
|
|
|
|
self.wg_name, self.path_bin_trojan, temp_config_path,
|
|
|
|
))
|
|
|
|
return
|
|
|
|
|
|
|
|
if self.opt_use_systemd:
|
|
|
|
self.result_postup.append('systemd-run --unit wg-ops-tasks-{}-udp2raw-client-{} --collect {} -config {}'.format(
|
|
|
|
self.wg_name, str(uuid.uuid4()), self.path_bin_trojan, temp_config_path,
|
2022-03-17 18:02:29 +08:00
|
|
|
))
|
|
|
|
return
|
2022-03-12 04:11:50 +08:00
|
|
|
|
|
|
|
self.container_bootstrap.append({
|
|
|
|
"type": "trojan-client",
|
|
|
|
"listen": int(listen_port),
|
|
|
|
"password": tunnel_password,
|
|
|
|
"remote": remote_addr,
|
|
|
|
"target": int(target_port),
|
|
|
|
"sni": ssl_sni,
|
|
|
|
})
|
|
|
|
|
|
|
|
def parse(self, content):
|
|
|
|
self.wg_hash = sha256(content.encode()).hexdigest()
|
|
|
|
errprint('[INFO] config hash: {}'.format(self.wg_hash))
|
|
|
|
|
|
|
|
# parse input
|
|
|
|
input_mode = ''
|
|
|
|
current_peer = []
|
|
|
|
for line in content.split('\n'):
|
|
|
|
# tags to filter out (never enter compile module)
|
|
|
|
if line.startswith('#store:key'):
|
|
|
|
parts = line.split(' ')[1:]
|
|
|
|
private_pem = parts[0]
|
|
|
|
private_pem = base64.b64decode(private_pem).decode()
|
|
|
|
|
|
|
|
self.local_private_key, self.local_public_key = get_rsa_keypair_from_private_pem(private_pem)
|
|
|
|
errprint('Loaded 1 PEM private key')
|
|
|
|
continue
|
|
|
|
|
|
|
|
if line.startswith('[Interface]'):
|
|
|
|
input_mode = 'interface'
|
|
|
|
continue
|
|
|
|
|
|
|
|
if line.startswith('[Peer]'):
|
|
|
|
input_mode = 'peer'
|
|
|
|
if current_peer:
|
|
|
|
self.input_peer.append(current_peer)
|
|
|
|
current_peer = []
|
|
|
|
continue
|
|
|
|
|
|
|
|
if input_mode == 'interface':
|
|
|
|
self.input_interface.append(line)
|
|
|
|
elif input_mode == 'peer':
|
|
|
|
current_peer.append(line)
|
|
|
|
else:
|
|
|
|
errprint('[WARN] Unexpected line: {}'.format(line))
|
|
|
|
|
|
|
|
if current_peer:
|
|
|
|
self.input_peer.append(current_peer)
|
|
|
|
|
|
|
|
def compile_interface(self):
|
|
|
|
self.result_interface.append('[Interface]')
|
|
|
|
|
2022-03-16 19:47:52 +08:00
|
|
|
filtered_input_interface = []
|
2022-03-12 04:11:50 +08:00
|
|
|
unresolved_peers = []
|
|
|
|
|
|
|
|
# pre-compile registry-related
|
|
|
|
for line in self.input_interface:
|
|
|
|
if line.startswith('ListenPort'):
|
|
|
|
self.wg_port = int(line.split('=')[1])
|
|
|
|
if line.startswith('MTU'):
|
|
|
|
self.wg_mtu = int(line.split('=')[1])
|
|
|
|
if line.startswith('PrivateKey'):
|
|
|
|
wg_private_key = '='.join(line.split('=')[1:]).strip()
|
|
|
|
self.wg_pubkey = subprocess.check_output(["wg", "pubkey"], input=wg_private_key.encode()).decode().strip()
|
|
|
|
|
|
|
|
if line.startswith('#registry '):
|
|
|
|
parts = line.split(' ')[1:]
|
|
|
|
reg_name = parts[0]
|
|
|
|
|
|
|
|
self.registry_domain = "https://{}".format(reg_name)
|
|
|
|
elif line.startswith('#registry-insecure'):
|
|
|
|
parts = line.split(' ')[1:]
|
|
|
|
reg_name = parts[0]
|
|
|
|
|
|
|
|
self.registry_domain = "http://{}".format(reg_name)
|
|
|
|
errprint('[WARN] Insecure registry may have potential danger, only use for test purpose.')
|
|
|
|
elif line.startswith('#name'):
|
|
|
|
parts = line.split(' ')[1:]
|
|
|
|
client_name = parts[0]
|
|
|
|
|
|
|
|
self.registry_client_name = client_name
|
|
|
|
self.flag_require_registry = True
|
|
|
|
elif line.startswith('#connect-to'):
|
|
|
|
parts = line.split(' ')[1:]
|
|
|
|
target_name = parts[0]
|
|
|
|
|
|
|
|
unresolved_peers.append(target_name)
|
|
|
|
self.flag_require_registry = True
|
|
|
|
elif line.startswith('#accept-client'):
|
|
|
|
parts = line.split(' ')[1:]
|
|
|
|
tunnel_name = parts[0]
|
|
|
|
client_name = parts[1]
|
|
|
|
client_ip = parts[2]
|
|
|
|
client_allowed = parts[3]
|
|
|
|
peer_allowed = parts[4]
|
|
|
|
|
|
|
|
self.pending_accepts.append({
|
|
|
|
"tunnel": tunnel_name,
|
|
|
|
"client": client_name,
|
|
|
|
"client_ip": client_ip,
|
|
|
|
"allowed": client_allowed,
|
|
|
|
"peer_allowed": peer_allowed,
|
|
|
|
})
|
|
|
|
self.flag_require_registry = True
|
|
|
|
else:
|
2022-03-16 19:47:52 +08:00
|
|
|
filtered_input_interface.append(line)
|
2022-03-12 04:11:50 +08:00
|
|
|
|
|
|
|
# registry init
|
|
|
|
if self.flag_require_registry:
|
|
|
|
if not self.local_private_key:
|
|
|
|
errprint('registry required but no existing private key found, generating new...')
|
|
|
|
|
|
|
|
self.local_private_key, self.local_public_key = generate_rsa_keypair()
|
2022-03-19 04:46:17 +08:00
|
|
|
private_pem, _ = get_pem_from_rsa_keypair(self.local_private_key, self.local_public_key)
|
2022-03-12 04:11:50 +08:00
|
|
|
|
2022-03-16 19:47:52 +08:00
|
|
|
if self.opt_allow_modify:
|
2022-03-12 04:11:50 +08:00
|
|
|
errprint('[MODIFY] appending to {}...'.format(self.opt_source_path))
|
|
|
|
with open(self.opt_source_path, 'a') as f:
|
|
|
|
f.write('\n#store:key {}\n'.format(base64.b64encode(private_pem.encode()).decode()))
|
|
|
|
errprint('[MODIFY] source file modifed, please re-run to continue.')
|
|
|
|
else:
|
|
|
|
errprint('[ERROR] cannot modify source file, please re-run with -i option.')
|
|
|
|
exit(1)
|
|
|
|
|
|
|
|
self.registry_ensure()
|
|
|
|
|
|
|
|
# registry fetch connect-to
|
|
|
|
for peer_client_name in unresolved_peers:
|
|
|
|
errprint('[REGISTRY-RESOLVE] Resolving connect-to {}...'.format(peer_client_name))
|
|
|
|
peer_client_config = self.registry_resolve(peer_client_name)
|
|
|
|
if not peer_client_config:
|
|
|
|
errprint('[WARN] Unable to resolve client: {}'.format(peer_client_name))
|
|
|
|
continue
|
|
|
|
|
|
|
|
peer_config = peer_client_config["peers"][self.registry_client_name]
|
|
|
|
{
|
|
|
|
"udp2raw": self.add_udp2raw_client_with,
|
|
|
|
"gost": self.add_gost_client_with,
|
|
|
|
"trojan": self.add_trojan_client_with,
|
|
|
|
}.get(peer_config["type"], lambda x, y: False)(peer_client_config, peer_config)
|
|
|
|
|
|
|
|
# compile interface
|
2022-03-16 19:47:52 +08:00
|
|
|
for line in filtered_input_interface:
|
|
|
|
if line.startswith('PostUp'):
|
|
|
|
self.result_postup.append(','.join(line.split('=')[1:]).strip())
|
|
|
|
continue
|
|
|
|
if line.startswith('PostDown'):
|
|
|
|
self.result_postdown.append(','.join(line.split('=')[1:]).strip())
|
|
|
|
continue
|
|
|
|
|
2022-03-12 04:11:50 +08:00
|
|
|
if not line.startswith('#'):
|
|
|
|
self.result_interface.append(line)
|
|
|
|
continue
|
|
|
|
|
|
|
|
elif line.startswith('#enable-bbr'):
|
|
|
|
self.result_postup.append('sysctl net.core.default_qdisc=fq')
|
|
|
|
self.result_postup.append('sysctl net.ipv4.tcp_congestion_control=bbr')
|
|
|
|
elif line.startswith('#enable-forward'):
|
|
|
|
self.result_postup.append('sysctl net.ipv4.ip_forward=1')
|
|
|
|
elif line.startswith('#iptables-forward'):
|
|
|
|
self.result_postup.append('iptables -A FORWARD -i {} -j ACCEPT'.format(self.wg_name))
|
|
|
|
self.result_postdown.append('iptables -D FORWARD -i {} -j ACCEPT'.format(self.wg_name))
|
|
|
|
elif line.startswith('#iptables-gateway'):
|
|
|
|
self.result_postup.append('iptables -t nat -A POSTROUTING -o {} -j MASQUERADE'.format(self.wg_name))
|
|
|
|
self.result_postdown.append('iptables -t nat -D POSTROUTING -o {} -j MASQUERADE'.format(self.wg_name))
|
2022-03-16 21:22:21 +08:00
|
|
|
elif line.startswith('#enable-dns-reload'):
|
|
|
|
self.flag_enable_dns_reload = True
|
2022-03-12 04:11:50 +08:00
|
|
|
elif line.startswith('#route-to'):
|
|
|
|
self.flag_is_route_forward = True
|
|
|
|
|
|
|
|
parts = line.split(' ')[1:]
|
|
|
|
table_name = parts[0]
|
|
|
|
|
|
|
|
self.result_postup.append('ip route add 0.0.0.0/0 dev {} table {}'.format(self.wg_name, table_name))
|
|
|
|
errprint('[WARN] Please ensure custom route table {} exists.'.format(table_name))
|
|
|
|
elif line.startswith('#route-from'):
|
|
|
|
self.flag_is_route_lookup = True
|
|
|
|
|
|
|
|
parts = line.split(' ')[1:]
|
|
|
|
table_name = parts[0]
|
|
|
|
|
|
|
|
self.lookup_table = table_name
|
|
|
|
errprint('[WARN] Please ensure custom route table {} exists.'.format(table_name))
|
|
|
|
elif line.startswith('#podman-user'):
|
|
|
|
parts = line.split(' ')[1:]
|
|
|
|
user_name = parts[0]
|
|
|
|
|
|
|
|
if user_name == "root":
|
|
|
|
errprint('[WARN] ignoring root as podman user.')
|
|
|
|
else:
|
|
|
|
self.podman_user = user_name
|
2022-03-19 04:46:17 +08:00
|
|
|
elif line.startswith('#systemd-user'):
|
|
|
|
parts = line.split(' ')[1:]
|
|
|
|
user_name = parts[0]
|
|
|
|
|
|
|
|
if user_name == "root":
|
|
|
|
errprint('[WARN] ignoring root as systemd-run user.')
|
|
|
|
else:
|
|
|
|
self.systemd_user = user_name
|
2022-03-12 04:11:50 +08:00
|
|
|
elif line.startswith('#udp2raw-server'):
|
|
|
|
parts = line.split(' ')[1:]
|
|
|
|
tunnel_name = parts[0]
|
|
|
|
tunnel_port = int(parts[1])
|
|
|
|
tunnel_passwd = parts[2]
|
|
|
|
|
|
|
|
if self.podman_user:
|
|
|
|
errprint('[Error] udp2raw tunnel need root as podman user, got {}'.format(self.podman_user))
|
|
|
|
exit(1)
|
|
|
|
|
|
|
|
self.add_udp2raw_server(tunnel_name, tunnel_port, tunnel_passwd)
|
|
|
|
self.flag_container_must_host = True
|
|
|
|
elif line.startswith('#udp2raw-client '):
|
|
|
|
parts = line.split(' ')[1:]
|
|
|
|
tunnel_name = parts[0]
|
|
|
|
tunnel_port = int(parts[1])
|
|
|
|
tunnel_remote = parts[2]
|
|
|
|
tunnel_passwd = parts[3]
|
|
|
|
|
|
|
|
if self.podman_user:
|
|
|
|
errprint('[Error] udp2raw tunnel need root as podman user, got {}'.format(self.podman_user))
|
|
|
|
exit(1)
|
|
|
|
|
|
|
|
self.add_udp2raw_client(tunnel_name, tunnel_port, tunnel_passwd, tunnel_remote)
|
|
|
|
elif line.startswith('#udp2raw-client-mux '):
|
|
|
|
parts = line.split(' ')[1:]
|
|
|
|
tunnel_name = parts[0]
|
|
|
|
tunnel_mux = int(parts[1])
|
|
|
|
tunnel_port = int(parts[2])
|
|
|
|
tunnel_remote = parts[3]
|
|
|
|
tunnel_passwd = parts[4]
|
|
|
|
|
|
|
|
if self.podman_user:
|
|
|
|
errprint('[Error] udp2raw tunnel need root as podman user, got {}'.format(self.podman_user))
|
|
|
|
exit(1)
|
|
|
|
|
|
|
|
self.add_udp2raw_client_mux(tunnel_name, tunnel_mux, tunnel_port + 1 + mux_idx, tunnel_passwd, tunnel_remote)
|
|
|
|
elif line.startswith('#gost-server '):
|
|
|
|
parts = line.split(' ')[1:]
|
|
|
|
tunnel_name = parts[0]
|
|
|
|
tunnel_port = int(parts[1])
|
|
|
|
|
|
|
|
self.add_gost_server(tunnel_name, tunnel_port)
|
|
|
|
self.add_expose(tunnel_port, mode='tcp')
|
|
|
|
elif line.startswith('#gost-client '):
|
|
|
|
parts = line.split(' ')[1:]
|
|
|
|
tunnel_name = parts[0]
|
|
|
|
tunnel_port = int(parts[1])
|
|
|
|
tunnel_remote = parts[2]
|
|
|
|
|
|
|
|
self.add_gost_client(tunnel_name, tunnel_port, tunnel_remote)
|
|
|
|
elif line.startswith('#gost-client-mux '):
|
|
|
|
parts = line.split(' ')[1:]
|
|
|
|
tunnel_name = parts[0]
|
|
|
|
tunnel_mux = int(parts[1])
|
|
|
|
tunnel_port = int(parts[2])
|
|
|
|
tunnel_remote = parts[3]
|
|
|
|
|
|
|
|
self.add_gost_client_mux(tunnel_name, tunnel_mux, tunnel_port, tunnel_remote)
|
|
|
|
elif line.startswith('#trojan-server'):
|
|
|
|
parts = line.split(' ')[1:]
|
|
|
|
tunnel_name = parts[0]
|
|
|
|
tunnel_port = int(parts[1])
|
|
|
|
tunnel_passwd = parts[2]
|
|
|
|
tunnel_cert = parts[3]
|
|
|
|
tunnel_key = parts[4]
|
|
|
|
|
|
|
|
self.add_trojan_server(tunnel_name, tunnel_port, tunnel_passwd, tunnel_cert, tunnel_key)
|
|
|
|
self.add_expose(tunnel_port, mode='tcp')
|
|
|
|
elif line.startswith('#trojan-client '):
|
|
|
|
parts = line.split(' ')[1:]
|
|
|
|
tunnel_name = parts[0]
|
|
|
|
tunnel_port = int(parts[1])
|
|
|
|
tunnel_passwd = parts[2]
|
|
|
|
tunnel_remote = parts[3]
|
|
|
|
tunnel_target = int(parts[4])
|
|
|
|
|
|
|
|
self.add_trojan_client(tunnel_name, tunnel_port, tunnel_passwd, tunnel_remote, tunnel_target)
|
|
|
|
elif line.startswith('#trojan-client-mux '):
|
|
|
|
parts = line.split(' ')[1:]
|
|
|
|
tunnel_name = parts[0]
|
|
|
|
tunnel_mux = int(parts[1])
|
|
|
|
tunnel_port = int(parts[2])
|
|
|
|
tunnel_passwd = parts[3]
|
|
|
|
tunnel_remote = parts[4]
|
|
|
|
tunnel_target = int(parts[5])
|
|
|
|
|
|
|
|
self.tunnel_local_endpoint[tunnel_name] = "gateway:{}".format(tunnel_port)
|
|
|
|
self.add_muxer(tunnel_port, tunnel_port+1, tunnel_mux)
|
|
|
|
for mux_idx in range(tunnel_mux):
|
|
|
|
self.add_trojan_client(tunnel_port + 1 + mux_idx, tunnel_passwd, tunnel_remote, tunnel_target)
|
|
|
|
|
|
|
|
if self.podman_user:
|
|
|
|
self.add_expose(tunnel_port)
|
|
|
|
self.tunnel_local_endpoint[tunnel_name] = "127.0.0.1:{}".format(tunnel_port)
|
|
|
|
else:
|
|
|
|
errprint('[WARN] comment or unknown hint: {}'.format(line))
|
|
|
|
|
2022-03-16 19:47:52 +08:00
|
|
|
if not self.wg_mtu and self.container_bootstrap:
|
2022-03-12 04:11:50 +08:00
|
|
|
errprint('[WARN] MTU not detected, using suggested mtu value (1280).')
|
|
|
|
self.result_interface.append('MTU=1280')
|
|
|
|
|
2022-03-17 18:02:29 +08:00
|
|
|
if self.opt_use_tmux:
|
|
|
|
self.result_postdown.append('''sleep 1; tmux kill-session -t tunnel-{}'''.format(self.wg_name))
|
|
|
|
|
2022-03-19 04:46:17 +08:00
|
|
|
if self.opt_use_tmux or self.opt_use_systemd:
|
|
|
|
self.container_bootstrap.clear()
|
|
|
|
self.result_container_prebootstrap.clear()
|
|
|
|
self.result_container_postbootstrap.clear()
|
2022-03-17 18:02:29 +08:00
|
|
|
|
2022-03-12 04:11:50 +08:00
|
|
|
if self.container_bootstrap:
|
2022-03-19 04:46:17 +08:00
|
|
|
tmp_filepath = self.add_write_tmpfile_bytes(json.dumps(self.container_bootstrap, ensure_ascii=False).encode(), '.json')
|
2022-03-12 04:11:50 +08:00
|
|
|
self.result_container_prebootstrap.append(self.get_podman_cmd_with(
|
|
|
|
'podman cp {} {}:/root/conf/bootstrap.json'.format(tmp_filepath, self.get_container_name())
|
|
|
|
))
|
|
|
|
|
|
|
|
if self.result_container_prebootstrap or self.result_container_postbootstrap:
|
|
|
|
self.result_postup.append(self.get_podman_cmd_with(
|
|
|
|
'podman container exists {} && podman stop {} && podman rm {}; $(exit 0)'.format(self.get_container_name(), self.get_container_name(), self.get_container_name())
|
|
|
|
))
|
|
|
|
|
|
|
|
self.result_postup.append(self.get_podman_cmd_with(
|
|
|
|
'podman network exists {} && podman network rm {}; $(exit 0)'.format(self.get_container_network_name(), self.get_container_network_name())
|
|
|
|
))
|
|
|
|
|
|
|
|
if not self.flag_container_must_host:
|
|
|
|
self.result_postup.append(self.get_podman_cmd_with(
|
|
|
|
'podman network create {}'.format(self.get_container_network_name())
|
|
|
|
))
|
|
|
|
|
|
|
|
if not self.flag_container_must_host and self.container_expose_port:
|
|
|
|
cmd_ports = ["-p {}:{}/{}".format(this_port['port'], this_port['port'], this_port['mode']) for this_port in self.container_expose_port]
|
|
|
|
cmd_ports = ' '.join(cmd_ports)
|
|
|
|
else:
|
|
|
|
cmd_ports = ''
|
|
|
|
|
|
|
|
self.result_postup.append(self.get_podman_cmd_with(
|
|
|
|
'podman run --cap-add NET_RAW -v {}:/root/bin -v {}:/root/app {} --name {} --network {} -d wg-ops-runenv'.format(
|
|
|
|
self.path_bin_dir, self.path_app_dir, cmd_ports, self.get_container_name(), self.get_container_network_name())
|
|
|
|
))
|
|
|
|
self.result_postup.append(self.get_podman_cmd_with(
|
|
|
|
'podman exec {} mkdir -p /root/ssl /root/runner /root/conf'.format(self.get_container_name())
|
|
|
|
))
|
|
|
|
|
|
|
|
if not self.flag_container_must_host and not self.podman_user:
|
|
|
|
self.result_postup.append("CT_IP=$({}); iptables -A FORWARD -d $CT_IP -j ACCEPT; iptables -A INPUT -s $CT_IP -j ACCEPT".format(
|
|
|
|
self.get_podman_cmd_with('/usr/bin/python3 {} {} {}'.format(self.path_get_ip, self.get_container_network_name(), self.get_container_name()))))
|
|
|
|
self.result_postdown.append("CT_IP=$({}); iptables -D FORWARD -d $CT_IP -j ACCEPT; iptables -D INPUT -s $CT_IP -j ACCEPT".format(
|
|
|
|
self.get_podman_cmd_with('/usr/bin/python3 {} {} {}'.format(self.path_get_ip, self.get_container_network_name(), self.get_container_name()))))
|
|
|
|
|
|
|
|
self.result_postdown.append(self.get_podman_cmd_with('podman stop {}'.format(self.get_container_name())))
|
|
|
|
self.result_postdown.append(self.get_podman_cmd_with('podman rm {}'.format(self.get_container_name())))
|
|
|
|
|
|
|
|
if not self.flag_container_must_host:
|
|
|
|
self.result_postdown.append(self.get_podman_cmd_with('podman network rm {}'.format(self.get_container_network_name())))
|
|
|
|
|
|
|
|
self.result_postup.extend(self.result_container_prebootstrap)
|
|
|
|
|
|
|
|
if self.flag_container_must_host:
|
|
|
|
self.result_postup.append(self.get_podman_cmd_with(
|
|
|
|
'podman exec -t -e GATEWAY_IP=127.0.0.1 -e WG_PORT={} {} /usr/bin/python3 /root/app/bootstrap.py'.format(self.wg_port, self.get_container_name())
|
|
|
|
))
|
|
|
|
elif self.podman_user:
|
|
|
|
self.result_postup.append(self.get_podman_cmd_with(
|
|
|
|
'CT_GATEWAY=$(/usr/bin/python3 {}); podman exec -t -e GATEWAY_IP=$CT_GATEWAY -e WG_PORT={} {} /usr/bin/python3 /root/app/bootstrap.py'.format(
|
|
|
|
self.path_get_lan_ip, self.wg_port, self.get_container_name())
|
|
|
|
))
|
|
|
|
else:
|
|
|
|
self.result_postup.append(self.get_podman_cmd_with(
|
|
|
|
'CT_GATEWAY=$(/usr/bin/python3 {} {}); podman exec -t -e GATEWAY_IP=$CT_GATEWAY -e WG_PORT={} {} /usr/bin/python3 /root/app/bootstrap.py'.format(
|
|
|
|
self.path_get_gateway, self.get_container_network_name(), self.wg_port, self.get_container_name())
|
|
|
|
))
|
|
|
|
|
|
|
|
self.result_postup.extend(self.result_container_postbootstrap)
|
|
|
|
|
|
|
|
# registry fetch accept-client
|
|
|
|
if self.flag_require_registry and self.pending_accepts:
|
|
|
|
resolved_upload_arr = {}
|
|
|
|
|
|
|
|
for accept_info in self.pending_accepts:
|
|
|
|
peer_client_name = accept_info["client"]
|
|
|
|
errprint('[REGISTRY-RESOLVE] Resolving accept-client {}...'.format(peer_client_name))
|
|
|
|
peer_client_config = self.registry_query(peer_client_name)
|
|
|
|
if not peer_client_config:
|
|
|
|
errprint('[WARN] Unable to resolve client: {}'.format(peer_client_name))
|
|
|
|
continue
|
|
|
|
|
|
|
|
self.append_input_peer_serverside(peer_client_config["wgkey"], accept_info["allowed"])
|
|
|
|
|
|
|
|
peer_tunnel_info = copy.copy(self.tunnel_server_reports[accept_info['tunnel']])
|
|
|
|
peer_tunnel_info["allowed"] = accept_info["peer_allowed"]
|
|
|
|
|
|
|
|
public_key = get_rsa_pubkey_from_public_pem(peer_client_config["pubkey"])
|
|
|
|
resolved_upload_arr[peer_client_name] = rsa_encrypt_base64(public_key, json.dumps(peer_tunnel_info, ensure_ascii=False).encode())
|
|
|
|
|
|
|
|
if resolved_upload_arr:
|
|
|
|
self.registry_ensure(peers=resolved_upload_arr)
|
|
|
|
|
|
|
|
def compile_peers(self):
|
|
|
|
if self.flag_is_route_forward and len(self.input_peer) > 1:
|
|
|
|
errprint('[WARN] route-forward should used with ONLY one peer.')
|
|
|
|
|
|
|
|
for this_peer_idx, this_peer_lines in enumerate(self.input_peer):
|
|
|
|
current_pubkey = ''
|
|
|
|
current_allowed = ''
|
2022-03-16 21:22:21 +08:00
|
|
|
current_endpoint = ''
|
2022-03-12 04:11:50 +08:00
|
|
|
if self.flag_is_route_lookup:
|
|
|
|
current_lookup = self.lookup_table
|
|
|
|
else:
|
|
|
|
current_lookup = ''
|
|
|
|
|
|
|
|
# pre-scan
|
|
|
|
for line in this_peer_lines:
|
|
|
|
if line.startswith('PublicKey'):
|
|
|
|
current_pubkey = '='.join(line.split('=')[1:]).strip()
|
|
|
|
if line.startswith('AllowedIPs'):
|
2022-03-16 21:22:21 +08:00
|
|
|
current_allowed = line.split('=')[1].strip().split(',')
|
|
|
|
if line.startswith('Endpoint'):
|
2022-03-16 21:44:36 +08:00
|
|
|
current_endpoint = line.split('=')[1].strip()
|
2022-03-12 04:11:50 +08:00
|
|
|
|
|
|
|
self.result_peers.append('[Peer]')
|
|
|
|
|
|
|
|
for line in this_peer_lines:
|
|
|
|
if not line.startswith('#'):
|
|
|
|
self.result_peers.append(line)
|
|
|
|
continue
|
|
|
|
|
|
|
|
if line.startswith('#use-tunnel'):
|
2022-03-16 21:22:21 +08:00
|
|
|
current_endpoint = ''
|
|
|
|
|
2022-03-12 04:11:50 +08:00
|
|
|
parts = line.split(' ')[1:]
|
|
|
|
tunnel_name = parts[0]
|
|
|
|
|
|
|
|
tunnel_addr = self.tunnel_local_endpoint[tunnel_name]
|
|
|
|
if ":" in tunnel_addr:
|
|
|
|
addr_parts = tunnel_addr.split(':')
|
|
|
|
addr_host = addr_parts[0]
|
|
|
|
addr_port = int(addr_parts[1])
|
|
|
|
|
|
|
|
if addr_host == "gateway":
|
|
|
|
tunnel_addr = ""
|
|
|
|
if self.flag_container_must_host or self.podman_user:
|
|
|
|
self.result_postup.append("wg set {} peer {} endpoint 127.0.0.1:{}".format(
|
|
|
|
self.wg_name, current_pubkey, addr_port))
|
|
|
|
else:
|
|
|
|
self.result_postup.append("CT_IP=$({}); wg set {} peer {} endpoint $CT_IP:{}".format(
|
|
|
|
self.get_podman_cmd_with('/usr/bin/python3 {} {} {}'.format(self.path_get_ip, self.get_container_network_name(), self.get_container_name())),
|
|
|
|
self.wg_name, current_pubkey, addr_port))
|
|
|
|
elif tunnel_addr:
|
|
|
|
tunnel_addr = "127.0.0.1:{}".format(tunnel_addr)
|
|
|
|
|
|
|
|
if tunnel_addr:
|
|
|
|
self.result_peers.append('Endpoint={}'.format(tunnel_addr))
|
|
|
|
elif line.startswith('#route-from'):
|
|
|
|
parts = line.split(' ')[1:]
|
|
|
|
table_name = parts[0]
|
|
|
|
|
|
|
|
if table_name != self.lookup_table:
|
|
|
|
current_lookup = table_name
|
|
|
|
errprint('[WARN] Please ensure custom route table {} exists.'.format(table_name))
|
|
|
|
else:
|
|
|
|
errprint('[WARN] comment or unknown hint: {}'.format(line))
|
|
|
|
|
2022-03-16 21:22:21 +08:00
|
|
|
if self.flag_enable_dns_reload and current_endpoint:
|
|
|
|
task_uuid = str(uuid.uuid4())
|
2022-03-17 18:21:01 +08:00
|
|
|
self.result_postup.append('systemd-run --unit wg-ops-task-{}-dnsreload-{} --collect --timer-property AccuracySec=10 --on-calendar *:*:0/30 /usr/bin/python3 {} {} {} {}'.format(
|
2022-03-16 21:22:21 +08:00
|
|
|
self.wg_name, task_uuid, self.path_reload_dns, self.wg_name, current_pubkey, current_endpoint))
|
|
|
|
self.flag_require_systemd_clean = True
|
|
|
|
|
2022-03-12 04:11:50 +08:00
|
|
|
if self.flag_is_route_forward and this_peer_idx == 0:
|
|
|
|
self.result_postup.insert(0, 'wg set {} peer {} allowed-ips 0.0.0.0/0'.format(self.wg_name, current_pubkey))
|
|
|
|
|
|
|
|
if current_lookup:
|
|
|
|
for ip_cidr in current_allowed:
|
|
|
|
self.result_postup.append('ip rule add from {} lookup {}'.format(ip_cidr, current_lookup))
|
|
|
|
self.result_postdown.append('ip rule del from {} lookup {}'.format(ip_cidr, current_lookup))
|
|
|
|
|
2022-03-19 04:46:17 +08:00
|
|
|
def compile_final(self):
|
|
|
|
if self.flag_require_tmpfile_clean:
|
|
|
|
self.result_postup.insert(0, 'rm /tmp/wg-ops-tmpfile-{}-*'.format(self.wg_name))
|
|
|
|
self.result_postup.append('rm /tmp/wg-ops-tmpfile-{}-*'.format(self.wg_name))
|
|
|
|
|
|
|
|
if self.flag_require_systemd_clean or self.opt_use_systemd:
|
2022-03-16 21:44:36 +08:00
|
|
|
self.result_postup.insert(0, 'systemctl stop wg-ops-task-{}-*'.format(self.wg_name))
|
2022-03-16 21:22:21 +08:00
|
|
|
self.result_postdown.insert(0, 'systemctl stop wg-ops-task-{}-*'.format(self.wg_name))
|
|
|
|
|
2022-03-12 04:11:50 +08:00
|
|
|
def get_result(self):
|
|
|
|
current_time = time.strftime("%Y-%m-%d %H:%M:%S")
|
|
|
|
gen_result_postup = ["PostUp={}".format(line) for line in self.result_postup]
|
|
|
|
gen_result_postdown = ["PostDown={}".format(line) for line in self.result_postdown]
|
|
|
|
|
|
|
|
return '''# Generated by wg-ops at {}. DO NOT EDIT.
|
|
|
|
{}
|
|
|
|
{}
|
|
|
|
{}
|
|
|
|
{}
|
|
|
|
'''.format(current_time, '\n'.join(self.result_interface), '\n'.join(gen_result_postup), '\n'.join(gen_result_postdown), '\n'.join(self.result_peers))
|