Documentation ¶
Overview ¶
NAT 探测协助包(UDP)
Index ¶
- Constants
- Variables
- func ClientDial(ctx context.Context, conn *net.UDPConn, raddr *net.UDPAddr, sn ClientSN, ...) <-chan int
- func DecryptAddr(data []byte, key *[32]byte) (*net.UDPAddr, error)
- func EncodeLiveNAT(cnt uint8, sn ClientSN, addr []byte) ([]byte, error)
- func EncodePunch(kind string, p *Puncher) ([]byte, error)
- func EncryptAddr(addr *net.UDPAddr, key *[32]byte) ([]byte, error)
- func EncryptSN(sn ClientSN, key *[32]byte) ([]byte, error)
- func EncryptSn33(cnt uint8, sn ClientSN, key *[32]byte) ([]byte, error)
- func GenerateClientSN(seed [32]byte, ip netip.Addr) (ClientSN, Rnd16, error)
- func GenerateSnKey(seed [32]byte, rest Rnd16) [32]byte
- func Listen(ctx context.Context, addr *net.UDPAddr, seed [32]byte) <-chan *ClientInfo
- func ListenSend(conn *net.UDPConn, raddr *net.UDPAddr, sn ClientSN) error
- func LiveListen(ctx context.Context, seed [32]byte, addr *net.UDPAddr)
- func LivingTest(ctx context.Context, conn, conn2 *net.UDPConn, raddr *net.UDPAddr, cnt uint8, ...) (bool, error)
- func LivingTime(ctx context.Context, conn, conn2 *net.UDPConn, raddr, laddr *net.UDPAddr, ...) <-chan time.Duration
- func NewHost(raddr *net.UDPAddr, sn ClientSN) error
- func NewPort(raddr *net.UDPAddr, sn ClientSN) error
- func Resolve(ctx context.Context, paddr *net.UDPAddr, conn *net.UDPConn, sn ClientSN) <-chan NatLevel
- type ClientInfo
- type ClientSN
- type LiveNAT
- type NatLevel
- type Puncher
- type Punches
- func (*Punches) Descriptor() ([]byte, []int)deprecated
- func (x *Punches) GetAddr() []byte
- func (x *Punches) GetExtra() string
- func (x *Punches) GetKind() string
- func (x *Punches) GetLevel() int32
- func (*Punches) ProtoMessage()
- func (x *Punches) ProtoReflect() protoreflect.Message
- func (x *Punches) Reset()
- func (x *Punches) String() string
- type Rnd16
Constants ¶
const LenSN = 32
LenSN 序列号长度。 格式:16 + 16 = 32
Variables ¶
var File_liveudp_proto protoreflect.FileDescriptor
var File_pinhole_proto protoreflect.FileDescriptor
Functions ¶
func ClientDial ¶
func ClientDial(ctx context.Context, conn *net.UDPConn, raddr *net.UDPAddr, sn ClientSN, rnd Rnd16, key *[32]byte) <-chan int
ClientDial 客户机向服务器初始拨号发送UDP消息。 客户端在获得服务器UDP监听地址和序列号以及密钥后执行。 环境: - 适用于尚未建立过UDP链路,不知道网络是否友好UDP时。
RFC3489:冗余多次发送,最多9次。 间隔时间(ms):100, 200, 400, 800, 1600, 1600, 1600, 1600, 1600 结束 累计时长(ms):100, 300, 700, 1500, 3100, 4700, 6300, 7900, 9500 超时
@ctx 当客户端从TCP连接收到回应后,ctx通知取消发送 @conn 客户端的UDP监听连接,会在此连接上发送消息 @raddr 服务器UDP监听地址 @sn 序列号(当前事务ID),从服务器端获得,原样发送 @rnd 半个随机数种子 @key 对称加密/解密密钥 @return 一个通道,告知实际发送的次数
func DecryptAddr ¶
DecryptAddr 解密网络地址数据 与DecodeLiveNAT配合使用,在服务器验证序列号合法之后,解密提取目标地址。 使用者:服务器
func EncodeLiveNAT ¶
EncodeLiveNAT 编码LiveNAT的数据 其中批次和序列号为明文,地址在protoBuf编码之前会被加密。 @cnt 发送批次 @sn 服务器分配的序列号 @addr 客户端先前的UDP地址(已加密) 使用者:客户端
func EncodePunch ¶
EncodePunch 编码打洞信息包 内部通过 Punches 结构封装传递。 使用: 编码数据传递到网络前,还应使用 config.EncodeProto 编码, 并加入 config.COMMAND_STUN 指示。 @kind 应用类型名 @p 打洞信息包 @return 用于网络传输的字节序列
func EncryptAddr ¶
EncryptAddr 加密网络地址 与EncodeLiveNAT配合使用,在其之前加密地址信息。 使用者:客户端
func EncryptSn33 ¶
EncryptSn33 加密批次&序列号 服务器端回送LiveNAT消息时加密。简单数据无需protoBuf编码。 安全性: 客户端发送的序列号是明文的,因为服务器需要明文序列号提取密钥构造因子。 服务器回送序列号时进行加密,使得明文序列号是单向的,可有效避免监听。 使用者:服务器
func GenerateClientSN ¶
GenerateClientSN 创建一个IP特定的序列号 包含特定的结构,外部难以攻击。 服务器端可以直接关联对端IP计算验证(VerifySN)。 结构: IP + (seed:32 + rand:16) => data seed 为服务器端固定种子值,每次启动后随机构造。 生成: - Hash(data) => hash - rand:16 + hash[16:32] => 序列号(sn) 即: - 对外暴露16字节的随机序列,以及哈希结果的后半段。 - 隐藏服务器端种子seed,以及哈希结果的前半段(可另有用途)。 参数: @seed 随机数种子,服务器启动后自动生成,运行期间固定不变 @ip 对端节点的公网IP,通常从TCP连接获取 @return1 匹配对端的一个随机序列号(隐含对端IP) @return2 哈希结果的前段未用16字节,可用于隐式密码种子
注记:
- return2 可以用来和 seed 组合成密钥(Sum256后),提供给客户端加密UDP数据。 这在客户端初次发送UDP信息和 STUN:Live 探测中有用。
- return2 也可以用来作为map的键,引用提出请求的客户端的TCP连接。 这在服务器回报客户端的UDP地址时有用。
func GenerateSnKey ¶
GenerateSnKey 创建序列号关联密钥 该密钥用来加密客户端发送给服务器的自身的另一个UDP地址。 这由服务器调用,结果密钥通过原有TCP链路发送给客户端(和序列号等信息一起)。 注记: 服务器验证客户端的UDP消息时,即时生成密钥即可。 @seed 服务器端随机种子 @rest 序列号验证后的第二个返回值 @return 地址加密密钥
func Listen ¶
Listen STUN 监听服务。 与TCP链路一起配合,接收客户端的初始拨号,提取其公网地址。 向TCP链路提供客户端的基本信息。 @ctx 上下文控制 @addr 本地UDP监听地址 @seed 服务器随机数种子 @return 客户端的信息告知通道
func ListenSend ¶
ListenSend 服务器从监听连接发送UDP消息。 通讯的本地端口为监听器端口,不会新建一个端口。 此为服务器的正常回应,UDP链路已通。最多尝试2次(最多2个数据包)。 @conn 服务器Listen创建的UDP监听连接 @raddr 目标客户端的UDP地址 @sn 客户序列号。首字节会设置标志,表示Listen发送
func LiveListen ¶
LiveListen 启动NAT生命期探测服务 作为一个单独的服务存在,服务器仅需根据简单的规则回应即可。 即并不与NAT类型探测(STUN:Cone/Sym)服务合并在一起。 使用者:服务器 @ctx 上下文控制 @seed 服务器随机数种子 @addr 本地监听地址
func LivingTest ¶
func LivingTest(ctx context.Context, conn, conn2 *net.UDPConn, raddr *net.UDPAddr, cnt uint8, sn ClientSN, addr []byte, key *[32]byte) (bool, error)
LivingTest 客户端NAT映射生命期探测(单次)。 客户端用一个新的UDP端口发送信息包(NAT会新建一个映射)。 此测试是在客户端NAT探测之后执行,已经验证NAT是否已支持UDP链路。 因此只发送1个冗余数据包,避免服务器不必要的负担。 数据: - 当前测试批次、序列号。合并在一起共33字节。 - 服务器回复的目标UDP地址。 行为: 1. 在原来的监听端口号上监听回复。 2. 在新发送消息的连接上读取回复。 判断: - 在 1. 上读取到回复,表示NAT映射没有改变。返回true - 在 2. 上读取到回复,表示NAT映射已经改变(复用了之前的端口)。返回false - 如果都没有收到回复,表示NAT映射已经完全改变。返回false 注记: 这仅是执行单次测试,最终的生命期计算需要多次探测。 用户可以自己设计时间间隔策略,但也可以直接使用下面的LivingTime探测函数。
@ctx 上下文环境控制 @conn 客户端UDP原监听连接 @con2 客户端新拨号的连接,在此连接上发送探测 @raddr 服务器UDP监听地址,拨号目标 @cnt 发送批次计数 @sn 原始序列号,用于对比验证 @addr 已加密地址密文 @key 对称加密/解密密钥,用于验证服务器回应的序列号 @return 是否已经改变
func LivingTime ¶
func LivingTime(ctx context.Context, conn, conn2 *net.UDPConn, raddr, laddr *net.UDPAddr, sn ClientSN, key *[32]byte) <-chan time.Duration
LivingTime 测试探查NAT映射生命期。 按逐渐增加的时间间隔持续调用LivingTest测试, 最后一次成功的时间间隔即是NAT生命期(近似值)。 时间间隔: 从30秒开始,每次间隔时间增加14秒,即:30s, 44s, 58s, 72s ... 使用: 客户端在原连接上探知了自己的UDP公网地址和NAT类型后,即可开始此流程。 这里初次的探测包会在30秒之后才发送。
服务器端: - 一个专门的端口监听并读取LiveNAT数据。 - 如果接收的数据长度大于33字节,这后续部分为加密的地址信息。 - 如果接收的数据长度小于等于33字节,则忽视(客户端维持新端口活跃的消息,无需理睬)。 - 验证序列号、提取隐藏序列构造密钥解密地址,然后向该地址发送回应包。 - 回应包仅需包含原数据前33字节(批次+序列号),加密传送,客户端据此验证回复。 - 这是一种通用设计,服务器无需存储和分辨对端。
@ctx 上下文环境控制(如超时取消) @conn 客户端UDP原监听连接,会同时在此连接上监听回复 @conn2 客户端新拨号的连接,在此连接上持续发送探测 @raddr 服务器UDP监听地址,新拨号的目标 @laddr 本地UDP目标地址(服务器发送的目标) @sn 序列号,从服务器端获得,原样发送和接收(服务端原样返回) @return 一个通道,告知结果(零值无意义)
func NewHost ¶
NewHost 新服务器发送UDP消息。 由收到 NewHost 请求的节点执行发送(新主机自然为一个新IP)。 对方: - 收到 & 1.N => FullC 类型 - 收到 & 1.Y => 公网之上(Open Internet) - 未收到 & 1.Y => Sym UDP Firewall - 未收到 & 1.N => P-RC | Sym 类型(注:也含RC,但RC已由3.判断出来) @raddr 目标客户端的UDP地址 @sn 客户序列号,首字节会设置标志,表示NewHost发送
Types ¶
type ClientInfo ¶
Client 客户端基本信息
type ClientSN ¶
ClientSN 客户端序列号类型 [:16] 一个随机字节序列,是构造序列号的种子之一。 [16:] 两个种子计算的Sum256结果的局部(后段)。
func DecodeLiveNAT ¶
DecodeLiveNAT 解码/解密LiveNAT编码数据。 注意序列号部分为明文,因为需要用此部分来构建密钥。 使用者: - 由服务器端接收数据后调用。 - 在验证序列号合法之后,即可解密地址密文(DecryptAddr)。 @data protoBuf编码的数据 @return1 发送批次 @return2 客户端序列号 @return3 目标地址的密文数据
type LiveNAT ¶
type LiveNAT struct { Sn33 []byte `protobuf:"bytes,1,opt,name=sn33,proto3" json:"sn33,omitempty"` // 批次+序列号(1+32字节) Xaddr []byte `protobuf:"bytes,2,opt,name=xaddr,proto3" json:"xaddr,omitempty"` // 公网地址(IP+Port),已加密 // contains filtered or unexported fields }
LiveNAT 消息包 包含客户端提供的目标UDP地址和序列号。 序列号之前会前置一个批次字节,以方便客户端辨别发送的时间段。
func (*LiveNAT) Descriptor
deprecated
func (*LiveNAT) ProtoMessage ¶
func (*LiveNAT) ProtoMessage()
func (*LiveNAT) ProtoReflect ¶
func (x *LiveNAT) ProtoReflect() protoreflect.Message
type NatLevel ¶
type NatLevel int
NatLevel NAT层级
const ( NAT_LEVEL_NULL NatLevel = iota // 0: Public | Public@UPnP | Full Cone NAT_LEVEL_RC // 1: Restricted Cone (RC) NAT_LEVEL_PRC // 2: Port Restricted Cone (P-RC) NAT_LEVEL_SYM // 3: Symmetric NAT (Sym) | Sym UDP Firewall NAT_LEVEL_PRCSYM // 4: P-RC | Sym NAT_LEVEL_ERROR // 5: UDP不可用,或探测错误默认值 )
NAT 4级分层定义
type Puncher ¶
type Puncher struct { Addr *net.UDPAddr // 公网地址 Level NatLevel // NAT 层级(0 ~ 3) Extra string // 额外信息 }
Puncher 打洞信息包
func DecodePunch ¶
DecodePunch 解码打洞信息包 注记: 服务端会先用 config.DecodeProto 解码获取 config.COMMAND_STUN 指示, 然后得到的内容数据才是 EncodePunch 的编码数据,可用于此。 @data 网络传输过来的已编码数据 @return1 应用类型名 @return2 打洞信息包
type Punches ¶
type Punches struct { Kind string `protobuf:"bytes,1,opt,name=kind,proto3" json:"kind,omitempty"` // 应用类型 Addr []byte `protobuf:"bytes,2,opt,name=addr,proto3" json:"addr,omitempty"` // 公网地址(IP+Port) Level int32 `protobuf:"varint,3,opt,name=level,proto3" json:"level,omitempty"` // NAT 层级(0: Pub/FullC; 1: RC; 2: P-RC; 3: Sym) Extra string `protobuf:"bytes,4,opt,name=extra,proto3" json:"extra,omitempty"` // 额外信息(作为一种可能的灵活性),可选 // contains filtered or unexported fields }
请求打洞协助消息 由客户端自己传递相关信息,因此可以向任意服务器请求STUN服务。
func (*Punches) Descriptor
deprecated
func (*Punches) ProtoMessage ¶
func (*Punches) ProtoMessage()
func (*Punches) ProtoReflect ¶
func (x *Punches) ProtoReflect() protoreflect.Message
type Rnd16 ¶
type Rnd16 [16]byte
Rnd16 16字节随机序列
func VerifySN ¶
VerifySN 验证序列号是否正确。 环境: 序列号的发送是在TCP链路上,因此生成采用的IP是从TCP连接上获取。 序列号的验证是在UDP链路上,因此验证针对的IP是从UDP连接上获取。 因此需要合理假设:一个NAT内的客户端同一时间发送的TCP和UDP会有同一个源IP地址。 说明: - seed 服务器的随机数种子,服务器启动后即固定不变。 - ip 对端节点的公网IP,从当前UDP连接上获取(其应当与TCP链路上相同)。 - sn 对端节点随UDP数据包发送过来的序列号。 验证: 根据上面生成函数的结构说明进行计算: - sn[:16] => rand:16 - seed:32 + rand:16 + ip:xxx => data - Sum256(data) => hash 结果: @return1: sn[16:] ?= hash[16:] @return2: 提取的哈希结果的前段16字节