从零开始做一个SDWAN

VPN和SD-WAN的区别

VPN(Virtual Private Network)和SD-WAN(Software-Defined Wide Area Network)是两种不同的网络技术,它们在目的、功能和实施方式上有一些重要的区别。 目的和应用场景:

VPN:VPN主要用于在公共网络上创建一个加密的隧道,以确保数据的安全传输。它通常用于连接远程办公人员或远程站点到公司内部网络,或者在云服务提供商和企业之间建立安全的连接。VPN的主要目的是提供隐私和安全,将数据包加密,以保护数据在传输过程中不被窃取或篡改。

SD-WAN:SD-WAN是一种广域网(WAN)技术,旨在改善广域网络的性能、可用性和管理。它通过智能路由、负载均衡、应用程序优化和连接多样性等功能来提供更高的网络性能和可用性。SD-WAN的主要目的是优化网络流量,提高带宽利用率,并降低网络延迟,以改善应用程序性能。

网络拓扑

VPN:VPN通常建立在点对点或点对多点拓扑上,将远程站点或用户连接到中央网络。它的重点是连接性和隐私。所有数据流量需要通过VPN服务器中转。

SD-WAN:SD-WAN通常建立在分布式拓扑上,涵盖多个地理位置和连接类型。它的重点是性能优化和带宽管理。当需要访问另一个机房的服务时才通过互联网转发流量到机房里,数据流量通过P2P通信,不从服务器中转。

安全性

VPN:VPN强调加密和隐私,确保数据在传输过程中受到保护。它通常使用安全协议如IPsec或SSL/TLS来建立安全连接。

SD-WAN:SD-WAN通常也包括加密和安全功能,但其主要焦点在于性能优化和负载均衡。一些SD-WAN解决方案可能会结合VPN以提供完整的网络安全性。

应用程序优化

VPN:VPN通常不提供主动的应用程序性能优化。它只负责数据的加密和传输。

SD-WAN:SD-WAN通常具有应用程序识别和优化功能,可以识别和加速关键应用程序,从而提供更好的性能。

管理和可见性

VPN:VPN的管理相对简单,通常涉及到连接的配置和用户的身份验证。它可能不提供广泛的网络性能和可见性分析。

SD-WAN:SD-WAN通常提供更高级的网络管理和监控工具,以优化网络性能、监控连接和应用程序的使用情况,并进行故障排除。 综上所述,VPN和SD-WAN是两种不同的网络技术,它们的重点和功能不同。

VPN:是单隧道,一次只能连接一个机房,正常上网的流量也要走VPN服务器端发出。

SD-WAN:是运行在本机的软路由,可以同时连接多个机房,并且正常上网走本机的流量。

TUN设备

Linux TUN(Tunnel)设备是一种虚拟网络设备,它用于创建用户态程序和内核之间的通信通道,以便用户态程序可以处理网络数据包。TUN设备通常用于创建VPN(Virtual Private Network)和其他网络隧道,以实现网络隔离、数据加密和路由功能。

TUN设备(Tunnel设备)工作在网络协议栈的第三层,也就是网络层。具体来说,它是一个虚拟网络设备,负责处理和传输网络层(IP层)数据包。 TUN设备主要用于创建用户态程序和内核之间的通信通道,以便用户态程序可以读取和处理网络层数据包。

以下是一些关于Linux TUN设备的重要信息和用法:

虚拟设备:TUN设备是一种虚拟网络设备,类似于物理网络接口,但没有对应的硬件。它被创建为一个字符设备文件,通常是 /dev/net/tun。

数据包传输:TUN设备用于用户态程序和内核之间的数据包传输。用户态程序可以通过TUN设备读取和写入IP数据包。

隧道和VPN:TUN设备通常用于创建网络隧道,例如VPN。通过TUN设备,用户态VPN客户端程序可以将数据包封装并发送 到网络隧道,然后内核将其路由到网络中。

数据包处理:用户态程序可以使用TUN设备来处理数据包,例如加密、解密、路由、过滤等操作。这使得TUN设备成为实现网络安全和隔离的有力工具。

从上图可以更直观的看出TUN设备和物理设备的区别:虽然它们的一端都是连着网络协议栈,但是物理网卡另一端连接的是物理网络,而TUN设备另一端连接的是一个应用层程序,这样协议栈发送给TUN的数据包就可以被这个应用程序读取到,此时这个应用程序可以对数据包进行一些自定义的修改(比如封装成 UDP),然后又通过网络协议栈发送出去------其实

这就是目前大多数VPN程序的工作原理。

Windows也是支持TUN设备的以wintun.dll的方式调用。

JNI与JNA

Java JNI(Java Native Interface)和Java JNA(Java Native Access)都是用于在Java应用程序中与本地(原生)库进行交互的工具,但它们在实现和使用上有一些重要的区别:

实现方式:

JNI:Java JNI是一种Java平台的标准,它允许Java代码通过本地方法调用来与本地库进行交互。JNI需要在Java代码和本地C/C++代码之间编写本地方法接口(JNI接口),并将Java对象转换为本地数据类型。

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

复杂度:

JNI:使用JNI需要编写本地方法接口(JNI接口),这可能会增加代码的复杂性和维护成本。JNI还需要手动管理内存分配和释放,因此容易引入内存泄漏和错误。

JNA:JNA的使用相对简单,无需编写本地方法接口,而且自动处理内存管理。这使得JNA更容易学习和使用,尤其对于不熟悉C/C++的Java开发者来说更友好。

性能:

JNI:JNI通常具有更高的性能,因为它直接与本地代码进行交互,无需额外的中间层。然而,JNI的性能也取决于编写的本地代码的质量。

JNA:JNA的性能可能会略低于JNI,因为它需要进行Java到本地库之间的数据类型转换和调用。对于某些对性能要求非常高的应用程序,JNI可能更适合。

跨平台性:

JNI:由于JNI依赖于本地C/C++代码,因此需要为每个目标平台编写不同的本地实现。这使得跨平台开发更复杂。

JNA:JNA具有更好的跨平台性,因为它本身是纯Java实现,不需要为不同平台编写不同的本地代码。JNA库会处理底层平台差异。

开发效率:

JNI:虽然JNI可以提供更高的性能,但在开发效率方面可能不如JNA,因为它需要编写额外的本地接口和手动管理内存。

JNA:JNA在开发效率方面更有优势,因为它无需编写本地接口,更容易上手,尤其适合快速原型开发和跨平台应用程序。

JNA调用wintun

www.wintun.net/

java 复制代码
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;
}
java 复制代码
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一般是家用电脑没有公网IP,node与node之间不能直接通信。需要通过STUN服务发现公网IP和端口用于P2P通信。

IPV4协议解析

从TUN设备上读取到的都是IP数据包,业务上使用需要对IPv4协议进行解析

www.rfc-editor.org/rfc/rfc791....

IPv4数据报文是IPv4协议中用于在计算机网络中传输数据的基本单位。它是IPv4网络通信中的核心部分,包含了网络通信所需的信息,例如源IP地址、目标IP地址、协议、数据负载等。以下是一个典型的IPv4数据报文的结构:

IPv4数据报文的主要字段包括:

版本(Version):4位字段,指示协议版本,IPv4的版本为4。

头部长度(Header Length):4位字段,指示IPv4头部的长度,以32位字(4字节)为单位。通常情况下,IPv4头部长度为20字节,但它可以变化,取决于选项字段的存在。

区分服务(Differentiated Services,DS):8位字段,用于指定数据报文的服务质量和优先级。通常被称为"服务类型"(Type of Service)字段,用于实现服务质量(QoS)。

总长度(Total Length):16位字段,指示整个IPv4数据报文的总长度,包括头部和数据。最大总长度为65535字节。

标识(Identification):16位字段,用于唯一标识分片的数据报文,通常在分片时使用。

标志(Flags):3位字段,用于控制数据报文的分片和重组过程。包括"不分片"、"更多分片"和"分片偏移"。

分片偏移(Fragment Offset):13位字段,指示分片在原始数据报文中的位置。

生存时间(Time to Live,TTL):8位字段,表示数据报文在网络中可以经过多少跃点(路由器)后被丢弃。TTL字段用于防止数据报文在网络中无限循环。

协议(Protocol):8位字段,指示数据报文中封装的上层协议,例如TCP、UDP、ICMP等。

首部校验和(Header Checksum):16位字段,用于检测IPv4头部的错误。校验和字段用于验证头部信息的完整性。

源IP地址(Source IP Address):32位字段,指示数据报文的发送者的IP地址。

目标IP地址(Destination IP Address):32位字段,指示数据报文的接收者的IP地址。

选项(Options):可选字段,用于指定附加的信息和控制选项,例如记录路由、时间戳等。选项字段的存在和长度可以变化。

数据(Data):可选字段,包含传输的实际数据,长度和内容根据协议和应用程序的需要而变化。

css 复制代码
  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    |
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
java 复制代码
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

六、IPv4 checksum代码分析

java 复制代码
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协议解析

www.rfc-editor.org/rfc/rfc5389...

STUN(Session Traversal Utilities for NAT)协议的RFC 5389规范是用于处理网络地址转换(NAT)和防火墙穿越的协议标准。STUN协议的主要目标是帮助在NAT或防火墙后面的设备(例如,VoIP电话、视频会议终端等)发现其公共IP地址和端口,以便建立对等连接或进行其他网络通信。 NAT穿越:STUN协议允许设备发现自己在NAT或防火墙后面的情况,并获取其在公共Internet上的可用IP地址和端口。这对于建立对等连接和进行点对点通信非常重要。

UDP协议:STUN协议基于UDP(User Datagram Protocol)工作,通常使用3478端口。UDP是一种面向无连接的协议,适用于实时通信应用程序。 请求-响应模式:STUN使用请求-响应模式,客户端发送STUN请求到STUN服务器,服务器将响应返回给客户端,响应包含客户端的公共IP地址和端口。 Keep-Alive:STUN协议还支持保持活动状态的功能,以确保NAT映射不会过期并导致通信中断。这对于长时间持续通信非常重要。 RFC 5389协议扩展:RFC 5389定义了STUN协议的基本规范,但STUN也支持多种扩展和变体,用于更复杂的网络配置和需求。这些扩展可以包括TURN(Traversal Using Relays around NAT)和ICE(Interactive Connectivity Establishment)等。 总之,STUN协议是一种用于解决NAT和防火墙问题的协议,它允许设备发现其公共IP地址和端口,并帮助建立对等连接和实时通信。STUN协议在实现多种实时通信应用程序和服务时发挥着重要作用,如VoIP、视频会议、在线游戏等。

diff 复制代码
 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)                  |
|                                                               |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
java 复制代码
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

MTU(Maximum Transmission Unit,最大传输单元)是一个计算机网络术语,用于描述可以在网络中以单个数据包的形式传输的最大数据量。它通常以字节为单位表示。MTU是网络链路或设备所支持的最大数据包大小,超过这个大小的数据包将被分片(分成更小的片段)以适应链路的MTU限制。

链路和网络设备:不同的网络链路和设备(如以太网、Wi-Fi、DSL、3G/4G网络等)具有不同的MTU限制。MTU通常由网络硬件或操作系统配置来设置。

数据包分片:如果发送的数据包大于链路或设备的MTU限制,网络设备将对其进行分片,将数据包分成适合MTU的片段。接收端设备会重新组装这些分片以还原原始数据包。

通信效率:通常情况下,更大的MTU可以提高网络通信的效率,因为较小的数据包需要更多的网络开销和处理。然而,如果数据包超过链路的MTU,将会导致分片和重新组装,可能会引入一些延迟和性能损失。 Path MTU Discovery(PMTUD):为了避免数据包在网络中被分片,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字节左右。

CIDR

CIDR(Classless 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

java 复制代码
11000000_10101000_00000001_00000000 (192.168.1.0)
11000000_10101000_00000001_11111111 (192.168.1.255)
11111111_11111111_11111111_00000000 (255.255.255.0)

结论

起始IP:192.168.1.0

结束IP:192.168.1.255

子网掩码:255.255.255.0

子网数量:256

java 复制代码
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分配一个静态的虚拟IP,controller会为node保持这个IP,不再分配给别的node以防止IP冲突的问题。

Mesh

一般的node只能和其他的node之间相互通信,但是不能像VPN一样访问node所在局域网内的其他主机服务。这时候需要使用到支持mesh的node,mesh在向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网卡接收。

java 复制代码
route add 192.168.1.5/32 10.1.0.2
java 复制代码
route print -4
活动路由:
网络目标      网络掩码            网关            接口              跃点数
0.0.0.0      0.0.0.0           172.168.1.1     172.168.1.2      5
192.168.1.5  255.255.255.255   在链路上         10.1.0.2         6

UDP打洞分析

STUN RFC 5389中定义了一种用于在NAT(Network 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 Mapping(EIM):这是最宽松的映射规则。它意味着当设备在内部网络上建立了一个映射关系后,不仅可以接收来自相同目标IP地址和端口的数据包,还可以接收来自任何IP地址和端口的数据包。这意味着映射是基于设备的,而不是特定的通信对。

b. Address-Dependent Mapping(ADM):这种映射规则更为限制。它要求来自相同目标IP地址的数据包必须使用相同的映射,但允许来自不同目标IP地址的数据包使用不同的映射。这个规则比EIM更严格,但仍然相对宽松。

c. Address and Port-Dependent Mapping(APDM):这是最严格的映射规则。它要求来自相同目标IP地址和端口的数据包必须使用相同的映射,不允许不同的通信对使用相同的映射。这种规则非常严格,通常由对网络安全性要求非常高的NAT设备实施。

Filtering规则:

a. Endpoint-Independent Filtering(EIF):这是最宽松的过滤规则。它允许通过NAT的数据包不仅可以来自相同的源IP地址和端口,还可以来自任何源IP地址和端口。这种规则非常宽松,通常用于允许STUN协议消息通过NAT。

b. Address-Dependent Filtering(ADF):这种过滤规则要求来自相同源IP地址的数据包必须使用相同的映射。但不同源IP地址的数据包可以使用不同的映射。这比EIF规则要求更严格。

c. Address and Port-Dependent Filtering(APDF):这是最严格的过滤规则。它要求来自相同源IP地址和端口的数据包必须使用相同的映射,不允许不同源IP地址和端口的数据包使用相同的映射。这种规则非常严格,通常由要求高度安全性的NAT设备实施。 总之,STUN协议允许不同级别的映射和过滤规则,具体取决于NAT设备的实现和安全要求。不同的规则级别提供了不同程度的灵活性和安全性,以满足各种网络环境的需求。

Linux转发配置

在上述例子步骤7还是有问题的,由于TUN设备是三层的虚拟网卡,不具备二层的数据链路层的转发功能,因此需要配置tun0的数据包由eth0负责转发。

java 复制代码
vi /etc/sysctl.conf
net.ipv4.ip_forward = 1

net.ipv4.ip_forward = 0: 这是默认设置,表示禁用IPv4数据包的转发。 当此参数的值为0时,Linux内核不会将接收到的IPv4数据包重新路由到其他网络接口。这意味着Linux系统仅接受本地目标的数据包,并不具备路由功能。

net.ipv4.ip_forward = 1: 这个设置启用了IPv4数据包的转发。 当此参数的值为1时,Linux内核将根据路由表信息,将接收到的IPv4数据包转发到适当的网络接口,以便它们能够到达其目标。这是典型的路由器或网络关口设备的配置。

java 复制代码
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接口。

java 复制代码
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。

组件依赖

coding......

相关推荐
FF在路上18 分钟前
Knife4j调试实体类传参扁平化模式修改:default-flat-param-object: true
java·开发语言
真的很上进25 分钟前
如何借助 Babel+TS+ESLint 构建现代 JS 工程环境?
java·前端·javascript·css·react.js·vue·html
众拾达人1 小时前
Android自动化测试实战 Java篇 主流工具 框架 脚本
android·java·开发语言
皓木.1 小时前
Mybatis-Plus
java·开发语言
不良人天码星1 小时前
lombok插件不生效
java·开发语言·intellij-idea
守护者1701 小时前
JAVA学习-练习试用Java实现“使用Arrays.toString方法将数组转换为字符串并打印出来”
java·学习
源码哥_博纳软云1 小时前
JAVA同城服务场馆门店预约系统支持H5小程序APP源码
java·开发语言·微信小程序·小程序·微信公众平台
禾高网络1 小时前
租赁小程序成品|租赁系统搭建核心功能
java·人工智能·小程序
学会沉淀。1 小时前
Docker学习
java·开发语言·学习
如若1232 小时前
对文件内的文件名生成目录,方便查阅
java·前端·python