Files
goodlink/stun2/cli.go
kony 50bd5abe18 u
2025-03-21 15:12:08 +08:00

227 lines
7.2 KiB
Go

package stun2
import (
"bytes"
"crypto/rand"
"encoding/binary"
"fmt"
"goodlink/utils"
"log"
"net"
"time"
)
func getStunIpPort5(attrType uint16, attributes []byte, attrLength uint16, magicCookie []byte, transactionID []byte) (string, int, error) {
// https://www.rfc-editor.org/rfc/rfc5389.html#section-15.1
// MAPPED-ADDRESS
// 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 0 0 0 0 0 0| Family | Port |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | |
// | Address (32 bits or 128 bits) |
// | |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// https://www.rfc-editor.org/rfc/rfc5389.html#section-15.2
// XOR-MAPPED-ADDRESS
// 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
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |x x x x x x x x| Family | X-Port |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | X-Address (Variable)
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
attributeValue := attributes[4 : 4+attrLength]
family := attributeValue[1]
var ip, port []byte
switch family {
case 1:
ip = attributeValue[4:8]
port = attributeValue[2:4]
case 2:
ip = attributeValue[4:20]
default:
return "", 0, fmt.Errorf("unknown address family")
}
if attrType == 0x0020 { // XOR-Mapped Address
for i := 0; i < 4; i++ {
ip[i] ^= magicCookie[i]
}
if family == 2 {
for i := 4; i < len(ip); i++ {
ip[i] ^= transactionID[i-4]
}
}
}
port2 := binary.BigEndian.Uint16(port)
return net.IP(ip).String(), int(port2), nil
}
func getStunIpPort4(response []byte, response_len int, needType uint16, magicCookie []byte, transactionID []byte) (string, int, error) {
start := 0
for {
if start >= response_len-4 {
break
}
// Parse STUN attributes
attributes := response[start:]
attrType := binary.BigEndian.Uint16(attributes[:2])
attrLength := binary.BigEndian.Uint16(attributes[2:4])
if attrLength < 8 {
break
}
if attrType == needType {
return getStunIpPort5(attrType, attributes, attrLength, magicCookie, transactionID)
}
start = start + int(attrLength) + 4
}
return "", 0, fmt.Errorf("attrType not found")
}
func getStunResponse(conn *net.UDPConn, addr string, buf *bytes.Buffer) ([]byte, int, error) {
udpAddr, err := net.ResolveUDPAddr("udp4", addr)
if err != nil {
return nil, 0, err
}
_, err = conn.WriteToUDP(buf.Bytes(), udpAddr)
if err != nil {
return nil, 0, err
}
response := make([]byte, 1024)
conn.SetReadDeadline(time.Now().Add(1000 * time.Millisecond))
n, err := conn.Read(response)
defer conn.SetReadDeadline(time.Time{})
if err != nil {
return nil, 0, err
}
if n < 32 {
return nil, 0, fmt.Errorf("invalid response")
}
// Parse STUN message
if !bytes.Equal(response[4:8], buf.Bytes()[4:8]) {
return nil, 0, fmt.Errorf("invalid magic cookie in response")
}
if !bytes.Equal(response[8:20], buf.Bytes()[8:20]) {
return nil, 0, fmt.Errorf("transaction ID mismatch in response")
}
return response, n, nil
}
func getStunIpPort2(conn *net.UDPConn, addr string, buf *bytes.Buffer, magicCookie []byte, transactionID []byte) (string, int, string, int) {
// https://www.rfc-editor.org/rfc/rfc5389.html#section-6
// STUN Message Structure
// 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) |
// | |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
response, n, err := getStunResponse(conn, addr, buf)
if err != nil {
log.Printf("getStunResponse error: %v", err)
return "", 0, "", 0
}
// https://www.rfc-editor.org/rfc/rfc5389.html#section-15
// STUN Attributes
// 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
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | Type | Length |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | Value (variable) ....
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
wan_ip1, wan_port1, err := getStunIpPort4(response[20:], n, 0x0001, magicCookie, transactionID)
if err != nil {
wan_ip1, wan_port1, err = getStunIpPort4(response[20:], n, 0x0020, magicCookie, transactionID)
}
if err != nil {
log.Printf("getStunIpPort4 error: %v", err)
return "", 0, "", 0
}
change_ip, change_port, err := getStunIpPort4(response[20:], n, 0x0005, magicCookie, transactionID)
if err != nil {
log.Printf("getStunIpPort4 error: %v", err)
}
return wan_ip1, wan_port1, change_ip, change_port
}
func GetStunIpPort(conn *net.UDPConn) (wan_ip string, wan_port1, wan_port2, wan_port3 int) {
var change_ip string
var change_port int
utils.Log().Debug("获取本端地址")
ips, err := net.LookupIP("stun.easyvoip.com")
if err != nil {
fmt.Println("Error:", err)
return
}
// STUN message header
var buf bytes.Buffer
// Start with fixed 0x00, message type: 0x01, message length: 0x0000
buf.Write([]byte{0x00, 0x01, 0x00, 0x00})
magicCookie := []byte{0x21, 0x12, 0xA4, 0x42}
buf.Write(magicCookie)
transactionID := make([]byte, 12)
rand.Read(transactionID)
buf.Write(transactionID)
for {
for _, ip := range ips {
wan_ip, wan_port1, change_ip, change_port = getStunIpPort2(conn, ip.String()+":3478", &buf, magicCookie, transactionID)
if wan_ip == "" || wan_port1 == 0 || change_ip == "" || change_port == 0 {
time.Sleep(1 * time.Second)
continue
}
_, wan_port2, _, _ = getStunIpPort2(conn, ip.String()+":3479", &buf, magicCookie, transactionID)
if wan_port2 == 0 {
time.Sleep(1 * time.Second)
continue
}
_, wan_port3, _, _ = getStunIpPort2(conn, fmt.Sprintf("%s:%d", change_ip, change_port), &buf, magicCookie, transactionID)
if wan_port3 == 0 {
time.Sleep(1 * time.Second)
continue
}
return
}
time.Sleep(1 * time.Second)
}
}
func TestStun() {
conn4, _ := net.ListenUDP("udp4", nil)
defer conn4.Close()
wan_ip, wan_port1, wan_port2, wan_port3 := GetStunIpPort(conn4)
log.Printf("wan_ip: %s, wan_port1: %d, wan_port2: %d, wan_port3: %d", wan_ip, wan_port1, wan_port2, wan_port3)
}