├── input └── .gitignore ├── requirement.txt ├── wg ├── wg.sh └── wg.conf ├── down.sh ├── babeldweb2.sh ├── bird_igp_metric.conf ├── bird_ibgp.conf ├── setip.sh ├── ovpn ├── client.ovpn └── server.ovpn ├── bird_edgedevice.conf ├── babeld.conf ├── up.sh ├── LICENCE ├── update_cost.py ├── update.sh ├── all_node.yaml ├── README.md ├── bird.conf.example ├── generate_config_func.py └── generate_config.py /input/.gitignore: -------------------------------------------------------------------------------- 1 | #* 2 | #!.gitignore 3 | -------------------------------------------------------------------------------- /requirement.txt: -------------------------------------------------------------------------------- 1 | pyyaml 2 | jinja2 3 | pynacl 4 | ruamel.yaml -------------------------------------------------------------------------------- /wg/wg.sh: -------------------------------------------------------------------------------- 1 | ip link add dev {{ ifname }} type wireguard 2 | wg setconf {{ ifname }} {{ confpath }}.conf -------------------------------------------------------------------------------- /down.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -x 3 | kill $(cat /var/run/babeld.pid) 4 | 5 | 6 | {% for down in downs -%} 7 | {{ down }} 8 | {% endfor %} -------------------------------------------------------------------------------- /babeldweb2.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | babelweb2 -http={{ babeldweb.http }}{% for n in result %} -node=[{{ n.self_ip.v6 }}]:{{ babeldweb.babeld }}{% endfor %} -------------------------------------------------------------------------------- /bird_igp_metric.conf: -------------------------------------------------------------------------------- 1 | function get_igp_metric() { 2 | {% for neigh in neighbors %} if net ~ {{ neigh.prefix }} then return {{ neigh.metric }}; 3 | {% endfor %} 4 | return 1000; 5 | } -------------------------------------------------------------------------------- /bird_ibgp.conf: -------------------------------------------------------------------------------- 1 | include "igp_metric.conf"; 2 | 3 | {% for iface in interfaces %} 4 | protocol bgp '{{ iface.name }}' from ibgps { 5 | neighbor {{ iface.ip }} ; 6 | }; 7 | {% endfor %} 8 | -------------------------------------------------------------------------------- /setip.sh: -------------------------------------------------------------------------------- 1 | ip link set {{ ifname }} up 2 | {% if MTU > 0 -%} 3 | ip link set mtu {{ MTU }} dev {{ ifname }} 4 | {% endif %} 5 | ip addr add {{ ipv4 }}/32 dev {{ ifname }} 6 | ip addr add {{ ipv6 }}/128 dev {{ ifname }} 7 | ip addr add {{ ipv6ll }}/64 dev {{ ifname }} scope link 8 | -------------------------------------------------------------------------------- /wg/wg.conf: -------------------------------------------------------------------------------- 1 | [Interface] 2 | PrivateKey = {{ wg.pri }} 3 | ListenPort = {{ wg.port }} 4 | 5 | [Peer] 6 | PublicKey = {{ wg.pub }} 7 | PresharedKey = {{ wg.psk }} 8 | AllowedIPs = 0.0.0.0/0, ::/0 9 | {% if wg.endpoint is not none -%} 10 | Endpoint = {{ wg.endpoint }} 11 | {% else -%} 12 | PersistentKeepalive = 25 13 | {% endif %} 14 | 15 | -------------------------------------------------------------------------------- /ovpn/client.ovpn: -------------------------------------------------------------------------------- 1 | mode client 2 | remote {{ server.endpoint }}:{{ server.port }} 3 | writepid {{ confpath }}.pid 4 | cipher AES-256-CBC 5 | data-ciphers AES-256-CBC 6 | data-ciphers-fallback AES-256-CBC 7 | proto tcp-client 8 | dev-type tap 9 | dev {{ client.ifname }} 10 | ca {{ confpath }}ca.crt 11 | cert {{ confpath }}.crt 12 | key {{ confpath }}.key 13 | tls-client 14 | -------------------------------------------------------------------------------- /ovpn/server.ovpn: -------------------------------------------------------------------------------- 1 | mode server 2 | port {{ server.port }} 3 | writepid {{ confpath }}.pid 4 | cipher AES-256-CBC 5 | data-ciphers AES-256-CBC 6 | data-ciphers-fallback AES-256-CBC 7 | proto tcp-server 8 | dev-type tap 9 | dev {{ server.ifname }} 10 | ca {{ confpath }}ca.crt 11 | cert {{ confpath }}.crt 12 | key {{ confpath }}.key 13 | dh {{ confpath }}.pem 14 | tls-server 15 | -------------------------------------------------------------------------------- /bird_edgedevice.conf: -------------------------------------------------------------------------------- 1 | protocol static { 2 | route 172.22.77.32/32 via "eth0" { bgp_community.add( (MY_COMMUNITY, 11111)); }; 3 | 4 | ipv4 { 5 | import all; 6 | export none; 7 | }; 8 | } 9 | 10 | protocol static { 11 | route fd28:cb8f:4c92::/64 via "eth0" { bgp_community.add( (MY_COMMUNITY, 11111)); }; 12 | 13 | ipv6 { 14 | import all; 15 | export none; 16 | }; 17 | } 18 | -------------------------------------------------------------------------------- /babeld.conf: -------------------------------------------------------------------------------- 1 | default type tunnel 2 | default faraway true 3 | default enable-timestamps true 4 | default link-quality true 5 | default split-horizon true 6 | default rtt-min 1 7 | default rtt-max 1024 8 | default max-rtt-penalty 1024 9 | default rxcost 8 10 | 11 | import-table 114 12 | 13 | local-path /var/run/babeld/ro.sock 14 | #local-path-readwrite /var/run/babeld/rw.sock 15 | 16 | skip-kernel-setup true 17 | random-id true 18 | ipv6-subtrees true 19 | 20 | in ip {{ allow_net.v4 }} allow 21 | in ip {{ allow_net.v6 }} allow 22 | in deny 23 | 24 | redistribute ip {{ allow_net.v4 }} allow 25 | redistribute ip {{ allow_net.v6 }} allow 26 | redistribute proto 114 allow 27 | redistribute local deny 28 | redistribute deny 29 | 30 | {% for iface in interfaces -%} 31 | interface {{ iface }} 32 | {% endfor %} 33 | 34 | -------------------------------------------------------------------------------- /up.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | resolveip() 4 | { 5 | : ${1:?Usage: resolve name} 6 | ( 7 | PATH=$PATH:/usr/bin 8 | lookupresult=$(getent ahostsv4 "$1") 9 | if [ $? -eq 0 ]; then 10 | resultaddr=$(echo $lookupresult | head -n 1 | awk '{print $1}') 11 | echo $resultaddr 12 | return 0 13 | fi 14 | lookupresult=$(getent ahostsv6 $1) 15 | if [ $? -eq 0 ]; then 16 | resultaddr=$(echo $lookupresult | head -n 1 | awk '{print $1}') 17 | echo "[$resultaddr]" 18 | return 0 19 | fi 20 | echo "0.0.0.0" 21 | return 127 22 | ) 23 | } 24 | set -x 25 | ip route add {{ self_ip.v4 }}/32 dev lo proto 114 table 114 scope link 26 | ip route add {{ self_ip.v6 }}/128 dev lo proto 114 table 114 scope link 27 | 28 | cp bird/igp_metric.zero.conf bird/igp_metric.conf 29 | 30 | {% for up in ups -%} 31 | {{ up }} 32 | {% endfor %} 33 | mkdir -p /var/run/babeld 34 | rm /var/run/babeld/rw.sock || true 35 | rm /var/run/babeld/ro.sock || true 36 | babeld -D -I /var/run/babeld.pid -S /var/lib/babeld/state -c babeld.conf 37 | 38 | -------------------------------------------------------------------------------- /LICENCE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Kusakabe Si 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /update_cost.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | import jinja2 3 | import socket 4 | import os 5 | import time 6 | sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) 7 | sock.connect("/var/run/babeld/ro.sock") 8 | 9 | def recvall(sock): 10 | BUFF_SIZE = 128 11 | sock.settimeout(1) 12 | data = b'' 13 | waitmore = 0 14 | while True: 15 | part = sock.recv(BUFF_SIZE) 16 | data += part 17 | #print(part.decode("utf8") ,end = "") 18 | if data[-3:].decode("utf8") == "ok\n": 19 | #print("recvall done") 20 | return data 21 | if len(part) < BUFF_SIZE: 22 | #print(f"partsize={len(part)} wait more") 23 | if len(part) == 0: 24 | waitmore += 1 25 | if waitmore > 5: 26 | return "" 27 | return "" 28 | 29 | sockret = recvall(sock).decode("utf8") 30 | sock.sendall(b"dump\n") 31 | sockret = recvall(sock).decode("utf8") 32 | sock.sendall(b"quit\n") 33 | #print(sockret) 34 | 35 | datas = {} 36 | neighbors = [] 37 | for line in sockret.split("\n"): 38 | if "installed yes" in line: 39 | elem = line.split(" ")[1:] 40 | data = {} 41 | for k,v in zip(elem[::2], elem[1::2]): 42 | data[k] = v 43 | datakey = data["prefix"].split("/")[0].replace(":","_").replace(".","_") 44 | datas[datakey] = data["metric"] 45 | neighbors += [{"prefix":data["prefix"] , "metric": data["metric"]}] 46 | print( {"prefix":data["prefix"] , "metric": data["metric"]} ) 47 | #print(neighbors) 48 | ibgptemplate = jinja2.Template(open('bird/igp_metric.conf.j2').read()) 49 | 50 | result=ibgptemplate.render(neighbors = neighbors) 51 | 52 | open("bird/igp_metric.conf" , "w").write(result) -------------------------------------------------------------------------------- /update.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | resolveip() 4 | { 5 | : ${1:?Usage: resolve name} 6 | ( 7 | PATH=$PATH:/usr/bin 8 | lookupresult=$(getent ahostsv4 "$1") 9 | if [ $? -eq 0 ]; then 10 | resultaddr=$(echo $lookupresult | head -n 1 | awk '{print $1}') 11 | echo $resultaddr 12 | return 0 13 | fi 14 | lookupresult=$(getent ahostsv6 $1) 15 | if [ $? -eq 0 ]; then 16 | resultaddr=$(echo $lookupresult | head -n 1 | awk '{print $1}') 17 | echo "[$resultaddr]" 18 | return 0 19 | fi 20 | echo "0.0.0.0" 21 | return 127 22 | ) 23 | } 24 | 25 | get_wg_peer_down () { 26 | #1:name, 2:peer key, 3:peer endpoint, 4:confpath 27 | current_time=$(date +%s) 28 | last_handshake=$(wg show "$1" latest-handshakes | grep "$2" | awk "{print \$2}") 29 | if [ -z "$last_handshake" ]; then 30 | last_handshake=0 31 | wg setconf "$1" "$4" 32 | fi 33 | last_to_now=$(("$current_time"-"$last_handshake")) 34 | if [ "$last_to_now" -gt "180" ]; then 35 | return 0 #success, means down 36 | fi 37 | return 1 #fail, means up 38 | } 39 | 40 | update_wg_peer() { 41 | if get_wg_peer_down "$1" "$2" "$3" "$4"; then 42 | wg set "$1" listen-port "5$(head /dev/urandom | tr -dc "0123456789" | head -c4)" 43 | wg set "$1" peer "$2" endpoint "$3" 44 | fi 45 | } 46 | 47 | get_ip_down () { 48 | #1:ip 2:ifname 49 | if ! ping -c 1 -W 3 "$1" -I "$2" 50 | then 51 | return 0 #success, means down 52 | fi 53 | return 1 # fail. means up 54 | } 55 | 56 | {% for resolvip in resolvips -%} 57 | {{ resolvip }} 58 | {% endfor %} 59 | 60 | {% for con in reconns -%} 61 | if get_ip_down "{{ con["ip"] }}" "{{ con["ifname"] }}"; then 62 | {{ con["script"] }} 63 | fi 64 | {% endfor %} 65 | 66 | {% for up in ups -%} 67 | {{ up }} 68 | {% endfor %} -------------------------------------------------------------------------------- /all_node.yaml: -------------------------------------------------------------------------------- 1 | output_dir: output 2 | babeldweb: 3 | http: "127.0.0.1:7641" 4 | babeld: 7642 5 | defaults: 6 | DDNS: false 7 | port_base: 18000 8 | port_base_i: 36000 9 | server_perf: 100 10 | MTU: 1500 11 | tunnel: 12 | '-4': 13 | -1: wg 14 | '-6': 15 | -1: wg 16 | iface_prefix: dn42-i- 17 | network: 18 | v4: 192.168.42.0/27 19 | v6: fd07:d159:fc38::/48 20 | v6ll: "fe80::1817:0" 21 | node_list: 22 | 1: 23 | name: tw 24 | DDNS: true 25 | server_perf: 80 26 | MTU: 1492 27 | endpoints: 28 | '-4': 4.tw.kskb.moe 29 | '-6': 6.tw.kskb.moe 30 | 4: 31 | name: jp 32 | endpoints: 33 | '-4': 4.jp.kskb.moe 34 | '-6': 6.jp.kskb.moe 35 | 5: 36 | name: hk 37 | endpoints: 38 | '-4': 4.hk.kskb.moe 39 | '-6': 6.hk.kskb.moe 40 | 8: 41 | name: usfmt 42 | endpoints: 43 | '-4': 4.us.kskb.moe 44 | '-6': 6.us.kskb.moe 45 | 10: 46 | name: de 47 | endpoints: 48 | '-4': NAT 49 | '-6': 6.de.kskb.moe 50 | 12: 51 | name: cnjs 52 | port_base: 42070 53 | server_perf: 150 54 | endpoints: 55 | '-4': cnjs.kskb.moe 56 | tunnel: 57 | '-4': 58 | -1: wg_high 59 | 16: 60 | name: cnzj 61 | DDNS: true 62 | port_base: 14870 63 | server_perf: 150 64 | MTU: 1480 65 | endpoints: 66 | '-4': 4.cnzj.kskb.moe 67 | '-6': 6.cnzj.kskb.moe 68 | tunnel: 69 | '-4': 70 | -1: wg_high 71 | '-6': 72 | 1: null 73 | 17: 74 | name: cnwh 75 | DDNS: true 76 | port_base: 21201 77 | server_perf: 50 78 | MTU: 1492 79 | endpoints: 80 | '-4': 4.cnwh.kskb.moe 81 | '-6': 6.cnwh.kskb.moe 82 | tunnel: 83 | '-4': 84 | -1: wg_high 85 | '-6': 86 | 1: null 87 | 4: null 88 | 5: null 89 | 16: null 90 | 18: null 91 | 18: 92 | name: cngd 93 | MTU: 1420 94 | endpoints: 95 | '-4': NAT 96 | '-6': 6.cngd.kskb.moe 97 | tunnel: 98 | '-4': 99 | -1: wg_high 100 | 19: 101 | name: cncs 102 | port_base: 31980 103 | endpoints: 104 | '-4': 4.cncs.kskb.moe 105 | tunnel: 106 | '-4': 107 | -1: wg_high 108 | 20: 109 | name: usfmt2 110 | MTU: 1480 111 | endpoints: 112 | '-4': 4.yi.us.kskb.moe 113 | 21: 114 | name: de2 115 | port_base: 13481 116 | endpoints: 117 | '-4': hz.moe.de.kskb.moe 118 | '-6': 6.moe.de.kskb.moe 119 | 22: 120 | name: cnsc 121 | port_base: 11080 122 | server_perf: 45 123 | endpoints: 124 | '-4': 4.cnsc.kskb.moe 125 | tunnel: 126 | '-4': 127 | -1: openvpn -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | babeld-ibgp-generator 2 | ==== 3 | 4 | 1. 把 `all_node.yaml`複製到 input 資料夾 5 | 2. 把 `generate_config_func.py` 複製到 input 資料夾 6 | 3. 修改 `input/all_node.yaml` 7 | 4. 執行 `python3 generate_config.py` 8 | 5. 本專案不會對電腦系統做出任何變更。只是生成一大堆 wireguard 設定檔,自己部屬去對應節點自己執行 9 | 10 | all_node.yaml 說明 11 | ----- 12 | ![image](https://user-images.githubusercontent.com/73118488/155201183-3fd2ed49-3f5b-4d8b-951e-f32de2b65d01.png) 13 | 14 | defaults: 預設值,這個數值會套用到所有節點上,除非在底下改寫 15 | 16 | * port_base: 18000 分配外部端口的起始。 18000 18001 18002 這樣分配下去 17 | * port_base_i: 36000 分配**內部端口**的起始。 36000 36001 36002 ... 18 | * server_perf: 100 雙方都有公網IP的時候,成為server的傾向。數值大的成為server 19 | * tunnel: 20 | * -1: wg 和目標建立的隧道類型。 -1代表所有目標 21 | 22 | 內部端口: wg_udp2raw就需分配一個內部端口,讓wg和udp2raw連線。此端口無須暴露在外網,不用占去寶貴的port forward名額 23 | 外部端口: udp2raw和對面udp2raw連線的端口。此端口需要被port forward 24 | 25 | ### tunnel 26 | tunnel則是指定節點之間如何互聯。 27 | `?-所有人`: 這個是在`default裡面的值`,意思是`所有人-所有人`使用wg互聯 28 | ![image](https://user-images.githubusercontent.com/73118488/155415520-373420a7-d049-464f-a838-c375758e3d66.png) 29 | 30 | 紅色: `日本-美國`之間使用`openvpn`互聯 31 | 黃色: `德國-南京`之間不互聯 32 | 綠色: `南京-"-1"`,也就是`南京-所有人`所有人使用 `wg_udp2raw` 方式互聯 33 | ![image](https://user-images.githubusercontent.com/73118488/155202813-c9f2dffe-e509-45ae-9e74-db19157e2063.png) 34 | 35 | 此時發生衝突, 1->12 使用`wg`, 12->1 使用`wg_udp2raw`。就會使用優先權高的`wg_udp2raw` 36 | 37 | 優先權定義在 `generate_config_func.py` 裡面,擺在越上面越優先 38 | ![image](https://user-images.githubusercontent.com/73118488/155201439-af24fdf0-766d-4ab2-8f65-1df85910fc84.png) 39 | 40 | 整體來說就是 `美國-日本`用openvpn,`南京-其他人`(跨GFW)用wg_udp2raw,`德國-南京`不連,剩下的使用wg互聯 41 | 42 | 43 | ### server_perf 44 | wg是點對點,沒有這個問題,但是openvpn/udp2raw都要一邊做server,一邊做client。 45 | 如果其中一邊是NAT,那就一定是另一邊做server。如果兩邊都是NAT,就不會建立連線 46 | ![image](https://user-images.githubusercontent.com/73118488/155203821-6db252b2-1b86-40f0-a713-d0f1d58d079c.png) 47 | 但如果兩邊都不是NAT,則由 server_perf 數值高的做server ,另一邊做client 48 | * udp2raw server不支援域名,必須使用IP 49 | * gre tunnel兩邊都必須使用IP,不能使用域名 50 | 51 | ### AF 52 | endpoint裡面有 `-4` 和 `-6` ,可以自訂義增加別的,或是只使用一個。相同AF之間會互相建立。 53 | 以我的例子來說,因為ipv4/ipv6延遲可能不一樣,所以節點間同時建立 ipv4 和 ipv6 連線,由babeld挑選延遲低的走 54 | 55 | ### DDNS 56 | 見 Output章節 57 | 58 | # Output 59 | 生成的結果會放在output資料夾,用name區分每個節點的設定檔。 60 | ![image](https://user-images.githubusercontent.com/73118488/155538861-1333f46b-1422-437f-9bd8-2170e8bfe839.png) 61 | 用github/gitee或是什麼方法同步到自己的節點上就可以了 62 | 63 | 裡面有3個重要的檔案 64 | #### up.sh 65 | 裡面是建立tunnel的指令。請自行加入開機自啟動 66 | ![image](https://user-images.githubusercontent.com/73118488/155537463-eb83eaf7-a9f5-4392-a31f-efbd43a014ec.png) 67 | 以及igp_tunnels資料夾,包含了建立tunnel所需的設定檔。公鑰/私鑰/預共享金鑰都會自動生成 68 | ![image](https://user-images.githubusercontent.com/73118488/155537746-1f8aa0a5-79f3-4962-910c-61bdee0adfeb.png) 69 | 70 | 還有最後一行的啟動babeld的指令,執行前請先確認自己的babeld是關閉的 71 | ``` 72 | babeld -D -I /var/run/babeld.pid -S /var/lib/babeld/state -c babeld.conf 73 | ``` 74 | 75 | #### down.sh 76 | 第一行關閉babeld,然後刪除自己建立的tunnel 77 | ![image](https://user-images.githubusercontent.com/73118488/155538004-bcd8d43c-bb30-4064-b23e-4d8454475734.png) 78 | 用來一鍵關閉+刪除本腳本建立的tunnel 79 | 80 | #### update.sh 81 | 還記得剛剛的 DDNS=True ,我說`見 Output章節`嗎,這個就是更新wg endpoint的腳本。 82 | 被標記成DDNS=True的節點,會出現在update.sh裡面。 83 | 內容是重新解析域名,指定到wg接口上。 自己把它放在`crontab`就可以了 84 | ![image](https://user-images.githubusercontent.com/73118488/155538122-064677ed-27f8-42c2-88c2-dabdc272824a.png) 85 | 86 | 87 | generate_config_func.py 說明 88 | ------ 89 | 90 | 隧道具體的建立方式,在這邊定義。我目前已經完成 None, "wg" , "wg_udp2raw" , "gre" 91 | "openvpn"則尚未完成 92 | ![image](https://user-images.githubusercontent.com/73118488/155201953-5587acf5-6ab2-4882-bfb3-cf2b773f1b71.png) 93 | get_gre / get_wg_udp2raw / get_wg / get_openvpn 四個function會回傳具體的設定檔,up/down腳本 94 | 95 | 非常歡迎自訂義新的tunnel種類 96 | 輸入長這樣: 97 | ![image](https://user-images.githubusercontent.com/73118488/155205044-0f306f62-2960-4d6a-9c6c-eb2d41c52d94.png) 98 | 99 | 返回值是 server 和 client 的設定檔,有 `confs` `up` `down`三個部分 100 | ![image](https://user-images.githubusercontent.com/73118488/155206208-ddf722ba-a0c0-4ad2-b426-92b1adf21bf1.png) 101 | 102 | 可以去 `test_func.ipynb` 測試 103 | 104 | ## Dependance 105 | ``` 106 | cd ~ 107 | apt install -y git babeld wireguard-tools python3-pip net-tools libc-bin gawk ntp 108 | systemctl enable ntp 109 | systemctl start ntp 110 | pip3 install -r requirement.txt 111 | wget https://github.com/wangyu-/udp2raw/releases/download/20200818.0/udp2raw_binaries.tar.gz 112 | tar -xvzf udp2raw_binaries.tar.gz -C udp2raw 113 | mv udp2raw/udp2raw_amd64 /usr/bin/udp2raw 114 | ``` -------------------------------------------------------------------------------- /bird.conf.example: -------------------------------------------------------------------------------- 1 | ################################################ 2 | # Variable header # 3 | ################################################ 4 | define MY_COMMUNITY = ${DN42_COMM}; 5 | define OWNAS = ${DN42_E_AS}; 6 | define OWNIP = ${DN42_IPV4}; 7 | define OWNIPv6 = ${DN42_IPV6}; 8 | define OWNNET = ${DN42_IPV4_NET_BOARDCAST}; 9 | define OWNNETv6 = ${DN42_IPV6_NET_BOARDCAST}; 10 | define OWNNET_ANYCAST = ${DN42_IPV4_NET_ANYCAST}; 11 | define OWNNETv6_ANYCAST = ${DN42_IPV6_NET_ANYCAST}; 12 | 13 | define OWNNETSET = [${DN42_IPV4_NET_OWN}+]; 14 | define OWNNETSETv6 = [${DN42_IPV6_NET_OWN}+]; 15 | define DN42_REGION = ${DN42_REGION}; 16 | 17 | 18 | define OWNPAS = 138517; 19 | define OWNPIPv6 = ${PUB_IPV6}; 20 | define OWNPNETv6 = ${PUB_IPV6_NET_BOARDCAST}; 21 | ################################################ 22 | # Header end # 23 | ################################################ 24 | 25 | router id OWNIP; 26 | 27 | protocol device { 28 | scan time 10; 29 | } 30 | 31 | filter add_no_export { 32 | bgp_community.add((65535, 65281)); # No-export 33 | accept; 34 | }; 35 | 36 | 37 | protocol direct { 38 | ipv4 { 39 | import filter add_no_export; 40 | }; 41 | ipv6 { 42 | import filter add_no_export; 43 | }; 44 | interface "dn42-i*"; 45 | }; 46 | 47 | /* 48 | * Utility functions 49 | */ 50 | 51 | function is_self_net() { 52 | return net ~ OWNNETSET; 53 | } 54 | 55 | function is_self_net_v6() { 56 | return net ~ OWNNETSETv6; 57 | } 58 | 59 | function is_valid_network() { 60 | if (MY_COMMUNITY, 11111) ~ bgp_community then return false; 61 | bgp_community.delete([(MY_COMMUNITY, *)]); # Delete all my communities while in/export 62 | if ((64511, DN42_REGION) ~ bgp_community && source = RTS_BGP) then { 63 | bgp_local_pref = bgp_local_pref + 10; 64 | } 65 | return net ~ [ 66 | 172.20.0.0/14{21,29}, # dn42 67 | 172.20.0.0/24{28,32}, # dn42 Anycast 68 | 172.21.0.0/24{28,32}, # dn42 Anycast 69 | 172.22.0.0/24{28,32}, # dn42 Anycast 70 | 172.23.0.0/24{28,32}, # dn42 Anycast 71 | 172.31.0.0/16+, # ChaosVPN 72 | 10.100.0.0/14+, # ChaosVPN 73 | 10.127.0.0/16{16,32}, # neonetwork 74 | 10.0.0.0/8{15,24} # Freifunk.net 75 | ]; 76 | } 77 | 78 | function is_valid_network_v6() { 79 | if (MY_COMMUNITY, 11111) ~ bgp_community then return false; 80 | bgp_community.delete([(MY_COMMUNITY, *)]); # Delete all my communities while in/export 81 | if ((64511, DN42_REGION) ~ bgp_community && source = RTS_BGP) then { 82 | bgp_local_pref = bgp_local_pref + 10; 83 | } 84 | return net ~ [ 85 | fd00::/8{44,64} # ULA address space as per RFC 4193 86 | ]; 87 | } 88 | 89 | function is_dn42() { 90 | case net.type { 91 | NET_IP4: return net ~ [ 92 | 172.20.0.0/14+, # dn42 93 | 172.31.0.0/16+, # ChaosVPN 94 | 10.0.0.0/8+ 95 | ]; 96 | NET_IP6: return net ~ [ 97 | fd00::/8+ # ULA address space as per RFC 4193 98 | ]; 99 | } 100 | return false; 101 | } 102 | 103 | function is_global() { 104 | case net.type { 105 | NET_IP4: return false; 106 | NET_IP6: return net ~ [ 107 | 2000::/3{8,48} # RFC 3587 108 | ]; 109 | } 110 | return false; 111 | } 112 | 113 | roa4 table dn42_roa; 114 | roa6 table dn42_roa_v6; 115 | 116 | protocol static { 117 | roa4 { table dn42_roa; }; 118 | include "/etc/bird/roa_dn42.conf"; 119 | }; 120 | 121 | protocol static { 122 | roa6 { table dn42_roa_v6; }; 123 | include "/etc/bird/roa_dn42_v6.conf"; 124 | }; 125 | 126 | protocol static { 127 | route OWNNET reject { bgp_community.add( (64511, DN42_REGION)); }; 128 | 129 | ipv4 { 130 | import all; 131 | export none; 132 | }; 133 | } 134 | 135 | protocol static { 136 | route OWNNETv6 reject { bgp_community.add( (64511, DN42_REGION)); }; 137 | route OWNPNETv6 reject; 138 | ipv6 { 139 | import all; 140 | export none; 141 | }; 142 | } 143 | 144 | template bgp dnpeers { 145 | local as OWNAS; 146 | path metric 1; 147 | interpret communities on; 148 | 149 | ipv4 { 150 | extended next hop on; 151 | import keep filtered on; 152 | import filter { 153 | if is_valid_network() && !is_self_net() then { 154 | if (roa_check(dn42_roa, net, bgp_path.last) != ROA_VALID) then { 155 | print "[dn42] ROA check failed from ",bgp_path.first , " ifname:", ifname ," for ", net, " ASN ", bgp_path.last; 156 | reject; 157 | } else accept; 158 | } else reject; 159 | }; 160 | 161 | export filter { if is_valid_network() && source ~ [RTS_STATIC, RTS_BGP] then accept; else reject; }; 162 | import limit 1000 action block; 163 | }; 164 | 165 | ipv6 { 166 | import keep filtered on; 167 | import filter { 168 | if is_valid_network_v6() && !is_self_net_v6() then { 169 | if (roa_check(dn42_roa_v6, net, bgp_path.last) != ROA_VALID) then { 170 | print "[dn42] ROA check failed from ",bgp_path.first , " ifname:", ifname ," for ", net, " ASN ", bgp_path.last; 171 | reject; 172 | } else accept; 173 | } else reject; 174 | }; 175 | export filter { if is_valid_network_v6() && source ~ [RTS_STATIC, RTS_BGP] then accept; else reject; }; 176 | import limit 1000 action block; 177 | }; 178 | }; 179 | 180 | template bgp pubpeers { 181 | local as OWNPAS; 182 | path metric 1; 183 | interpret communities on; 184 | allow local as 1; 185 | ipv4 { 186 | extended next hop on; 187 | import filter { 188 | if is_global() then { 189 | accept; 190 | } else reject; 191 | }; 192 | 193 | export none; 194 | import limit 10000000 action block; 195 | }; 196 | 197 | ipv6 { 198 | import filter { 199 | if is_global() then { 200 | accept; 201 | } else reject; 202 | }; 203 | export filter { if is_global() && source ~ [RTS_STATIC] then { 204 | accept; 205 | } 206 | else { 207 | reject; 208 | } 209 | }; 210 | import limit 10000000 action block; 211 | }; 212 | }; 213 | 214 | filter ibgp_filter { 215 | if (65535, 65281) ~ bgp_community then reject; 216 | case net.type { 217 | NET_IP4: if is_dn42() then { 218 | bgp_next_hop = OWNIP; 219 | accept; 220 | } 221 | NET_IP6: if is_dn42() then { 222 | bgp_next_hop = OWNIPv6; 223 | accept; 224 | } 225 | } 226 | reject; 227 | }; 228 | 229 | template bgp ibgps { 230 | local as OWNAS; 231 | neighbor as OWNAS; 232 | path metric on; 233 | med metric on; 234 | interpret communities on; 235 | multihop; 236 | enable extended messages on; 237 | ipv4 { 238 | gateway recursive; 239 | import all; 240 | export filter ibgp_filter; 241 | }; 242 | ipv6 { 243 | gateway recursive; 244 | import all; 245 | export filter ibgp_filter; 246 | }; 247 | }; 248 | 249 | 250 | template bgp dnnodes { 251 | local as OWNAS; 252 | path metric on; 253 | med metric on; 254 | direct; 255 | enable extended messages on; 256 | ipv4 { 257 | next hop self yes; 258 | extended next hop on; 259 | import all; 260 | export all; 261 | }; 262 | ipv6 { 263 | next hop self yes; 264 | import all; 265 | export all; 266 | }; 267 | }; 268 | 269 | include "/etc/bird/babeld/ibgp.conf"; 270 | include "/etc/bird/edgedevice.conf"; 271 | #include "/etc/bird/nodes/*.conf"; 272 | include "/etc/bird/peers/*.conf"; 273 | include "/etc/bird/pubpeers/*.conf"; 274 | include "/etc/bird/babeld/ibgp.conf"; 275 | 276 | protocol kernel { 277 | #scan time 20; 278 | ipv4 { 279 | 280 | import filter babeld; 281 | export filter { 282 | krt_prefsrc = OWNIP; 283 | if (MY_COMMUNITY, 11111) ~ bgp_community then accept; 284 | if source = RTS_STATIC then reject; 285 | if is_self_net() then reject; 286 | accept; 287 | }; 288 | }; 289 | } 290 | 291 | protocol kernel { 292 | #scan time 20; 293 | ipv6 { 294 | import filter babeld; 295 | export filter { 296 | if is_dn42() then { 297 | krt_prefsrc = OWNIPv6; 298 | } else if is_global() then { 299 | krt_prefsrc = OWNPIPv6; 300 | } else { 301 | reject; 302 | } 303 | if (MY_COMMUNITY, 11111) ~ bgp_community then accept; 304 | if source = RTS_STATIC then reject; 305 | if is_self_net_v6() then reject; 306 | accept; 307 | }; 308 | }; 309 | }; -------------------------------------------------------------------------------- /generate_config_func.py: -------------------------------------------------------------------------------- 1 | import yaml 2 | import jinja2 3 | import nacl.public 4 | from base64 import b64encode 5 | from nacl.public import PrivateKey 6 | from ipaddress import IPv4Network , IPv6Network ,IPv4Address, IPv6Address 7 | from jinja2 import Template, DebugUndefined 8 | from subprocess import Popen, PIPE 9 | 10 | psk_db = {} 11 | keypair_db = {} 12 | port_allocate_db = {} 13 | port_allocate_db_i = {} 14 | openvpnkey_db = {} 15 | 16 | af_info = { 17 | "-4":{"MTU":20}, 18 | "-6":{"MTU":40} 19 | } 20 | 21 | def vars_load(var): 22 | print("INFO: loading old state") 23 | global psk_db 24 | global keypair_db 25 | global port_allocate_db 26 | global port_allocate_db_i 27 | global openvpnkey_db 28 | if "psk_db" in var: 29 | psk_db = var["psk_db"] 30 | if "keypair_db" in var: 31 | keypair_db = var["keypair_db"] 32 | if "port_allocate_db" in var: 33 | port_allocate_db = var["port_allocate_db"] 34 | if "port_allocate_db_i" in var: 35 | port_allocate_db_i = var["port_allocate_db_i"] 36 | if "openvpnkey_db" in var: 37 | openvpnkey_db = var["openvpnkey_db"] 38 | 39 | def vars_dump(): 40 | return { 41 | "psk_db": psk_db, 42 | "keypair_db": keypair_db, 43 | "port_allocate_db": port_allocate_db, 44 | "port_allocate_db_i": port_allocate_db_i, 45 | "openvpnkey_db": openvpnkey_db, 46 | } 47 | 48 | def keygen(): 49 | private = PrivateKey.generate() 50 | return ( 51 | b64encode(bytes(private)).decode("ascii"), 52 | b64encode(bytes(private.public_key)).decode("ascii"), 53 | ) 54 | def get_psk(id,id2): 55 | dbkey = (min(id,id2),max(id,id2)) 56 | if dbkey in psk_db: 57 | return psk_db[dbkey] 58 | else: 59 | psk ,_ = keygen() 60 | psk_db[dbkey] = psk 61 | return psk_db[dbkey] 62 | 63 | def get_keypair(id): 64 | if id in keypair_db: 65 | return keypair_db[id] 66 | keypair_db[id] = keygen() 67 | return keypair_db[id] 68 | 69 | def allocate_port(id,id2,port_db,port_base): 70 | if id not in port_db: 71 | port_db[id] = { id2: port_base } 72 | return port_db[id][id2] 73 | elif id2 not in port_db[id]: 74 | for p in range(port_base, 65535): 75 | pfound = False 76 | for _,usedp in port_db[id].items(): 77 | if p == usedp: 78 | pfound = True 79 | break 80 | if pfound == False: 81 | port_db[id][id2] = p 82 | return port_db[id][id2] 83 | return port_db[id][id2] 84 | 85 | def get_wg(server,client): 86 | conftemplate = Template(open('wg/wg.conf').read(), undefined=DebugUndefined) 87 | setuptemplate = Template(open('wg/wg.sh').read(), undefined=DebugUndefined) 88 | server["port"] = 0 89 | client["port"] = 0 90 | def renderconf(server,client): 91 | spri,spub = get_keypair(server["id"]) 92 | cpri,cpub = get_keypair(client["id"]) 93 | if "port" not in server or server["port"] == 0: 94 | server["port"] = allocate_port(server["name"],client["ifname"],port_allocate_db,server["port_base"]) if server["endpoint"] != "NAT" else 0 95 | if "port" not in client or client["port"] == 0: 96 | client["port"] = allocate_port(client["name"],server["ifname"],port_allocate_db,client["port_base_i"]) if client["endpoint"] != "NAT" else 0 97 | render_params = { 98 | 'wg': { 99 | 'pri': spri, 100 | 'port': server["port"], 101 | "pub": cpub, 102 | "psk": get_psk(server["id"],client["id"]), 103 | "endpoint": client["endpoint"] + ":" + str(client["port"]) if client["endpoint"] != "NAT" else None 104 | }, 105 | } 106 | return conftemplate.render(**render_params) 107 | def rendersetup(param): 108 | render_params = { 109 | "ifname" : param["ifname"] 110 | } 111 | return setuptemplate.render(**render_params) 112 | spri,spub = get_keypair(server["id"]) 113 | cpri,cpub = get_keypair(client["id"]) 114 | conf_s = { 115 | "up": rendersetup(client), 116 | "update": f'', 117 | "reconnect": "", 118 | "down": "ip link del " + client["ifname"], 119 | "confs": {".conf": renderconf(server,client) }, 120 | "MTU": 40 121 | } 122 | conf_c = { 123 | "up": rendersetup(server), 124 | "update": f'wg set { server["ifname"] } peer "{ spub }" endpoint "{ server["endpoint_ip"] + ":" + str(server["port"]) }"', 125 | "reconnect": f'update_wg_peer { server["ifname"] } "{ spub }" "{ server["endpoint_ip"] + ":" + str(server["port"]) }" "{{{{ confpath }}}}.conf"', 126 | "down": "ip link del " + server["ifname"], 127 | "confs": {".conf": renderconf(client,server) }, 128 | "MTU": 40 129 | } 130 | return conf_s , conf_c 131 | 132 | def get_wg_udp2raw(server,client): 133 | #print(server,client) 134 | #return 135 | server["port"] = allocate_port(server["name"],client["ifname"],port_allocate_db ,server["port_base"]) 136 | server["port_wg_local"] = allocate_port(server["name"],client["ifname"],port_allocate_db_i,server["port_base_i"]) 137 | client["port_udp2raw_local"] = allocate_port(client["name"],server["ifname"],port_allocate_db_i,client["port_base_i"]) 138 | server_w = {**server} 139 | client_w = {**client} 140 | server_w["endpoint"] = "127.0.0.1" 141 | server_w["port"] = server["port_wg_local"] 142 | client_w["endpoint"] = "NAT" 143 | wg_s, _ = get_wg(server_w,client_w) 144 | server_w["port"] = client["port_udp2raw_local"] 145 | _ , wg_c = get_wg(server_w,client_w) 146 | conf_s, conf_c = wg_s, wg_c 147 | conf_s["up"] += "\n" + f'udp2raw -s -l 0.0.0.0:{server["port"]} -r 127.0.0.1:{server["port_wg_local"]} -a -k "{get_psk(server["id"],client["id"])[:10]}" --raw-mode faketcp &' 148 | conf_s["up"] += '\necho $! > {{ confpath }}.pid' 149 | conf_c["up"] += "\n" + f'udp2raw -c -r {server["endpoint_ip"]}:{server["port"]} -l 127.0.0.1:{client["port_udp2raw_local"]} -k "{get_psk(server["id"],client["id"])[:10]}" --raw-mode faketcp -a &' 150 | conf_c["up"] += '\necho $! > {{ confpath }}.pid' 151 | conf_s["down"] += '\nkill $(cat {{ confpath }}.pid)' 152 | conf_c["down"] += '\nkill $(cat {{ confpath }}.pid)' 153 | conf_s["update"] = "" 154 | conf_c["update"] = "# Not support yet" 155 | conf_s["reconnect"] = "" 156 | conf_c["reconnect"] = "# Not support yet" 157 | conf_s["MTU"] = 140 158 | conf_c["MTU"] = 140 159 | return conf_s,conf_c 160 | 161 | def get_gre(server,client): 162 | conf_s = { 163 | "up": f'ip tunnel add {client["ifname"]} mode gre remote { client["endpoint_ip"] } ttl 255', 164 | "update": "", 165 | "reconnect":"", 166 | "down": "ip link del " + client["ifname"], 167 | "confs": {}, 168 | "MTU": 4 169 | } 170 | conf_c = { 171 | "up": f'ip tunnel add {server["ifname"]} mode gre remote { server["endpoint_ip"] } ttl 255', 172 | "update": "", 173 | "reconnect":"", 174 | "down": "ip link del " + server["ifname"], 175 | "confs": {}, 176 | "MTU": 4 177 | } 178 | if server["endpoint"] == "NAT": 179 | raise ValueError(f'Endpoint can\'t be NAT at gre tunnel: { client["id"] } -> { server["id"] }' ) 180 | if client["endpoint"] == "NAT": 181 | raise ValueError(f'Endpoint can\'t be NAT at gre tunnel: { server["id"] } -> { client["id"] }' ) 182 | return conf_s , conf_c 183 | 184 | def get_openvpn(server,client): 185 | server["port"] = allocate_port(server["name"],client["ifname"],port_allocate_db,server["port_base"]) 186 | ovpncfg = get_openvpn_config(server["id"],client["id"]) 187 | conf_s = { 188 | "up": f'openvpn --port { server["port"] } --cipher AES-256-CBC --proto tcp-server --dev-type tun --dev { client["ifname"] } --secret $(pwd)/{{{{ confpath }}}}.key --script-security 2 --up $(pwd)/{{{{ setupippath }}}} --writepid $(pwd)/{{{{ confpath }}}}.pid --log /dev/stdout --daemon', 189 | "update": "", 190 | "reconnect":"", 191 | "down": 'kill $(cat {{ confpath }}.pid)', 192 | "confs": { ".key": ovpncfg["static.key"] }, 193 | "MTU": 20 194 | } 195 | conf_c = { 196 | "up": f'openvpn --remote { server["endpoint"] } --port { server["port"] } --cipher AES-256-CBC --proto tcp-client --dev-type tun --dev { server["ifname"] } --secret $(pwd)/{{{{ confpath }}}}.key --script-security 2 --up $(pwd)/{{{{ setupippath }}}} --writepid $(pwd)/{{{{ confpath }}}}.pid --log /dev/stdout --daemon', 197 | "update": "", 198 | "reconnect":"", 199 | "down": 'kill $(cat {{ confpath }}.pid)', 200 | "confs": { ".key": ovpncfg["static.key"] }, 201 | "MTU": 20 202 | } 203 | return conf_s , conf_c 204 | 205 | def get_openvpn_key(id,id2): 206 | dictkey = (id,id2) 207 | if dictkey in openvpnkey_db: 208 | return openvpnkey_db[dictkey] 209 | proc = Popen("openvpn --genkey --secret /dev/stdout", shell=True, stdout=PIPE) 210 | stdout, stderr = proc.communicate() 211 | if stderr != None and len(stderr) > 0: 212 | raise Exception(stderr) 213 | openvpnkey_db[dictkey] = stdout.decode() 214 | return openvpnkey_db[dictkey] 215 | 216 | def get_openvpn_config(id,id2): 217 | key = get_openvpn_key(id,id2) 218 | return { 219 | "static.key" : key 220 | } 221 | 222 | 223 | def get_v4(id,net): 224 | first = net[0] 225 | ip = first + id 226 | if ip in net: 227 | return ip 228 | else: 229 | raise ValueError(f'{ip} is not in {net}') 230 | def get_v6(id,net): 231 | first = net[0] 232 | ip = first + id * (2**64) + 1 233 | if ip in net: 234 | return ip 235 | else: 236 | raise ValueError(f'{ip} is not in {net}') 237 | def get_v6ll(id,ip): 238 | return ip + id 239 | 240 | tunnels = { 241 | None: None, 242 | "gre": get_gre, 243 | "wg_udp2raw":get_wg_udp2raw, 244 | "wg_high":get_wg, 245 | "openvpn": get_openvpn, 246 | "wg":get_wg, 247 | 248 | } 249 | 250 | tunnelist = list(tunnels.keys()) -------------------------------------------------------------------------------- /generate_config.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | import jinja2 3 | import yaml 4 | import os 5 | import shutil 6 | import string 7 | import ipaddress 8 | 9 | import ruamel.yaml 10 | from ruamel.yaml.constructor import SafeConstructor 11 | 12 | os.umask(0o022) 13 | 14 | class PrettySafeLoader(yaml.SafeLoader): 15 | def construct_python_tuple(self, node): 16 | return tuple(self.construct_sequence(node)) 17 | 18 | PrettySafeLoader.add_constructor( 19 | u'tag:yaml.org,2002:python/tuple', 20 | PrettySafeLoader.construct_python_tuple) 21 | yaml.Dumper.ignore_aliases = lambda *args : True 22 | 23 | if not os.path.isfile("input/all_node.yaml"): 24 | print("WARN: input/all_node.yaml not found, using default template") 25 | shutil.copyfile("all_node.yaml", "input/all_node.yaml", follow_symlinks=True) 26 | 27 | if not os.path.isfile("input/generate_config_func.py"): 28 | print("WARN: input/generate_config_func.py not found, using default template") 29 | shutil.copyfile("generate_config_func.py", "input/generate_config_func.py", follow_symlinks=True) 30 | 31 | from input.generate_config_func import * 32 | 33 | gen_conf = ruamel.yaml.safe_load(open("input/all_node.yaml").read()) 34 | 35 | if os.path.isfile("input/state.yaml"): 36 | vars_load(yaml.load(open("input/state.yaml").read(), Loader=PrettySafeLoader)) 37 | 38 | for k,v in gen_conf["node_list"].items(): 39 | gen_conf["node_list"][k]["param"] = {} 40 | for dk, dv in gen_conf["defaults"].items(): 41 | if dk not in gen_conf["node_list"][k]: 42 | gen_conf["node_list"][k][dk] = dv 43 | elif type(dv) == dict: 44 | gen_conf["node_list"][k][dk] = {**dv, **gen_conf["node_list"][k][dk]} 45 | 46 | net4 = IPv4Network(gen_conf["network"]["v4"]) 47 | net6 = IPv6Network(gen_conf["network"]["v6"]) 48 | net6ll = IPv6Address(gen_conf["network"]["v6ll"]) 49 | 50 | result = {gen_conf["node_list"][n]["name"]:{"igp_tunnels":{},"bird/ibgp.conf":[],"bird/ibgp.conf.j2":"","resolvips":{},"ups":{},"updates":{},"reconnects":{},"downs":{},"self_net":{}} for n in gen_conf["node_list"]} 51 | 52 | def get_iface_full(name,af): 53 | n = gen_conf["iface_prefix"] + name + af 54 | if len(n) >= 16: 55 | raise ValueError(f"The interface name: {n} must be less than 16 (IFNAMSIZ) bytes.") 56 | return n 57 | 58 | def get_tun(af,node, id2): 59 | node_tun_info = {**gen_conf["defaults"]["tunnel"][af], **node["tunnel"][af]} 60 | if id2 not in node_tun_info: 61 | return node_tun_info[-1] , 1 62 | return node_tun_info[id2] , 0 63 | 64 | def get_bash_var_name(strin): 65 | allowed = set( string.ascii_letters + string.digits + "_" ) 66 | return "".join(map(lambda x: x if x in allowed else "_" , strin)) 67 | 68 | for id, node in gen_conf["node_list"].items(): 69 | os.makedirs(gen_conf["output_dir"] + "/" + node["name"], exist_ok=True) 70 | os.makedirs(gen_conf["output_dir"] + "/" + node["name"] + "/igp_tunnels", exist_ok=True) 71 | os.makedirs(gen_conf["output_dir"] + "/" + node["name"] + "/bird", exist_ok=True) 72 | 73 | def get_net(ip,length): 74 | return ipaddress.ip_interface(str(ip) + "/" + str(length)).network 75 | 76 | for id, node in gen_conf["node_list"].items(): 77 | for id2, node2 in gen_conf["node_list"].items(): 78 | if id == id2: 79 | continue 80 | result[node["name"]]["bird/ibgp.conf"] += [{ "name": get_bash_var_name(gen_conf["iface_prefix"] + node2["name"]) ,"ip":get_v6(id2,net6) }] 81 | 82 | for af, end in node["endpoints"].items(): 83 | if af not in node2["endpoints"]: # process only if both side has same af 84 | continue 85 | if node["endpoints"][af] == "NAT" and node2["endpoints"][af] == "NAT": # skip if both side are NATed 86 | continue 87 | tuntype1, wildcard1 = get_tun(af,node,id2) 88 | tuntype2, wildcard2 = get_tun(af,node2,id) 89 | if (wildcard1,tunnelist.index(tuntype1)) > (wildcard2,tunnelist.index(tuntype2)): 90 | tuntype = tuntype2 91 | else: 92 | tuntype = tuntype1 93 | if tuntype1 != tuntype2: 94 | print("WARN: {af}: Tunnel type not match: {s}->{e}:{t1} , {e}->{s}:{t2}, selecting {tun}".format(af=af,s=node["name"],e=node2["name"],t1=tuntype1,t2=tuntype2,tun=tuntype)) 95 | if tuntype == None: 96 | continue 97 | #print(node["name"],">",node2["name"]) 98 | side_a = { 99 | **node, 100 | "id": id, 101 | "ifname": get_iface_full(node["name"],af), 102 | "endpoint": node["endpoints"][af], 103 | "endpoint_ip": "$ip_" + get_bash_var_name(node["name"] + af), 104 | "params": node["param"][tuntype] if tuntype in node["param"] else None 105 | } 106 | side_b = { 107 | **node2, 108 | "id": id2, 109 | "ifname": get_iface_full(node2["name"],af), 110 | "endpoint": node2["endpoints"][af], 111 | "endpoint_ip": "$ip_" + get_bash_var_name(node2["name"] + af), 112 | "params": node2["param"][tuntype] if tuntype in node2["param"] else None 113 | } 114 | 115 | setiptemplate = jinja2.Template(open('setip.sh').read()) 116 | MTU = min(node["MTU"],node2["MTU"]) 117 | MTU -= af_info[af]["MTU"] 118 | if side_a["endpoint"] == "NAT": 119 | continue 120 | try: 121 | if side_b["endpoint"] == "NAT" or (node["server_perf"],id) >= (node2["server_perf"],id2): 122 | aconf, bconf = tunnels[tuntype](side_a,side_b) 123 | else: 124 | bconf, aconf = tunnels[tuntype](side_b,side_a) 125 | MTU -= aconf["MTU"] 126 | def postprocess(conf,side,idd,nod,side2): 127 | integrate_up = True 128 | if "{{setupippath}}" in conf["up"].replace(" ",""): 129 | integrate_up = False 130 | conf["up"] = f'{get_bash_var_name(side2["endpoint_ip"][1:])}=$(resolveip {side2["endpoint"]})\n' + conf["up"] if side2["endpoint"] != "NAT" else conf["up"] 131 | if integrate_up: 132 | conf["up"] += "\n" + setiptemplate.render(ifname=side2["ifname"],MTU=MTU,ipv4=get_v4(idd,net4),ipv6=get_v6(idd,net6),ipv6ll=get_v6ll(idd,net6ll)) 133 | else: 134 | setupipsh = "#!/bin/bash\n" 135 | setupipsh += setiptemplate.render(ifname=side2["ifname"],MTU=MTU,ipv4=get_v4(idd,net4),ipv6=get_v6(idd,net6),ipv6ll=get_v6ll(idd,net6ll)) 136 | setupipsh = jinja2.Template(setupipsh).render(confpath = side2["ifname"],setupippath="igp_tunnels/" + side2["ifname"]+"_setip.sh") 137 | conf["confs"]["_setip.sh"] = setupipsh 138 | # resolvip 139 | conf["resolvip"] = "" 140 | if side2["endpoint"] != "NAT": 141 | conf["resolvip"] = f'{get_bash_var_name(side2["endpoint_ip"][1:])}=$(resolveip {side2["endpoint"]})' 142 | # up 143 | conf["up"] = jinja2.Template(conf["up"]).render(confpath = "igp_tunnels/" + side2["ifname"],setupippath="igp_tunnels/" + side2["ifname"]+"_setip.sh") 144 | conf["up"] = "\n".join(filter(None,conf["up"].split("\n"))) + "\n" 145 | # update 146 | if side2["DDNS"] == True: 147 | conf["update"] = "\n".join(filter(None,conf["update"].split("\n"))) + "\n" 148 | conf["update"] = jinja2.Template(conf["update"]).render(confpath = "igp_tunnels/" + side2["ifname"]) 149 | else: 150 | conf["update"] = "" 151 | # down 152 | conf["down"] = jinja2.Template(conf["down"]).render(confpath = "igp_tunnels/" + side2["ifname"]) 153 | # reconnect 154 | conf["reconnect"] = "\n".join(filter(None,conf["reconnect"].split("\n"))) + "\n" 155 | conf["reconnect"] = jinja2.Template(conf["reconnect"]).render(confpath = "igp_tunnels/" + side2["ifname"]) 156 | conf["reconnect_info"] = {"ip":get_v6ll(side2["id"] ,net6ll),"ifname":side2["ifname"],"script":conf["reconnect"]} 157 | for ck,cv in conf["confs"].items(): 158 | conf["confs"][ck] = jinja2.Template(cv).render(confpath = "igp_tunnels/" + side2["ifname"]) 159 | postprocess(aconf,side_a,id,node,side_b) 160 | postprocess(bconf,side_b,id2,node2,side_a) 161 | except ValueError as e: 162 | print("WARN: " + str(e)) 163 | continue 164 | if "confs" in aconf: 165 | result[gen_conf["node_list"][id ]["name"]]["igp_tunnels"][side_b["ifname"]] = aconf["confs"] 166 | if "confs" in bconf: 167 | result[gen_conf["node_list"][id2]["name"]]["igp_tunnels"][side_a["ifname"]] = bconf["confs"] 168 | result[gen_conf["node_list"][id ]["name"]]["ups"][str(id2).zfill(3) + af] = aconf["up"] 169 | result[gen_conf["node_list"][id2]["name"]]["ups"][str(id ).zfill(3) + af] = bconf["up"] 170 | result[gen_conf["node_list"][id ]["name"]]["resolvips"][str(id2).zfill(3) + af] = aconf["resolvip"] 171 | result[gen_conf["node_list"][id2]["name"]]["resolvips"][str(id ).zfill(3) + af] = bconf["resolvip"] 172 | result[gen_conf["node_list"][id ]["name"]]["updates"][str(id2).zfill(3) + af] = aconf["update"] 173 | result[gen_conf["node_list"][id2]["name"]]["updates"][str(id ).zfill(3) + af] = bconf["update"] 174 | result[gen_conf["node_list"][id ]["name"]]["reconnects"][str(id2).zfill(3) + af] = aconf["reconnect_info"] 175 | result[gen_conf["node_list"][id2]["name"]]["reconnects"][str(id ).zfill(3) + af] = bconf["reconnect_info"] 176 | result[gen_conf["node_list"][id ]["name"]]["downs"][str(id2).zfill(3) + af] = aconf["down"] 177 | result[gen_conf["node_list"][id2]["name"]]["downs"][str(id ).zfill(3) + af] = bconf["down"] 178 | result[gen_conf["node_list"][id ]["name"]]["self_net"] = {"v4": str(get_net(get_v4(id,net4) , 32)) , "v6": str(get_net(get_v6(id,net6), 64))} 179 | result[gen_conf["node_list"][id ]["name"]]["self_ip"] = {"v4": get_v4(id,net4) , "v6": get_v6(id2,net6)} 180 | result[gen_conf["node_list"][id2]["name"]]["self_net"] = {"v4": str(get_net(get_v4(id2,net4) , 32)) , "v6": str(get_net(get_v6(id,net6), 64))} 181 | result[gen_conf["node_list"][id2]["name"]]["self_ip"] = {"v4": get_v4(id2,net4) , "v6": get_v6(id2,net6)} 182 | 183 | 184 | for s,sps in result.items(): 185 | for e , confs in sps["igp_tunnels"].items(): 186 | for ext, content in confs.items(): 187 | open(gen_conf["output_dir"] + "/" + s + "/igp_tunnels/" + e + ext , "w", newline='\n').write(content) 188 | if ext.endswith(".sh"): 189 | os.chmod(gen_conf["output_dir"] + "/" + s + "/igp_tunnels/" + e + ext, 0o755) 190 | render_params = { 191 | 'allow_net': { 192 | 'v4': gen_conf["network"]["v4"], 193 | 'v6': gen_conf["network"]["v6"], 194 | }, 195 | 'self_net': sps["self_net"], 196 | 'interfaces': list(sps["igp_tunnels"].keys()) 197 | } 198 | babeldtemplate = jinja2.Template(open('babeld.conf').read()) 199 | babeldconf = babeldtemplate.render(**render_params) 200 | open(gen_conf["output_dir"] + "/" + s + "/babeld.conf" , "w", newline='\n').write(babeldconf) 201 | open(gen_conf["output_dir"] + "/" + s + "/up.sh" , "w", newline='\n').write( jinja2.Template(open('up.sh').read()).render(ups = list(sps["ups"].values()), self_ip = sps["self_ip"])) 202 | os.chmod(gen_conf["output_dir"] + "/" + s + "/up.sh" , 0o755) 203 | open(gen_conf["output_dir"] + "/" + s + "/update.sh" , "w", newline='\n').write( jinja2.Template(open('update.sh').read()).render(resolvips = list(filter(None,sps["resolvips"].values())) ,ups = list(filter(None,sps["updates"].values())), reconns = list(filter(lambda x:x["script"] != "",sps["reconnects"].values())))) 204 | os.chmod(gen_conf["output_dir"] + "/" + s + "/update.sh" , 0o755) 205 | open(gen_conf["output_dir"] + "/" + s + "/down.sh" , "w", newline='\n').write( jinja2.Template(open('down.sh').read()).render(downs = list(sps["downs"].values()))) 206 | os.chmod(gen_conf["output_dir"] + "/" + s + "/down.sh" , 0o755) 207 | open(gen_conf["output_dir"] + "/" + s + "/bird/ibgp.conf" , "w", newline='\n').write( jinja2.Template(open('bird_ibgp.conf').read()).render( interfaces = sps["bird/ibgp.conf"] ) ) 208 | open(gen_conf["output_dir"] + "/" + s + "/bird/igp_metric.conf.j2" , "w", newline='\n').write(open("bird_igp_metric.conf").read()) 209 | open(gen_conf["output_dir"] + "/" + s + "/bird/igp_metric.zero.conf" , "w", newline='\n').write(jinja2.Template(open('bird_igp_metric.conf').read()).render( neighbors = []) ) 210 | open(gen_conf["output_dir"] + "/" + s + "/update_cost.py" , "w", newline='\n').write(open("update_cost.py").read()) 211 | os.chmod(gen_conf["output_dir"] + "/" + s + "/update_cost.py" , 0o755) 212 | 213 | open(gen_conf["output_dir"] + "/babelweb2.sh" , "w", newline='\n').write(jinja2.Template(open('babeldweb2.sh').read()).render( result = result.values() , babeldweb = gen_conf["babeldweb"]) ) 214 | os.chmod(gen_conf["output_dir"] + "/babelweb2.sh" , 0o755) 215 | open("input/state.yaml","w", newline='\n').write(ruamel.yaml.dump(vars_dump())) 216 | --------------------------------------------------------------------------------