├── .gitignore ├── DN42AP_regenerate_config.py ├── DN42AutoPeer.py ├── DN42GIT.py ├── DN42whois.py ├── LICENSE ├── README.md ├── my_config.yaml ├── my_parameters.yaml ├── requirement.txt ├── ssh └── .gitignore └── ssl └── .gitignore /.gitignore: -------------------------------------------------------------------------------- 1 | dn42data/* 2 | __pycache__/* 3 | dn42data/* 4 | dn42data 5 | whoisdata/* 6 | whoisdata 7 | my_config.json 8 | my_parameters.json 9 | -------------------------------------------------------------------------------- /DN42AP_regenerate_config.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | import os 3 | import yaml 4 | import time 5 | import json 6 | import asyncio 7 | import pathlib 8 | from distutils.dir_util import copy_tree 9 | import DN42AutoPeer 10 | 11 | conf_dir = DN42AutoPeer.wgconfpath + "/peerinfo" 12 | 13 | bkfdr = os.path.expanduser(f"~/dn42ap_{str(int(time.time()))}") 14 | 15 | backup = input(f"This script will clear all old files in {DN42AutoPeer.wgconfpath} and {DN42AutoPeer.bdconfpath}, backup it into {bkfdr} ? (Y/N)") 16 | 17 | allowed_myip = [] 18 | if "DN42_IPV4" in os.environ: 19 | allowed_myip += [os.environ["DN42_IPV4"]] 20 | if "DN42_IPV6" in os.environ: 21 | allowed_myip += [os.environ["DN42_IPV6"]] 22 | if "DN42AP_ALLOWED_MYIP" in os.environ: 23 | allowed_myip += json.loads(os.environ["DN42AP_ALLOWED_MYIP"]) 24 | 25 | if backup == "y" or backup == "Y": 26 | print(bkfdr) 27 | print(os.path.basename(DN42AutoPeer.wgconfpath)) 28 | print(os.path.basename(DN42AutoPeer.bdconfpath)) 29 | os.mkdir(bkfdr) 30 | os.mkdir(os.path.join(bkfdr, os.path.basename(DN42AutoPeer.wgconfpath))) 31 | os.mkdir(os.path.join(bkfdr, os.path.basename(DN42AutoPeer.bdconfpath))) 32 | 33 | copy_tree(DN42AutoPeer.wgconfpath, os.path.join(bkfdr, os.path.basename(DN42AutoPeer.wgconfpath))) 34 | copy_tree(DN42AutoPeer.bdconfpath, os.path.join(bkfdr, os.path.basename(DN42AutoPeer.bdconfpath))) 35 | 36 | def saveConfig(new_config): 37 | for path,content in new_config["config"].items(): 38 | # print("================================") 39 | # print(path) 40 | # print(content) 41 | fileparent = pathlib.Path(path).parent.absolute() 42 | if not os.path.isdir(fileparent): 43 | os.makedirs(fileparent, mode=0o700 , exist_ok=True) 44 | with open(path,"w") as conffd: 45 | conffd.write(content) 46 | if content.startswith("#!"): 47 | os.chmod(path, 0o755) 48 | # print("================================") 49 | 50 | for f in os.listdir(DN42AutoPeer.bdconfpath): 51 | if f.endswith(".conf"): 52 | os.remove(os.path.join(DN42AutoPeer.bdconfpath,f)) 53 | 54 | for f in os.listdir(DN42AutoPeer.wgconfpath): 55 | if f.endswith(".conf") or f.endswith(".sh") : 56 | os.remove(os.path.join(DN42AutoPeer.wgconfpath,f)) 57 | 58 | async def main(): 59 | for old_conf_file in os.listdir(conf_dir): 60 | if old_conf_file.endswith(".yaml") and os.path.isfile(f"{conf_dir}/{old_conf_file}"): 61 | try: 62 | print(old_conf_file) 63 | old_conf = yaml.load(open(f"{conf_dir}/{old_conf_file}").read(),Loader=yaml.SafeLoader) 64 | action , paramaters = DN42AutoPeer.get_paramaters(old_conf,isAdmin=True) 65 | paramaters = await DN42AutoPeer.check_reg_paramater(paramaters,skip_check=old_conf_file[:-5],git_pull=False,allow_invalid_as=True,allowed_custom_myip=allowed_myip) 66 | new_config = DN42AutoPeer.newConfig(paramaters,overwrite=True) 67 | except Exception as e: 68 | raise e 69 | saveConfig(new_config) 70 | 71 | loop = asyncio.get_event_loop() 72 | coroutine = main() 73 | loop.run_until_complete(coroutine) -------------------------------------------------------------------------------- /DN42AutoPeer.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | import os 3 | import re 4 | import jwt 5 | import time 6 | import pgpy 7 | import yaml 8 | import json 9 | import shlex 10 | import errno 11 | import shutil 12 | import random 13 | import string 14 | import socket 15 | import base64 16 | import pathlib 17 | import asyncio 18 | import hashlib 19 | import OpenSSL 20 | import textwrap 21 | import requests 22 | import datetime 23 | import ipaddress 24 | import traceback 25 | import nacl.public 26 | import tornado.web 27 | import tornado.gen 28 | from git import Repo 29 | import tornado.ioloop 30 | import multiprocessing 31 | from urllib import parse 32 | import tornado.httpclient 33 | import requests.packages.urllib3 34 | from Crypto.PublicKey import RSA 35 | from ipaddress import IPv4Network , IPv6Network , IPv4Interface , IPv6Interface, IPv4Address , IPv6Address 36 | from ipaddress import IPv6Network 37 | from subprocess import Popen, PIPE, STDOUT 38 | from tornado.httpclient import HTTPClientError 39 | import DN42whois 40 | from DN42GIT import DN42GIT 41 | 42 | requests.packages.urllib3.disable_warnings() 43 | 44 | import argparse 45 | parser = argparse.ArgumentParser() 46 | parser.add_argument("-e", "--envfile",action='append', help="envfile", type=str) 47 | parser.add_argument("-c", "--config",action='store', help="envfile", type=str) 48 | parser.add_argument("-p", "--parms",action='store', help="envfile", type=str) 49 | args = parser.parse_args() 50 | envs = {} 51 | if args.envfile: 52 | for efs in args.envfile: 53 | es = open(efs).read().split("\n") 54 | for e in es: 55 | if "=" in e: 56 | k,v = e.split("=",1) 57 | os.environ[k] = v 58 | 59 | confpath = "my_config.yaml" 60 | parmpath = "my_parameters.yaml" 61 | 62 | peerHostDisplayText = "(Peer endpoint hidden, authenticate to show)" 63 | 64 | if args.config: 65 | confpath = args.config 66 | if args.parms: 67 | parmpath = args.parms 68 | 69 | print("Starting...") 70 | os.environ['GIT_SSH_COMMAND'] = "ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no" 71 | 72 | my_paramaters = yaml.load(open(parmpath).read(),Loader=yaml.Loader) 73 | my_config = yaml.load(open(confpath).read(),Loader=yaml.Loader) 74 | 75 | if my_config["jwt_secret"] == None: 76 | my_config["jwt_secret"] = ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(32)) 77 | open(confpath,"w").write(yaml.dump(my_config, sort_keys=False)) 78 | 79 | jwt_secret = my_config["jwt_secret"] 80 | 81 | def try_read_env(params,pkey,ekey,ValType=str,default=None): 82 | if ekey in os.environ: 83 | if ValType == bool: 84 | params[pkey] = os.environ[ekey].lower() == "true" 85 | elif ValType == "json": 86 | params[pkey] = json.loads( os.environ[ekey] ) 87 | else: 88 | params[pkey] = ValType(os.environ[ekey]) 89 | print(f"Load {pkey} from env, val: {params[pkey]}") 90 | if pkey not in params: 91 | if default == None: 92 | raise ValueError("default for " + pkey + " are not set") 93 | params[pkey] = default 94 | 95 | use_speed_limit = False 96 | if "WG_SPEED_LIMIT" in os.environ: 97 | try: 98 | if int(os.environ["WG_SPEED_LIMIT"]) > 0: 99 | use_speed_limit = True 100 | except: 101 | pass 102 | 103 | try_read_env(my_paramaters,"myIPV4",'DN42_IPV4') 104 | try_read_env(my_paramaters,"myIPV6",'DN42_IPV6') 105 | try_read_env(my_paramaters,"myIPV4LL",'DN42_IPV4_LL') 106 | try_read_env(my_paramaters,"myIPV6LL",'DN42_IPV6_LL') 107 | try_read_env(my_paramaters,"myIPV4",'DN42AP_MY_IPV4') 108 | try_read_env(my_paramaters,"myIPV6",'DN42AP_MY_IPV6') 109 | try_read_env(my_paramaters,"myIPV4LL",'DN42AP_MY_IPV4_LL') 110 | try_read_env(my_paramaters,"myIPV6LL",'DN42AP_MY_IPV6_LL') 111 | try_read_env(my_paramaters,"myHost",'DN42AP_ENDPOINT') 112 | try_read_env(my_paramaters,"myHostDisplay",'DN42AP_HOST_DISPLAY') 113 | try_read_env(my_paramaters,"myASN",'DN42_E_AS') 114 | try_read_env(my_paramaters,"myContact",'DN42_CONTACT') 115 | try_read_env(my_paramaters,"allowExtNh",'DN42AP_ALLOW_ENH',bool,False) 116 | try_read_env(my_config,"myHostHidden",'DN42AP_HOST_HIDDEN',bool,False) 117 | try_read_env(my_config,"peerEndpointHidden",'DN42AP_PEER_ENDPOINT_HIDDEN',bool,False) 118 | try_read_env(my_config,"registerAdminOnly",'DN42AP_REGISTER_ADMINONLY',bool,False) 119 | try_read_env(my_config,"html_title",'DN42AP_TITLE') 120 | try_read_env(my_config,"git_repo_url",'DN42AP_GIT_REPO_URL') 121 | try_read_env(my_config,"listen_host",'DN42AP_LISTEN_HOST') 122 | try_read_env(my_config,"listen_port",'DN42AP_PORT') 123 | try_read_env(my_config,"myWG_Pri_Key",'WG_PRIVKEY') 124 | try_read_env(my_config,"urlprefix",'DN42AP_URLPREFIX') 125 | try_read_env(my_config,"wgconfpath",'DN42AP_WGCONFPATH') 126 | try_read_env(my_config,"bdconfpath",'DN42AP_BIRDCONFPATH') 127 | try_read_env(my_config,"gitsyncpath",'DN42AP_GIT_SYNC_PATH') 128 | try_read_env(my_config,"admin_mnt",'DN42AP_ADMIN') 129 | try_read_env(my_config,"register_redirect",'DN42AP_REGISTER_REDIRECT') 130 | try_read_env(my_config,"wg_port_search_range",'DN42AP_PORT_RANGE',str) 131 | try_read_env(my_config,"dn42_whois_server","DN42AP_WHOIS_SERVER","json") 132 | try_read_env(my_config,"dn42repo_base","DN42AP_REPO_BASE",str) 133 | try_read_env(my_config,"init_device",'DN42AP_INIT_DEVICE',bool) 134 | try_read_env(my_config,"reset_wgconf_interval",'DN42AP_RESET_WGCONF',int,0) 135 | 136 | RRstate_repo = DN42GIT(my_config["gitsyncpath"]) 137 | 138 | use_remote_command = "" 139 | if "DN42AP_REMOTE_COMMAND" in os.environ: 140 | use_remote_command = os.environ['DN42AP_REMOTE_COMMAND'] 141 | 142 | def es2none(p): 143 | if p == "": 144 | return None 145 | return p 146 | 147 | my_paramaters["myIPV4"] = es2none(my_paramaters["myIPV4"]) 148 | my_paramaters["myIPV6"] = es2none(my_paramaters["myIPV6"]) 149 | my_paramaters["myIPV6LL"] = es2none(my_paramaters["myIPV6LL"]) 150 | my_paramaters["myHost"] = es2none(my_paramaters["myHost"]) 151 | my_paramaters["myASN"] = my_paramaters["myASN"] if my_paramaters["myASN"].startswith("AS") else "AS" + my_paramaters["myASN"] 152 | 153 | 154 | node_name = "" 155 | try: 156 | node_name = os.environ['NODE_NAME'] 157 | except Exception as e: 158 | pass 159 | 160 | 161 | wgconfpath = my_config["wgconfpath"] 162 | bdconfpath = my_config["bdconfpath"] 163 | 164 | pathlib.Path(wgconfpath + "/peerinfo").mkdir(parents=True, exist_ok=True) 165 | 166 | client_valid_keys = ["peer_plaintext","peer_pub_key_pgp","peer_signature", "peerASN","peerName", "hasIPV4", "peerIPV4","hasIPV4LL","peerIPV4LL", "hasIPV6", "peerIPV6", "hasIPV6LL", "peerIPV6LL","MP_BGP","Ext_Nh", "hasHost", "peerHost", "peerWG_Pub_Key","peerWG_PS_Key", "peerContact", "PeerID","myIPV4","myIPV6","myIPV4LL","myIPV6LL","customDevice","customDeviceSetup","myWG_Pri_Key","transitMode","myWG_MTU","birdAddConf"] 167 | client_valid_keys_admin_only = ["customDevice","customDeviceSetup","myWG_Pri_Key","peerName","birdAddConf","transitMode","myWG_MTU"] 168 | dn42repo_base = my_config["dn42repo_base"] 169 | DN42_valid_ipv4s = my_config["DN42_valid_ipv4s"] 170 | DN42_valid_ipv6s = my_config["DN42_valid_ipv6s"] 171 | valid_ipv4_lilo = my_config["valid_ipv4_linklocal"] 172 | valid_ipv6_lilo = my_config["valid_ipv6_linklocal"] 173 | wg_allowed_ips = DN42_valid_ipv4s + DN42_valid_ipv6s + [valid_ipv4_lilo , valid_ipv6_lilo ] 174 | whois = DN42whois.whois(*my_config["dn42_whois_server"]) 175 | whois_query = whois.query 176 | 177 | method_hint = {"ssh-rsa":"""

Paste following command to your terminal to get your signature.

178 | 179 | echo -n "{text2sign}" | ssh-keygen -Y sign -n dn42ap -f ~/.ssh/id_rsa 180 | """, 181 | "ssh-ed25519":"""

Paste following command to your terminal to get your signature.

182 | 183 | echo -n "{text2sign}" | ssh-keygen -Y sign -n dn42ap -f ~/.ssh/id_ed25519 184 | """, 185 | "pgp-fingerprint": """

Paste following command to your terminal to get your PGP public key and signature.

186 | 187 | # Export PGP public key
188 | gpg --armor --export --fingerprint {fingerprint}
189 |
190 | # sign message with your PGP private key
191 | echo -n "{text2sign}" | gpg --clearsign --detach-sign -u {fingerprint}
192 |
193 | # Done. You can copy the signature now
194 |
""", 195 | "PGPKEY": """

Paste following command to your terminal to get your PGP public key and signature.

196 | 197 | # Export PGP public key
198 | gpg --armor --export --fingerprint {fingerprint}
199 |
200 | # sign message with your PGP private key
201 | echo -n "{text2sign}" | gpg --clearsign --detach-sign -u {fingerprint}
202 |
203 | # Done. You can copy the signature now
204 |
""" 205 | } 206 | 207 | async def get_signature_html(baseURL,paramaters): 208 | peerASN = paramaters["peerASN"] 209 | peerMNT, peerADM = await get_info_from_asn(peerASN) 210 | try: 211 | peerADMname = (await get_auth_info(["person","role"],peerADM[0]))["display"][0] 212 | except Exception as e: 213 | peerADMname = "" 214 | methods = await get_auth_method(peerMNT, peerADM) 215 | text2sign = jwt.encode({'ASN': peerASN, "exp":datetime.datetime.utcnow() + datetime.timedelta(minutes = 30) }, jwt_secret, algorithm='HS256') 216 | methods_class = {"Supported":{},"Unsupported":{}} 217 | for m,v,mnt in methods: 218 | if m in method_hint: 219 | if m not in methods_class["Supported"]: 220 | methods_class["Supported"][m] = [] 221 | methods_class["Supported"][m] += [v] 222 | if m in { "PGPKEY" , "pgp-fingerprint" }: 223 | if paramaters["peer_pub_key_pgp"] == "": 224 | paramaters["peer_pub_key_pgp"] = await try_get_pub_key(v) 225 | else: 226 | if m not in methods_class["Unsupported"]: 227 | methods_class["Unsupported"][m] = [] 228 | methods_class["Unsupported"][m] += [v] 229 | retstr = f""" 230 | 231 | 232 | { my_config["html_title"] } 233 | 234 | 252 | 253 | 254 | 255 |

{ my_config["html_title"] }

256 |

Dear { peerADMname }:

257 | """ 258 | if len(methods_class["Supported"]) == 0: 259 | retstr += f"""

    Sorry, we couldn't find any available authentication method in your mntner object or admin contact in the DN42 registry.

Please contact me to peer manually.

""" 260 | else: 261 | retstr += f"""

    Please sign our message with your private key registered in your mntner object or admin contact in the DN42 registry.

""" 262 | retstr += "

Supported auth method:

" if len(list(methods_class["Supported"].keys())) != 0 else "" 263 | for m,v in methods_class["Supported"].items(): 264 | retstr += f"""""" 265 | for v_item in v: 266 | retstr += f"""""" 267 | retstr += "
Allowed {m}(s):
{v_item}
" 268 | retstr += method_hint[m].format(text2sign = text2sign,fingerprint=v[0]) 269 | retstr += "

Unupported auth method:

" if len(list(methods_class["Unsupported"].keys())) != 0 else "" 270 | for m,v in methods_class["Unsupported"].items(): 271 | retstr += f"""""" 272 | for v_item in v: 273 | retstr += f"""""" 274 | retstr += "
{m}
{v_item}
" 275 | retstr += f""" 276 |
277 |
\n""" 278 | paramaters = { valid_key: paramaters[valid_key] for valid_key in client_valid_keys if valid_key in paramaters} 279 | paramaters["peer_plaintext"] = text2sign 280 | for k,v in paramaters.items(): 281 | if k in client_valid_keys_admin_only: 282 | continue 283 | if v == None: 284 | v = "" 285 | elif v == True: 286 | v = "on" 287 | retstr += f'\n' 288 | retstr +=""" 289 |
290 | 291 | 292 | """ 293 | return retstr 294 | 295 | def wgpri2pub(pri): 296 | try: 297 | pb = base64.b64decode(pri) 298 | pp = nacl.public.PrivateKey(pb) 299 | return base64.b64encode(bytes(pp.public_key)).decode("ascii") 300 | except Exception as e: 301 | return "Wireguard Key: " + str(e) 302 | 303 | async def get_html(paramaters,action="OK",peerSuccess=False): 304 | peer_plaintext = paramaters["peer_plaintext"] 305 | peer_pub_key_pgp = paramaters["peer_pub_key_pgp"] 306 | peer_signature = paramaters["peer_signature"] 307 | peerASN = paramaters["peerASN"] 308 | hasIPV4 = paramaters["hasIPV4"] 309 | hasIPV4Disabled = "" 310 | peerIPV4 = paramaters["peerIPV4"] 311 | hasIPV6 = paramaters["hasIPV6"] 312 | hasIPV6Disabled = "" 313 | peerIPV6 = paramaters["peerIPV6"] 314 | hasIPV4LL = paramaters["hasIPV4LL"] 315 | hasIPV4LLDisabled = "" 316 | peerIPV4LL = paramaters["peerIPV4LL"] 317 | hasIPV6LL = paramaters["hasIPV6LL"] 318 | hasIPV6LLDisabled = "" 319 | peerIPV6LL = paramaters["peerIPV6LL"] 320 | MP_BGP = paramaters["MP_BGP"] 321 | MP_BGP_Disabled = "" 322 | Ext_Nh = paramaters["Ext_Nh"] 323 | Ext_Nh_Disabled = "" 324 | hasHost = paramaters["hasHost"] 325 | hasHost_Readonly = "" 326 | peerHost = paramaters["peerHost"] 327 | peerHostDisplay = peerHostDisplayText 328 | peerWG_Pub_Key = paramaters["peerWG_Pub_Key"] 329 | peerWG_PS_Key = paramaters["peerWG_PS_Key"] 330 | peerContact = paramaters["peerContact"] 331 | PeerID = paramaters["PeerID"] 332 | myASN = paramaters["myASN"] 333 | myHost = paramaters["myHost"] 334 | myIPV4 = paramaters["myIPV4"] 335 | myIPV6 = paramaters["myIPV6"] 336 | myIPV4LL = paramaters["myIPV4LL"] 337 | myIPV6LL = paramaters["myIPV6LL"] 338 | myWG_Pub_Key = paramaters["myWG_Pub_Key"] 339 | myContact = paramaters["myContact"] 340 | if myIPV4 == None: 341 | myIPV4 = "" 342 | if myIPV6 == None: 343 | myIPV6 = "" 344 | if myIPV4LL == None: 345 | myIPV4LL = "" 346 | if myIPV6LL == None: 347 | myIPV6LL = "" 348 | if myHost == None: 349 | myHost = "" 350 | if myIPV4 == "": 351 | hasIPV4 = False 352 | hasIPV4Disabled = "disabled" 353 | peerIPV4 = "Sorry, I don't support IPv4 address." 354 | if myIPV6 == "": 355 | hasIPV6 = False 356 | hasIPV6Disabled = "disabled" 357 | peerIPV6 = "Sorry, I don't support IPv6 address." 358 | if myIPV4LL == "": 359 | hasIPV4LL = False 360 | hasIPV4LLDisabled = "disabled" 361 | peerIPV4LL = "Sorry, I don't support IPv4 link local address." 362 | if myIPV6LL == "": 363 | hasIPV6LL = False 364 | hasIPV6LLDisabled = "disabled" 365 | peerIPV6LL = "Sorry, I don't support IPv6 link local address." 366 | if not (myIPV4!="") and (myIPV6!="" or myIPV6LL!=""): 367 | MP_BGP_Disabled = "disabled" 368 | Ext_Nh_Disabled = "disabled" 369 | if my_config["peerEndpointHidden"] and action == "Show": 370 | if peer_signature != "" and peer_signature != None: 371 | try: 372 | mntner = await verify_user_signature(paramaters["peerASN"],paramaters["peer_plaintext"],paramaters["peer_pub_key_pgp"],peer_signature) 373 | peerHostDisplay = peerHost 374 | except Exception as e: 375 | pass 376 | else: 377 | peerHostDisplay = peerHost 378 | if myHost == "": 379 | hasHost = True 380 | hasHost_Readonly = 'onclick="alert(\\"Sorry, I don\'t have a public IP so that your endpoint can\'t be null.\\");return false;"' 381 | hasHost_Readonly = 'onclick="alert(\'Sorry, I don\\\'t have a public IP so that your endpoint can\\\'t be null.\');return false;";' 382 | myHostDisplay = paramaters["myHostDisplay"] 383 | else: 384 | if PeerID == None: 385 | myHostDisplay = "(Register to get the endpoint) :" 386 | else: 387 | myHostDisplay = "(Authenticate to show the endpoint) :" 388 | if my_config["myHostHidden"]: 389 | if peer_signature != "" and peer_signature != None: 390 | try: 391 | mntner = await verify_user_signature(paramaters["peerASN"],paramaters["peer_plaintext"],paramaters["peer_pub_key_pgp"],peer_signature) 392 | myHostDisplay = myHost + ":" 393 | except Exception as e: 394 | pass 395 | else: 396 | myHostDisplay = myHost + ":" 397 | if PeerID == None: 398 | myHostDisplay += f" [{my_config['wg_port_search_range']}]" 399 | else: 400 | myHostDisplay += str(PeerID) 401 | edit_btn_disabled = "disabled" 402 | try: 403 | peerInfo = yaml.load(open(wgconfpath + "/peerinfo/" + str(paramaters["PeerID"]) + ".yaml").read(),Loader=yaml.SafeLoader) 404 | edit_btn_disabled = "" 405 | except FileNotFoundError as e: 406 | pass 407 | jsscripts = """ 408 | prevVars={ 409 | v4: "hasIPV4", 410 | v6: "hasIPV6LL", 411 | nov4: "v4only" 412 | } 413 | function getV4() { 414 | return document.getElementsByName("hasIPV4")[0].checked || document.getElementsByName("hasIPV4LL")[0].checked 415 | } 416 | function getV6() { 417 | return document.getElementsByName("hasIPV6")[0].checked || document.getElementsByName("hasIPV6LL")[0].checked 418 | } 419 | 420 | function onV4() { 421 | if (document.getElementsByName("hasIPV4")[0].checked == true){ 422 | document.getElementsByName("hasIPV4LL")[0].checked = false; 423 | document.getElementsByName("Ext_Nh")[0].checked = false; 424 | prevVars.v4 = "hasIPV4" 425 | } else { 426 | if( (getV4() || getV6()) == false){ 427 | alert("We can't establish BGP session without any IP.") 428 | document.getElementsByName("hasIPV4")[0].checked = true; 429 | return false; 430 | } 431 | if (prevVars.nov4 == "enh"){ 432 | document.getElementsByName("Ext_Nh")[0].checked = true 433 | } else { 434 | document.getElementsByName("MP_BGP")[0].checked = false 435 | } 436 | } 437 | } 438 | function onV4LL() { 439 | if (document.getElementsByName("hasIPV4LL")[0].checked == true){ 440 | document.getElementsByName("hasIPV4")[0].checked = false; 441 | document.getElementsByName("Ext_Nh")[0].checked = false; 442 | prevVars.v4 = "hasIPV4LL" 443 | } else { 444 | if( (getV4() || getV6()) == false){ 445 | alert("We can't establish BGP session without any IP.") 446 | document.getElementsByName("hasIPV4LL")[0].checked = true; 447 | return false; 448 | } 449 | if (prevVars.nov4 == "enh"){ 450 | document.getElementsByName("Ext_Nh")[0].checked = true 451 | } else { 452 | document.getElementsByName("MP_BGP")[0].checked = false 453 | } 454 | } 455 | } 456 | function onV6() { 457 | if (document.getElementsByName("hasIPV6")[0].checked == true){ 458 | document.getElementsByName("hasIPV6LL")[0].checked = false; 459 | document.getElementsByName("MP_BGP")[0].disabled = false; 460 | document.getElementsByName("Ext_Nh")[0].disabled = false; 461 | prevVars.v6 = "hasIPV6" 462 | } else { 463 | if( (getV4() || getV6()) == false){ 464 | alert("We can't establish BGP session without any IP.") 465 | document.getElementsByName("hasIPV6")[0].checked = true; 466 | return false; 467 | } 468 | if (getV6() == false){ 469 | document.getElementsByName("MP_BGP")[0].checked = false; 470 | document.getElementsByName("Ext_Nh")[0].checked = false 471 | document.getElementsByName("MP_BGP")[0].disabled = true; 472 | document.getElementsByName("Ext_Nh")[0].disabled = true; 473 | prevVars.nov4 = "v4only" 474 | } 475 | } 476 | } 477 | function onV6LL() { 478 | if (document.getElementsByName("hasIPV6LL")[0].checked == true){ 479 | document.getElementsByName("hasIPV6")[0].checked = false; 480 | document.getElementsByName("MP_BGP")[0].disabled = false; 481 | document.getElementsByName("Ext_Nh")[0].disabled = false; 482 | prevVars.v6 = "hasIPV6LL" 483 | } else { 484 | if( (getV4() || getV6()) == false){ 485 | alert("We can't establish BGP session without any IP.") 486 | document.getElementsByName("hasIPV6LL")[0].checked = true; 487 | return false; 488 | } 489 | if (getV6() == false){ 490 | document.getElementsByName("MP_BGP")[0].checked = false; 491 | document.getElementsByName("Ext_Nh")[0].checked = false 492 | document.getElementsByName("MP_BGP")[0].disabled = true; 493 | document.getElementsByName("Ext_Nh")[0].disabled = true; 494 | prevVars.nov4 = "v4only" 495 | } 496 | } 497 | } 498 | function onMPBGP() { 499 | if (document.getElementsByName("MP_BGP")[0].checked == true){ 500 | document.getElementsByName(prevVars.v4)[0].checked = true; 501 | } 502 | if (document.getElementsByName("MP_BGP")[0].checked == false){ 503 | document.getElementsByName("Ext_Nh")[0].checked = false; 504 | prevVars.nov4 = "v4only" 505 | } 506 | } 507 | function onENH() { 508 | if (document.getElementsByName("Ext_Nh")[0].checked == true){ 509 | document.getElementsByName("hasIPV4")[0].checked = false; 510 | document.getElementsByName("hasIPV4LL")[0].checked = false; 511 | document.getElementsByName("MP_BGP")[0].checked = true; 512 | prevVars.nov4 = "enh" 513 | } else { 514 | document.getElementsByName(prevVars.v4)[0].checked = true; 515 | } 516 | } 517 | """ 518 | return f""" 519 | 520 | 521 | 522 | { my_config["html_title"] } 523 | 524 | 553 | 554 | 555 | 556 |

{ my_config["html_title"] }

557 |

{"Peer success! " if peerSuccess else "Please fill "}Your Info

558 | 561 |
562 |

Authentication

563 | 564 | 565 | 566 | 567 | 568 | 569 |
Your ASN
Plain text to sign
Your PGP public key
(leave it blank if you don't use it)
Your signature
Fill your ASN, Click the button, Follow the instruction
570 |

Registration

571 | 572 | 573 | 574 | 575 | 576 | 577 | 578 | 579 | 580 | 581 | 582 | 583 | 584 | 585 |
BGP Session Info:
DN42 IPv4
IPv4 Link local
DN42 IPv6
IPv6 Link local
Multiprotocol BGP
Extended next hop
Wireguard Connection Info:
Your Clearnet Endpoint (domain or ip:port)
Your Wireguard Public Key
Your Wireguard Pre-Shared Key (Optional)
Your Telegram ID or e-mail
Register a new peer and get the peer ID
586 |

Management

587 | 588 | 589 | 590 |
Your Peer ID
591 |

{"Peer success! " if peerSuccess else "This is "}My Info

592 | 593 | 594 | 595 | 596 | 597 | 598 | 599 | 600 | 601 | 602 |
My ASN
DN42 IPv4
DN42 IPv6
IPv4 Link local
IPv6 Link local
Connectrion Info:
My Clearnet Endpoint
My WG Public Key
My Contact
603 |
604 | 605 | 606 | """ 607 | 608 | remove_empty_line = lambda s: "\n".join(filter(lambda x:len(x)>0, s.replace("\r\n","\n").replace("\r","\n").split("\n"))) 609 | async def get_info_from_asn(asn): 610 | asn_info = await whois_query("aut-num/" + asn) 611 | data = DN42whois.proc_data(asn_info) 612 | mnts = get_key_default(data,"mnt-by",[]) 613 | adms = get_key_default(data,"admin-c",[]) 614 | return mnts , adms 615 | 616 | async def get_auth_info(categories,name): 617 | for category in categories: 618 | try: 619 | auth_info = await whois_query(category + "/" + name) 620 | except FileNotFoundError as e: 621 | continue 622 | ret = DN42whois.proc_data(auth_info) 623 | if "auth" not in ret: 624 | ret["auth"] = [] 625 | if "pgp-fingerprint" in ret: 626 | ret["auth"] += ["pgp-fingerprint " + ret["pgp-fingerprint"][0]] 627 | 628 | if "person" in ret and len(ret["person"]) > 0: 629 | ret["display"] = [ret["person"][0]] 630 | elif "role" in ret and len(ret["role"]) > 0: 631 | ret["display"] = [ret["role"][0]] 632 | else: 633 | ret["display"] = ["[Error: Name not found]"] 634 | return ret 635 | raise FileNotFoundError(errno.ENOENT, os.strerror(errno.ENOENT), categories + "/" + name) 636 | 637 | async def get_auth_method(mnts,admins): # return [[method,auth_key,mnter]] 638 | authes = [] 639 | for mnt in mnts: 640 | try: 641 | authes += [[a,mnt] for a in (await get_auth_info(["mntner"],mnt))["auth"]] 642 | except FileNotFoundError as e: 643 | pass 644 | for admin in admins: 645 | try: 646 | authes += [[a,admin] for a in (await get_auth_info(["person","role"],admin))["auth"]] 647 | except FileNotFoundError as e: 648 | pass 649 | auth_dict = {} 650 | for auth in authes: 651 | a,mnt = auth 652 | if a.startswith("PGPKEY"): 653 | method , pgp_sign8 = a.split("-",1) 654 | pgp_pubkey_str = await try_get_pub_key(pgp_sign8) 655 | if pgp_pubkey_str == "": 656 | continue 657 | try: 658 | pub = pgpy.PGPKey.from_blob(remove_empty_line(pgp_pubkey_str).encode("utf8"))[0] 659 | real_fingerprint = pub.fingerprint.replace(" ","") 660 | ainfo = method + " " + real_fingerprint 661 | except Exception as e: 662 | ainfo = method + "_Error " + str(e).replace(" ","_") 663 | else: 664 | ainfo = a 665 | auth_dict[ainfo] = mnt 666 | ret = [] 667 | for r,v in auth_dict.items(): 668 | if len(r.split(" ",1)) == 2: 669 | m,a = r.split(" ",1) 670 | ret += [[m,a,v]] 671 | return ret 672 | 673 | async def try_get_pub_key(pgpsig): 674 | if len(pgpsig) < 8: 675 | return "" 676 | pgpsig = pgpsig[-8:] 677 | try: 678 | result = await whois_query("key-cert/PGPKEY-" + pgpsig) 679 | result = list(filter(lambda l:l.startswith("certif:"),result.split("\n"))) 680 | result = list(map(lambda x:x.split(":")[1].lstrip(),result)) 681 | result = "\n".join(result) 682 | return remove_empty_line(result) 683 | except Exception as e: 684 | pass 685 | return "" 686 | 687 | 688 | def verify_signature_pgp(plaintext,fg,pub_key,raw_signature): 689 | pub = pgpy.PGPKey.from_blob(remove_empty_line(pub_key).encode("utf8"))[0] 690 | fg_in = fg.replace(" ","").upper() 691 | fg_p = pub.fingerprint.replace(" ","") 692 | if fg_in != fg_p: 693 | raise ValueError("fingerprint not match") 694 | sig = pgpy.PGPSignature.from_blob(remove_empty_line(raw_signature).encode("utf8")) 695 | if not pub.verify(plaintext,sig): 696 | raise ValueError("signature verification failed") 697 | return True 698 | 699 | def verify_signature_pgpn8(plaintext,fg,pub_key,raw_signature): 700 | pub = pgpy.PGPKey.from_blob(remove_empty_line(pub_key).encode("utf8"))[0] 701 | fg_in = fg.replace(" ","") 702 | fg_p = pub.fingerprint.replace(" ","") 703 | if fg_in != fg_p: 704 | raise ValueError("fingerprint not match") 705 | sig = pgpy.PGPSignature.from_blob(remove_empty_line(raw_signature).encode("utf8")) 706 | if not pub.verify(plaintext,sig): 707 | raise ValueError("signature verification failed") 708 | return True 709 | 710 | def verify_signature_ssh_rsa(plaintext,pub_key,raw_signature): 711 | sess = ''.join(random.choices(string.ascii_uppercase + string.digits, k=10)) 712 | pathlib.Path("ssh").mkdir(parents=True, exist_ok=True) 713 | sigfile_path = "ssh/tmp" + sess + ".sig" 714 | pubfile_path = "ssh/tmp" + sess + ".pub" 715 | open(sigfile_path,"w").write(raw_signature) 716 | open(pubfile_path,"w").write(sess + " ssh-rsa " + pub_key) 717 | command = 'ssh-keygen',"-Y","verify","-f",pubfile_path,"-n","dn42ap","-I",sess,"-s",sigfile_path 718 | p = Popen(command, stdout=PIPE, stdin=PIPE, stderr=PIPE) 719 | stdout_data = p.communicate(input=plaintext.encode())[0] 720 | os.remove(sigfile_path) 721 | os.remove(pubfile_path) 722 | if stdout_data.startswith(b"Good"): 723 | return True 724 | else: 725 | raise ValueError(stdout_data) 726 | 727 | def verify_signature_ssh_ed25519(plaintext,pub_key,raw_signature): 728 | sess = ''.join(random.choices(string.ascii_uppercase + string.digits, k=10)) 729 | pathlib.Path("ssh").mkdir(parents=True, exist_ok=True) 730 | sigfile_path = "ssh/tmp" + sess + ".sig" 731 | pubfile_path = "ssh/tmp" + sess + ".pub" 732 | open(sigfile_path,"w").write(raw_signature) 733 | open(pubfile_path,"w").write(sess + " ssh-ed25519 " + pub_key) 734 | command = 'ssh-keygen',"-Y","verify","-f",pubfile_path,"-n","dn42ap","-I",sess,"-s",sigfile_path 735 | p = Popen(command, stdout=PIPE, stdin=PIPE, stderr=PIPE) 736 | stdout_data = p.communicate(input=plaintext.encode())[0] 737 | os.remove(sigfile_path) 738 | os.remove(pubfile_path) 739 | if stdout_data.startswith(b"Good"): 740 | return True 741 | else: 742 | raise ValueError(stdout_data) 743 | 744 | def removern(strin): 745 | if type(strin) == str: 746 | return strin.replace("\r\n","\n").replace("\r","\n") 747 | elif type(strin) == bytes: 748 | return strin.replace(b"\r\n",b"\n").replace(b"\r",b"\n") 749 | return strin 750 | def verify_signature(plaintext,pub_key,pub_key_pgp,raw_signature,method): 751 | if method=="pgp-fingerprint": 752 | return verify_signature_pgp(plaintext,pub_key,pub_key_pgp,raw_signature) 753 | elif method=="PGPKEY": 754 | return verify_signature_pgpn8(plaintext,pub_key,pub_key_pgp,raw_signature) 755 | elif method=="ssh-rsa": 756 | return verify_signature_ssh_rsa(plaintext,pub_key,raw_signature) 757 | elif method=="ssh-ed25519": 758 | return verify_signature_ssh_ed25519(plaintext,pub_key,raw_signature) 759 | raise NotImplementedError("method not implement") 760 | 761 | async def verify_user_signature(peerASN,plaintext,pub_key_pgp,raw_signature): 762 | try: 763 | plaintext = removern(plaintext) 764 | pub_key_pgp = removern(pub_key_pgp) 765 | raw_signature = removern(raw_signature) 766 | if plaintext == "" or plaintext == None: 767 | raise ValueError('Plain text to sign can\'t be null, please click the button "Get Signature" first.') 768 | raw_signature = raw_signature.replace("\r\n","\n").replace("\r","\n") 769 | if raw_signature == "" or raw_signature == None: 770 | raise ValueError('Signature can\'t be null, please click the button "Get Signature" and follow the instruction.') 771 | sig_info = jwt.decode(plaintext.encode("utf8"),jwt_secret,algorithms=["HS256"]) 772 | if sig_info["ASN"] != peerASN: 773 | raise ValueError("JWT verification failed. You are not the mntner of " + sig_info["ASN"]) 774 | supported_method= ["ssh-rsa"] 775 | # verify user signature 776 | mntner, admin = await get_info_from_asn(peerASN) 777 | authes = await get_auth_method(mntner, admin) 778 | tried = False 779 | authresult = [{"Your input":{"plaintext":plaintext,"signature":raw_signature,"pub_key_pgp":pub_key_pgp}}] 780 | for method,pub_key,mnt in authes: 781 | try: 782 | if verify_signature(plaintext,pub_key,pub_key_pgp,raw_signature,method) == True: 783 | return mnt 784 | except Exception as e: 785 | authresult += [{"Source": "User credential","Method": method , "Result": type(e).__name__ + ": " + str(e), "Content": pub_key}] 786 | # verify admin signature 787 | mntner_admin = [my_config["admin_mnt"]] 788 | try: 789 | authes_admin = await get_auth_method(mntner_admin,[]) 790 | for method,pub_key,mnt in authes_admin: 791 | try: 792 | if verify_signature(plaintext,pub_key,pub_key_pgp,raw_signature,method) == True: 793 | return mnt 794 | except Exception as e: 795 | authresult += [{"Source": "Admin credential", "Method": method , "Result": type(e).__name__ + ": " + str(e), "Content": pub_key}] 796 | except Exception as e: 797 | pass 798 | raise ValueError(yaml.dump(authresult, sort_keys=False,default_style='|')) 799 | except Exception as e: 800 | class AuthenticationError(type(e)): 801 | def init(m): 802 | super(m) 803 | AuthenticationError.__name__ = "AuthenticationError: " + type(e).__name__ 804 | raise AuthenticationError(str(e)) 805 | 806 | def get_err_page(paramaters,title,error,big_title="Server Error", tab_title = None,redirect=None): 807 | if tab_title == None: 808 | tab_title = title 809 | retstr = f""" 810 | 811 | 812 | 813 | 814 | { tab_title } 815 | 828 | 829 | 830 | 831 |
832 |
833 |

{ title }

834 |

{str(error).replace(chr(10),"
").replace(" "," ")}

835 |

836 |
\n""" 837 | paramaters = { valid_key: paramaters[valid_key] for valid_key in client_valid_keys if valid_key in paramaters} 838 | for k,v in paramaters.items(): 839 | if k in client_valid_keys_admin_only: 840 | continue 841 | if v == None: 842 | v = "" 843 | elif v == True: 844 | v = "on" 845 | retstr += f'\n' 846 | if redirect == None: 847 | retstr +='' 848 | else: 849 | retstr += f""" 850 | 851 | """ 852 | retstr +=""" 853 |
854 |
855 |
856 | 857 | 858 | """ 859 | return retstr 860 | 861 | def check_wg_key(wgkey): 862 | wg_keylen = 32 863 | if len(wgkey) > wg_keylen*2: 864 | raise ValueError(f"Wireguard key {wgkey} too long") 865 | base64_valid_chars = set(string.ascii_letters + string.digits + "+/=") 866 | if not set(wgkey).issubset(base64_valid_chars): 867 | raise ValueError(f"Wireguard key {wgkey} contains invalid character: {set(filter(lambda x:x not in base64_valid_chars,wgkey))}") 868 | key_raw = base64.b64decode(wgkey) 869 | if len(key_raw) != 32: 870 | raise ValueError(f"Wireguard key {wgkey} are not {wg_keylen} bytes len") 871 | 872 | def check_valid_ip_range(af,IPranges,ip,name,only_ip = True): 873 | if af == "IPv4": 874 | IPNet = IPv4Network 875 | IPInt = IPv4Interface 876 | elif af == "IPv6": 877 | IPNet = IPv6Network 878 | IPInt = IPv6Interface 879 | else: 880 | raise ValueError("Unknown af:",af) 881 | if ip == None or ip == "": 882 | raise ValueError(f"{str(ip)} is not a vaild IPv4 or IPv6 address") 883 | if only_ip: 884 | if "/" in ip: 885 | raise ValueError(ip + " is not a valid IPv4 or IPv6 address, you may need to remove /" + ip.split("/")[1]) 886 | if IPNet(ip).num_addresses != 1: 887 | raise ValueError(ip + " contains more than one IP") 888 | for iprange in IPranges: 889 | if IPNet(iprange,strict=False).supernet_of(IPInt(ip).network): 890 | return True 891 | raise ValueError(ip + " are not in " + name + " range: " + str(IPranges)) 892 | 893 | async def check_asn_ip(admin,mntner,asn,af,ip,only_ip=True): 894 | if af == "IPv4": 895 | IPNet = IPv4Network 896 | IPInt = IPv4Interface 897 | allowed = DN42_valid_ipv4s 898 | descr = "DN42 IPv4" 899 | elif af == "IPv6": 900 | IPNet = IPv6Network 901 | IPInt = IPv6Interface 902 | allowed = DN42_valid_ipv6s 903 | descr = "DN42 IPv6" 904 | else: 905 | raise ValueError("Unknown af:",af) 906 | check_valid_ip_range(af,IPranges=allowed,ip=ip,name=descr,only_ip=only_ip) 907 | peerIP_info = DN42whois.proc_data((await whois_query(ip))) 908 | originASN = ["nobody"] 909 | ipowner = [] 910 | if "origin" in peerIP_info: 911 | originASN = peerIP_info["origin"] 912 | if asn in peerIP_info["origin"]: 913 | return True 914 | if "mnt-by" in peerIP_info: 915 | if "DN42-MNT" in peerIP_info["mnt-by"]: 916 | peerIP_info["mnt-by"].remove("DN42-MNT") 917 | ipowner += peerIP_info["mnt-by"] 918 | if bool(set(mntner) & set(peerIP_info["mnt-by"])): 919 | return True 920 | if "admin-c" in peerIP_info: 921 | ipowner += peerIP_info["admin-c"] 922 | if bool(set(admin) & set(peerIP_info["admin-c"])): 923 | return True 924 | raise PermissionError("IP " + ip + f" owned by {originASN}({set(ipowner)}) instead of {asn}({set(admin + mntner)})") 925 | 926 | async def check_reg_paramater(paramaters,skip_check=None,git_pull=True,allow_invalid_as=False,allowed_custom_myip=[]): 927 | if (paramaters["hasIPV4"] or paramaters["hasIPV4LL"] or paramaters["hasIPV6"] or paramaters["hasIPV6LL"]) == False: 928 | raise ValueError("You can't peer without any IP.") 929 | if paramaters["peerASN"] == "AS" + paramaters["myASN"]: 930 | raise ValueError("You can't peer with my ASN.") 931 | try: 932 | mntner,admin = await get_info_from_asn(paramaters["peerASN"]) 933 | except FileNotFoundError as e: 934 | if allow_invalid_as: 935 | mntner,admin = [["DN42-MNT"],["BURBLE-DN42"]] 936 | else: 937 | raise e 938 | ######################### hasIPV4 939 | if paramaters["hasIPV4"]: 940 | if paramaters["myIPV4"] == None: 941 | raise NotImplementedError("Sorry, I don't have IPv4 address.") 942 | await check_asn_ip(admin,mntner,paramaters['peerASN'],"IPv4",paramaters["peerIPV4"],only_ip=True) 943 | if paramaters["myIPV4"] == my_paramaters["myIPV4"] or paramaters["myIPV4"] in allowed_custom_myip: 944 | pass 945 | else: 946 | if "/" not in paramaters["myIPV4"]: 947 | await check_asn_ip(admin,mntner,paramaters['peerASN'],"IPv4",paramaters["myIPV4"],only_ip=True) 948 | else: 949 | await check_asn_ip(admin,mntner,paramaters['peerASN'],"IPv4",paramaters["myIPV4"],only_ip=False) 950 | check_valid_ip_range("IPv4",[paramaters["myIPV4"]],paramaters["peerIPV4"],"allocated IPv4 to me") 951 | else: 952 | paramaters["peerIPV4"] = None 953 | ######################### hasIPV6 954 | if paramaters["hasIPV6"]: 955 | if paramaters["myIPV6"] == None: 956 | raise NotImplementedError("Sorry, I don't have IPv6 address.") 957 | await check_asn_ip(admin,mntner,paramaters['peerASN'],"IPv6",paramaters["peerIPV6"],only_ip=True) 958 | if paramaters["myIPV6"] == my_paramaters["myIPV6"] or paramaters["myIPV6"] in allowed_custom_myip: 959 | pass 960 | else: 961 | if "/" not in paramaters["myIPV6"]: 962 | await check_asn_ip(admin,mntner,paramaters['peerASN'],"IPv6",paramaters["myIPV6"],only_ip=True) 963 | else: 964 | await check_asn_ip(admin,mntner,paramaters['peerASN'],"IPv6",paramaters["myIPV6"],only_ip=False) 965 | check_valid_ip_range("IPv6",[paramaters["myIPV6"]],paramaters["peerIPV6"],"allocated IPv6 to me") 966 | else: 967 | paramaters["peerIPV6"] = None 968 | ######################### hasIPV4LL 969 | if paramaters["hasIPV4LL"]: 970 | if paramaters["myIPV4LL"] == None: 971 | raise NotImplementedError("Sorry, I don't have IPv4 link-local address.") 972 | check_valid_ip_range("IPv4",[valid_ipv4_lilo],paramaters["peerIPV4LL"],"link-local ipv4") 973 | check_valid_ip_range("IPv4",[valid_ipv4_lilo],paramaters["myIPV4LL"].split("/")[0],"link-local ipv4") 974 | paramaters["myIPV4LL"] = paramaters["myIPV4LL"].split("/")[0] + "/" + valid_ipv4_lilo.split("/")[1] 975 | else: 976 | paramaters["peerIPV4LL"] = None 977 | ######################### hasIPV6LL 978 | if paramaters["hasIPV6LL"]: 979 | if paramaters["myIPV6LL"] == None: 980 | raise NotImplementedError("Sorry, I don't have IPv6 link-local address.") 981 | check_valid_ip_range("IPv6",[valid_ipv6_lilo],paramaters["peerIPV6LL"],"link-local ipv6") 982 | check_valid_ip_range("IPv6",[valid_ipv6_lilo],paramaters["myIPV6LL"].split("/")[0],"link-local ipv6") 983 | paramaters["myIPV6LL"] = paramaters["myIPV6LL"].split("/")[0] + "/" + valid_ipv6_lilo.split("/")[1] 984 | else: 985 | paramaters["peerIPV6LL"] = None 986 | if paramaters["MP_BGP"]: 987 | if not (paramaters["hasIPV6"] or paramaters["hasIPV6LL"]): 988 | raise ValueError("Value Error. You need a IPv6 address to use multiprotocol BGP.") 989 | if not paramaters["Ext_Nh"]: 990 | if not (paramaters["hasIPV4"] or paramaters["hasIPV4LL"]): 991 | raise ValueError("Value Error. You need a IPv4 address to enable multiprotocol BGP unless you support extended next hop.") 992 | if paramaters["Ext_Nh"]: 993 | if not (paramaters["hasIPV6"] or paramaters["hasIPV6LL"]): 994 | raise ValueError("Value Error. You need a IPv6 address to use extended next hop.") 995 | if not paramaters["MP_BGP"]: 996 | raise ValueError("Value Error. You need enable multiprotocol BGP to use extended next hop.") 997 | if paramaters["allowExtNh"] == False: 998 | raise NotImplementedError("Sorry, I don't support extended next hop.") 999 | 1000 | if paramaters["peerWG_PS_Key"] == "": 1001 | paramaters["peerWG_PS_Key"] == None 1002 | if paramaters["customDevice"] == None: 1003 | if paramaters["hasHost"]: 1004 | if paramaters["peerHost"] == None and (my_paramaters["myHost"] == None): 1005 | raise ValueError("Sorry, I don't have a public IP so that your endpoint can't be null.") 1006 | if paramaters["peerHost"] == None or ":" not in paramaters["peerHost"]: 1007 | raise ValueError("Parse Error, Host must looks like address:port.") 1008 | hostaddr,port = paramaters["peerHost"].rsplit(":",1) 1009 | port = int(port) 1010 | if hostaddr[0] == "[" and hostaddr[-1] == "]": 1011 | hostaddr = hostaddr[1:-1] 1012 | elif ":" in hostaddr: 1013 | raise ValueError(f"Parse Error, IPv6 Address as endpoint, it should be like [{hostaddr}]:{port}.") 1014 | addrinfo = socket.getaddrinfo(hostaddr,port) 1015 | else: 1016 | paramaters["peerHost"] = None 1017 | peerKey = paramaters["peerWG_Pub_Key"] 1018 | if peerKey == None or len(peerKey) == 0: 1019 | raise ValueError('"Your WG Public Key" can\'t be null.') 1020 | if peerKey == paramaters["myWG_Pub_Key"]: 1021 | raise ValueError('You can\'t use my wireguard public key as your wireguard public key.') 1022 | check_wg_key(peerKey) 1023 | check_wg_key(paramaters["myWG_Pri_Key"]) 1024 | else: 1025 | paramaters["peerWG_Pub_Key"] = "" 1026 | paramaters["myWG_Pub_Key"] = "" 1027 | if git_pull: 1028 | RRstate_repo.pull() 1029 | conf_dir = wgconfpath + "/peerinfo" 1030 | if os.path.isdir(conf_dir): #Check this node hasn't peer with us before 1031 | for old_conf_file in os.listdir(conf_dir): 1032 | if old_conf_file.endswith(".yaml") and os.path.isfile(f"{conf_dir}/{old_conf_file}"): 1033 | if skip_check != None and old_conf_file[:-5] == str(skip_check): 1034 | continue 1035 | old_conf = yaml.load(open(f"{conf_dir}/{old_conf_file}").read(),Loader=yaml.SafeLoader) 1036 | if paramaters["peerIPV4"] != None and old_conf["peerIPV4"] == paramaters["peerIPV4"]: 1037 | raise FileExistsError(f'This IPv4 address {paramaters["peerIPV4"]} already exisis in "{old_conf_file}", please remove the peering first.') 1038 | if paramaters["peerIPV6"] != None and old_conf["peerIPV6"] == paramaters["peerIPV6"]: 1039 | raise FileExistsError(f'This IPv6 address {paramaters["peerIPV6"]} already exisis in "{old_conf_file}", please remove the peering first.') 1040 | if old_conf["peerHost"] != None and old_conf["peerHost"] == paramaters["peerHost"]: 1041 | raise FileExistsError(f'This endpoint "{paramaters["peerHost"]}" already exisis in "{old_conf_file}", please remove the peering first.') 1042 | if old_conf["peerASN"] != None and old_conf["peerASN"] == paramaters["peerASN"]: 1043 | raise FileExistsError(f'This ASN "{paramaters["peerASN"]}" already exisis in "{old_conf_file}", please remove the peering or update it first.') 1044 | return paramaters 1045 | 1046 | def replace_str(text,replace): 1047 | for k,v in replace.items(): 1048 | text = text.replace(k,v) 1049 | return text 1050 | 1051 | def indent2(text,fill): 1052 | if "\n" not in text: 1053 | return text 1054 | tail,body = text.split("\n",1) 1055 | return tail + "\n" + textwrap.indent(body,fill) 1056 | 1057 | def get_peeronly_filter(io,af,peerASN,myASN,noExport=True): 1058 | commadd = "" 1059 | if noExport == True: 1060 | commadd = "bgp_community.add((65535,65281));" 1061 | if io == "i": 1062 | if af == 4: 1063 | return textwrap.dedent(f"""\ 1064 | if is_valid_network() && bgp_path.last = {peerASN} then {{ 1065 | if (roa_check(dn42_roa, net, bgp_path.last) != ROA_VALID) then {{ 1066 | print "[dn42] ROA check failed from ",bgp_path.first , " ifname:", ifname ," for ", net, " ASN:", bgp_path.last; 1067 | reject; 1068 | }} 1069 | {commadd} 1070 | accept; 1071 | }} 1072 | reject; 1073 | """) 1074 | elif af == 6: 1075 | return textwrap.dedent(f"""\ 1076 | if is_valid_network_v6() && bgp_path.last = {peerASN} then {{ 1077 | if (roa_check(dn42_roa_v6, net, bgp_path.last) != ROA_VALID) then {{ 1078 | print "[dn42] ROA check failed from ",bgp_path.first , " ifname:", ifname ," for ", net, " ASN:", bgp_path.last; 1079 | reject; 1080 | }} 1081 | {commadd} 1082 | accept; 1083 | }} 1084 | reject; 1085 | """) 1086 | if io == "o": 1087 | if af == 4: 1088 | return textwrap.dedent(f"""\ 1089 | if (bgp_path.last = {myASN} && (roa_check(dn42_roa, net, bgp_path.last) = ROA_VALID)) || (is_self_net() && is_valid_network() && source ~ [RTS_STATIC, RTS_BGP]) then{{ 1090 | if is_valid_network() then{{ 1091 | {commadd} 1092 | accept; 1093 | }} 1094 | }} 1095 | reject; 1096 | """) 1097 | elif af == 6: 1098 | return textwrap.dedent(f"""\ 1099 | if (bgp_path.last = {myASN} && (roa_check(dn42_roa_v6, net, bgp_path.last) = ROA_VALID)) || (is_self_net_v6() && is_valid_network_v6() && source ~ [RTS_STATIC, RTS_BGP]) then{{ 1100 | if is_valid_network_v6() then{{ 1101 | {commadd} 1102 | accept; 1103 | }} 1104 | }} 1105 | reject; 1106 | """) 1107 | def get_transit_filter(io,af,peerASN,myASN,noExport=True): 1108 | commadd = "" 1109 | if noExport == True: 1110 | commadd = "bgp_community.add((65535,65281));" 1111 | if io == "i": 1112 | if af == 4: 1113 | return textwrap.dedent(f"""\ 1114 | {commadd} 1115 | if is_valid_network() && !is_self_net() then {{ 1116 | if (roa_check(dn42_roa, net, bgp_path.last) != ROA_VALID) then {{ 1117 | print "[dn42] ROA check failed from ",bgp_path.first , " ifname:", ifname ," for ", net, " ASN:", bgp_path.last; 1118 | reject; 1119 | }} 1120 | {commadd} 1121 | accept; 1122 | }} 1123 | reject; 1124 | """) 1125 | elif af == 6: 1126 | return textwrap.dedent(f"""\ 1127 | {commadd} 1128 | if is_valid_network_v6() && !is_self_net_v6() then {{ 1129 | if (roa_check(dn42_roa_v6, net, bgp_path.last) != ROA_VALID) then {{ 1130 | print "[dn42] ROA check failed from ",bgp_path.first , " ifname:", ifname ," for ", net, " ASN ", bgp_path.last; 1131 | reject; 1132 | }} 1133 | {commadd} 1134 | accept; 1135 | }} 1136 | reject; 1137 | """) 1138 | if io == "o": 1139 | if af == 4: 1140 | return textwrap.dedent(f"""\ 1141 | {commadd} 1142 | if is_valid_network() && source ~ [RTS_STATIC, RTS_BGP] then {{ 1143 | accept; 1144 | }} 1145 | reject; 1146 | """) 1147 | elif af == 6: 1148 | return textwrap.dedent(f"""\ 1149 | {commadd} 1150 | if is_valid_network_v6() && source ~ [RTS_STATIC, RTS_BGP] then {{ 1151 | accept; 1152 | }} 1153 | reject; 1154 | """) 1155 | def get_ix_filter(io,af,peerASN,myASN,noExport=True): 1156 | if io == "i": 1157 | if af == 4: 1158 | return textwrap.dedent(f"""\ 1159 | if (bgp_path.last = bgp_path.first && (roa_check(dn42_roa, net, bgp_path.last) = ROA_VALID)) then{{ 1160 | if is_valid_network() then{{ 1161 | accept; 1162 | }} 1163 | }} 1164 | reject; 1165 | """) 1166 | elif af == 6: 1167 | return textwrap.dedent(f"""\ 1168 | if (bgp_path.last = bgp_path.first && (roa_check(dn42_roa_v6, net, bgp_path.last) = ROA_VALID)) then{{ 1169 | if is_valid_network_v6() then{{ 1170 | accept; 1171 | }} 1172 | }} 1173 | reject; 1174 | """) 1175 | 1176 | def newConfig(paramaters,overwrite=False): 1177 | peerASN = int(paramaters["peerASN"][2:]) 1178 | peerKey = paramaters["peerWG_Pub_Key"] 1179 | peerPSK = paramaters["peerWG_PS_Key"] 1180 | peerContact = paramaters["peerContact"] 1181 | peerName = paramaters["peerName"] 1182 | peerID = paramaters["PeerID"] 1183 | peerHost = paramaters["peerHost"] 1184 | peerIPV4 = paramaters["peerIPV4"] 1185 | peerIPV6 = paramaters["peerIPV6"] 1186 | peerIPV4LL = paramaters["peerIPV4LL"] 1187 | peerIPV6LL = paramaters["peerIPV6LL"] 1188 | transitMode = paramaters["transitMode"] 1189 | MP_BGP = paramaters["MP_BGP"] 1190 | Ext_Nh = paramaters["Ext_Nh"] 1191 | myIPV4 = paramaters["myIPV4"] 1192 | myIPV6 = paramaters["myIPV6"] 1193 | myIPV4LL = paramaters["myIPV4LL"] 1194 | myIPV6LL = paramaters["myIPV6LL"] 1195 | myhost = paramaters["myHost"] 1196 | myasn = paramaters["myASN"][2:] 1197 | privkey = paramaters["myWG_Pri_Key"] 1198 | publkey = paramaters["myWG_Pub_Key"] 1199 | birdAddConf = paramaters["birdAddConf"] 1200 | mtu = paramaters["myWG_MTU"] 1201 | customDevice = paramaters["customDevice"] 1202 | customDeviceSetup = paramaters["customDeviceSetup"] 1203 | successdisplay = { "My ASN": paramaters["myASN"]} 1204 | peerV4use = None 1205 | myV4use = None 1206 | myV4useIP = None 1207 | if peerIPV4LL != None: 1208 | peerV4use = peerIPV4LL 1209 | myV4use = myIPV4LL 1210 | myV4useIP = myIPV4LL.split("/")[0] if myIPV4LL != None else None 1211 | successdisplay["IPv4 Link local"] = myV4useIP 1212 | if IPv4Address(peerV4use) == IPv4Address(myV4useIP): 1213 | raise ValueError("Your tunnel IPv6 link local address are conflicted with mine: " + str(IPv4Address(peerV4use))) 1214 | elif peerIPV4 != None: 1215 | peerV4use = peerIPV4 1216 | myV4use = myIPV4 1217 | myV4useIP = myIPV4.split("/")[0] if myIPV4 != None else None 1218 | successdisplay["DN42 IPv4"] = myV4useIP 1219 | if IPv4Address(peerV4use) == IPv4Address(myV4useIP): 1220 | raise ValueError("Your tunnel IPv4 address are conflicted with mine: " + str(IPv4Address(peerV4use))) 1221 | peerV6use =None 1222 | myV6use = None 1223 | myV6useIP =None 1224 | if peerIPV6LL != None: 1225 | peerV6use = peerIPV6LL 1226 | myV6use = myIPV6LL 1227 | myV6useIP = myIPV6LL.split("/")[0] if myIPV6LL != None else None 1228 | successdisplay["IPv6 Link local"] = myV6useIP 1229 | if IPv6Address(peerV6use) == IPv6Address(myV6useIP): 1230 | raise ValueError("Your tunnel IPv6 link local address are conflicted with mine: " + str(IPv6Address(peerV6use))) 1231 | elif peerIPV6 != None: 1232 | peerV6use = peerIPV6 1233 | myV6use = myIPV6 1234 | myV6useIP = myIPV6.split("/")[0] if myIPV6 != None else None 1235 | successdisplay["DN42 IPv6"] = myV6useIP 1236 | if IPv6Address(peerV6use) == IPv6Address(myV6useIP): 1237 | raise ValueError("Your tunnel IPv6 address are conflicted with mine: " + str(IPv6Address(peerV6use))) 1238 | if peerContact == None or len(peerContact) == 0: 1239 | raise ValueError('"Your Telegram ID or e-mail" can\'t be null.') 1240 | portlist = list(sorted(map(lambda x:int(x.split(".")[0]),filter(lambda x:x[-4:] == "yaml", os.listdir(wgconfpath + "/peerinfo"))))) 1241 | if peerID == None: 1242 | port_range = eval(my_config["wg_port_search_range"]) 1243 | for p in port_range: 1244 | try: 1245 | sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 1246 | sock.bind(("0.0.0.0", p)) 1247 | sock.close() 1248 | except Exception as e: 1249 | try: 1250 | sock.close() 1251 | except Exception as e: 1252 | pass 1253 | print(f"Try peer ID:{p} failed, reason:{e}") 1254 | continue 1255 | if p not in portlist: 1256 | peerID = p 1257 | print("Select peer ID:", peerID) 1258 | break 1259 | print(f"Try peer ID:{p} failed, reason: exists in portlist") 1260 | else: 1261 | peerID = int(peerID) 1262 | if peerID == None: 1263 | raise IndexError("PeerID not available, contact my to peer manually. ") 1264 | if peerID in portlist and overwrite == False: 1265 | raise IndexError("PeerID already exists.") 1266 | paramaters["PeerID"] = peerID 1267 | #if peerName == None: 1268 | peerContact_sess = peerContact 1269 | if peerContact_sess.startswith("https://t.me/"): 1270 | peerContact_sess = peerContact_sess.split("https://t.me/",1)[1] 1271 | if peerContact_sess.startswith("https://"): 1272 | peerContact_sess = peerContact_sess.split("https://",1)[1] 1273 | if "@" in peerContact_sess: 1274 | username, domain = peerContact_sess.split("@",1) 1275 | if username in ["dn42","admin","root","abuse","user","tg","telegram","irc","skype","git","whatsapp"]: 1276 | peerContact_sess = domain 1277 | elif username != "": 1278 | peerContact_sess = username + "_" + domain 1279 | peerName = str(int(peerID) % 10000).zfill(4) + peerContact_sess 1280 | peerName = peerName.replace("-","_") 1281 | peerName = re.sub(r"[^A-Za-z0-9_]+", '', peerName) 1282 | peerName = peerName[:10] 1283 | 1284 | if customDevice == None: 1285 | if_name = "dn42-" + peerName 1286 | else: 1287 | if_name = customDevice 1288 | customDeviceSetup = customDeviceSetup.replace( "%if_name" , if_name ) 1289 | if peerHost != None: 1290 | customDeviceSetup = customDeviceSetup.replace( "%peer_host" , peerHost ) 1291 | 1292 | 1293 | wgconf = textwrap.dedent(f"""\ 1294 | [Interface] 1295 | PrivateKey = { privkey } 1296 | ListenPort = { str(peerID) } 1297 | [Peer] 1298 | PublicKey = { peerKey } 1299 | AllowedIPs = { ", ".join( wg_allowed_ips ) } 1300 | """) 1301 | if peerHost != None and peerHost != "": 1302 | wgconf += f"Endpoint = { peerHost }\n" 1303 | if peerPSK != None and peerPSK != "": 1304 | wgconf += f"PresharedKey = { peerPSK }\n" 1305 | 1306 | wgsh = textwrap.dedent(f"""\ 1307 | ip link add dev {if_name} type wireguard 1308 | wg setconf {if_name} {wgconfpath}/{peerName}.conf 1309 | """) 1310 | setupsh = textwrap.dedent(f"""\ 1311 | ip link set {if_name} up 1312 | """) 1313 | if int(mtu) > 0: 1314 | setupsh += textwrap.dedent(f"""\ 1315 | ip link set mtu {mtu} dev {if_name} 1316 | """) 1317 | if use_speed_limit: 1318 | setupsh += f"wondershaper {if_name} $WG_SPEED_LIMIT $WG_SPEED_LIMIT || true\n" 1319 | 1320 | birdPeerV4 = peerV4use 1321 | birdMyV4 = myV4useIP 1322 | birdPeerV6 = peerV6use 1323 | birdMyV6 = myV6useIP 1324 | if peerV4use != None: 1325 | if peerV6use != None and MP_BGP == True and Ext_Nh == True: 1326 | birdPeerV4 = None 1327 | birdMyV4 = None 1328 | if Ext_Nh == False: 1329 | if "/" in myV4use: 1330 | setupsh += f"ip addr add {myV4use} dev {if_name} scope link\n" 1331 | else: 1332 | setupsh += f"ip addr add {myV4useIP} peer {peerIPV4} dev {if_name} scope link\n" 1333 | 1334 | if peerV6use != None: 1335 | if "/" in myV6use: 1336 | setupsh += f"ip addr add {myV6use} dev {if_name} scope link\n" 1337 | else: 1338 | setupsh += f"ip addr add {myV6useIP} peer {peerV6use} dev {if_name}\n" 1339 | setupsh += f"ip route add {peerV6use}/128 src {myV6useIP} dev {if_name}\n" 1340 | 1341 | birdconf = "" 1342 | channel4 = "" 1343 | channel6 = "" 1344 | filter4i = "" 1345 | filter4e = "" 1346 | filter6i = "" 1347 | filter6e = "" 1348 | 1349 | if Ext_Nh == True: 1350 | channel4 += "extended next hop on;\n" 1351 | if transitMode == "Regular" or transitMode == "Private Peering": 1352 | pass 1353 | elif transitMode == "PeerOnly" or transitMode == "Public Peering": 1354 | filter4i += get_peeronly_filter("i",4,peerASN,myasn,True) 1355 | filter6i += get_peeronly_filter("i",6,peerASN,myasn,True) 1356 | filter4e += get_peeronly_filter("o",4,peerASN,myasn,True) 1357 | filter6e += get_peeronly_filter("o",6,peerASN,myasn,True) 1358 | elif transitMode == "Upstream" or transitMode == "Transit Providers": 1359 | filter4i += get_transit_filter("i",4,peerASN,myasn,True) 1360 | filter6i += get_transit_filter("i",6,peerASN,myasn,True) 1361 | filter4e += get_peeronly_filter("o",4,peerASN,myasn,False) 1362 | filter6e += get_peeronly_filter("o",6,peerASN,myasn,False) 1363 | elif transitMode == "Downstream" or transitMode == "Customer": 1364 | filter4i += get_peeronly_filter("i",4,peerASN,myasn,False) 1365 | filter6i += get_peeronly_filter("i",6,peerASN,myasn,False) 1366 | filter4e += get_transit_filter("o",4,peerASN,myasn,True) 1367 | filter6e += get_transit_filter("o",6,peerASN,myasn,True) 1368 | elif transitMode == "IX": 1369 | filter4i += get_ix_filter("i",4,peerASN,myasn,False) 1370 | filter6i += get_ix_filter("i",6,peerASN,myasn,False) 1371 | filter4e += get_peeronly_filter("o",4,peerASN,myasn,False) 1372 | filter6e += get_peeronly_filter("o",6,peerASN,myasn,False) 1373 | else: 1374 | raise ValueError("Unknow transitMode: " + transitMode) 1375 | if "chan4" in birdAddConf: 1376 | channel4 += "\n".join(birdAddConf["chan4"]) + "\n" 1377 | if "chan6" in birdAddConf: 1378 | channel6 += "\n".join(birdAddConf["chan6"]) + "\n" 1379 | if "filter4i" in birdAddConf: 1380 | filter4i += "\n".join(birdAddConf["filter4i"]) + "\n" 1381 | if "filter6i" in birdAddConf: 1382 | filter4e += "\n".join(birdAddConf["filter6i"]) + "\n" 1383 | if "filter4e" in birdAddConf: 1384 | filter6i += "\n".join(birdAddConf["filter4e"]) + "\n" 1385 | if "filter6e" in birdAddConf: 1386 | filter6e += "\n".join(birdAddConf["filter6e"]) + "\n" 1387 | ######################### 1388 | if filter4i != "": 1389 | channel4 += textwrap.dedent(f"""\ 1390 | import filter{{ 1391 | { indent2(filter4i," ") } 1392 | }}; 1393 | """) 1394 | if filter4e != "": 1395 | channel4 += textwrap.dedent(f"""\ 1396 | export filter{{ 1397 | { indent2(filter4e," ") } 1398 | }}; 1399 | """) 1400 | if filter6i != "": 1401 | channel6 += textwrap.dedent(f"""\ 1402 | import filter{{ 1403 | { indent2(filter6i," ") } 1404 | }}; 1405 | """) 1406 | if filter6e != "": 1407 | channel6 += textwrap.dedent(f"""\ 1408 | export filter{{ 1409 | { indent2(filter6e," ") } 1410 | }}; 1411 | """) 1412 | if channel4 != "": 1413 | channel4 = textwrap.dedent(f"""\ 1414 | ipv4 {{ 1415 | { indent2(channel4," ") } 1416 | }}; 1417 | """) 1418 | channel4 = indent2(channel4," ") 1419 | if channel6 != "": 1420 | channel6 = textwrap.dedent(f"""\ 1421 | ipv6 {{ 1422 | { indent2(channel6," ") } 1423 | }}; 1424 | """) 1425 | channel6 = indent2(channel6," ") 1426 | 1427 | if MP_BGP == True: 1428 | if birdPeerV6 != None: 1429 | birdconf += textwrap.dedent(f"""\ 1430 | protocol bgp dn42_{peerName}_v6 from dnpeers {{ 1431 | source address {birdMyV6}; 1432 | neighbor {birdPeerV6} % '{if_name}' as {peerASN}; 1433 | {channel4} 1434 | {channel6} 1435 | }}; 1436 | """) 1437 | else: 1438 | if birdPeerV4 != None: 1439 | birdconf += textwrap.dedent(f"""\ 1440 | protocol bgp dn42_{peerName}_v4 from dnpeers {{ 1441 | source address {birdMyV4}; 1442 | neighbor {birdPeerV4} % '{if_name}' as {peerASN}; 1443 | {channel4} 1444 | ipv6 {{ 1445 | import none; 1446 | export none; 1447 | }}; 1448 | }}; 1449 | """) 1450 | if birdPeerV6 != None: 1451 | birdconf += textwrap.dedent(f"""\ 1452 | protocol bgp dn42_{peerName}_v6 from dnpeers {{ 1453 | source address {birdMyV6}; 1454 | neighbor {birdPeerV6} % '{if_name}' as {peerASN}; 1455 | ipv4 {{ 1456 | import none; 1457 | export none; 1458 | }}; 1459 | {channel6} 1460 | }}; 1461 | """) 1462 | 1463 | paramaters["peerName"] = peerName 1464 | paramaters_save = { valid_key: paramaters[valid_key] for valid_key in client_valid_keys if valid_key in paramaters} 1465 | paramaters_save["peer_signature"] = "" 1466 | paramaters_save["peer_plaintext"] = "" 1467 | paramaters_save["peerName"] = peerName 1468 | 1469 | devsh = "\n".join([ "#!/bin/bash" , wgsh , setupsh]) 1470 | retconfig = { 1471 | f"{wgconfpath}/{peerName}.conf": wgconf, 1472 | f"{wgconfpath}/peerinfo/{peerID}.yaml": yaml.dump(paramaters_save), 1473 | f"{bdconfpath}/{peerName}.conf": birdconf, 1474 | f"{wgconfpath}/{peerName}.sh": devsh, 1475 | } 1476 | 1477 | if customDevice != None: 1478 | devsh = "\n".join([ "#!/bin/bash" , customDeviceSetup , setupsh]) 1479 | del retconfig[f"{wgconfpath}/{peerName}.conf"] 1480 | retconfig[f"{wgconfpath}/{peerName}.sh"] = devsh 1481 | 1482 | return { 1483 | "config":retconfig, 1484 | "if_name": if_name, 1485 | "peerName": peerName, 1486 | "paramaters": paramaters, 1487 | "paramaters_save": paramaters_save, 1488 | "successdisplay": successdisplay, 1489 | } 1490 | 1491 | def initDevice(): 1492 | print_and_exec(f"ip link add dn42-dummy type dummy") 1493 | print_and_exec(f"ip link set up dn42-dummy") 1494 | print_and_exec(f'ip addr add { my_paramaters["myIPV4"] } dev dn42-dummy') 1495 | print_and_exec(f'ip addr add { my_paramaters["myIPV6"] } dev dn42-dummy') 1496 | for thesh in filter(lambda x:x[-3:] == ".sh", sorted(os.listdir(wgconfpath))): 1497 | print_and_exec(wgconfpath + "/" + thesh) 1498 | def syncWG(): 1499 | interval = my_config["reset_wgconf_interval"] 1500 | print("Sync WG interval:",interval) 1501 | if interval <= 0: 1502 | return 1503 | conf_dir = wgconfpath + "/peerinfo" 1504 | while True: 1505 | time.sleep(interval) 1506 | if os.path.isdir(conf_dir): #Check this node hasn't peer with us before 1507 | for conf_file in sorted(os.listdir(conf_dir)): 1508 | if conf_file.endswith(".yaml") and os.path.isfile(f"{conf_dir}/{conf_file}"): 1509 | conf = yaml.load(open(f"{conf_dir}/{conf_file}").read(),Loader=yaml.SafeLoader) 1510 | if conf["customDevice"] != None: 1511 | continue 1512 | if conf["peerWG_Pub_Key"] == None: 1513 | continue 1514 | if conf["peerHost"] == None: 1515 | continue 1516 | ifname = "dn42-" + conf["peerName"] 1517 | peerpubkey = conf["peerWG_Pub_Key"] 1518 | peerendpoint = conf["peerHost"] 1519 | print_and_exec(f"wg set {shlex.quote(ifname)} peer {shlex.quote(peerpubkey)} endpoint {shlex.quote(peerendpoint)}") 1520 | 1521 | def print_and_exec(command): 1522 | if use_remote_command != "": 1523 | command = f'echo {shlex.quote(command + "; exit")} | nc {use_remote_command}' 1524 | print(command) 1525 | os.system(command) 1526 | time.sleep(1) 1527 | 1528 | def print_and_rm(file): 1529 | print("rm " + file) 1530 | try: 1531 | os.remove(file) 1532 | except Exception as e: 1533 | print(e) 1534 | 1535 | def print_and_rmrf(tree): 1536 | print("rm -rf " + tree) 1537 | shutil.rmtree(tree) 1538 | 1539 | def saveConfig(new_config,sync=True): 1540 | if sync: 1541 | RRstate_repo.pull() 1542 | runsh = False 1543 | for path,content in new_config["config"].items(): 1544 | print("================================") 1545 | print(path) 1546 | print(content) 1547 | fileparent = pathlib.Path(path).parent.absolute() 1548 | if not os.path.isdir(fileparent): 1549 | os.makedirs(fileparent, mode=0o700 , exist_ok=True) 1550 | with open(path,"w") as conffd: 1551 | conffd.write(content) 1552 | if content.startswith("#!"): 1553 | os.chmod(path, 0o755) 1554 | runsh = True 1555 | print("================================") 1556 | peerName = new_config["peerName"] 1557 | if sync: 1558 | RRstate_repo.push(f'{node_name} peer add {peerName}') 1559 | if runsh: 1560 | print_and_exec(f"{wgconfpath}/{peerName}.sh") 1561 | print_and_exec("birdc configure") 1562 | return None 1563 | 1564 | def deleteConfig(peerID,peerName,deleteDevice=True,sync=True): 1565 | if sync: 1566 | RRstate_repo.pull() 1567 | print_and_rm(f"{wgconfpath}/{peerName}.conf") 1568 | print_and_rm(f"{wgconfpath}/{peerName}.sh") 1569 | print_and_rm(f"{wgconfpath}/peerinfo/{peerID}.yaml") 1570 | print_and_rm(f"{bdconfpath}/{peerName}.conf") 1571 | if sync: 1572 | RRstate_repo.push(f'{node_name} peer del {peerName}') 1573 | if deleteDevice: 1574 | if_name = "dn42-" + peerName 1575 | print_and_exec(f"ip link del {if_name}") 1576 | print_and_exec("birdc configure") 1577 | return None 1578 | 1579 | def updateConfig(peerID,peerName,new_config,deleteDevice=True,sync=True): 1580 | if sync: 1581 | RRstate_repo.pull() 1582 | deleteConfig(peerID,peerName,deleteDevice=deleteDevice,sync=False) 1583 | saveConfig(new_config,sync=False) 1584 | if sync: 1585 | RRstate_repo.push(f'{node_name} peer update {peerName}') 1586 | 1587 | def get_key_default(Dictn,key,default): 1588 | if key in Dictn and Dictn[key] != "" and Dictn[key] != None: 1589 | ValType = type(default) 1590 | if ValType == bool and type(Dictn[key]) == str: 1591 | return Dictn[key].lower() == "true" or Dictn[key].lower() == "on" 1592 | if (ValType == dict or ValType == list) and type(Dictn[key]) == str: 1593 | return json.loads(type(Dictn[key])) 1594 | elif default != None: 1595 | return ValType(Dictn[key]) 1596 | else: 1597 | return Dictn[key] 1598 | return default 1599 | 1600 | def qsd2d(qsd): 1601 | return {k:v[0] for k,v in qsd.items()} 1602 | 1603 | def isFormTrue(inp): 1604 | if inp == "on" or inp == "True" or inp == True: 1605 | return True 1606 | return False 1607 | 1608 | def try_get_param(peerID,key,default=""): 1609 | try: 1610 | peerInfo = yaml.load(open(wgconfpath + "/peerinfo/" + str(peerID) + ".yaml").read(),Loader=yaml.SafeLoader) 1611 | except FileNotFoundError as e: 1612 | return default 1613 | if key in peerInfo: 1614 | return peerInfo[key] 1615 | return default 1616 | 1617 | def get_paramaters(paramaters,default_params=my_paramaters,isAdmin=False): 1618 | action = get_key_default(paramaters,"action","OK") 1619 | paramaters = { valid_key: paramaters[valid_key] for valid_key in client_valid_keys if (valid_key in paramaters) } 1620 | if not isAdmin: 1621 | for k in client_valid_keys_admin_only: 1622 | if k in paramaters: 1623 | del paramaters[k] 1624 | paramaters["peer_plaintext"] = get_key_default(paramaters,"peer_plaintext","") 1625 | paramaters["peer_pub_key_pgp"] = get_key_default(paramaters,"peer_pub_key_pgp","") 1626 | paramaters["peer_signature"] = get_key_default(paramaters,"peer_signature","") 1627 | paramaters["peerASN"] = get_key_default(paramaters,"peerASN",None) 1628 | paramaters["hasIPV4"] = get_key_default(paramaters,"hasIPV4",False) 1629 | paramaters["peerIPV4"] = get_key_default(paramaters,"peerIPV4",None) 1630 | paramaters["hasIPV4LL"] = get_key_default(paramaters,"hasIPV4LL",False) 1631 | paramaters["peerIPV4LL"] = get_key_default(paramaters,"peerIPV4LL",None) 1632 | paramaters["hasIPV6"] = get_key_default(paramaters,"hasIPV6",False) 1633 | paramaters["peerIPV6"] = get_key_default(paramaters,"peerIPV6",None) 1634 | paramaters["hasIPV6LL"] = get_key_default(paramaters,"hasIPV6LL",False) 1635 | paramaters["peerIPV6LL"] = get_key_default(paramaters,"peerIPV6LL",None) 1636 | paramaters["myIPV4"] = get_key_default(paramaters,"myIPV4",default_params["myIPV4"]) if default_params["myIPV4"] != "" else "" 1637 | paramaters["myIPV6"] = get_key_default(paramaters,"myIPV6",default_params["myIPV6"]) if default_params["myIPV6"] != "" else "" 1638 | paramaters["myIPV4LL"] = get_key_default(paramaters,"myIPV4LL",default_params["myIPV4LL"]) if default_params["myIPV4LL"] != "" else "" 1639 | paramaters["myIPV6LL"] = get_key_default(paramaters,"myIPV6LL",default_params["myIPV6LL"]) if default_params["myIPV6LL"] != "" else "" 1640 | paramaters["myWG_Pri_Key"] = get_key_default(paramaters,"myWG_Pri_Key",my_config["myWG_Pri_Key"]) 1641 | paramaters["myWG_MTU"] = get_key_default(paramaters,"myWG_MTU",1280) 1642 | paramaters["transitMode"] = get_key_default(paramaters,"transitMode","Regular") 1643 | paramaters["customDevice"] = get_key_default(paramaters,"customDevice",None) 1644 | paramaters["customDeviceSetup"]= get_key_default(paramaters,"customDeviceSetup","") 1645 | paramaters["MP_BGP"] = get_key_default(paramaters,"MP_BGP",False) 1646 | paramaters["Ext_Nh"] = get_key_default(paramaters,"Ext_Nh",False) 1647 | paramaters["hasHost"] = get_key_default(paramaters,"hasHost",False) 1648 | paramaters["peerHost"] = get_key_default(paramaters,"peerHost",None) 1649 | paramaters["peerWG_Pub_Key"] = get_key_default(paramaters,"peerWG_Pub_Key","") 1650 | paramaters["peerWG_PS_Key"] = get_key_default(paramaters,"peerWG_PS_Key","") 1651 | paramaters["peerContact"] = get_key_default(paramaters,"peerContact","") 1652 | paramaters["peerName"] = get_key_default(paramaters,"peerName",None) 1653 | paramaters["PeerID"] = get_key_default(paramaters,"PeerID",None) 1654 | paramaters["birdAddConf"] = get_key_default(paramaters,"birdAddConf",{}) 1655 | 1656 | paramaters["myWG_Pub_Key"] = wgpri2pub(paramaters["myWG_Pri_Key"]) 1657 | #print(yaml.safe_dump(paramaters)) 1658 | paramaters = {**default_params,**paramaters} 1659 | return action , paramaters 1660 | 1661 | def remove_sensitive(paramaters): 1662 | ret = {} 1663 | for k,v in paramaters.items(): 1664 | if k in client_valid_keys_admin_only: 1665 | continue 1666 | ret[k] = v 1667 | return ret 1668 | 1669 | async def action(paramaters): 1670 | action , paramaters = get_paramaters(paramaters) 1671 | try: 1672 | try: 1673 | if paramaters["PeerID"] != None: 1674 | if int(paramaters["PeerID"]) < 0: 1675 | raise ValueError("Invalid PeerID") 1676 | except Exception as e: 1677 | filename = paramaters["PeerID"] + ".yaml" 1678 | paramaters["PeerID"] = None 1679 | raise FileNotFoundError(errno.ENOENT, os.strerror(errno.ENOENT), filename) 1680 | if action=="OK": 1681 | if paramaters["peerASN"] == None: 1682 | paramaters["hasIPV4"] = True 1683 | paramaters["hasIPV6LL"] = True 1684 | paramaters["MP_BGP"] = True 1685 | paramaters["Ext_Nh"] = False 1686 | paramaters["hasHost"] = True 1687 | else: 1688 | try: 1689 | peerInfo = yaml.load(open(wgconfpath + "/peerinfo/" + paramaters["PeerID"] + ".yaml").read(),Loader=yaml.SafeLoader) 1690 | _, peerInfo = get_paramaters(peerInfo,isAdmin=True) 1691 | paramaters["myWG_Pub_Key"] = peerInfo["myWG_Pub_Key"] 1692 | except Exception as e: 1693 | pass 1694 | return 200, await get_html(paramaters,action=action,peerSuccess=False) 1695 | if action == "Show": 1696 | if paramaters["PeerID"] == None: 1697 | raise FileNotFoundError(errno.ENOENT, os.strerror(errno.ENOENT), "") 1698 | try: 1699 | peerInfo = yaml.load(open(wgconfpath + "/peerinfo/" + paramaters["PeerID"] + ".yaml").read(),Loader=yaml.SafeLoader) 1700 | except FileNotFoundError as e: 1701 | e.filename = paramaters["PeerID"] + ".yaml" 1702 | raise e 1703 | peerInfo = { valid_key: peerInfo[valid_key] for valid_key in client_valid_keys if valid_key in peerInfo } 1704 | _, peerInfo = get_paramaters(peerInfo,isAdmin=True) 1705 | if bool(paramaters["peer_plaintext"]): 1706 | del peerInfo["peer_plaintext"] 1707 | if bool(paramaters["peer_pub_key_pgp"]): 1708 | del peerInfo["peer_pub_key_pgp"] 1709 | if bool(paramaters["peer_signature"]): 1710 | del peerInfo["peer_signature"] 1711 | paramaters = {**paramaters,**peerInfo} 1712 | return 200, await get_html(paramaters,action=action,peerSuccess=True) 1713 | # Check ASN is valid for following action 1714 | if paramaters["peerASN"] == None: 1715 | raise ValueError("peerASN can't be null.") 1716 | if paramaters["peerASN"].startswith("AS"): 1717 | check_num = int(paramaters['peerASN'][2:]) 1718 | else: 1719 | check_num = int(paramaters['peerASN']) 1720 | paramaters["peerASN"] = "AS" + paramaters["peerASN"] 1721 | #Actions need ASN 1722 | if action=="Delete" or action=="Update": 1723 | mntner = await verify_user_signature(paramaters["peerASN"],paramaters["peer_plaintext"],paramaters["peer_pub_key_pgp"],paramaters["peer_signature"]) 1724 | if paramaters["peerHost"] == peerHostDisplayText: 1725 | paramaters["peerHost"] = try_get_param(paramaters["PeerID"],"peerHost","") 1726 | try: 1727 | peerInfo = yaml.load(open(wgconfpath + "/peerinfo/" + paramaters["PeerID"] + ".yaml").read(),Loader=yaml.SafeLoader) 1728 | except FileNotFoundError as e: 1729 | e.filename = paramaters["PeerID"] + ".yaml" 1730 | raise e 1731 | if peerInfo["peerASN"] != paramaters["peerASN"]: 1732 | raise PermissionError("Peer ASN not match") 1733 | if action=="Delete": 1734 | deleteConfig(peerInfo["PeerID"],peerInfo["peerName"],deleteDevice=peerInfo["customDevice"]==None) 1735 | paramaters["PeerID"] = None 1736 | return 200, get_err_page(paramaters,"Profile deleted:" ,yaml.dump(remove_sensitive(peerInfo),sort_keys=False).replace("\n","
"),big_title="Success!") 1737 | elif action=="Update": 1738 | del paramaters["myWG_Pri_Key"] 1739 | del paramaters["myWG_Pub_Key"] 1740 | _, peerInfo = get_paramaters(peerInfo,isAdmin=True) 1741 | paramaters_in = { valid_key: paramaters[valid_key] for valid_key in client_valid_keys if (valid_key in paramaters) } 1742 | for k in client_valid_keys_admin_only: 1743 | if k in paramaters_in: 1744 | del paramaters_in[k] 1745 | paramaters = {**peerInfo,**paramaters_in} 1746 | paramaters = await check_reg_paramater(paramaters,skip_check=paramaters["PeerID"]) 1747 | new_config = newConfig(paramaters,overwrite=True) 1748 | if mntner == my_config["admin_mnt"]: 1749 | paramaters["peer_pub_key_pgp"] = "" 1750 | updateConfig(peerInfo["PeerID"],peerInfo["peerName"],new_config,deleteDevice=peerInfo["customDevice"]==None,sync=True) 1751 | return 200, get_err_page(paramaters,"Profile updated:" ,yaml.dump(remove_sensitive(new_config["paramaters_save"]),sort_keys=False).replace("\n","
"),big_title="Success!") 1752 | elif action=="Get Signature": 1753 | return 200, await get_signature_html(dn42repo_base,paramaters) 1754 | elif action == "Register": 1755 | mntner = await verify_user_signature(paramaters["peerASN"],paramaters["peer_plaintext"],paramaters["peer_pub_key_pgp"],paramaters["peer_signature"]) 1756 | if mntner not in my_config["admin_mnt"]: 1757 | paramaters["PeerID"] = None 1758 | if my_config["registerAdminOnly"]: 1759 | raise PermissionError("Guest registration is not enabled at this node, please contact admin.") 1760 | paramaters = await check_reg_paramater(paramaters) 1761 | new_config = newConfig(paramaters) 1762 | if mntner == my_config["admin_mnt"]: 1763 | paramaters["peer_pub_key_pgp"] = "" 1764 | paramaters = new_config["paramaters"] 1765 | saveConfig(new_config) 1766 | if paramaters["myHost"] == None: 1767 | myHostDisplay = paramaters["myHostDisplay"] 1768 | else: 1769 | myHostDisplay = paramaters["myHost"] + ":" + str(paramaters["PeerID"]) 1770 | myInfo = { **new_config["successdisplay"] , 1771 | "Endpoint Address":myHostDisplay, 1772 | "My WG Public Key":paramaters["myWG_Pub_Key"], 1773 | "My Contact": paramaters["myContact"] 1774 | } 1775 | return 200, get_err_page(paramaters, f'Your PeerID is: { paramaters["PeerID"] }
My info:',yaml.dump(remove_sensitive(myInfo), sort_keys=False),big_title="Peer Success!", tab_title = "Success!",redirect=my_config["register_redirect"]) 1776 | return 400, get_err_page(paramaters,"400 - Bad Request",ValueError("Unknow action" + str(action))) 1777 | except Exception as e: 1778 | title = type(e).__name__ 1779 | errcode = 400 1780 | if type(e) == FileNotFoundError: 1781 | title = "404 - File or directory not found." 1782 | errorcode = 404 1783 | e = "The resource you are looking for might have been removed, had its name changed, or is temporarily unavailable.\n " + str(e.filename) 1784 | #return errcode, get_err_page(paramaters,title,traceback.format_exc()) 1785 | print(traceback.format_exc()) 1786 | return errcode, get_err_page(paramaters,title,e) 1787 | 1788 | ipv4s = [ipaddress.ip_network("0.0.0.0/0")] 1789 | ipv6s = [ipaddress.ip_network("::/0")] 1790 | 1791 | 1792 | def get_ip(r): 1793 | rip = ipaddress.ip_address( r.remote_ip ) 1794 | if type(rip) == ipaddress.IPv4Address: 1795 | clist = ipv4s 1796 | else: 1797 | clist = ipv6s 1798 | for c in clist: 1799 | if rip in c and "CF-Connecting-IP" in r.headers: 1800 | return r.headers["CF-Connecting-IP"] 1801 | return r.remote_ip 1802 | 1803 | 1804 | class actionHandler(tornado.web.RequestHandler): 1805 | def __init__(self, *args, **kwargs): 1806 | super(actionHandler, self).__init__(*args, **kwargs) 1807 | def set_default_headers(self, *args, **kwargs): 1808 | # Just for fun, pretend I am a php server 1809 | self.set_header('server','Microsoft-IIS/7.5') 1810 | self.set_header('x-powered-by','PHP/5.4.2') 1811 | async def get(self, *args, **kwargs): 1812 | paramaters = { k: self.get_argument(k) for k in self.request.arguments } 1813 | print("GET " + self.request.uri + f' ({get_ip(self.request)}) ' , paramaters) 1814 | code, ret = await action(paramaters) 1815 | self.set_status(code) 1816 | self.write(ret) 1817 | async def post(self, *args, **kwargs): 1818 | paramaters = { k: self.get_argument(k) for k in self.request.arguments } 1819 | print("POST " + self.request.uri + f' ({get_ip(self.request)}) ' , paramaters) 1820 | code, ret = await action(paramaters) 1821 | self.set_status(code) 1822 | self.write(ret) 1823 | 1824 | nfpage = """ 1825 | 1826 | 1827 | 1828 | 1829 | 404 - File or directory not found. 1830 | 1843 | 1844 | 1845 | 1846 |
1847 |
1848 |

404 - File or directory not found.

1849 |

The resource you are looking for might have been removed, had its name changed, or is temporarily unavailable.

1850 |
1851 |
1852 | 1853 | 1854 | """ 1855 | 1856 | class My404Handler(tornado.web.RequestHandler): 1857 | # Override prepare() instead of get() to cover all possible HTTP methods. 1858 | def prepare(self): 1859 | self.set_status(404) 1860 | self.write(nfpage) 1861 | def post(self, *args, **kwargs): 1862 | pass 1863 | def get(self, *args, **kwargs): 1864 | pass 1865 | 1866 | if __name__ == '__main__': 1867 | try: 1868 | ipv4s = [ipaddress.ip_network(n) for n in requests.get("https://www.cloudflare.com/ips-v4", verify=False, timeout=3).text.split("\n")] 1869 | ipv6s = [ipaddress.ip_network(n) for n in requests.get("https://www.cloudflare.com/ips-v6", verify=False, timeout=3).text.split("\n")] 1870 | except Exception as e: 1871 | print(traceback.format_exc()) 1872 | if my_config["urlprefix"] == "" or my_config["urlprefix"] == "/": 1873 | url_prefix = "" 1874 | url_prefix_pre = "" 1875 | elif my_config["urlprefix"][-1] == "/": 1876 | url_prefix = my_config["urlprefix"] 1877 | url_prefix_pre = my_config["urlprefix"][:-1] 1878 | else: 1879 | url_prefix = my_config["urlprefix"] + "/" 1880 | url_prefix_pre = my_config["urlprefix"] 1881 | app = tornado.web.Application(handlers=[ 1882 | ('/' + url_prefix, actionHandler), 1883 | ('/' + url_prefix + 'action_page.php', actionHandler), 1884 | ('/' + url_prefix_pre, tornado.web.RedirectHandler, {"url": url_prefix}), 1885 | ('/' + url_prefix_pre, tornado.web.RedirectHandler, {"url": url_prefix}), 1886 | ('/', tornado.web.RedirectHandler, {"url": url_prefix}), 1887 | (r"(.*)", My404Handler), 1888 | ]) 1889 | if my_config["init_device"] == True: 1890 | initDevice() 1891 | syncwg = multiprocessing.Process(target=syncWG, args=()) 1892 | syncwg.start() 1893 | server = tornado.httpserver.HTTPServer(app, ssl_options=my_config["ssl_options"] ) 1894 | server.listen(my_config["listen_port"],my_config["listen_host"]) 1895 | print("Done. Start serving http(s) on " + my_config["listen_host"]+ ":" + str(my_config["listen_port"])) 1896 | tornado.ioloop.IOLoop.current().start() 1897 | -------------------------------------------------------------------------------- /DN42GIT.py: -------------------------------------------------------------------------------- 1 | from git import Repo 2 | 3 | class DN42GIT(): 4 | def __init__(self, gitpath): 5 | if gitpath != None: 6 | self.repo = Repo(gitpath) 7 | else: 8 | self.repo = None 9 | def pull(self): 10 | if self.repo != None: 11 | self.repo.remotes.origin.pull() 12 | def push(self,msg): 13 | if self.repo != None: 14 | self.repo.git.add(all=True) 15 | self.repo.index.commit(msg) 16 | self.repo.remotes.origin.push() -------------------------------------------------------------------------------- /DN42whois.py: -------------------------------------------------------------------------------- 1 | import re 2 | import os 3 | import time 4 | import errno 5 | import asyncio 6 | import tornado 7 | import traceback 8 | from git import Repo 9 | import ipaddress 10 | from tornado.httpclient import HTTPClientError 11 | from urllib.parse import urlparse 12 | class whois(): 13 | def __init__(self, proto, url): 14 | self.proto = proto 15 | if proto == "tcp": 16 | self.whois = tcp_whois(url) 17 | elif proto == "git": 18 | self.whois = git_whois(url,"dn42data",600) 19 | elif proto == "http" or proto == "https": 20 | if not url.startswith("http"): 21 | url = proto + "://" + url 22 | self.whois = http_whois(url) 23 | else: 24 | raise Exception("Support tcp or git only") 25 | async def query(self,query): 26 | return await self.whois.query(query) 27 | 28 | prefixes = {"as-block","as-set","aut-num","dns","inet6num","inetnum","key-cert","mntner","organisation","person","registry","role","route","route6","route-set","schema","tinc-key"} 29 | 30 | def remove_prefix(query): 31 | if "/" in query: 32 | prefix, body = query.split("/",1) 33 | if prefix in prefixes: 34 | query = body 35 | return query 36 | 37 | ngc = """Copy from https://lantian.pub/article/modify-website/serve-dn42-whois-with-nginx.lantian 38 | rewrite "^/([0-9]{1})$" /aut-num/AS424242000$1 last; 39 | rewrite "^/([0-9]{2})$" /aut-num/AS42424200$1 last; 40 | rewrite "^/([0-9]{3})$" /aut-num/AS4242420$1 last; 41 | rewrite "^/([0-9]{4})$" /aut-num/AS424242$1 last; 42 | rewrite "^/([Aa][Ss]|)([0-9]+)$" /aut-num/AS$2 last; 43 | rewrite "^/([0-9]+)\.([0-9]+)\.([0-9]+)\.([0-9]+)[/_]([0-9]+)$" /inet_route/$1.$2.$3.$4_$5 last; 44 | rewrite "^/([0-9]+)\.([0-9]+)\.([0-9]+)\.([0-9]+)$" /inet_route/$1.$2.$3.$4_32 last; 45 | rewrite "^/([0-9a-fA-F:]+)[/_]([0-9]+)$" /inet_route6/$1_$2 last; 46 | rewrite "^/([0-9a-fA-F:]+)$" /inet_route6/$1_128 last; 47 | rewrite "^/([^/]+)-([Dd][Nn]42)$" /person/$1-DN42 last; 48 | rewrite "^/([^/]+)-([Nn][Ee][Oo][Nn][Ee][Tt][Ww][Oo][Rr][Kk])$" /person/$1-NEONETWORK last; 49 | rewrite "^/([^/]+)-([Mm][Nn][Tt])$" /mntner/$1-MNT last; 50 | rewrite "^/([^/]+)-([Ss][Cc][Hh][Ee][Mm][Aa])$" /schema/$1-SCHEMA last; 51 | rewrite "^/([Oo][Rr][Gg])-(.+)$" /organisation/ORG-$2 last; 52 | rewrite "^/([Ss][Ee][Tt])-(.+)-([Tt][Ii][Nn][Cc])$" /tinc-keyset/SET-$2-TINC last; 53 | rewrite "^/([^/]+)-([Tt][Ii][Nn][Cc])$" /tinc-key/$1-TINC last; 54 | rewrite "^/([Rr][Ss])-(.+)$" /route-set/RS-$2 last; 55 | rewrite "^/([Aa][Ss])([0-9]+)-([Aa][Ss])([0-9]+)$" /as-block/$1$2-$3$4 last; 56 | rewrite "^/[Aa][Ss](.+)$" /as-set/AS$1 last; 57 | rewrite "^/[Pp][Gg][Pp][Kk][Ee][Yy][-](.+)$" /key-cert/PGPKEY-$1 last; 58 | rewrite "^/([^/]+)$" /dns/$1 last;""" 59 | 60 | def add_prefix(query): 61 | if "/" in query: 62 | prefix, body = query.split("/",1) 63 | if prefix in prefixes: 64 | return query 65 | for c in filter(lambda x:"rewrite" in x, ngc.split("\n")): 66 | matches = re.finditer(r"\"(.*)\" (.*) last", c, re.MULTILINE) 67 | sub_re, replace_pattern = list(matches)[0].groups() 68 | sub_re = sub_re[0] + sub_re[2:] 69 | required_group_num = max( map( lambda x:int(x.groups()[0]), re.finditer(r"\$(\d+)", replace_pattern, re.MULTILINE))) 70 | sub_match = list( map( lambda x:x.groups(), re.finditer(sub_re, query, re.MULTILINE) )) 71 | sub_matches = [] 72 | if len(sub_match) == 1: 73 | sub_matches = sub_match[0] 74 | if len(sub_matches) >= required_group_num: 75 | for i in range(required_group_num): 76 | replace_pattern = replace_pattern.replace("$" + str(i+1),sub_matches[i]) 77 | return replace_pattern[1:] 78 | 79 | def proc_data(data_in): 80 | ret_dict = {} 81 | for data_in_item in data_in.split("\n"): 82 | if len(data_in_item) == 0: 83 | continue 84 | if data_in_item[0] == "%": 85 | continue 86 | if ":" not in data_in_item: 87 | continue 88 | key , val = data_in_item.split(":",1) 89 | val = val.lstrip() 90 | if key in ret_dict: 91 | ret_dict[key] = [val] + ret_dict[key] 92 | else: 93 | ret_dict[key] = [val] 94 | return ret_dict 95 | 96 | class tcp_whois(): 97 | def __init__(self, url): 98 | self.host, self.port = url.rsplit(":",1) 99 | self.port = int(self.port) 100 | async def query(self,query): 101 | query = remove_prefix(query) 102 | return await self.socket_query(query) 103 | async def socket_query(self,query): 104 | query = query.strip() 105 | reader, writer = await asyncio.open_connection(self.host, self.port) 106 | writer.write(query.encode("utf8")) 107 | writer.write("\n".encode("utf8")) 108 | await writer.drain() 109 | writer.write_eof() 110 | data = await reader.read() 111 | writer.close() 112 | await writer.wait_closed() 113 | result = data.decode("utf8") 114 | result_item = result.split("\n") 115 | result_item = list(filter(lambda l:not l.startswith("%") and ":" in l,result_item)) 116 | if len(result_item) == 0: 117 | raise FileNotFoundError(errno.ENOENT, os.strerror(errno.ENOENT), query) 118 | return "\n".join(result_item) 119 | 120 | class git_whois(): 121 | def __init__(self, url, local_git,pull_cooldown): 122 | os.environ['GIT_SSH_COMMAND'] = "ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no" 123 | if not os.path.isdir(local_git): 124 | self.repo = Repo.clone_from(url, local_git,depth=1,env={'GIT_SSL_NO_VERIFY': '1'},config='http.sslVerify=false') 125 | else: 126 | self.repo = Repo(local_git) 127 | self.repo.remotes.origin.set_url(url) 128 | self.cooldown = pull_cooldown 129 | self.pulltime = time.time() 130 | self.local_git = local_git 131 | self.repo.remotes.origin.pull() 132 | async def query(self,query): 133 | query = query.strip() 134 | if time.time() - self.pulltime > self.cooldown: 135 | print(self.repo.remotes.origin) 136 | self.repo.remotes.origin.pull() 137 | self.pulltime = time.time() 138 | query = add_prefix(query) 139 | if query.startswith("inetnum/") or query.startswith("inet6num/") or query.startswith("route/") or query.startswith("route6/") or query.startswith("inet_route/") or query.startswith("inet_route6/"): 140 | if query.startswith("inetnum/") or query.startswith("route/") or query.startswith("inet_route/"): 141 | max_length=32 142 | elif query.startswith("inet6num/") or query.startswith("route6/") or query.startswith("inet_route6/"): 143 | max_length=128 144 | prefix,body = query.split("/",1) 145 | ip,length = ("",0) 146 | if "_" in body: 147 | ip,length = body.split("_") 148 | length = int(length) 149 | elif "/" in body: 150 | ip,length = body.split("/") 151 | length = int(length) 152 | else: 153 | ip = body 154 | length = max_length 155 | if length > max_length: 156 | raise FileNotFoundError(errno.ENOENT, os.strerror(errno.ENOENT), query) 157 | ret_result = "" 158 | inet_route_result = {"inet":False,"route":False} 159 | for i in range(length,-1,-1): 160 | try: 161 | try_net = ipaddress.ip_network(ip + "/" + str(i),strict=False) 162 | if prefix not in ("inet_route","inet_route6"): 163 | return self.file_query(prefix + "/" + str(try_net.network_address) + "_" + str(i)) 164 | else: 165 | query_body = str(try_net.network_address) + "_" + str(i) 166 | if prefix == "inet_route": 167 | qi = "inetnum/" 168 | qr = "route/" 169 | elif prefix == "inet_route6": 170 | qi = "inet6num/" 171 | qr = "route6/" 172 | try: 173 | if inet_route_result["inet"] == False: 174 | ret_result += self.file_query(qi + query_body) 175 | inet_route_result["inet"] = True 176 | except FileNotFoundError as e: 177 | pass 178 | try: 179 | if inet_route_result["route"] == False: 180 | ret_result += self.file_query(qr + query_body) 181 | inet_route_result["route"] = True 182 | except FileNotFoundError as e: 183 | pass 184 | if inet_route_result["inet"] == True and inet_route_result["route"] == True: 185 | break 186 | except FileNotFoundError as e: 187 | pass 188 | if ret_result != "": 189 | return ret_result 190 | raise FileNotFoundError(errno.ENOENT, os.strerror(errno.ENOENT), query) 191 | else: 192 | return self.file_query(query) 193 | def file_query(self,query): 194 | path = os.path.join(self.local_git , "data" , query ) 195 | try: 196 | response = open(path,"rb").read() 197 | result = response.decode("utf8") 198 | result_item = result.split("\n") 199 | result_item = list(filter(lambda l:not l.startswith("%") and ":" in l,result_item)) 200 | return f"% Information related to '{query}':\n" + "\n".join(result_item) + "\n\n" 201 | except FileNotFoundError: 202 | raise FileNotFoundError(errno.ENOENT, os.strerror(errno.ENOENT), query) 203 | 204 | 205 | class http_whois(): 206 | def __init__(self, url): 207 | if not url.startswith("http"): 208 | raise Exception('URL not startswith "http"') 209 | self.url = url 210 | async def query(self,query): 211 | query = query.strip() 212 | query = add_prefix(query) 213 | if query.startswith("inetnum/") or query.startswith("inet6num/") or query.startswith("route/") or query.startswith("route6/") or query.startswith("inet_route/") or query.startswith("inet_route6/"): 214 | if query.startswith("inetnum/") or query.startswith("route/") or query.startswith("inet_route/"): 215 | max_length=32 216 | elif query.startswith("inet6num/") or query.startswith("route6/") or query.startswith("inet_route6/"): 217 | max_length=128 218 | prefix,body = query.split("/",1) 219 | ip,length = ("",0) 220 | if "_" in body: 221 | ip,length = body.split("_") 222 | length = int(length) 223 | elif "/" in body: 224 | ip,length = body.split("/") 225 | length = int(length) 226 | else: 227 | ip = body 228 | length = max_length 229 | if length > max_length: 230 | raise FileNotFoundError(errno.ENOENT, os.strerror(errno.ENOENT), query) 231 | ret_result = "" 232 | inet_route_result = {"inet":False,"route":False} 233 | http_prequaries = {} 234 | loop = asyncio.get_event_loop() 235 | for i in range(length,-1,-1): 236 | try_net = ipaddress.ip_network(ip + "/" + str(i),strict=False) 237 | if prefix not in ("inet_route","inet_route6"): 238 | qq = prefix + "/" + str(try_net.network_address) + "_" + str(i) 239 | http_prequaries[qq] = loop.create_task(self.http_query(qq)) 240 | else: 241 | query_body = str(try_net.network_address) + "_" + str(i) 242 | if prefix == "inet_route": 243 | qi = "inetnum/" 244 | qr = "route/" 245 | elif prefix == "inet_route6": 246 | qi = "inet6num/" 247 | qr = "route6/" 248 | http_prequaries[qi + query_body] = loop.create_task(self.http_query(qi + query_body)) 249 | http_prequaries[qr + query_body] = loop.create_task(self.http_query(qr + query_body)) 250 | for i in range(length,-1,-1): 251 | try: 252 | try_net = ipaddress.ip_network(ip + "/" + str(i),strict=False) 253 | if prefix not in ("inet_route","inet_route6"): 254 | return self.file_query(prefix + "/" + str(try_net.network_address) + "_" + str(i)) 255 | else: 256 | query_body = str(try_net.network_address) + "_" + str(i) 257 | if prefix == "inet_route": 258 | qi = "inetnum/" 259 | qr = "route/" 260 | elif prefix == "inet_route6": 261 | qi = "inet6num/" 262 | qr = "route6/" 263 | try: 264 | if inet_route_result["inet"] == False: 265 | ret_result += await http_prequaries[qi + query_body] 266 | inet_route_result["inet"] = True 267 | except FileNotFoundError as e: 268 | pass 269 | try: 270 | if inet_route_result["route"] == False: 271 | ret_result += await http_prequaries[qr + query_body] 272 | inet_route_result["route"] = True 273 | except FileNotFoundError as e: 274 | pass 275 | if inet_route_result["inet"] == True and inet_route_result["route"] == True: 276 | break 277 | except FileNotFoundError as e: 278 | pass 279 | for k,q in http_prequaries.items(): 280 | q.cancel() 281 | if ret_result != "": 282 | return ret_result 283 | raise FileNotFoundError(errno.ENOENT, os.strerror(errno.ENOENT), query) 284 | else: 285 | return await self.http_query(query) 286 | async def http_query(self,query): 287 | client = tornado.httpclient.AsyncHTTPClient() 288 | try: 289 | response_f = client.fetch(self.url + "/" + query) 290 | response = await response_f 291 | result = response.body.decode("utf8") 292 | result_item = result.split("\n") 293 | result_item = list(filter(lambda l:not l.startswith("%") and ":" in l,result_item)) 294 | return f"% Information related to '{query}':\n" + "\n".join(result_item) + "\n\n" 295 | except asyncio.CancelledError: 296 | response_f.cancel() 297 | client.close() 298 | return "" 299 | except HTTPClientError as e: 300 | if e.code == 404: 301 | raise FileNotFoundError(errno.ENOENT, os.strerror(errno.ENOENT), query) 302 | raise e 303 | 304 | def get_whois_hendler(my_whois): 305 | async def whois_hendler(reader, writer): 306 | try: 307 | query = b"" 308 | while True: 309 | indata = await reader.read(1024) 310 | query += indata 311 | if len(indata) == 0: # connection closed 312 | writer.close() 313 | break 314 | if b"\n" in indata: 315 | try: 316 | print("WHOIS query: " + query.decode().strip()) 317 | outdata = await my_whois.query(query.decode()) 318 | except Exception as e: 319 | traceback.print_exc() 320 | outdata = "% Not found" 321 | writer.write(outdata.encode()) 322 | await writer.drain() 323 | writer.close() 324 | break 325 | except Exception as e: 326 | traceback.print_exc() 327 | return whois_hendler 328 | 329 | async def whois_server(): 330 | HOST = '0.0.0.0' 331 | PORT = 43 332 | print('prepareing for whois...') 333 | my_whois = git_whois("https://github.com/KusakabeSi/dn42-registry","whoisdata",600) 334 | #my_whois = http_whois("https://cdn.jsdelivr.net/gh/KusakabeSi/dn42-registry/data") 335 | 336 | whois_hendler = get_whois_hendler(my_whois) 337 | print('server start at: %s:%s' % (HOST, PORT)) 338 | server = await asyncio.start_server(whois_hendler,HOST,PORT) 339 | print('wait for connection...') 340 | if __name__ == '__main__': 341 | loop = asyncio.get_event_loop() 342 | asyncio.ensure_future(whois_server()) 343 | loop.run_forever() 344 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Peer with me at DN42 network!! 2 | 3 | https://net.whojk.com/ 4 | 5 | #### Special thanks to KuskakbeSi 6 | 7 | peer with KuskakbeSi: 8 | https://dn42.kskb.eu.org/ 9 | 10 | ## installition 11 | 12 | ``` 13 | pip3 install -r requirement.txt 14 | ``` 15 | 16 | Then fill your server infomation to ```my_config.json``` and ```my_parameters.json``` 17 | 18 | ``` 19 | python3 ./DN42AutoPeer.py 20 | ``` 21 | -------------------------------------------------------------------------------- /my_config.yaml: -------------------------------------------------------------------------------- 1 | jwt_secret: null 2 | html_title: DN42 Self-Service Peering Portal 3 | git_repo_url: https://github.com/HuJK/DN42-AutoPeer 4 | listen_host: 0.0.0.0 5 | listen_port: '4242' 6 | ssl_options: null 7 | myWG_Pri_Key: '' 8 | urlprefix: '' 9 | register_redirect: null 10 | wgconfpath: /etc/dn42ap 11 | bdconfpath: /etc/bird/peers 12 | gitsyncpath: null 13 | dn42_whois_server: 14 | - git 15 | - "https://dn42ap:qZ8Z5szMt2RsWbdMyEku@gitlab.com/kskbshi/data/dn42-registry" 16 | dn42repo_base: https://explorer.burble.com/?#/ 17 | admin_mnt: '' 18 | DN42_valid_ipv4s: 19 | - 172.20.0.0/14 20 | - 172.31.0.0/16 21 | - 10.0.0.0/8 22 | DN42_valid_ipv6s: 23 | - fd00::/8 24 | valid_ipv4_linklocal: 169.254.42.0/24 25 | valid_ipv6_linklocal: fe80::/64 26 | wg_port_search_range: range(20000 + peerASN % 10000 ,20100 + peerASN % 10000 ) 27 | init_device: true 28 | reset_wgconf_interval: 0 29 | myHostHidden: false 30 | peerEndpointHidden: false 31 | registerAdminOnly: false 32 | -------------------------------------------------------------------------------- /my_parameters.yaml: -------------------------------------------------------------------------------- 1 | myIPV4: '' 2 | myIPV6: '' 3 | myIPV4LL: '' 4 | myIPV6LL: '' 5 | myHost: '' 6 | myHostDisplay: '' 7 | myASN: AS4242420000 8 | myContact: '' 9 | allowExtNh: true 10 | -------------------------------------------------------------------------------- /requirement.txt: -------------------------------------------------------------------------------- 1 | pycryptodome 2 | gitpython 3 | pyOpenSSL 4 | tornado 5 | pyyaml 6 | pyjwt 7 | PGPy 8 | -------------------------------------------------------------------------------- /ssh/.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore everything 2 | * 3 | 4 | # But not these files... 5 | !.gitignore -------------------------------------------------------------------------------- /ssl/.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore everything 2 | * 3 | 4 | # But not these files... 5 | !.gitignore --------------------------------------------------------------------------------