紀錄一下DN42內網搭建的過程

紀錄一下DN42搭建內網的過程

  1. 鏈路層
  2. 內網路由
  3. Anycast+DNS/RDNS
  4. Looking Glass+AutoPeer

鏈路隧道

首先要搞定的就是各節點的內網互連手段。
在DN42網路中,一般是使用VPN隧道。
真實網路則是光纜/網路線/無線電/wifi/衛星,當然你也可以在DN42中用這些
但DN42是用來低成本學習網路的,VPN最便宜

我自己用的VPN是我自己刻的Etherguard-VPN
這是一個Layer2 VPN,能根據單向延遲來選路,而不是雙向延遲。具體工作原理

隧道有分Layer2和Layer3,在這之上又分許多不同的軟體
L2和L3最大的差異就是是否攜帶了EtherFrame,EtherFrame裡面攜帶的是Src MacAddr和Dst MacAddr
L2 VPN有帶,L3 VPN沒帶

假設今天來了一個封包,我們本地看到這個封包,有3個下一跳可以選

如果是L2 VPN,選擇完以後,我們只須把下一跳的MacAddr填入EtherFrame,然後送入VPN即可。只建立一個隧道,後面多個下一跳很容易做到
如果是L3 VPN,因為沒有EtherFrame,我們必須對每個下一跳,分別建立各自的VPN隧道
有n個下一跳,就要建立n條隧道。然後根據下一跳,送入指定的隧道。只建立一個隧道,後面對應多個下一跳是不可能的

在我們的情境中
L2 VPN像是一台switch,所有電腦接入就在相同內網了
L3 VPN則像是單根網路線,p2p只有兩端。多台電腦互聯,就需要多根網路線了

L3 VPN,一對多連線模式也是存在的。像是openvpn/wireguard多個peer。但是在此種情境中,路由表示固定好的。wg設定檔的allowed ips,openvpn發配的ip,就已經決定好這個ip段該發去的peer節點了,由vpn server執行路由
但是DN42這裡,路由功能必須在自身節點上執行,而不是VPN server執行
所以L3 VPN只能使用p2p模式

但我自己覺得L3不是很方便,以這位群友為例,他的拓樸,內網就要配9條wg隧道

iBGP要求任意2節點的iBGP session兩兩互相可達,iBGP沒有維護內網路由的功能

L2 VPN 由VPN自身提供斷線繞路功能。但是在網路拓樸上,所有節點都是嚇一跳可達。繞路的行為被隱藏起來了
L3 VPN 只能p2p。內網全域的可達性就必須用OSPF/babel等協議維護

大家都使用哪個VPN方案呢? 這是某個DN42群裡的小調查
列出一些常見的VPN和層數
Layer 2 Layer 3
Zerotier Wireguard
n2n IPIP
VpnCloud GRE
Tinc Tinc
OpenVPN OpenVPN

作業系統是Debian 11,啟用bullseye-backports

echo "deb http://deb.debian.org/debian bullseye-backports main" >> /etc/apt/sources.list
apt update

安裝gitbird 2.0.8golang 1.17

apt install git golang-src/bullseye-backports golang-go/bullseye-backports bird2/bullseye-backports
git clone https://github.com/KusakabeSi/EtherGuard-VPN
cd EtherGuard-VPN
make

然後請參考這篇教學,建立一個gensuper.yaml
我的情況:

Config output dir: /tmp/eg_gen
ConfigTemplate for super node: ""
ConfigTemplate for edge node: ""
Network name: dn42-egnet
Super Node:
  Listen port: 14141                                              #赫茲雲的port
  EdgeAPI prefix: /kskb_eg_net/eg_api
  Endpoint(IPv4)(optional): de.hertz.vm.kskb.eu.org               #赫茲雲的ip
  Endpoint(IPv6)(optional): "[::]"
  Endpoint(EdgeAPI): https://dn42egapi.kskb.eu.org/kskb_eg_net/eg_api #這個EdgeAPI之後會套一層argo tunnel
Edge Node: Node IDs: "[1,9,10]" MacAddress prefix: "" IPv4 range: "" IPv6 range: "" IPv6 LL range: fe80::42:1817:1/112 # VPN接口上只放了ipv6 link-local地址,真正的地址放在dn42-dummy上面,並且透過iBGP廣播給其他節點

然後就生成了4個etherguard設定檔,一個super node,三個edge node。

./etherguard-go -mode gencfg -cfgmode super -config ~/gensuper.yaml
/tmp/eg_gen/dn42-egnet_edge01.yaml
/tmp/eg_gen/dn42-egnet_edge09.yaml
/tmp/eg_gen/dn42-egnet_edge10.yaml
/tmp/eg_gen/dn42-egnet_super.yaml
mv /tmp/eg_gen /etc/eggo

把edge分發到各台電腦上,然後在Supernode執行

./etherguard-go -config [設定檔位置] -mode super

在EdgeNode執行

./etherguard-go -config [設定檔位置] -mode edge

註冊argo tunnel,用來幫EdgeAPI加上https,還不用renew證書,太方便了

cloudflared tunnel create dn42egapi
cloudflared tunnel route dns dn42egapi dn42egapi.kskb.eu.org
cloudflared  --credentials-file /root/.cloudflared/ddef52c6-ae6a-4151-a177-03168657018c.json --url http://127.0.0.1:14141 tunnel run dn42egapi

然後想辦法讓上面那些程式跑在背景+開機自啟即可。
如果抄我的作法,各位各顯神通吧,看是systemd還是tmux還是rc-local放背景

到這邊,我的鏈路層搭建完畢了






我自己是用systemd來管理服務的,以下單純紀錄一下我自己的配置

Etherguard

https://github.com/KusakabeSi/EtherGuard-VPN/tree/master/example_config/systemd

cloudflared

#/etc/systemd/system/cloudflared.service
[Unit]
Description=Argo Tunnel
After=network.target

[Service]
TimeoutStartSec=0
Type=notify
ExecStart=cloudflared  --credentials-file /root/.cloudflared/ddef52c6-ae6a-4151-a177-03168657018c.json --url http://127.0.0.1:14141 tunnel run dn42egapi
Restart=on-failure
RestartSec=5s

[Install]
WantedBy=multi-user.target

啟動服務

systemctl daemon-reload
cd /etc/eggo
mv dn42-egnet_super.yaml  super.yaml
mv dn42-egnet_edge10.yaml edge.yaml

systemctl start etherguard-super
systemctl start etherguard-edge
systemctl start cloudflared
systemctl enable etherguard-super
systemctl enable etherguard-edge
systemctl enable cloudflared

路由協議

接下來是路由協議的部分

本地新增一個dummy interface,把自己的IP掛在裡面
隧道ip是隧道ip,僅限隧道使用。真實ip則是掛在dummy-interface上,和socket作互動
ip link add dn42-dummy type dummy
ip addr add 172.22.77.33/28 dev dn42-dummy
ip addr add fd28:cb8f:4c92::33/48 dev dn42-dummy
ip link set dn42-dummy up
問: 為什麼隧道上面要掛上隧道ip(通常使用link-local),而真實ip只掛在dummy-interface上呢?
答: 因為我們要用動態路由協議

例如 eth0 綁定192.168.0.1/24 ,就隱含了192.168.0.0/24 via eth0 的靜態路由
或是 ip addr add 172.22.77.33 peer 172.20.22.44 dev eth0,就隱含了 172.20.22.44/32 via eth0 的靜態路由
動態路由協議溝通時,使用隧道ip進行溝通。如果隧道ip掛了,就知道鏈路中斷,需要重新計算/分發路由
如果隧道ip==真實ip,鏈路中段也就意味著真實ip也掛了。雖然屬於真實ip,路由協議仍會幫這個ip分發新的路由。但是綁定設備隱含的/32路由具有最高優先權,導致連接仍然中斷

所以路由器和路由器之間的連線,隧道地址要和真實地址分開
隧道地址單純用來讓路由協議溝通,以及探測連接成功與否
真實ip則是路由由路由協議分發

不過iBGP其實不用link-local,可以直接用真實地址。
因為iBGP並不負責內網路由的計算,只負責在各節點間傳遞外網路由。
內網路由用ospf/babel等協議溝時,會自己用 ff02::5/ff02::1:6 這樣的ip進行溝通
在此行況下,iBGP就需要啟用multihop。就不能用鏈路地址了,必須使用真實地址
因為鏈路地址的可達性隨鏈路狀態變化,斷線就沒了。但是iBGP應該隨時保持fullmesh連線狀態
此時,就必須iBGP peer時使用真實地址,內網真實地址的路由ospf/babel分發。iBGP只管連線就好,傳遞外網路由

繼上一篇搭建好鏈路層以後,我的網路結構大致上長這樣:


為求簡我內網iGP預計僅使用iBGP,不使用OSPF/babel/Openflow/SDN等

大家都有自己的作法,這是某個DN42群裡的小調查



因此,我的bird.conf修改成這樣:

接下來,針對我的修改處做出解釋:

首先,我使用了bgp community功能。
community的功能是自己賦予的,我使用了 (21817, *) 這一段,做為我內網使用。
應該不會有別人也用這段吧?不會吧?

我定義了LocalNET,意思是「本台機器管理的網段」
我在Static路由裡面新增一段LocalNET,賦予自訂義 comminuty (21817,11111) ,標記這條路由是LocalNET。 (65535,6582) 則是no-advertise,不要再把這條路由給別人了


然後protocol kernel修改成這樣:
意思分別是
  1. Static路由拒絕,不要把本地定義的Static路由寫入系統路由表
  2. LocalNET接受,iBGP傳來的LocalNET要寫入系統路由表
  3. is_self_net() 拒絕。內網部分應該由LocalNET處理
然後,修改 is_valid_network() 這2個function



這2個函數會在eBGP,import/export 的時候呼叫,所以我幫他們增加了功能
  1. 如果出現了(21817,11111) 屬性,就拒絕。 LocalNET只在iBGP內傳遞,不應被廣播出去
  2. 如果出現了(21817,*) 屬性,就刪掉。因為這是我內部使用的,不管是別人給我/我給別人的路由上,都得刪掉
最後就是iBGP的template了
v4/v6都是import all/export all

然後在 nodes/us.conf 寫入這些,建立BGP session:
每對連線都要寫一個設定檔,需要n(n-1)份設定檔。
KSKB-DN42 網路,6份設定檔我是手寫
Kusakabe-Neo,9個節點72份設定檔,我則是用python腳本生成了

以上這些操作,達到的效果類似這樣:
對內,每台機器都廣播各自的LocalNET,各自廣播得範圍不能重複,避免送錯地方
對外,則是廣播整塊OWNNET,也就是DN42申請到的IP範圍

大概如圖所示
外面的封包會先抵達任何一個邊界路由器,然後經由iBGP宣告的LocalNET抵達真正該去的地方
類似藍色箭頭,按照1 2 3的順序抵達

不過這樣搞是我自己想的,不知道清蒸不清蒸

Anycast IP + DNS/RDNS

我是先搭建 Kusakabe-Neo 的DNS/RDNS/Anycast的,當時是參考這個範本

經過我的修修改改,弄出這些設定檔。其中一部分是bird裝好就有的

然後KSKB-Network的部分,我就打算直接抄Kusakabe-Neo弄出來的設定檔了

首先是Anycast IP,在所有節點的dn42-dummy上面。都新增Anycast IP
我決定使用 172.22.77.46 和 fd28:cb8f:4c92:bbbb::53 作為我的dns以及Anycast IP

ip addr add 172.22.77.46 dev dn42-dummy
ip addr add fd28:cb8f:4c92:bbbb::53 dev dn42-dummy

因為我們所有節點對外廣播的時候,都是宣告有這個IP的
但是這個anycast ip我並未加入bird設定檔的LocalNET裡面,所以內網並沒有廣播這個ip
如果未設定,那他就會因為找不到路,而丟棄這個封包,造成部分區域連不上anycast
所以每台都要設定,沒有內網路由,而是全部節點都把這個IP當成本地IP

不想要全部節點都配置,可以這樣改:
bird裡面新增Anycast的網段,並且設定「如果本地有Anycast,就不import。如果本地沒有,就import」
就可以讓沒有設定Anycast的節點,內網路由到最近的有Anycast的節點

但是我節點少,決定全部節點都部屬。而且多部屬一些也能增加冗餘

再來,更新一下ns資訊。以前是抄藍天的,分為ns1和ns2。
但是發現使用Anycast更好,因為原本是隨機選擇,只能附載平衡而不能fallback。

不如用Anycast,A節點掛了,同時BGP就沒了,於是會被路由到B節點上,有fallback效果
節點同時存活,也能因為Anycast自動抵達最近節點,有附載平衡的效果


首先安裝bind9

apt install bind9 bind9utils

不過...本篇就真的只是記錄了,不太具有教學性質
因為接連打這麼多,我累了。裡面的設定檔的含意,可能不會有過多的解釋

git clone https://github.com/KSKBpage/miscblog
cd miscblog/articles/20211214-Neonetwork-DNS-RDNS
mv /etc/bind/ /etc/bind_bak
cp -r bind /etc/bind

然後找出有我Kusakabe-Neo的資訊,替換成我KSKB-Network的資訊

/etc/bind
grep -Irn kskb.neo

可以看到這4個檔案包含了我Kusakabe-Neo的資訊

db.10.127.111
db.fd10.127.e00f
db.kskb.neo
named.conf.default-zones

根據KSKB-Network的資訊重命名

mv db.10.127.111 db.172.22.77.28
mv db.fd10.127.e00f db.fd28:cb8f:4c92
mv db.kskb.neo db.kskb.dn42

然後修改 named.conf.default-zones 的這些zone,都要改成指向剛剛的檔案

kskb.neo
111.127.10.in-addr.arpa
f.0.0.e.7.2.1.0.0.1.d.f.ip6.arpa

其中有一點不一樣,因為NeoNetwork我是/24 網段,但是DN42是 /28
所以要用上 rfc2317,格式不太一樣,長這樣

zone "32/28.77.22.172.in-addr.arpa." {
    type master;
    file "/etc/bind/db.172.22.77.32";
};

接著編輯這些檔案,改成自己的DNS紀錄。最上面有個SOA紀錄,那邊也要記得改,容易忘記
還有,PTR紀錄的ip是倒過來寫的

db.172.22.77.28
db.fd28:cb8f:4c92
db.kskb.dn42

然後,記得編輯named.conf.options 。
額外加入 check-names master ignore; ,因為預設情況 / 字元好像不能出現在zone裡面,要加上這個選項去允許他

    check-names master ignore;
    listen-on port 53 { 172.22.77.46; };
    listen-on-v6 port 53 { fd28:cb8f:4c92:bbbb::53; };
    # 為了安全性只允許自己人進行遞迴查詢
    allow-recursion { 172.22.77.32/28; fd28:cb8f:4c92/48 ;};

然後把 setup_dummy.sh 改成自己的Anycast,設定開機自啟動

最後,把改好的設定檔同步到所有節點,設定好dummy interface和ip,並啟動bind9

apt install bind9 bind9utils
cd /etc
mv bind bind_bak
git clone https://github.com/kskbconfig/KSKB-Network-BIND9 bind
bind/setup_dummy.sh
echo "/etc/bind/setup_dummy.sh" >> /etc/rc.local
systemctl restart named
systemctl enable named

Looking Glass + AutoPeer

累了,紀錄下過程.. 能work即可,就當紀錄給自己看

peer資訊頁

我是用cloudflare page生成各節點的peer資訊頁
https://github.com/KSKBpage/dn42node

編輯一下xx.env,就會生成對應網頁了

Looking Glass/Autopeer

nginx 設定檔,用途是分流2個後端,looking glass和autopeer
map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
}


server {
    listen 127.0.0.1:3236;
    client_max_body_size 0;

    server_name _;

    # Managing literal requests to the JupyterHub front end
    location / {
        proxy_pass http://127.0.0.1:3235;
        proxy_set_header Host $host;
        proxy_set_header X-Real-Scheme $scheme;
        proxy_set_header X-Real-IP $remote_addr;
        #proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_http_version  1.1;

        # websocket headers
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
    }

    location /autopeer/ {
        proxy_pass http://127.0.0.1:4242;
        proxy_set_header Host $host;
        proxy_set_header X-Real-Scheme $scheme;
        proxy_set_header X-Real-IP $remote_addr;
        #proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_http_version  1.1;

        # websocket headers
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
    }

}
cloudflared 反代nginx,讓外界連線
cloudflared tunnel create tw42
cloudflared tunnel route dns tw42 tw42.kskb.eu.org
下載編譯必要的東西
cd ~/gitrs
git clone https://github.com/KusakabeSi/DN42-AutoPeer.git
git clone https://github.com/KusakabeSi/bird-lg-go.git
cd bird-lg-go
# Build frontend binary
cd frontend
go build -ldflags "-w -s" -o frontend
cd ..

# Build proxy binary
cd proxy
go build -ldflags "-w -s" -o proxy
cd ..
設定DN42-Autopeer
把倉庫 https://github.com/KusakabeSi/DN42-AutoPeer.git clone下來
把自己的資訊填入 my_config.json 和 my_parameters.json
注意的是 urlprefix 要填 autopeer ,和nginx 設定檔呼應

然後把下面4隻程式跑在背景。懶得寫 systemd 了,把這些塞進 /etc/rc.local 就完事了  
tmux new -d -s dn42ap -c DN42-AutoPeer   python3 DN42AutoPeer.py
tmux new -d -s dn42cf                    cloudflared  --credentials-file  /root/.cloudflared/66fe2b05-81ff-47dc-826c-4180eb27c82d.json --url http://127.0.0.1:3236 tunnel run tw42
tmux new -d -s birdlgp -c bird-lg-go     proxy/proxy --bird=/var/run/bird/bird.ctl --listen="127.0.0.1:3234"
tmux new -d -s birdlgf -c bird-lg-go     frontend/frontend --bgpmap-info="asn,as-name" --servers="Taiwan<127.0.0.1>" --listen="0.0.0.0:3235" --proxy-port=3234 --whois=172.22.0.43 --dns-interface="" --navbar-brand="Peer with me (Taiwan)!" --navbar-brand-url="/autopeer/"

留言