从零开始做一个塔防游戏,从零开始做一个显卡
终极管理员 知识笔记 111阅读
。

JNAJava JNA是一个独立的Java库它使用Java的反射机制和动态链接来实现与本地库的交互。与JNI不同JNA无需编写本地方法接口而是通过Java接口和注解来描述与本地函数的映射。
复杂度

JNI使用JNI需要编写本地方法接口JNI接口这可能会增加代码的复杂性和维护成本。JNI还需要手动管理内存分配和释放因此容易引入内存泄漏和错误。
JNAJNA的使用相对简单无需编写本地方法接口而且自动处理内存管理。这使得JNA更容易学习和使用尤其对于不熟悉C/C的Java开发者来说更友好。
性能
JNIJNI通常具有更高的性能因为它直接与本地代码进行交互无需额外的中间层。然而JNI的性能也取决于编写的本地代码的质量。
JNAJNA的性能可能会略低于JNI因为它需要进行Java到本地库之间的数据类型转换和调用。对于某些对性能要求非常高的应用程序JNI可能更适合。
跨平台性
JNI由于JNI依赖于本地C/C代码因此需要为每个目标平台编写不同的本地实现。这使得跨平台开发更复杂。
JNAJNA具有更好的跨平台性因为它本身是纯Java实现不需要为不同平台编写不同的本地代码。JNA库会处理底层平台差异。
开发效率
JNI虽然JNI可以提供更高的性能但在开发效率方面可能不如JNA因为它需要编写额外的本地接口和手动管理内存。
JNAJNA在开发效率方面更有优势因为它无需编写本地接口更容易上手尤其适合快速原型开发和跨平台应用程序。
JNA调用wintun
private static class WinTun { static { try { Native.register(WinTun.class, wintun); } catch (Exception e) { throw new ExceptionInInitializerError(e); } } public static final int ERROR_NO_MORE_ITEMS 259; public static final int WINTUN_MIN_RING_CAPACITY 0x20000; public static final int WINTUN_MAX_RING_CAPACITY 0x4000000; public static native Pointer WintunCreateAdapter(WString Name, WString TunnelType, String RequestedGUID) throws LastErrorException; public static native void WintunCloseAdapter(Pointer Adapter) throws LastErrorException; public static native Pointer WintunStartSession(Pointer Adapter, int Capacity) throws LastErrorException; public static native void WintunEndSession(Pointer Session) throws LastErrorException; public static native Pointer WintunReceivePacket(Pointer Session, Pointer PacketSize) throws LastErrorException; public static native void WintunReleaseReceivePacket(Pointer Session, Pointer Packet) throws LastErrorException; public static native Pointer WintunAllocateSendPacket(Pointer Session, long PacketSize) throws LastErrorException; public static native void WintunSendPacket(Pointer Session, Pointer Packet) throws LastErrorException; public static native Pointer WintunGetReadWaitEvent(Pointer Session) throws LastErrorException; public static native void WintunGetAdapterLUID(Pointer Adapter, Pointer Luid) throws LastErrorException; public static native int WintunGetRunningDriverVersion() throws LastErrorException;}
public void test() { Pointer adapter WintunCreateAdapter(new WString(tun), new WString(sdwan), UUID.randomUUID().toString()); Pointer session WintunStartSession(adapter, WINTUN_MAX_RING_CAPACITY); while (true) { //receive Pointer packetSizePointer new Memory(Native.POINTER_SIZE); Pointer packetPointer WintunReceivePacket(session, packetSizePointer); try { int packetSize packetSizePointer.getInt(0); byte[] bytes packetPointer.getByteArray(0, packetSize); ByteBuf byteBuf Unpooled.buffer(bytes.length); byteBuf.writeBytes(bytes); //todo process ...... //send Pointer packetPointer WintunAllocateSendPacket(session, bytes.length); packetPointer.write(0, bytes, 0, bytes.length); WintunSendPacket(session, packetPointer); } finally { WintunReleaseReceivePacket(session, packetPointer); } }}
SD-WAN工作原理 Controller:
负责给Node的虚拟IP分配、向Node推送其他Node信息和配置管理。
Node:
每个上线的主机为一个Node节点可以直接连接同一个SD-WAN controller下的所有节点或访问Mesh下的主机。
Mesh:
Mesh也是Node的一个通常部署在Linux服务器上。当一个局域网下部署了Mesh后这个局域网下的主机都可以通过Mesh被访问到。
STUN
node一般是家用电脑没有公网IPnode与node之间不能直接通信。需要通过STUN服务发现公网IP和端口用于P2P通信。
从TUN设备上读取到的都是IP数据包业务上使用需要对IPv4协议进行解析
IPv4数据报文是IPv4协议中用于在计算机网络中传输数据的基本单位。它是IPv4网络通信中的核心部分包含了网络通信所需的信息例如源IP地址、目标IP地址、协议、数据负载等。以下是一个典型的IPv4数据报文的结构
IPv4数据报文的主要字段包括
版本Version4位字段指示协议版本IPv4的版本为4。
头部长度Header Length4位字段指示IPv4头部的长度以32位字4字节为单位。通常情况下IPv4头部长度为20字节但它可以变化取决于选项字段的存在。
区分服务Differentiated ServicesDS8位字段用于指定数据报文的服务质量和优先级。通常被称为“服务类型”Type of Service字段用于实现服务质量QoS。
总长度Total Length16位字段指示整个IPv4数据报文的总长度包括头部和数据。最大总长度为65535字节。
标识Identification16位字段用于唯一标识分片的数据报文通常在分片时使用。
标志Flags3位字段用于控制数据报文的分片和重组过程。包括“不分片”、“更多分片”和“分片偏移”。
分片偏移Fragment Offset13位字段指示分片在原始数据报文中的位置。
生存时间Time to LiveTTL8位字段表示数据报文在网络中可以经过多少跃点路由器后被丢弃。TTL字段用于防止数据报文在网络中无限循环。
协议Protocol8位字段指示数据报文中封装的上层协议例如TCP、UDP、ICMP等。
首部校验和Header Checksum16位字段用于检测IPv4头部的错误。校验和字段用于验证头部信息的完整性。
源IP地址Source IP Address32位字段指示数据报文的发送者的IP地址。
目标IP地址Destination IP Address32位字段指示数据报文的接收者的IP地址。
选项Options可选字段用于指定附加的信息和控制选项例如记录路由、时间戳等。选项字段的存在和长度可以变化。
数据Data可选字段包含传输的实际数据长度和内容根据协议和应用程序的需要而变化。
0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 -------------------------------- |Version| IHL |Type of Service| Total Length | -------------------------------- | Identification |Flags| Fragment Offset | -------------------------------- | Time to Live | Protocol | Header Checksum | -------------------------------- | Source Address | -------------------------------- | Destination Address | -------------------------------- | Options | Padding | --------------------------------
public void decode(ByteBuf byteBuf) { short head byteBuf.readUnsignedByte(); short version (byte) (head >> 4); byte headLen (byte) ((head & 0b00001111) * 4); short diffServices byteBuf.readUnsignedByte(); int totalLen byteBuf.readUnsignedShort(); int id byteBuf.readUnsignedShort(); int flags byteBuf.readUnsignedShort(); short liveTime byteBuf.readUnsignedByte(); int protocol byteBuf.readUnsignedByte(); int checksum byteBuf.readUnsignedShort(); byte[] srcIPBytes new byte[4]; byteBuf.readBytes(srcIPBytes); String srcIP bytes2IP(srcIPBytes); byte[] dstIPBytes new byte[4]; byteBuf.readBytes(dstIPBytes); String dstIP bytes2IP(dstIPBytes); ByteBuf payload byteBuf.readSlice(byteBuf.readableBytes());}public String bytes2IP(byte[] bytes) { int d1 bytes[0] & 0b11111111; int d2 bytes[1] & 0b11111111; int d3 bytes[2] & 0b11111111; int d4 bytes[3] & 0b11111111; String ip String.format(%s.%s.%s.%s, d1, d2, d3, d4); return ip;}
TUN设备是一个三层设备从/dev/net/tun字符设备上读取的是IP数据包写入的也只能是IP数据包。
SourceAddress为本机的IP地址如果是TUN设备上读取到的为预先配置在tun设备上的虚拟IP。
DestinationAddress为数据包要发送到的目标IP需要询问Controller这个IP是哪个Node或是Mesh可以路由的当路由时还需要修改源IP和目标IP并重新计算checksum
public int calcChecksum() { ByteBuf byteBuf Unpooled.buffer(); byte head (byte) ((version << 4) | (headerLen / 4)); byteBuf.writeByte(head); byteBuf.writeByte(diffServices); byteBuf.writeShort(totalLen); byteBuf.writeShort(id); byteBuf.writeShort(flags); byteBuf.writeByte(liveTime); byteBuf.writeByte(protocol); //checksum字段置为0 byteBuf.writeShort(0); byteBuf.writeBytes(ip2bytes(srcIP)); byteBuf.writeBytes(ip2bytes(dstIP)); //数据长度为奇数在该字节之后补一个字节 if (0 ! byteBuf.readableBytes() % 2) { byteBuf.writeByte(0); } int sum 0; while (byteBuf.readableBytes() > 0) { sum byteBuf.readUnsignedShort(); } int h sum >> 16; int l sum & 0b11111111_11111111; sum (h l); sum 0b11111111_11111111 & ~sum; return sum;}public static byte[] ip2bytes(String ip) { String[] split ip.split(\\.); byte[] bytes new byte[split.length]; for (int i 0; i < split.length; i) { bytes[i] (byte) Integer.parseInt(split[i]); } return bytes;}
STUN协议解析
STUNSession Traversal Utilities for NAT协议的RFC 5389规范是用于处理网络地址转换NAT和防火墙穿越的协议标准。STUN协议的主要目标是帮助在NAT或防火墙后面的设备例如VoIP电话、视频会议终端等发现其公共IP地址和端口以便建立对等连接或进行其他网络通信。
NAT穿越STUN协议允许设备发现自己在NAT或防火墙后面的情况并获取其在公共Internet上的可用IP地址和端口。这对于建立对等连接和进行点对点通信非常重要。
UDP协议STUN协议基于UDPUser Datagram Protocol工作通常使用3478端口。UDP是一种面向无连接的协议适用于实时通信应用程序。
请求-响应模式STUN使用请求-响应模式客户端发送STUN请求到STUN服务器服务器将响应返回给客户端响应包含客户端的公共IP地址和端口。
Keep-AliveSTUN协议还支持保持活动状态的功能以确保NAT映射不会过期并导致通信中断。这对于长时间持续通信非常重要。
RFC 5389协议扩展RFC 5389定义了STUN协议的基本规范但STUN也支持多种扩展和变体用于更复杂的网络配置和需求。这些扩展可以包括TURNTraversal Using Relays around NAT和ICEInteractive Connectivity Establishment等。
总之STUN协议是一种用于解决NAT和防火墙问题的协议它允许设备发现其公共IP地址和端口并帮助建立对等连接和实时通信。STUN协议在实现多种实时通信应用程序和服务时发挥着重要作用如VoIP、视频会议、在线游戏等。
0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1--------------------------------|0 0| STUN Message Type | Message Length |--------------------------------| Magic Cookie |--------------------------------| || Transaction ID (96 bits) || |--------------------------------
public static final byte[] Cookie new byte[]{(byte) 0x21, (byte) 0x12, (byte) 0xa4, (byte) 0x42};public void decode(ByteBuf byteBuf) { int type byteBuf.readUnsignedShort(); int len byteBuf.readUnsignedShort(); //cookie byteBuf.skipBytes(Cookie.length); byte[] tranIdBytes new byte[12]; byteBuf.readBytes(tranIdBytes); String tranId new String(tranIdBytes); ByteBuf attrs byteBuf.readSlice(len); while (attrs.readableBytes() > 0) { int t attrs.readUnsignedShort(); int l attrs.readUnsignedShort(); ByteBuf v attrs.readSlice(l); }}
MTU MTUMaximum Transmission Unit最大传输单元是一个计算机网络术语用于描述可以在网络中以单个数据包的形式传输的最大数据量。它通常以字节为单位表示。MTU是网络链路或设备所支持的最大数据包大小超过这个大小的数据包将被分片分成更小的片段以适应链路的MTU限制。
链路和网络设备不同的网络链路和设备如以太网、Wi-Fi、DSL、3G/4G网络等具有不同的MTU限制。MTU通常由网络硬件或操作系统配置来设置。
数据包分片如果发送的数据包大于链路或设备的MTU限制网络设备将对其进行分片将数据包分成适合MTU的片段。接收端设备会重新组装这些分片以还原原始数据包。
通信效率通常情况下更大的MTU可以提高网络通信的效率因为较小的数据包需要更多的网络开销和处理。然而如果数据包超过链路的MTU将会导致分片和重新组装可能会引入一些延迟和性能损失。
Path MTU DiscoveryPMTUD为了避免数据包在网络中被分片IPv4和IPv6协议支持Path MTU Discovery。这是一种机制通过在网络中发送探测数据包并监听ICMP错误消息以动态确定路径上的最小MTU从而确保发送的数据包不会被分片。
调整MTU在某些情况下网络管理员或用户可能需要调整设备的MTU设置以适应特定网络或应用程序的需求。例如在使用VPN或封装协议时可能需要增加MTU以容纳额外的头部信息。
总之MTU是网络中一个重要的参数它定义了可以在网络中传输的最大数据包大小。了解和管理MTU对于确保网络通信的可靠性和性能非常重要。在网络故障排除和性能优化时MTU是一个需要考虑的关键因素。
应用层可用字节数计算一个典型的MTU数据包包括数据链路层头部网络层头部传输层头部应用层数据
数据链路层头部Ethernet头部14字节
网络层头部IPv4头部20字节
传输层头部通常是TCP或UDP头部20字节TCP或8字节UDP
应用层数据取决于应用程序生成的数据
这些头部大小是一般情况下的标准值但可能会因特殊情况而变化。总的MTU大小通常为 1500 字节Ethernet常见的MTU大小但这个值可以根据网络设备和链路的配置而有所不同。
留给SD-WAN传输的应用层数据大概在1440字节左右。
CIDRCIDRClassless Inter-Domain Routing是一种用于 IP 地址分配和路由的方法它取代了传统的子网掩码和分类的 IP 地址分配方案。CIDR 引入了更灵活的方式来表示 IP 地址和网络使得网络规划和管理更加高效和精确。
无类别路由CIDR 是无类别路由的缩写这意味着它不再受限于传统的A、B、C类网络分类。传统的分类方式将 IP 地址分为不同的类别每个类别具有固定的网络和主机部分。CIDR 不再依赖于这些固定的类别而是允许更灵活地划分 IP 地址空间。
CIDR 表示法CIDR 使用一种称为CIDR表示法的格式来表示 IP 地址和子网掩码。这种表示法将 IP 地址后面跟着斜杠和一个前缀长度如192.168.1.0/24前缀长度表示网络部分的位数。例如/24 表示前24位是网络部分剩下的8位是主机部分。
精确的子网划分CIDR 允许更精确地划分 IP 地址空间而不受传统类别的限制。这意味着网络管理员可以根据实际需求创建不同大小的子网更有效地使用 IP 地址。
IPv6 支持CIDR 不仅适用于IPv4地址还适用于IPv6地址。IPv6地址空间非常庞大CIDR表示法在IPv6中的应用尤其重要。
超网和聚合CIDR还支持超网Supernetting和聚合Aggregation的概念。超网是将多个连续的 IP 地址块合并成一个更大的地址块而聚合是将多个较小的地址块汇总成一个更大的块以减少路由表的条目数量提高路由效率。
路由表优化CIDR 的引入有助于减小因路由表过于庞大而导致的路由器内存和处理器负载问题。通过使用CIDR表示法可以更紧凑地表示大量的网络和地址。
总的来说CIDR 是一种灵活和高效的 IP 地址分配和路由方案它允许网络管理员更精确地规划网络减小路由表的规模提高网络的性能和可管理性。它是现代互联网中广泛使用的技术之一。
以192.168.1.0/24为例计算地址池找出共享前缀部分在这个范围内共享的前缀部分是192.168.1.0。这是网络标识部分。
计算前缀长度共享前缀部分有24位所以前缀长度为24。
确定子网掩码前缀长度为24的子网掩码是由24个1位和8个0位组成的二进制数即255.255.255.0。以下是二进制表示
确定可用IP地址范围
将192.168.1.0转数字3232235776
计算 3232235776 Math.pow(2, 32 - 24) - 1 3232236031
3232236031 转IP地址为 192.168.1.255
11000000_10101000_00000001_00000000 (192.168.1.0)11000000_10101000_00000001_11111111 (192.168.1.255)11111111_11111111_11111111_00000000 (255.255.255.0)
结论
起始IP192.168.1.0
结束IP192.168.1.255
子网掩码255.255.255.0
子网数量256
public static int ip2int(String ip) { String[] split ip.split(\\.); int s 0; int bit 24; for (String sp : split) { int n Integer.parseInt(sp) << bit; s | n; bit - 8; } return s;}public static String int2ip(int s) { int d1 s >> 24 & 0b11111111; int d2 s >> 16 & 0b11111111; int d3 s >> 8 & 0b11111111; int d4 s & 0b11111111; String ip String.format(%s.%s.%s.%s, d1, d2, d3, d4); return ip;}
controller分配虚拟IP 每一个node向controller注册后controller根据配置的CIDR地址池中分配一个虚拟IP这个过程类似DHCP。node与controller之间通过TCP通信当node离线时分配的虚拟IP会被自动回收重用当node1需要访问node2的时候就是直接访问node2的虚拟IP。
当然也可以像路由器一样给node分配一个静态的虚拟IPcontroller会为node保持这个IP不再分配给别的node以防止IP冲突的问题。
Mesh一般的node只能和其他的node之间相互通信但是不能像VPN一样访问node所在局域网内的其他主机服务。这时候需要使用到支持mesh的nodemesh在向controller注册时会解析当前主机分配的内网IP、掩码计算CIDR向controller注册。
以node本地IP为172.168.1.2为例子需要PING企业内部的主机192.168.1.5
1.node1在tun0网卡上读取到PING 192.168.1.5的请求
2.node1向controller发起nodeARP命令询问192.168.1.5的IP数据包需要发送到哪个node/mesh
3.controller应答发送到mesh1并返回mesh1的公网映射地址
4.node1封装IP数据包成UDP数据包通过eth0发送到mesh1
5.mesh1从eth0读取UDP数据包
6.mesh1解析IP数据包写入tun0网卡
7.tun0数据包走eth0发出
8.内网主机接收到PING请求
Windows路由windows要想192.168.1.5能被访问通还需要配置route。默认情况下192.168.1.5会从windows的本地网络(eth0)发送出去。我们需要配置windows route使数据包被tun0网卡接收。
route add 192.168.1.5/32 10.1.0.2
route print -4活动路由:网络目标 网络掩码 网关 接口 跃点数0.0.0.0 0.0.0.0 172.168.1.1 172.168.1.2 5192.168.1.5 255.255.255.255 在链路上 10.1.0.2 6
UDP打洞分析 STUN RFC 5389中定义了一种用于在NATNetwork Address Translator和防火墙后面的设备之间建立UDP连接的协议。在RFC 5389中“mapping” 和 “filtering” 是两个重要的概念用于描述STUN协议的工作方式。
Mapping映射
Mapping 是指将内部私有IP地址和端口映射到外部公共IP地址和端口的过程。当设备位于NAT后面时它的内部IP地址和端口在传输到互联网上时会被NAT设备替换为外部IP地址和端口。这个映射允许外部设备向内部设备发送数据包同时NAT设备可以将传入的数据包正确路由到内部设备。
STUN协议中的Binding Request和Binding Response消息用于发现和创建这种映射关系。设备可以向STUN服务器发送Binding Request并从服务器接收Binding Response来获取其映射关系。
Filtering过滤
Filtering 涉及NAT设备的过滤规则用于确定哪些数据包可以通过NAT并进入内部网络。NAT设备通常会实施一些安全策略以限制外部流量进入内部网络。这些策略可能包括端口过滤、IP地址过滤等。
STUN协议中的Binding Request和Binding Response消息通常允许这些消息通过NAT设备因为它们是协助建立映射关系的关键消息。如果NAT设备将它们过滤掉STUN协议将无法正常工作。
Mapping规则
a. Endpoint-Independent MappingEIM这是最宽松的映射规则。它意味着当设备在内部网络上建立了一个映射关系后不仅可以接收来自相同目标IP地址和端口的数据包还可以接收来自任何IP地址和端口的数据包。这意味着映射是基于设备的而不是特定的通信对。
b. Address-Dependent MappingADM这种映射规则更为限制。它要求来自相同目标IP地址的数据包必须使用相同的映射但允许来自不同目标IP地址的数据包使用不同的映射。这个规则比EIM更严格但仍然相对宽松。
c. Address and Port-Dependent MappingAPDM这是最严格的映射规则。它要求来自相同目标IP地址和端口的数据包必须使用相同的映射不允许不同的通信对使用相同的映射。这种规则非常严格通常由对网络安全性要求非常高的NAT设备实施。
Filtering规则
a. Endpoint-Independent FilteringEIF这是最宽松的过滤规则。它允许通过NAT的数据包不仅可以来自相同的源IP地址和端口还可以来自任何源IP地址和端口。这种规则非常宽松通常用于允许STUN协议消息通过NAT。
b. Address-Dependent FilteringADF这种过滤规则要求来自相同源IP地址的数据包必须使用相同的映射。但不同源IP地址的数据包可以使用不同的映射。这比EIF规则要求更严格。
c. Address and Port-Dependent FilteringAPDF这是最严格的过滤规则。它要求来自相同源IP地址和端口的数据包必须使用相同的映射不允许不同源IP地址和端口的数据包使用相同的映射。这种规则非常严格通常由要求高度安全性的NAT设备实施。
总之STUN协议允许不同级别的映射和过滤规则具体取决于NAT设备的实现和安全要求。不同的规则级别提供了不同程度的灵活性和安全性以满足各种网络环境的需求。
在上述例子步骤7还是有问题的由于TUN设备是三层的虚拟网卡不具备二层的数据链路层的转发功能因此需要配置tun0的数据包由eth0负责转发。
vi /etc/sysctl.confnet.ipv4.ip_forward 1
net.ipv4.ip_forward 0
这是默认设置表示禁用IPv4数据包的转发。
当此参数的值为0时Linux内核不会将接收到的IPv4数据包重新路由到其他网络接口。这意味着Linux系统仅接受本地目标的数据包并不具备路由功能。
net.ipv4.ip_forward 1
这个设置启用了IPv4数据包的转发。
当此参数的值为1时Linux内核将根据路由表信息将接收到的IPv4数据包转发到适当的网络接口以便它们能够到达其目标。这是典型的路由器或网络关口设备的配置。
iptables -A FORWARD -i tun0 -o eth0 -j ACCEPT
这条iptables命令的含义是将数据包从网络接口tun0进入并允许它们通过网络接口eth0出去。具体解释如下
-A FORWARD这部分指示iptables将规则添加到FORWARD链中。FORWARD链是负责转发在Linux系统上通过的数据包的链。
-i tun0这部分指定了数据包的进入接口。这意味着这个规则将应用于从tun0接口进入的数据包。
-o eth0这部分指定了数据包的出口接口。这意味着这个规则将应用于通过eth0接口出去的数据包。
-j ACCEPT这部分指定了规则的动作。具体地它表示允许数据包通过。当数据包匹配这个规则时它将被允许通过FORWARD链进入tun0接口并离开eth0接口。
iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
这条iptables命令的含义是将数据包进行网络地址转换NAT并使用MASQUERADE方式处理它们。具体解释如下
-t nat这部分指示iptables将规则添加到nat表中。nat表用于进行网络地址转换。
-A POSTROUTING这部分指示规则将应用于数据包在离开系统之后的阶段。在此情况下它将应用于数据包在POSTROUTING阶段即在数据包离开系统之前。
-o eth0这部分指定了数据包的出口接口。这意味着这个规则将应用于通过eth0接口出去的数据包。
-j MASQUERADE这部分指定了规则的动作。具体地它表示使用MASQUERADE方式处理匹配的数据包。MASQUERADE是一种网络地址转换NAT技术通常用于将局域网内部的私有IP地址映射到公共IP地址上以便这些数据包可以在公共网络上正常传输。
数据包通过eth0接口出去之前对它们进行NAT处理具体来说是使用MASQUERADE方式。这通常用于在局域网内部使用私有IP地址的情况下将数据包路由到互联网上并确保它们能够正确返回到局域网内部以实现共享Internet连接的功能。
起名原理和技术上我们都已经梳理清楚了那我们给我们的SD-WAN起了个名字了。程序世界大家都喜欢用水果或是动物来给自己的项目起名字。
那我们的项目也一样叫做netThunder。