stun

package
v0.0.0-...-048466c Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 25, 2024 License: AGPL-3.0 Imports: 15 Imported by: 0

Documentation

Overview

NAT 探测协助包(UDP)

Index

Constants

View Source
const LenSN = 32

LenSN 序列号长度。 格式:16 + 16 = 32

Variables

View Source
var File_liveudp_proto protoreflect.FileDescriptor
View Source
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

func DecryptAddr(data []byte, key *[32]byte) (*net.UDPAddr, error)

DecryptAddr 解密网络地址数据 与DecodeLiveNAT配合使用,在服务器验证序列号合法之后,解密提取目标地址。 使用者:服务器

func EncodeLiveNAT

func EncodeLiveNAT(cnt uint8, sn ClientSN, addr []byte) ([]byte, error)

EncodeLiveNAT 编码LiveNAT的数据 其中批次和序列号为明文,地址在protoBuf编码之前会被加密。 @cnt 发送批次 @sn 服务器分配的序列号 @addr 客户端先前的UDP地址(已加密) 使用者:客户端

func EncodePunch

func EncodePunch(kind string, p *Puncher) ([]byte, error)

EncodePunch 编码打洞信息包 内部通过 Punches 结构封装传递。 使用: 编码数据传递到网络前,还应使用 config.EncodeProto 编码, 并加入 config.COMMAND_STUN 指示。 @kind 应用类型名 @p 打洞信息包 @return 用于网络传输的字节序列

func EncryptAddr

func EncryptAddr(addr *net.UDPAddr, key *[32]byte) ([]byte, error)

EncryptAddr 加密网络地址 与EncodeLiveNAT配合使用,在其之前加密地址信息。 使用者:客户端

func EncryptSN

func EncryptSN(sn ClientSN, key *[32]byte) ([]byte, error)

EncryptSN 加密序列号 用于客户端向服务器回送UDP信息以发现自己的公网地址。 使用者:客户端

func EncryptSn33

func EncryptSn33(cnt uint8, sn ClientSN, key *[32]byte) ([]byte, error)

EncryptSn33 加密批次&序列号 服务器端回送LiveNAT消息时加密。简单数据无需protoBuf编码。 安全性: 客户端发送的序列号是明文的,因为服务器需要明文序列号提取密钥构造因子。 服务器回送序列号时进行加密,使得明文序列号是单向的,可有效避免监听。 使用者:服务器

func GenerateClientSN

func GenerateClientSN(seed [32]byte, ip netip.Addr) (ClientSN, Rnd16, error)

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

func GenerateSnKey(seed [32]byte, rest Rnd16) [32]byte

GenerateSnKey 创建序列号关联密钥 该密钥用来加密客户端发送给服务器的自身的另一个UDP地址。 这由服务器调用,结果密钥通过原有TCP链路发送给客户端(和序列号等信息一起)。 注记: 服务器验证客户端的UDP消息时,即时生成密钥即可。 @seed 服务器端随机种子 @rest 序列号验证后的第二个返回值 @return 地址加密密钥

func Listen

func Listen(ctx context.Context, addr *net.UDPAddr, seed [32]byte) <-chan *ClientInfo

Listen STUN 监听服务。 与TCP链路一起配合,接收客户端的初始拨号,提取其公网地址。 向TCP链路提供客户端的基本信息。 @ctx 上下文控制 @addr 本地UDP监听地址 @seed 服务器随机数种子 @return 客户端的信息告知通道

func ListenSend

func ListenSend(conn *net.UDPConn, raddr *net.UDPAddr, sn ClientSN) error

ListenSend 服务器从监听连接发送UDP消息。 通讯的本地端口为监听器端口,不会新建一个端口。 此为服务器的正常回应,UDP链路已通。最多尝试2次(最多2个数据包)。 @conn 服务器Listen创建的UDP监听连接 @raddr 目标客户端的UDP地址 @sn 客户序列号。首字节会设置标志,表示Listen发送

func LiveListen

func LiveListen(ctx context.Context, seed [32]byte, addr *net.UDPAddr)

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

func NewHost(raddr *net.UDPAddr, sn ClientSN) error

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发送

func NewPort

func NewPort(raddr *net.UDPAddr, sn ClientSN) error

NewPort 服务器用一个新端口发送UDP消息。 由协助服务器自己发送,测试对方是否为 RC NAT 类型。 对方: - 收到 => RC - 未收到 => P-RC 或 Sym。 @raddr 目标客户端的UDP地址 @sn 客户序列号,首字节会设置标志,表示NewPort发送

func Resolve

func Resolve(ctx context.Context, paddr *net.UDPAddr, conn *net.UDPConn, sn ClientSN) <-chan NatLevel

Resolve 解析NAT类型。 根据收到的消息综合判断本客户端的NAT类型。 注:在客户端收到服务器从TCP链路的回复之后即可开始。 @ctx 外部上下文控制 @paddr 客户端UDP公网地址 @conn 客户端UDP监听地址 @sn 客户原始序列号(未设置前端标志字符) @return 一个通道,告知分析结果

Types

type ClientInfo

type ClientInfo struct {
	IP   net.IP   // IP
	Port int      // UDP端口
	SN   ClientSN // 序列号
}

Client 客户端基本信息

type ClientSN

type ClientSN [LenSN]byte

ClientSN 客户端序列号类型 [:16] 一个随机字节序列,是构造序列号的种子之一。 [16:] 两个种子计算的Sum256结果的局部(后段)。

func DecodeLiveNAT

func DecodeLiveNAT(data []byte) (uint8, ClientSN, []byte, error)

DecodeLiveNAT 解码/解密LiveNAT编码数据。 注意序列号部分为明文,因为需要用此部分来构建密钥。 使用者: - 由服务器端接收数据后调用。 - 在验证序列号合法之后,即可解密地址密文(DecryptAddr)。 @data protoBuf编码的数据 @return1 发送批次 @return2 客户端序列号 @return3 目标地址的密文数据

func DecryptSN

func DecryptSN(data []byte, key *[32]byte) (ClientSN, error)

DecryptSN 解密序列号 由服务器读取、解密和验证,并获取客户端的公网地址。 使用者:服务器

func DecryptSn33

func DecryptSn33(data []byte, key *[32]byte) (uint8, ClientSN, error)

DecryptSn33 解密批次&序列号 由客户端解密服务器回应的消息,提取批次和序列号。 使用者:客户端

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) Descriptor() ([]byte, []int)

Deprecated: Use LiveNAT.ProtoReflect.Descriptor instead.

func (*LiveNAT) GetSn33

func (x *LiveNAT) GetSn33() []byte

func (*LiveNAT) GetXaddr

func (x *LiveNAT) GetXaddr() []byte

func (*LiveNAT) ProtoMessage

func (*LiveNAT) ProtoMessage()

func (*LiveNAT) ProtoReflect

func (x *LiveNAT) ProtoReflect() protoreflect.Message

func (*LiveNAT) Reset

func (x *LiveNAT) Reset()

func (*LiveNAT) String

func (x *LiveNAT) String() string

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级分层定义

func Resolve2

func Resolve2(paddr1, paddr2 *net.UDPAddr) NatLevel

STUN:Sym 简单判断NAT类型。 仅在 STUN:Cone 主服务之后使用,判断为 P-RC | Sym 两者之一。 如果 Resolve 调用返回 NAT_LEVEL_PRCSYM 则需使用此函数。 注:无阻塞。 @paddr1 主服务时获取的客户端公网地址 @paddr2 本次副服务时获取的客户端公网地址

type Puncher

type Puncher struct {
	Addr  *net.UDPAddr // 公网地址
	Level NatLevel     // NAT 层级(0 ~ 3)
	Extra string       // 额外信息
}

Puncher 打洞信息包

func DecodePunch

func DecodePunch(data []byte) (string, *Puncher, error)

DecodePunch 解码打洞信息包 注记: 服务端会先用 config.DecodeProto 解码获取 config.COMMAND_STUN 指示, 然后得到的内容数据才是 EncodePunch 的编码数据,可用于此。 @data 网络传输过来的已编码数据 @return1 应用类型名 @return2 打洞信息包

func NewPunch

func NewPunch(addr *net.UDPAddr, nat NatLevel, tips string) *Puncher

NewPunch 创建一个UDP打洞包

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) Descriptor() ([]byte, []int)

Deprecated: Use Punches.ProtoReflect.Descriptor instead.

func (*Punches) GetAddr

func (x *Punches) GetAddr() []byte

func (*Punches) GetExtra

func (x *Punches) GetExtra() string

func (*Punches) GetKind

func (x *Punches) GetKind() string

func (*Punches) GetLevel

func (x *Punches) GetLevel() int32

func (*Punches) ProtoMessage

func (*Punches) ProtoMessage()

func (*Punches) ProtoReflect

func (x *Punches) ProtoReflect() protoreflect.Message

func (*Punches) Reset

func (x *Punches) Reset()

func (*Punches) String

func (x *Punches) String() string

type Rnd16

type Rnd16 [16]byte

Rnd16 16字节随机序列

func VerifySN

func VerifySN(seed [32]byte, ip netip.Addr, sn ClientSN) (bool, Rnd16)

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字节

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL