杯子茶室

关注有趣的事物

CUPNET v0.2 设计大纲 - 将CORE节点连接起来

网络 0 评 46 度
这是一个系列文章,你可以点击以下链接查看这个系列的其他文章。
CUPNET v0.2 设计大纲 - 自上而下,抗封锁软件定义的混合云(混乱云)网络架构设计构思详解
CUPNET v0.2 基础实现(0.5) - 搭建实验网络环境
CUPNET v0.2 基础实现(1) - 自动生成并推送Core节点的Wireguard VPN配置文件

简介

上一篇大致讲解了一下CUPNET的设计大纲,这是一个期望统合云上云下资源的混合云架构,主要使用Wireguard进行节点互联。如果你没看过上一篇文章,可以点击这里的传送门查看,里面大概阐述了我是如何设想这个混合云的架构的。

这个方案只考虑网络的互通互联,不考虑其他上层架构如何通过网络进行一体化部署,一体化计算和存储的方案之后或许也会专门写一两篇文章。

这篇文章将会描述如何将Core节点通过VPN连接起来。

这个架构其实就是一个简单的包含RR的iBGP,并不是很复杂。这篇文章需要讲的是上图的棕色线段落。可以看到Core节点是Full mesh(全连接)结构的,虽然图上未直接标识,但每个Core节点均与其他的所有Core节点建立了BGP Section。RR节点连接到Core节点,并且也与Core节点建立BGP Section。

Core节点与CE之间建立BGP协议,CE之间通过Core连接,这样可以实现全网的互联,也可以很顺利的打通VXLAN路由,实现大二层。通过外部Control节点也可以实现自动化配置全部设施的网络架构。对于CE与Core之间的连接,以及Control节点对于以下节点的控制,有空再进行规划和实验。

公网机器的Wireguard配置

点对点的Wireguard尽可能将AllowedIPs设置成::/0,0.0.0.0/0,然后将Table设置成off,以便于所有IP都可以通过Wireguard隧道。

搭建点对点的Wireguard需要以下的信息:

  • 本机主机密钥
  • 对端主机公钥
  • 本机主机点对点IPv4
  • 本机主机点对点IPv6
  • 对端主机点对点IPv4
  • 对端主机点对点IPv6
  • 本机主机监听端口
  • 本机主机公网地址
  • 对端主机公网地址
  • 对端主机监听端口

根据以上参数可以直接拼出以下配置文件。

[Interface]
PrivateKey = {本机主机密钥}
Table = off
PostUp = ip addr add {本机主机点对点IPv4}/32 peer {对端主机点对点IPv4}/32 dev %i; ip addr add {本机主机点对点IPv6} peer {对端主机点对点IPv6} dev %i
ListenPort = {本机主机监听端口}
MTU = {MTU大小}

[Peer]
PublicKey = {对端主机公钥} #由对端主机生成传输而来
AllowedIPs = 0.0.0.0/0,::/0
Endpoint = {对端主机公网地址}:{对端主机监听端口}

对于每个Core,我们需要建立前往对端Core的VPN,若规范化Core的参数,使用Core建立一个查询表,每个Core项包含以下基本参数:

  • 密钥
  • 公网地址

Wireguard的公钥可以直接通过密钥计算得出,而其他的参数通过规范化算法(例如规范前缀或是通过散列算法)也可以自动得出:

  • 点对点IPv4
  • 点对点IPv6
  • 对端应监听端口(既然是唯一用于标识己方的端口,若己方使用本端口)

其实可以通过一个主机ID和一个主密钥生成主机密钥,然后再通过主机密钥生成公钥。这样的话对于每个主机我们需要收集以下信息:

  • 主机ID
  • 公网地址

那么就要求对于控制节点或者说是控制端,我们需要生成一个主密钥,可以使用任何字符串,但尽量选择安全系数较高的密钥。

使用Python配合donna25519库可以实现以上信息的生成。


# 目标是生成密钥、点对点IPv4、点对点IPv6、监听端口与统合这些信息的配置文件以及写入文件

import binascii
from email.headerregistry import Address
import donna25519 as curve25519
import sys, getopt
import base64
import hashlib
import re

def main(argv):

    MainKey = ""
    CoreID = ""
    Mode = "key"
    SelfPublicIPAddress=""
    RemotePublicIPAddress=""
    PeerID = ""
    SelfConfig = ""
    RemoteConfig = ""
    HelpInfo = 'Usage: \n\
    geninfo.py -k <Mainkey> -c <Self ID> -u <Remote ID> -i <Self Public IP> -r <Remote Peer IP> -m <cfg/key/ipv4/ipv6/port/cfgfile> --sconf=<SelfConfigFile> --rconf=<RemoteConfigFile>\n\
    or:\n\
    geninfo.py --mainkey=<Mainkey> --id=<Self ID> --remote-id=<Remote ID> --ip=<Self Public IP> --remote-ip=<Remote Peer IP> --mode=<cfg/key/ipv4/ipv6/port/cfgfile> --sconf=<SelfConfigFile> --rconf=<RemoteConfigFile>'
    try:
        opts, args = getopt.getopt(argv,"hk:c:i:m:u:r:",["help","mainkey=","id=","ip=","mode=","remote-id=","remote-ip=","sconf=","rconf="])
    except getopt.GetoptError:
        print(HelpInfo)
        return()

    for opt, arg in opts:
        if opt in ("-h", "--help"):
            print(HelpInfo)
            sys.exit()
        elif opt in ("-k", "--mainkey"):
            MainKey = arg
        elif opt in ("-c", "--id"):
            CoreID = arg
        elif opt in ("-i", "--ip"):
            SelfPublicIPAddress = arg
        elif opt in ("-m", "--mode"):
            Mode = arg
        elif opt in ("-u", "--remote-id"):
            PeerID = arg
        elif opt in ("-r", "--remote-ip"):
            RemotePublicIPAddress = arg
        elif opt in ("--sconf"):
            SelfConfig = arg
        elif opt in ("--rconf"):
            RemoteConfig = arg
    #print(MainKey,CoreID,PublicIPAddress,Mode)
    #return()
    CoreInfo = corenode(MainKey,CoreID)
    PeerInfo = corenode(MainKey,PeerID)
    if (MainKey == "") & (CoreID == ""):
        print("Error!\n"+HelpInfo)
        return()
    if Mode == "key":
        print(CoreInfo.PrivateKey())
    elif Mode == "pub":
        print(CoreInfo.PublicKey())
    elif Mode == "ipv4":
        print(CoreInfo.ipv4())
    elif Mode == "ipv6":
        print(CoreInfo.ipv6())
    elif Mode == 'port':
        print(CoreInfo.port())
    elif Mode == 'cfg':
        print(wgConfig(CoreInfo,PeerInfo,RemotePublicIPAddress))
    elif Mode == 'cfgfile':
        # 写入文件
        with open(SelfConfig,'w') as SelfConfigfile:
            SelfConfigfile.write(wgConfig(CoreInfo,PeerInfo,RemotePublicIPAddress))
        with open(RemoteConfig,'w') as RemoteConfigfile:
            RemoteConfigfile.write(wgConfig(PeerInfo,CoreInfo,SelfPublicIPAddress))

def wgConfig(SelfNode, PeerNode, RemoteAddress) -> str:
    CfgInfo="\
[Interface]\n\
PrivateKey = " + SelfNode.PrivateKey() + "\n\
Table = off\n\
PostUp = \
ip addr add " + SelfNode.ipv4() + "/32 peer " + PeerNode.ipv4()+"/32 dev %i; \
ip addr add "+SelfNode.ipv6()+"/128 peer " + PeerNode.ipv6() + "/128 dev %i\n\
ListenPort = " + SelfNode.port() + "\n\
MTU = 1420\n\
\n\
[Peer]\n\
PublicKey = " + PeerNode.PublicKey() + "\n\
AllowedIPs = 0.0.0.0/0,::/0\n\
Endpoint = " + RemoteAddress + ":" + SelfNode.port()
    return(CfgInfo)

class corenode:

    def __init__(self, MainKey, CoreID):
        self.MainKey=MainKey
        self.CoreID=CoreID

    def PrivateKey(self):

        MidSec = self.midsec()
        PrivateKey = curve25519.PrivateKey(MidSec[:32].encode('ascii'))
        # 裁切32位并当做ascii编码成byte类型。
        PrivateKey_base64 = binascii.b2a_base64(PrivateKey.private).decode('ascii').replace('\n','')
        # donna25519输出的时候是byte,我们需要转换成base64之后转为字符串,再去除最后的换行符
        return(PrivateKey_base64)
    
    def PublicKey(self):
        MidSec = self.midsec()
        PublicKey = curve25519.PrivateKey(MidSec[:32].encode('ascii')).get_public()
        PublicKey_base64 = binascii.b2a_base64(PublicKey.public).decode('ascii').replace('\n','')
        return(PublicKey_base64)

    def ipv4(self):

        MidSec = self.midsec()
        Ipv4InHex = MidSec[:4]
        # 裁切4位
        Ipv4InHexList = re.findall(r".{2}", Ipv4InHex)
        # 分割为每两位的数组
        Ipv4Formatted = "169.254."+".".join(str(int(x,16)) for x in Ipv4InHexList)
        # 添加IPv4本地链路的地址前缀,格式化IPv4
        return(Ipv4Formatted)

    def ipv6(self):

        MidSec = self.midsec()
        Ipv6InHex = MidSec[:28]
        # 裁切28位,IPv6有32位16进制数字,这里取28位作为后缀
        Ipv6InHexList = re.findall(r".{4}", Ipv6InHex)
        # 分割为每4位的数组
        Ipv6Formatted = "fe80:"+":".join(str(x) for x in Ipv6InHexList)
        # 添加IPv6本地链路前缀,格式化IPv6
        return(Ipv6Formatted)

    def port(self):

        MidSec = self.midsec()
        PortHex = MidSec[:4]
        # 裁切4位,端口号有4位16进制数字(16bit)
        Port = int(PortHex,16)
        if Port < 10000:
            if Port > 5535:
                Port = (int(MidSec,16) % 6) * 10000 + Port
            else:
                Port = (int(MidSec,16) % 7) * 10000 + Port
        # 为低于10000的端口添加到高于10000,避免低端口号
        return(str(Port))

    def midsec(self):
        # 将主密钥与ID转为Base64之后,将字符串衔接,计算SHA256,并且取Hex字符串
        key1=self.MainKey
        key2=self.CoreID
        key1_base64 = base64.b64encode(key1.encode('ascii'))
        key2_base64 = base64.b64encode(key2.encode('ascii'))
        return(hashlib.sha256(key1_base64+key2_base64).hexdigest())

if __name__ == "__main__":
    main(sys.argv[1:])

以上脚本实现了简单的Wireguard配置生成,可以根据主密钥和双方的ID自动生成Peer信息。

公网机器的FRRouting配置

同理,FRRouting的Peer或许也可以这么处理。先看看FRR需要怎么配置。


router bgp {ASN}
 bgp router-id {本机路由器ID}
 neighbor fabric peer-group
 neighbor fabric remote-as external
 neighbor fabric bfd
 neighbor {对端1链路本地IPv4} peer-group fabric
 neighbor {对端1链路本地IPv6} peer-group fabric
 neighbor {对端2链路本地IPv4} peer-group fabric
 neighbor {对端2链路本地IPv6} peer-group fabric
 ...
 !
 address-family ipv4 unicast
  network {本机广播IPv4地址块1}
  network {本机广播IPv4地址块2}
  ...
 exit-address-family
 address-family ipv6 unicast
  neighbor fabric activate
  network {本机广播IPv6地址块1}
  network {本机广播IPv6地址块2}
  ...
 exit-address-family
 !
 address-family l2vpn evpn
  neighbor fabric activate
  advertise-all-vni
 exit-address-family
!

以上方式要求对端列表与本机地址块,考虑Core是Full Mush架构,除了自身以外的所有Core机均需要加入到FRR的邻居里面,还需要添加自己的路由地址块,这需要使用一个数据库或者配置文件进行记录并且剔除自身的ID。

除此之外,ASN或许也可以进行自动生成。4200000000到4294967294的ASN是可以在私有网络内自由使用的。可以界定为42xxx{Port},使用自定义的三个数字加上一个自动生成的端口号自动生成ASN。

按照现在的需求,数据格式编排为如下结构。

{
    "MainKey": "{这是主密钥}",
    "CoreNodes":[
        {
            "id": "{这是节点1的ID}",
            "router-id": "{这是节点1的router-id}",
            "ip": "{这是节点1的公网IP}",
            "network":[
                "{这是节点1的网络1}",
                "{这是节点1的网络2}"
                ...
            ]
        },
        {
            "id": "{这是节点2的ID}",
            "router-id": "{这是节点2的router-id}",
            "ip": "{这是节点2的公网IP}",
            "network":[
                "{这是节点2的网络1}",
                "{这是节点2的网络2}"
                ...
            ]
        },
        ...
    ]
}

这份表是用于Control节点的,Control节点需要连接到各个机器进行配置,故还可以添加每个节点的控制方式。在0.2版本先只使用SSH的密钥连接进行控制。除此之外,我还希望脚本仍然保留自定义端口、LinkLocal地址和ASN的可能性。将以上内容添加到表中如下所示。

{
    "MainKey": "{这是主密钥}",
    "CoreNodes":[
        {
            "id": "{这是节点1的ID}",
            "ip": "{这是节点1的公网IP}",
            "linkto-port": "节点1的向外连接端口",
            "asn": "4266660001",
            "linkLocalv4": "",
            "linkLocalv6": "",
            "linkType": "Public",
            "natSetting":{
                "wgPort":{
                    "wanPort":"17777",
                    "lanPort":"17778"
                }
            },
            "network":[
                "{这是节点1的网络1}",
                "{这是节点1的网络2}"
                ...
            ],
            "controlMethod": "ssh",
            "sshSetting":{
                "address":"{连接ssh的地址}",
                "port":"{连接ssh的端口}"
            }
        },
        {
            "id": "{这是节点2的ID}",
            "ip": "{这是节点1的公网IP}",
            "network":[
                "{这是节点2的网络1}",
                "{这是节点2的网络2}"
                ...
            ],
            "controlMethod": "ssh",
            "sshSetting":{
                "address":"{连接ssh的地址}",
                "port":"{连接ssh的端口}"
            }
        },
        ...
    ]
}

脚本就不一一贴出来了,晚点贴上Github地址。

Nat服务器的解决方案

对于一些在Nat后的服务器(国内也就Nat机子比较便宜一些了,其他性价比都难以言喻……),如上面列出的数据格式,这个方案也打算支持自动化配置Nat机子。

有两个可行的方法可以将Nat机并入到Core网络中:

  1. 不打开入站端口,与公网服务器进行连接并发送Keepalive包,维持UDP连接。
  2. 打开单一入站端口,与公网服务器进行Full mesh的Wireguard单一配置连接后再在此连接上搭建第二层隧道。

第一个选项对于Core网来说并不妥当,因为Core网我要求每个隧道均可以在双方各自主动地向对方发送连接,这样可以尽最大努力保证数据包能传送到对端。而第二个单端口选项需要在Wireguard上面再添加一层隧道,在现在的实现中我将会用Wireguard Over Wireguard套娃暂时解决这个问题。

对于Nat机子,配置此连接需要有以下信息:

  • 映射到公网的服务端口
  • 独立于上层连接的密钥对
  • 独立于上层连接的本地链路地址
  • 对端的连接信息

    • 对端地址
    • 对端连接

对于Wireguard这种Layer 3 VPN来说,只要将符合条件的IP路由到接口上,VPN会自动帮你把数据包妥当处理正确路由到对端。即便自己的链路IP地址和对端不是同一个IP段的,也可以顺利路由。

这种单端口的配置大概如下

Nat端


[Interface]
PrivateKey = {本机主机单端口专用密钥}
Address = {本机链路IP地址,推荐使用IPv6}
ListenPort = {本机主机NAT Full Mush内网监听端口,需要映射到公网}
MTU = {MTU大小}

[Peer]
PublicKey = {对端主机1单端口专用公钥} 
AllowedIPs = {对端主机1链路IP地址,推荐使用IPv6}
Endpoint = {对端主机1公网地址}:{对端主机1Nat Full Mush公网监听端口}

[Peer]
PublicKey = {对端主机2单端口专用公钥} 
AllowedIPs = {对端主机2链路IP地址,推荐使用IPv6}
Endpoint = {对端主机2公网地址}:{对端主机2Nat Full Mush公网监听端口}

如果如此配置,在上层的直连连接则需要使用以下配置。

[Interface]
PrivateKey = {本机主机密钥}
Table = off
PostUp = ip addr add {本机主机点对点IPv4}/32 peer {对端主机点对点IPv4}/32 dev %i; ip addr add {本机主机点对点IPv6} peer {对端主机点对点IPv6} dev %i
ListenPort = {本机主机监听端口}
MTU = {MTU大小}

[Peer]
PublicKey = {对端主机公钥} 
AllowedIPs = 0.0.0.0/0,::/0
Endpoint = {对端主机NAT专用链路IP地址}:{对端主机监听端口}

可以见到,使用单端口连接的话,将会改变原先接口的对端Endpoint地址和密钥对信息,其他的信息基本不变。同时添加了Nat Full Mush专用的己方密钥对、对端密钥对、己方链路IP地址、对端链路IP地址等……总体逻辑流程如下:

  1. 发现与NAT机子Peer,重新计算一套信息用于生成Underlay VPN。

    • 密钥对
    • 链路IP
    • 若自己为公网机器,生成端口
  2. 生成集合了全部机器的Underlay VPN配置文件
  3. 更改用于生成完全权限对等连接的配置信息中的Endpoint地址为Underlay地址

这个配置理论上可以做全网配置,即为即便拥有公网地址直接访问权限的主机也使用单一端口先进行Underlay隧道连接,再添加Overlay隧道套娃。我现在的实现是只局限于Nat与Nat或是Nat与公网机之间的连接。

为了和上层的连接信息有差异,此处的Wireguard密钥对配置和IP配置需要与先前有所不同。之前是使用主Key+ID生成的信息,此处再加上一个"VPN"字段进行生成,可以避免两者信息碰撞导致系统无法正确路由。具体实现不再演示。

推送方案

在解决了配置文件生成的问题后,下一个问题就是如何将配置推送到Core服务器上。若使用SSH的话,可以使用Python的paramiko包,若在机子上面安装服务端并接受通过HTTP或者gRpc一类协议推送配置文件,则需要制作一个API接口。

通过SSH进行管理

这个方式是使用SSH协议将配置文件传送到服务器上,然后执行重载服务或是重启服务的命令进行应用的。

先来看看如何使用paramiko包推送到服务器并应用配置文件的。

公钥密钥登录SSHClient

# 指定本地的RSA私钥文件,如果建立密钥对时设置的有密码,password为设定的密码,如无不用指定password参数
pkey = paramiko.RSAKey.from_private_key_file('/home/super/.ssh/id_rsa', password='12345')
# 建立连接
ssh = paramiko.SSHClient()
ssh.connect(hostname='192.168.2.129',
            port=22,
            username='super',
            pkey=pkey)
# 执行命令
stdin, stdout, stderr = ssh.exec_command('df -hl')
# 结果放到stdout中,如果有错误将放到stderr中
print(stdout.read().decode())
# 关闭连接
ssh.close()


# 指定本地的RSA私钥文件,如果建立密钥对时设置的有密码,password为设定的密码,如无不用指定password参数
pkey = paramiko.RSAKey.from_private_key_file('/home/super/.ssh/id_rsa', password='12345')
# 建立连接
trans = paramiko.Transport(('192.168.2.129', 22))
trans.connect(username='super', pkey=pkey)

# 将sshclient的对象的transport指定为以上的trans
ssh = paramiko.SSHClient()
ssh._transport = trans

# 执行命令,和传统方法一样
stdin, stdout, stderr = ssh.exec_command('df -hl')
print(stdout.read().decode())

# 关闭连接
trans.close()

可以看到以上脚本使用密钥登陆了SSH客户端,然后运行了df -hl命令,并输出了结果。除去直接在SSH里面通过标准输出写入文件以外,还可以使用SFTP方式将文件传输到服务端。

pkey = paramiko.RSAKey.from_private_key_file('/home/super/.ssh/id_rsa', password='12345')
# 实例化一个transport对象
trans = paramiko.Transport(('192.168.2.129', 22))
# 建立连接
trans.connect(username='super', pkey=pkey)

# 实例化一个 sftp对象,指定连接的通道
sftp = paramiko.SFTPClient.from_transport(trans)
# 发送文件
sftp.put(localpath='/tmp/11.txt', remotepath='/tmp/22.txt')
# 下载文件
# sftp.get(remotepath, localpath)
trans.close()

假设储存了配置的字典为ConfigList,字典键值为接口名称,值为配置信息,是一个隶属于server类里面的变量;server类内部也有一个SSHIPSSHPortUser变量提供了SSH的入口;同时还有一个全局变量privatekey,记录了密钥的文件地址,则通过SFTP传输文件,SSH重启Wireguard服务的方式如下。

pkey=paramiko.RSAKey.from_private_key_file(privatekey)
class server:
    ConfigList = {
        "node1" : config1,
        "node2" : config2
    }
    SSHIP='10.10.10.10'
    SSHPort=22
    User='root'

Remote = server()

trans = paramiko.Transport((Remote.SSHIP, Remote.SSHPort))
trans.connect(username=Remote.User,pkey=pkey)
sftp = paramiko.SFTPClient.from_transport(trans)

for x,y in Remote.ConfigList:
    sftp.putfo(fl=y,remotepath="/etc/wireguard/"+x+".conf")

ssh = paramiko.SSHClient()
ssh._transport = trans

for x,y in Remote.ConfigList:
    stdin, stdout, stderr = ssh.exec_command('wg-quick down '+x+';wg-quick up '+x)
    print(stdout.read().decode())

trans.close()

至于FRR也是以相似逻辑编写脚本即可。

编写配置文件,在上一篇搭建的实验环境里面进行测试。

{
    "MainKey": "3359F3",
    "asnPrefix": "414",
    "SSHKeyfile": "./id_rsa",
    "CoreNodes":[
        {
            "id": "core1",
            "router_id": "100.64.1.1",
            "ip": "100.90.1.1",
            "network4":[
                "100.64.1.0/24"
            ],
            "controlMethod": "ssh",
            "sshSetting":{
                "address":"100.90.1.1",
                "port": 22,
                "user":"root"
            }
        },
        {
            "id": "core2",
            "router_id": "100.64.2.1",
            "ip": "100.90.1.2",
            "network4":[
                "100.64.2.0/24"
            ],
            "controlMethod": "ssh",
            "sshSetting":{
                "address":"100.90.1.2",
                "port": 22,
                "user":"root"
            }
        },
        {
            "id": "core3",
            "router_id": "100.64.3.1",
            "ip": "100.90.1.3",
            "network4":[
                "100.64.3.0/24"
            ],
            "controlMethod": "ssh",
            "sshSetting":{
                "address":"100.90.1.3",
                "port": 22,
                "user":"root"
            }
        }
    ]
}

生成并导入RSA公钥,实现免密登录

ssh-keygen # 输入./id_rsa以在当前目录生成rsa密钥
ssh [email protected]
echo "ssh-rsa [你的密钥]" >> /root/.ssh/authorized_keys
ssh [email protected]
echo "ssh-rsa [你的密钥]" >> /root/.ssh/authorized_keys
ssh [email protected]
echo "ssh-rsa [你的密钥]" >> /root/.ssh/authorized_keys

给Docker容器创建各自的网络,并连接上容器

docker network create net-test-core1 --subnet=100.64.1.0/24
docker network create net-test-core2 --subnet=100.64.2.0/24
docker network create net-test-core3 --subnet=100.64.3.0/24

测试推送

编写API后端应用配置

传送给Core的数据不能包含主密钥,只能包含Core的Wireguard配置、FRR的配置。如上一篇文章提到的,以及这篇文章有所提及的,一个Core需要以下的信息:

  • 单一的FRR配置

    • 自己的ASN
    • Peer的ASN
    • Peer的本地链路地址
  • Wireguard配置组

    • 自己的密钥
    • 自己的本地链路地址
    • 自己的访问端口(对方的监听端口)
    • 对方的公钥
    • 对方的本地链路地址
    • 对方的IP地址
    • 对方访问端口(自己的监听端口)

由于在推送控制端已经做过一次配置文件的实现了,Core端可以照搬一些内容。但每个信息都需要由推送段推送而不能经由主密钥生成。推送的数据结构可以如下设计:

{
    "privateKey": "",
    "RemotePort": "",
    "asn": "",
    "linkLocalv4":"",
    "linkLocalv6":"",
    "isNat": False,
    "natConfig": {
        "wgPort":{
            "wanPort": 10000,
            "lanPort": 10000
        },
        "linkLocalv6": "",
        "PrivateKey_Value": "base64_text"
    },
    "wgPeers":[
        {
            "id": "",
            "linkLocalv4":"",
            "linkLocalv6":"",
            "publicKey":"",
            "IP":"",
            "PeerlinkinPort":"",
            "isNat": True,
            "natConfig": {
                "wgPort":{
                    "wanPort": 10000,
                    "lanPort": 10000
                },
                "linkLocalv6": "",
                "PublicKey_Value": "base64_text"
            }
        }
    ]
}

仅针对Core节点互联,规划使用以下的方法搭建Core节点的API,为了基本安全性,每次请求都需要在Get请求中带上Token证明控制端的身份:

Http方法URL动作
GEThttp://[hostname]/auth?login=[ciphertext]使用预共享密钥向服务器请求Token,返回Token
GEThttp://[hostname]/core/list返回当前配置,Json格式
GEThttp://[hostname]/core/status返回当前状态(连接启动状态、BGP状态),Json格式
GET & POSThttp://[hostname]/core/config推送并覆盖之前的配置文件,Json格式
GEThttp://[hostname]/core/config/apply应用/重启配置,需检查对比启动状态

服务端使用Flask搭建API服务端。关于Flask如何使用,详情的可以去查看教程,在这个实现里面只需要了解以下框架了解Get和Post请求的处理即可。

from flask import Flask, redirect, url_for, request, render_template

app = Flask(__name__)

@app.route('/')
def index():
    return render_template("login.html")

@app.route('/success/<name>')
def success(name):
    return 'from success: welcome %s' % name

@app.route('/login',methods = ['POST', 'GET']) # 页面名
def login(): # 页面入口
   if request.method == 'POST':
      print(1)
      user = request.form['nm'] # 获取POST参数
      page_method = request.form('method') # 获取POST参数
      if page_method == "1":
        return redirect(url_for('success',name = user))
      return 'Your name: ' + user + '\nPost request.'
   else:
      print(2) 
      user = request.args.get('nm') # 获取get参数
      page_method = request.args.get('method') # 获取get参数
      if page_method == "1":
        return redirect(url_for('success',name = user))
        # url_for的构建,第一个参数是url地址,默认为当前根目录,第二个是传入函数的参数
        # redirect()是重定向到某个URL,参数为URL地址
      return 'Your name: ' + user + '\nGet request.'


if __name__ == '__main__':
    app.run()

具体的构建代码不贴出,之后会放上Github。

线性代数笔记
发表评论
撰写评论