之前用Wireguard和Openvpn瞎搞了许多隧道,使用ospf初步连接了各个机子,随着设备数量越来越多网络完全混乱起来之后打算完全推翻重做。
新的网络仍然称为Cupnet,使用BGP协议作为基础,以高可用与软件定义为目标,设计一套协议组合以实现更为方便的网络管理与可用性更高的网络架构。
当前主要目标
高可用的网络
抗封锁的底层传输协议
- 支持V2ray各项协议作为传输层备份
- 支持Wireguard和OpenVPN作为底层协议
- 存活探测自动切换机制
- 实现软定义大二层的EVPN
- 实现一键式一站式在线发布配置
- 一体化的底层网络
未来可能的目标
- 分布式去中心化的密钥授权网络管理
- 结合EVPN的Vrrp分布式网关自动化配置
- Golang实现的应用层节点
基础
当前需要探索的目标如下:
- 撰写与自动化V2ray的传输层配置、WG对接V2ray的配置、FRR的IP、点对点路由、BGP Peer配置。
- 撰写与自动化EVPN的配置并实现绑定接口
- 自动化搭建用于客户端的L2TP+Ipsec服务器(若服务器支持)
- 对不同的连接方式配置优先级并使用守护进程探测接口连通性,在断联时及时启用备案
实现以中心化控制器驱动的服务端配置
- 配置双方的V2ray+Wireguard
- 配置双方FRR的IP与点对点路由、BGP Peer
- 配置双方的EVPN,包括网桥和VXLAN
- 使用URL为服务配置桥接网络接口
全部节点都使用Linux+FRRouting实现路由层(因为别的系统我不会用,而且懒得写别的系统脚本……),V2ray有可能更换成sing-box(这个代理协议最近有点火)。大目标是只需要配置一个域名和安装一个脚本,做一点小小的认证就可以作为受控节点直接接入Cupnet。
尝试
V2ray与WG自动对接部分(客户端-服务端模式)
基础说明
如果客户端位于具有审查的防火墙内,可以使用V2ray作为传输协议,连接到服务端的V2ray,隧道转发到Wireguard的端口内。大致如下
CWG---CV2ray===SV2ray---SWG
设计
这套系统里面的V2ray使用独立的Config文件搭建服务器,由于Wireguard的Endpoint要求必须使用网络地址进行连接,V2ray将会随机生成10000以上的一个数字,作为UDP端口号进行监听,并将所有的请求转发到对端的V2ray与指定的目标端口号。而Wireguard会连接本地V2ray监听的端口。
以上系统要求这四个部分的以下信息。
- Client Wireguard: 客户端密钥、客户端点对点IPv4、服务端点对点IPv4、客户端点对点IPv6、服务端点对点IPv6、服务端公钥、V2ray随机生成的端口、连接名称、默认MTU大小
- Client V2ray: V2ray随机生成的端口、服务端V2ray连接信息(包括地址、端口、UUID或密码、传输协议、伪装域名、PATH、TLS设定)、内部服务端端口
- Server V2ray: 服务端V2ray连接信息(包括地址、端口、UUID或密码、传输协议、伪装域名、PATH、TLS设定)、服务端端口(用于Block其他端口)、连接名称、内部服务端端口
- Server Wireguard: 服务端密钥、内部服务端端口、客户端点对点IPv4、服务端点对点IPv4、客户端点对点IPv6、服务端点对点IPv6、客户端公钥、连接名称、默认MTU大小
预想V2ray只使用Vmess TCP、Vmess+Ws、Vmess+Ws+tls这三种协议。
总共需要收集的信息:
Wireguard部分
- 客户端密钥
- 服务端密钥
- 客户端点对点IPv4(为IPv4链路网址前缀+客户端序号)
- 服务端点对点IPv4(为IPv4链路网址前缀+服务端序号)
- 客户端点对点IPv6(为IPv6链路网址前缀+客户端序号)
- 服务端点对点IPv6(为IPv6链路网址前缀+服务端序号)
- 内部服务端端口
V2ray部分
Vmess TCP
- 地址
- 端口
- UUID或密码
Vmess+Ws
- 地址
- 端口
- UUID或密码
- 伪装域名
- PATH
Vmess+Ws+TLS
- 地址
- 端口
- UUID或密码
- 伪装域名
- WS PATH
- TLS SNI
- 是否跳过证书验证
综合信息
- 连接名称(需要为英文,九个字节内)
- 客户端序号(3位,1-255)
- 服务端序号(3位,1-255)
- IPv4链路网址前缀(169.254.0.0/16中选择一块/24地址块)
- IPv6链路网址前缀(fe80::/10中选择一块/120地址块)
- 默认MTU大小
配置文件
根据以上需要的信息可以得出配置文件格式。
Client的Wireguard文件
# 文件名为 {连接名称}_{服务端序号}.conf
[Interface]
PrivateKey = {客户端密钥}
Table = off
PostUp = ip addr add {客户端点对点IPv4}/32 peer {服务端点对点IPv4}/32 dev %i; ip addr add {客户端点对点IPv6} peer {服务端点对点IPv6} dev %i
MTU = {默认MTU大小}
[Peer]
PublicKey = {服务端公钥} #由服务端生成传输而来
AllowedIPs = 0.0.0.0/0,::/0
Endpoint = 127.0.0.1:{V2ray随机生成的端口}
PersistentKeepalive = 10
Server的Wireguard文件
文件名为 {连接名称}_{客户端序号}.conf
[Interface]
PrivateKey = {服务端密钥}
ListenPort = {内部服务端端口}
Table = off
PostUp = ip addr add {服务端点对点IPv4}/32 peer {客户端点对点IPv4}/32 dev %i; ip addr add {服务端点对点IPv6} peer {客户端点对点IPv6} dev %i
MTU = {默认MTU大小}
[Peer]
PublicKey = {客户端公钥} #由客户端生成传输而来
AllowedIPs = 0.0.0.0/0,::/0
V2ray的服务端配置(TCP与WS)
{
"log":{"loglevel":"debug"},
"inbounds": [
//连接专有inbound_start
{
"tag": "{连接名称}-in",
"listen": "{地址}",
"port": {端口},
"protocol": "vmess",
"settings": {
"clients": [
{
"id": "{UUID}", //可随机生成
"alterId": 0
}
]
}
// 若使用WS,则需要添加以下分段
//start
,"streamSettings": {
"network": "ws",
"wsSettings": {
"path": "{WS PATH}"
}
}
//end
}
//连接专有inbound_end
],
"outbounds": [
//连接专有outbound_start
{
"protocol": "freedom",
"settings": {
"domainStrategy": "AsIs",
"redirect": "127.0.0.1:{内部服务端端口}"
},
"tag": "{连接名称}-out"
},
//连接专有outbound_end
{
"protocol": "blackhole",
"settings": {},
"tag": "block"
}
],
"routing":{
"rules":[
//连接专有routing_start
{
"type":"field",
"inboundTag": "{连接名称}-in",
"outboundTag":"{连接名称}-out",
"network":"udp"
}
//连接专有routing_end
]
}
}
V2ray的客户端配置
{
"log":{"loglevel":"debug"},
"inbounds": [
//连接专有inbound_start
{
"listen": "127.0.0.1",
"port": {V2ray随机生成的端口},
"protocol": "dokodemo-door",
"settings": {
"address":"127.0.0.1",
"port":{内部服务端端口},
"network":"udp"
},
"tag": "{连接名称}-in"
}
//连接专有inbound_end
],
"outbounds":[
//连接专有outbound_start
{
"protocol": "vmess",
"settings": {
"vnext": [
{
"address": "{地址}",
"port": {端口}, //若为TLS的话则为443
"users": [
{
"alterId": 0,
"id": "{UUID}"
}
]
}
]
},
"tag": "{连接名称}-out"
// 若使用WS,则需要添加以下分段
//start
,"streamSettings": {
"network": "ws",
"wsSettings": {
"headers": {
"Host": "{伪装域名}"
},
"path": "{WS PATH}"
},
// 若使用TLS,则需要添加以下分段
//start
"security": "tls",
"tlsSettings": {
"allowInsecure": {是否跳过证书验证},
"serverName": "{TLS SNI}"
}
//end
}
//end
}
//连接专有outbound_stop
],
"routing":{
"rules":[
//连接专有routing_start
{
"type":"field",
"inboundTag": "{连接名称}-in",
"outboundTag":"{连接名称}-out"
}
//连接专有routing_end
]
}
}
小结
对于以上连接方式,除去只能用在V2ray和Wireguard的资料以外,仍然有以下数据可以用在包括但不限于BGP Peer、EVPN等用途
- 客户端点对点IPv4(为IPv4链路网址前缀+客户端序号)
- 服务端点对点IPv4(为IPv4链路网址前缀+服务端序号)
- 客户端点对点IPv6(为IPv6链路网址前缀+客户端序号)
- 服务端点对点IPv6(为IPv6链路网址前缀+服务端序号)
- 连接名称
- 客户端序号(3位,1-255)
- 服务端序号(3位,1-255)
- IPv4链路本地地址前缀(169.254.0.0/16中选择一块/24地址块)
- IPv6链路本地地址前缀(fe80::/10中选择一块/120地址块)
Wireguard点对点模式
点对点模式相对简单许多,不需要套用代理直接对接的配置过程没有那么繁乱。
所需要的信息也较为简单:
- 默认MTU大小
- A主机密钥
- B主机密钥
- A主机公网地址
- B主机公网地址
- A主机点对点IPv4(为IPv4链路网址前缀+A主机序号)
- B主机点对点IPv4(为IPv4链路网址前缀+B主机序号)
- A主机点对点IPv6(为IPv6链路网址前缀+A主机序号)
- B主机点对点IPv6(为IPv6链路网址前缀+B主机序号)
- A主机监听端口
- B主机监听端口
A主机配置
# 文件名为 {连接名称}_{B主机序号}.conf
[Interface]
PrivateKey = {A主机密钥}
Table = off
PostUp = ip addr add {A主机点对点IPv4}/32 peer {B主机点对点IPv4}/32 dev %i; ip addr add {A主机点对点IPv6} peer {B主机点对点IPv6} dev %i
ListenPort = {A主机监听端口}
MTU = {默认MTU大小}
[Peer]
PublicKey = {B主机公钥} #由B主机生成传输而来
AllowedIPs = 0.0.0.0/0,::/0
Endpoint = {B主机公网地址}:{B主机监听端口}
B主机配置
# 文件名为 {连接名称}_{B主机序号}.conf
[Interface]
PrivateKey = {A主机密钥}
Table = off
PostUp = ip addr add {A主机点对点IPv4}/32 peer {B主机点对点IPv4}/32 dev %i; ip addr add {A主机点对点IPv6} peer {B主机点对点IPv6} dev %i
ListenPort = {A主机监听端口}
MTU = 1420
[Peer]
PublicKey = {B主机公钥} #由B主机生成传输而来
AllowedIPs = 0.0.0.0/0,::/0
Endpoint = B主机公网地址:{B主机监听端口}
BGP自动配置
BGP透过FRRouting实现,而FRRouting的终端vtysh可以使用终端命令直接键入指令,也可以使用配置文件进行配置。
对于BGP Peer来说,A端和B端各自至少需要这些信息才能顺利Peer。
A端
- B主机点对点IPv4(为IPv4链路网址前缀+B主机序号)
- B主机点对点IPv6(为IPv6链路网址前缀+B主机序号)
- A主机的ASN
- A主机的网络广播信息
- A主机的IPv4 /32环回地址(用于当做BGP ID)
B端
- A主机点对点IPv4(为IPv4链路网址前缀+A主机序号)
- A主机点对点IPv6(为IPv6链路网址前缀+A主机序号)
- B主机的ASN
- B主机的网络广播信息
通过键入指令的方式
# 对于A端
vtysh_path=/usr/bin/vtysh
peer_ipv4=[B主机点对点IPv4]
peer_ipv6=[B主机点对点IPv6]
self_asn=[A主机的ASN]
${vtysh_path} \
-c "configure terminal"\
-c "router bgp ${self_asn}" \
-c "no bgp ebgp-requires-policy" \
-c "neighbor fabric peer-group" \
-c "neighbor fabric remote-as external" \
-c "neighbor ${peer_ipv4} peer-group fabric" \
-c "neighbor ${peer_ipv6} peer-group fabric" \
-c "address-family ipv6" \
-c "neighbor fabric activate" \
-c "exit-address-family" \
-c "address-family l2vpn evpn" \
-c "neighbor fabric activate" \
-c "do write" \
-c "exit" \
-c "exit" \
-c "exit" \
-c "exit"
# 对于B端
vtysh_path=/usr/bin/vtysh
peer_ipv4=[A主机点对点IPv4]
peer_ipv6=[A主机点对点IPv6]
self_asn=[B主机的ASN]
${vtysh_path} \
-c "configure terminal"\
-c "router bgp ${self_asn}" \
-c "no bgp ebgp-requires-policy" \
-c "neighbor fabric peer-group" \
-c "neighbor fabric remote-as external" \
-c "neighbor ${peer_ipv4} peer-group fabric" \
-c "neighbor ${peer_ipv6} peer-group fabric" \
-c "address-family ipv6" \
-c "neighbor fabric activate" \
-c "exit-address-family" \
-c "address-family l2vpn evpn" \
-c "neighbor fabric activate" \
-c "do write" \
-c "exit" \
-c "exit" \
-c "exit" \
-c "exit"
使用配置文件进行设置
打开/etc/frr/frr.conf
配置文件。
A端
router bgp [A主机的ASN]
bgp router-id [A主机的IPv4 /32环回地址]
neighbor fabric peer-group
neighbor fabric remote-as external
neighbor [B主机点对点IPv4] peer-group fabric
neighbor [B主机点对点IPv6] peer-group fabric
!
address-family ipv4 unicast
neighbor fabric activate
network [A主机的网络广播信息]
exit-address-family
!
address-family ipv6 unicast
neighbor fabric activate
network [A主机的网络广播信息]
exit-address-family
!
address-family l2vpn evpn
neighbor fabric activate
exit-address-family
!
line vty
!
B端
router bgp [B主机的ASN]
bgp router-id [B主机的IPv4 /32环回地址]
neighbor fabric peer-group
neighbor fabric remote-as external
neighbor fabric disable-connected-check
neighbor [A主机点对点IPv4] peer-group fabric
neighbor [A主机点对点IPv6] peer-group fabric
!
address-family ipv4 unicast
neighbor fabric activate
network [B主机的网络广播信息]
exit-address-family
!
address-family ipv6 unicast
neighbor fabric activate
network [B主机的网络广播信息]
exit-address-family
!
address-family l2vpn evpn
neighbor fabric activate
exit-address-family
!
line vty
!
修改完毕后在双方机器执行systemctl reload frr
即可
VXLAN EVPN配置
在BGP EVPN打通了之后,可以使用VXLAN实现EVPN,打通大二层。关于VXLAN和EVPN是什么之后有机会再仔细讲讲。
打通了BGP Peer的EVPN地址族之后就可以直接创建VXLAN设备了。FFRouting会自动帮我们把VNI信息广播到网络上所有Peer了的设备。理论上同个网络的所有节点都可以作为Leaf设置VXLAN接口。
ip link add [VXLAN网络接口名称] type vxlan id [VNI] dstport 4789 local [本地可路由环回地址] nolearning
ip link set up [VXLAN网络接口名称]
可以把VXLAN接到Bridge上,也可以接到物理端口上。
ip link add [网桥接口名称] type bridge
ip link set [网桥接口名称] up
ip link set [VXLAN网络接口名称] master [网桥接口名称]
路由配置
每台机器的路由需要了解这些信息才能较好的进行配置
- 目标地址
- 网关
- 设备
- 优先级
- 路由表
- 来源IP(rule)
- 设备标记掩码(rule)
ip route add/change/delete [目标地址] via [网关] dev [设备] metric [优先级] table [路由表]
ip rule add/delete from [来源IP] fwmark [设备标记掩码] lookup [路由表]
防火墙自动配置
防火墙相对比较难自动配置,即便是官方的iptables-save都是保存参数直接执行。个人应该也可以使用这种方法保存防火墙信息。
iptables -A INPUT -i eth0 -j ACCEPT
iptables -A INPUT -i wg015v2_2 -j ACCEPT
Cupnet管理脚本的结构
根据以上的构思,主服务端的数据结构应该设计为下。
{
"log": {
"loglevel":"debug" //日志级别,有none、debug、info三个级别
},
"Global": {
"AddressPrefix":{
"IPv4": "169.254.172.0/24",
"IPv6": "fe80:3fda:a62f:9309:2a14:b115:a9fe:a000/116"
}, //链路本地地址前缀
"iBGPASNPrefix": 4266666, //七位ASN前缀
"priviteKey": "gFt7sIo39ZSb4SarNsGY19IXszJy3F/FQAs5I/FfPGA=" // 使用与Wireguard相同的curve25519交换密钥,然后通过AES加密
},
"Nodes": [
{
"tag": "one",
"id": 1, // 节点序号
// "activeApi": "http://example.com:5334", //可在公网寻路的api地址,若设备在NAT后可以选择空缺 暂时不可用
"ASN": 4266666001, //内部ASN名称,可选,默认自动生成前缀+序号
"publicIPv4": "100.100.100.100", // 公共IP地址,若设备在NAT后可以选择空缺
"privateIPv4": "10.10.0.1", // /32环回地址,将作为EVPN本地地址使用,若BgpSetting的enableIPv4Routing为真则必填
"privateIPv6": "fd10::1", // /128环回地址,将作为EVPN本地地址使用,若BgpSetting的enableIPv6Routing为真则必填
"linklocalIPv4": "169.254.66.1", // 链路IP地址,可选,用于BGP连接和隧道连接,默认为前缀+序号
"linklocalIPv6": "fe80:3fda:a62f:9309:2a14:b115:c7ea:3001", // 链路IP地址,可选,用于BGP连接和隧道连接默认为前缀+序号
"publicKey": "NkbNVrIYUurKHJbagbketxnhQm08xhvgmadC8d2gwTo=", // 随节点注册一起发送过来的公钥信息,必填
"IPv4Pool":[
"10.10.10.0/24",
"10.10.11.0/24"
], //广播的IPv4地址池
"IPv6Pool":[
"fd55::/16",
"fd54::/16"
], //广播的IPv6地址池
"BgpSetting":{
"enableEvpnRouting": true, // 开启EVPN,默认为真,若开启则下方两个也为真
"enableIPv4Routing": true, // 开启ipv4路由广播,默认为真
"enableIPv6Routing": true // 开启ipv6路由广播,默认为真
}
},
// ...
],
"Tunnel":[ // 隧道列表
{
"tunnelName": "wg015v2", // 隧道名称 必填
"protocol": "wireguard", // 隧道协议 必填
"node1":{ // 在代理的隧道中Node1是服务端,必填
"id": 1, // 参与代理隧道的节点ID
"listen": "127.0.0.1", // 选填
"port": 11534 // 选填,若不填则随机生成
},
"node2":{ // 在代理的隧道中Node2是客户端,必填
"id": 2, // 参与代理隧道的节点ID
},
"wgSetting":{
"keepAlive": 10 // 心跳包间隔,可选,开启代理后默认为25
},
"usingProxy": true, // 是否使用代理
"proxySetting":{
"protocol": "vmess", // 代理类型,现阶段仅支持vmess
"address": "100.100.100.100", // 代理服务器链接地址
"listen": "100.100.100.100", // 代理服务器监听地址,可选
"port": 10467, // vmess监听地址
"UUID": "8cd1e1a2-c532-448f-ab1b-d8c728d43a10", //vmess的UUID
"security": "auto", // 安全性设置
"transport": {
"type": "ws", // 传输协议,暂时只能为ws或none
"host": "example.com", // 伪装域名,可选
"path": "/wg015", // WS PATH
"tls":{ // TLS部分现阶段不可自动配置
"enable": true, // 是否开启TLS
"autoConfig": false, // TLS不可自动配置,此处必须为false
"allowInsecure": true, // 是否跳过证书验证
"serverName": "example.com", // TLS SNI
"tlsPort": 443
}
}
}
},
{
"tunnelName": "wg015",
"protocol": "wireguard",
"node1":{ // 在代理的隧道中Node1是服务端,必填
"id": 1, // 参与代理隧道的节点ID
"listen": "100.100.100.100", // 选填
"port": 11531 // 选填,若不填则随机生成
}, // 第一个节点
"node2":{ // 在代理的隧道中Node1是服务端,必填
"id": 2, // 参与代理隧道的节点ID
"listen": "100.100.100.101", // 选填
"port": 11530 // 选填,若不填则随机生成
} // 第二个节点
}
],
"Evpn":[ //
{
"tag": "k8s-l2", // 标签
"devName": "vxlan10",
"id": 1, // 标识ID,并非VXLAN ID
"vxlanid": 150, // VXLAN ID
"nodes":[1,2] // 标识参与的Node
}
],
"NodeBridge":[
{
"Node": 1, // 标识为节点1的配置
"brName": "br10", // 网桥名称
"devs":[
"vxlan10",
"eth1"
] // master绑定到网桥上的设备
}
],
"Routing":[
{
"node": 1,
"ipType": "v4",
"table": "main",
"route":[
{
"dest": "10.10.11.0/24",
"gateway": "10.9.0.1",
"table": "main",
"interface": "eth2",
"metric": 64
}
],
"rule":[
{
"src": "0.0.0.0/0"
},
{
"fwmark": "0xca6c"
}
]
}
],
"Firewall":[
{
"node": 1,
"ipv4Commend":[
"-A INPUT -i eth0 -j ACCEPT",
"-A INPUT -i wg015v2_2 -j ACCEPT"
], // iptables的参数
"ipv6Commend":[
"-A INPUT -i eth0 -j ACCEPT",
"-A INPUT -i wg015v2_2 -j ACCEPT"
] // ip6tables的参数
} //如此配置的安全性成疑,但是这个配置方法我感觉相对来说比较契合Linux的风格……
],
"Service":[
]
}
节点的数据结构如下
{
"log":{
"loglevel":"debug" //日志级别,有none、debug、info三个级别
},
"basicSetting":{
"id": 1,
"coreServerAPI": "http://example.com:42335", // 控制服务器URL
"corePublicKey": "OvbOtx3A02qYQITekBGPZzzyEkmx5apz0hQWOZ0Lc1I=", // 控制服务器公钥
"igpASN": 4266660001, // 节点ASN
"priviteKey": "CCpI4MEHRbdCIlUCDT6usFGJ9IXKMzJWPZT0/guUgGE=", // 节点私钥
"defaultAddress": "100.100.100.100"
},
// 以下内容都是收到推送之后自动写入配置文件的内容,节点每次收到推送都会写入Json
"pullInfo":{
"privateIPv4": "10.10.0.1", // /32环回地址,将作为EVPN本地地址使用,若BgpSetting的enableIPv4Routing为真则必填
"privateIPv6": "fd10::1", // /128环回地址,将作为EVPN本地地址使用,若BgpSetting的enableIPv6Routing为真则必填
"linklocalIPv4": "169.254.66.1", // 链路IP地址,可选,用于BGP连接和隧道连接,默认为前缀+序号
"linklocalIPv6": "fe80:3fda:a62f:9309:2a14:b115:c7ea:3001", // 链路IP地址,可选,用于BGP连接和隧道连接默认为前缀+序号
},
"Tunnel":[
{
"tunnelName": "wg015v2", // 隧道名称 必填
"protocol": "wireguard", // 隧道协议 必填
"peer":{ // 对端,必填
"id": 2, // 参与代理隧道的节点ID
"publicKey": "OvbOtx3A02qYQITekBGPZzzyEkmx5apz0hQWOZ0Lc1I="
},
"wgSetting":{
"keepAlive": 10 // 心跳包间隔,可选,开启代理后默认为25
},
"usingProxy": true, // 是否使用代理
"isClient": false, // 标识自己为客户端
"proxySetting":{
"protocol": "vmess", // 代理类型,现阶段仅支持vmess
"address": "example.com", // 代理服务器链接地址
"port": 10467, // vmess监听地址
"UUID": "8cd1e1a2-c532-448f-ab1b-d8c728d43a10", //vmess的UUID
"security": "auto", // 安全性设置
"transport": {
"type": "ws", // 传输协议,暂时只能为ws或none
"host": "example.com", // 伪装域名,可选
"path": "/wg015", // WS PATH
"tls":{ // TLS客户端可自动配置
"enable": true, // 是否开启TLS
"allowInsecure": true, // 是否跳过证书验证
"serverName": "example.com", // TLS SNI
}
}
}
},
{
"tunnelName": "wg015",
"protocol": "wireguard",
"peer":{ // 在代理的隧道中Node1是服务端,必填
"id": 1, // 参与代理隧道的节点ID
"listen": "100.100.100.100", // 选填
"port": 11531 // 选填,若不填则随机生成
}
}
],
"BGP":{
"IPv4Pool":[
"10.10.10.0/24",
"10.10.11.0/24"
], //广播的IPv4地址池
"IPv6Pool":[
"fd55::/16",
"fd54::/16"
], //广播的IPv6地址池
"BgpSetting":{
"enableEvpnRouting": true, // 开启EVPN,默认为真,若开启则下方两个也为真
"enableIPv4Routing": true, // 开启ipv4路由广播,默认为真
"enableIPv6Routing": true // 开启ipv6路由广播,默认为真
}
},
"Evpn":[ //
{
"devName": "vxlan10",
"id": 1, // 标识ID,并非VXLAN ID
"vxlanid": 150, // VXLAN ID
}
],
"NodeBridge":[
{
"brName": "br10", // 网桥名称
"devs":[
"vxlan10",
"eth1"
] // master绑定到网桥上的设备
}
],
"Routing":[
{
"ipType": "v4",
"table": "main",
"route":[
{
"dest": "10.10.11.0/24",
"gateway": "10.9.0.1",
"interface": "eth2",
"metric": 64
}
],
"rule":[
{
"src": "0.0.0.0/0",
"dest": ""
},
{
"fwmark": ""
}
]
}
],
"Firewall":[
{
"ipv4Commend":[
"-A INPUT -i eth0 -j ACCEPT",
"-A INPUT -i wg015v2_2 -j ACCEPT"
], // iptables的参数
"ipv6Commend":[
"-A INPUT -i eth0 -j ACCEPT",
"-A INPUT -i wg015v2_2 -j ACCEPT"
] // ip6tables的参数
} //如此配置的安全性成疑,但是这个配置方法我感觉相对来说比较契合Linux的风格……
]
}
开发构思
决定使用Python实现WebAPI网关,除去HTTPS的TLS加密以外,所有配置的推送使用Wireguard所使用的Curve25519非对称密钥进行ECDH密钥计算,然后用AES加密编码为BASE64后通过HTTP返回以及推送给客户端和服务端。
对于核心服务端方面主要的难点在于与客户端同步信息以及配置文件审查等。这方面主要都是数据传输与处理方面的工作,难点并不大。节点客户端需要解决各种配置信息的读取、系统信息的获取(防火墙、路由表、隧道、BGP等)、状态判断以及比对后的执行配置操作。参考Openwrt的配置保存应用方式,通过文件更新比对可以让客户端比较清晰地排列出新增、修改与删除操作。
以及为了避免重复添加配置,除去接口操作和防火墙操作,大部分配置更新都会通过修改配置文件实现配置的更新。
实现加密的Python代码大概如下
import random
import binascii
import donna25519 as curve25519
from Crypto.Cipher import AES
import base64
# use curve25519.PrivateKey() to create a curve25519 Private key.
class c25pkg:
def __init__( self, base64PriKey, base64RemoKey ):
self.base64RemoKey=base64RemoKey
self.base64PriKey=base64PriKey
self.priKey = curve25519.PrivateKey.load(binascii.a2b_base64(self.base64PriKey))
# convern the privite key to donna25519 module format
self.remoKey = curve25519.PublicKey(binascii.a2b_base64(self.base64RemoKey))
# convern the remote peer public key into donna25519 module format
self.shareKey = self.priKey.do_exchange(self.remoKey)
def aes_encrypt(self, data):
# encrypt into aes-ecb with sharekey
BLOCK_SIZE=16
pad = lambda s: s + (BLOCK_SIZE - len(s) % BLOCK_SIZE) * \
chr(BLOCK_SIZE - len(s) % BLOCK_SIZE)
key = self.shareKey
data = pad(data)
cipher = AES.new(key,AES.MODE_ECB)
result = cipher.encrypt(data.encode())
encodestrs = base64.b64encode(result)
enctext = encodestrs.decode('utf8')
return enctext
def aes_decrypt(self, data):
# decrypt aes-ecb
unpad = lambda s: s[:-ord(s[len(s) - 1:])]
key = self.shareKey
data = base64.b64decode(data)
cipher = AES.new(key, AES.MODE_ECB)
result = unpad(cipher.decrypt(data))
result = result.decode('utf8')
return result
本人没有受过任何计算机编程训练,以上代码基本都是边学边写慢慢憋出来的。有些地方不规范看起来不舒服的话没关系,之后写的大量代码估计都会是这样子的风格……
一些可能有用的工具
远程执行指令:https://dcais.github.io/blog/2018/11/03/ssh-remote-command/
一个很有用的Openwrt源:https://github.com/kiddin9/OpenWrt_x86-r2s-r4s-r5s-N1