mirror of
https://gitee.com/konyshe/goodlink.git
synced 2025-09-26 20:51:22 +08:00
227 lines
7.2 KiB
Go
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)
|
|
}
|