mirror of
https://github.com/xiaoqidun/probe.git
synced 2026-01-29 04:58:46 +08:00
feat(正式发布): 以Apache License 2.0协议开源
This commit is contained in:
+228
@@ -0,0 +1,228 @@
|
||||
// Copyright 2026 肖其顿 (XIAO QI DUN)
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"net"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
// MappingEndpointIndependent 独立映射模式常量
|
||||
const MappingEndpointIndependent = "Endpoint-Independent"
|
||||
|
||||
// MappingAddressDependent 地址相关映射模式常量
|
||||
const MappingAddressDependent = "Address-Dependent"
|
||||
|
||||
// MappingAddressPortDependent 地址端口相关映射模式常量
|
||||
const MappingAddressPortDependent = "Address and Port-Dependent"
|
||||
|
||||
// MappingUnknown 未知映射模式常量
|
||||
const MappingUnknown = "Unknown"
|
||||
|
||||
// FilteringEndpointIndependent 独立过滤模式常量
|
||||
const FilteringEndpointIndependent = "Endpoint-Independent"
|
||||
|
||||
// FilteringAddressDependent 地址相关过滤模式常量
|
||||
const FilteringAddressDependent = "Address-Dependent"
|
||||
|
||||
// FilteringAddressPortDependent 地址端口相关过滤模式常量
|
||||
const FilteringAddressPortDependent = "Address and Port-Dependent"
|
||||
|
||||
// FilteringUnknown 未知过滤模式常量
|
||||
const FilteringUnknown = "Unknown"
|
||||
|
||||
// NATOpen 公网类型常量
|
||||
const NATOpen = "Open Internet"
|
||||
|
||||
// NATFullCone 全锥型NAT常量
|
||||
const NATFullCone = "Full Cone"
|
||||
|
||||
// NATRestricted 限制锥型NAT常量
|
||||
const NATRestricted = "Restricted Cone"
|
||||
|
||||
// NATPortRestricted 端口限制锥型NAT常量
|
||||
const NATPortRestricted = "Port Restricted Cone"
|
||||
|
||||
// NATSymmetric 对称型NAT常量
|
||||
const NATSymmetric = "Symmetric"
|
||||
|
||||
// NATUDPBlocked UDP阻塞常量
|
||||
const NATUDPBlocked = "UDP Blocked"
|
||||
|
||||
// NATUnknown 未知类型常量
|
||||
const NATUnknown = "Unknown"
|
||||
|
||||
// NATResult NAT探测结果结构
|
||||
type NATResult struct {
|
||||
Type string
|
||||
Mapping string
|
||||
Filtering string
|
||||
MappedIP string
|
||||
}
|
||||
|
||||
// resolveAddr 解析探测目标地址
|
||||
// 入参: conn 当前连接, addrStr 目标地址字符串, network 网络协议
|
||||
// 返回: addr 解析后的网络地址, err 解析错误
|
||||
func resolveAddr(conn net.PacketConn, addrStr, network string) (net.Addr, error) {
|
||||
host, portStr, err := net.SplitHostPort(addrStr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
port, err := strconv.Atoi(portStr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, ok := conn.(*socks5PacketConn); ok {
|
||||
if net.ParseIP(host) == nil && host != "localhost" && host != "127.0.0.1" {
|
||||
return &SocksAddr{Host: host, Port: port}, nil
|
||||
}
|
||||
}
|
||||
return net.ResolveUDPAddr(network, addrStr)
|
||||
}
|
||||
|
||||
// performTest 执行单次STUN探测
|
||||
// 入参: conn 连接对象, serverAddr STUN服务器地址, network 协议, timeout 超时设定, changeIP 变更IP标志, changePort 变更端口标志
|
||||
// 返回: msg 响应消息, addr 响应源地址, err 探测错误
|
||||
func performTest(conn net.PacketConn, serverAddr string, network string, timeout time.Duration, changeIP, changePort bool) (*stunMessage, *net.UDPAddr, error) {
|
||||
dst, err := resolveAddr(conn, serverAddr, network)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
txID := [12]byte{}
|
||||
rand.Read(txID[:])
|
||||
req := encodeSTUNRequest(txID, changeIP, changePort)
|
||||
if _, err := conn.WriteTo(req, dst); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
conn.SetReadDeadline(time.Now().Add(timeout))
|
||||
defer conn.SetReadDeadline(time.Time{})
|
||||
buf := make([]byte, 2048)
|
||||
for i := 0; i < 3; i++ {
|
||||
n, addr, err := conn.ReadFrom(buf)
|
||||
if err != nil {
|
||||
if nerr, ok := err.(net.Error); ok && nerr.Timeout() {
|
||||
break
|
||||
}
|
||||
return nil, nil, err
|
||||
}
|
||||
msg, err := decodeSTUNResponse(buf[:n], txID)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
uAddr, ok := addr.(*net.UDPAddr)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
return msg, uAddr, nil
|
||||
}
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
// DetectNAT 执行NAT类型检测核心逻辑
|
||||
// 入参: conn 连接对象, primarySTUN 主服务器, secondarySTUN 辅服务器, network 协议, timeout 超时设定
|
||||
// 返回: result 检测结果
|
||||
func DetectNAT(conn net.PacketConn, primarySTUN, secondarySTUN, network string, timeout time.Duration) NATResult {
|
||||
res := NATResult{Type: NATUnknown, Mapping: MappingUnknown, Filtering: FilteringUnknown}
|
||||
resp1, _, err := performTest(conn, primarySTUN, network, timeout, false, false)
|
||||
if err != nil || resp1 == nil {
|
||||
res.Type = NATUDPBlocked
|
||||
return res
|
||||
}
|
||||
mappedAddr1 := resp1.GetMappedAddress()
|
||||
if mappedAddr1 == nil {
|
||||
return res
|
||||
}
|
||||
res.MappedIP = mappedAddr1.String()
|
||||
var targetSTUN2 string
|
||||
if secondarySTUN != "" {
|
||||
targetSTUN2 = secondarySTUN
|
||||
} else {
|
||||
changedAddr := resp1.GetChangedAddress()
|
||||
if changedAddr != nil {
|
||||
targetSTUN2 = net.JoinHostPort(changedAddr.IP.String(), strconv.Itoa(changedAddr.Port))
|
||||
} else {
|
||||
host, port, _ := net.SplitHostPort(primarySTUN)
|
||||
ips, err := net.LookupIP(host)
|
||||
if err == nil && len(ips) > 1 {
|
||||
primaryIP, _ := net.ResolveIPAddr("ip", host)
|
||||
wantIPv4 := network == "udp4"
|
||||
wantIPv6 := network == "udp6"
|
||||
if network == "udp" {
|
||||
wantIPv4 = primaryIP.IP.To4() != nil
|
||||
wantIPv6 = !wantIPv4
|
||||
}
|
||||
for _, ip := range ips {
|
||||
if ip.Equal(primaryIP.IP) {
|
||||
continue
|
||||
}
|
||||
isV4 := ip.To4() != nil
|
||||
if wantIPv4 && !isV4 {
|
||||
continue
|
||||
}
|
||||
if wantIPv6 && isV4 {
|
||||
continue
|
||||
}
|
||||
targetSTUN2 = net.JoinHostPort(ip.String(), port)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
var mappedAddr3 *net.UDPAddr
|
||||
if targetSTUN2 != "" {
|
||||
resp3, _, err := performTest(conn, targetSTUN2, network, timeout, false, false)
|
||||
if err == nil && resp3 != nil {
|
||||
mappedAddr3 = resp3.GetMappedAddress()
|
||||
}
|
||||
}
|
||||
if mappedAddr3 != nil {
|
||||
if mappedAddr1.String() == mappedAddr3.String() {
|
||||
res.Mapping = MappingEndpointIndependent
|
||||
} else {
|
||||
res.Mapping = MappingAddressPortDependent
|
||||
}
|
||||
} else {
|
||||
res.Mapping = MappingUnknown
|
||||
}
|
||||
resp2, _, _ := performTest(conn, primarySTUN, network, timeout, true, true)
|
||||
if resp2 != nil {
|
||||
res.Filtering = FilteringEndpointIndependent
|
||||
} else {
|
||||
resp4, _, _ := performTest(conn, primarySTUN, network, timeout, false, true)
|
||||
if resp4 != nil {
|
||||
res.Filtering = FilteringAddressDependent
|
||||
} else {
|
||||
res.Filtering = FilteringAddressPortDependent
|
||||
}
|
||||
}
|
||||
if res.Filtering == FilteringEndpointIndependent {
|
||||
res.Type = NATFullCone
|
||||
if res.Mapping == MappingUnknown {
|
||||
res.Mapping = MappingEndpointIndependent
|
||||
}
|
||||
} else if res.Mapping == MappingEndpointIndependent {
|
||||
switch res.Filtering {
|
||||
case FilteringAddressDependent:
|
||||
res.Type = NATRestricted
|
||||
case FilteringAddressPortDependent:
|
||||
res.Type = NATPortRestricted
|
||||
}
|
||||
} else {
|
||||
res.Type = NATSymmetric
|
||||
}
|
||||
return res
|
||||
}
|
||||
Reference in New Issue
Block a user