mirror of
https://github.com/oneclickvirt/gostun.git
synced 2025-10-05 23:26:52 +08:00
v0.0.2 - 进一步解耦
This commit is contained in:
381
cmd/main.go
381
cmd/main.go
@@ -1,75 +1,33 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/oneclickvirt/gostun/model"
|
||||
"github.com/oneclickvirt/gostun/stuncheck"
|
||||
"github.com/pion/logging"
|
||||
"github.com/pion/stun/v2"
|
||||
)
|
||||
|
||||
// From https://github.com/pion/stun/blob/master/cmd/stun-nat-behaviour/main.go
|
||||
// I only make changes to summarize the NAT type
|
||||
|
||||
// my changes start
|
||||
const GoStunVersion = "v0.0.1"
|
||||
var (
|
||||
NatMappingBehavior string
|
||||
NatFilteringBehavior string
|
||||
)
|
||||
// my changes end
|
||||
|
||||
type stunServerConn struct {
|
||||
conn net.PacketConn
|
||||
LocalAddr net.Addr
|
||||
RemoteAddr *net.UDPAddr
|
||||
OtherAddr *net.UDPAddr
|
||||
messageChan chan *stun.Message
|
||||
}
|
||||
|
||||
func (c *stunServerConn) Close() error {
|
||||
return c.conn.Close()
|
||||
}
|
||||
|
||||
var (
|
||||
addrStrPtr = flag.String("server", "stun.voipgate.com:3478", "STUN server address") //nolint:gochecknoglobals
|
||||
timeoutPtr = flag.Int("timeout", 3, "the number of seconds to wait for STUN server's response") //nolint:gochecknoglobals
|
||||
verbose = flag.Int("verbose", 0, "the verbosity level") //nolint:gochecknoglobals // my changes
|
||||
log logging.LeveledLogger //nolint:gochecknoglobals
|
||||
)
|
||||
|
||||
const (
|
||||
messageHeaderSize = 20
|
||||
)
|
||||
|
||||
var (
|
||||
errResponseMessage = errors.New("error reading from response message channel")
|
||||
errTimedOut = errors.New("timed out waiting for response")
|
||||
errNoOtherAddress = errors.New("no OTHER-ADDRESS in message")
|
||||
)
|
||||
|
||||
func main() {
|
||||
// flag.Parse()
|
||||
// my changes start
|
||||
var showVersion bool
|
||||
flag.BoolVar(&showVersion, "v", false, "show version")
|
||||
flag.IntVar(&model.Verbose, "verbose", 0, "the verbosity level")
|
||||
flag.IntVar(&model.Timeout, "timeout", 3, "the number of seconds to wait for STUN server's response")
|
||||
flag.StringVar(&model.AddrStr, "server", "stun.voipgate.com:3478", "STUN server address")
|
||||
flag.Parse()
|
||||
go func() {
|
||||
http.Get("https://hits.seeyoufarm.com/api/count/incr/badge.svg?url=https%3A%2F%2Fgithub.com%2Foneclickvirt%2Fgostun&count_bg=%2379C83D&title_bg=%23555555&icon=&icon_color=%23E7E7E7&title=hits&edge_flat=false")
|
||||
}()
|
||||
fmt.Println("项目地址:", "https://github.com/oneclickvirt/gostun")
|
||||
if showVersion {
|
||||
fmt.Println(GoStunVersion)
|
||||
fmt.Println(model.GoStunVersion)
|
||||
return
|
||||
}
|
||||
// my changes end
|
||||
var logLevel logging.LogLevel
|
||||
switch *verbose {
|
||||
switch model.Verbose {
|
||||
case 0:
|
||||
logLevel = logging.LogLevelWarn // default // my changes
|
||||
case 1:
|
||||
@@ -79,16 +37,15 @@ func main() {
|
||||
case 3:
|
||||
logLevel = logging.LogLevelTrace
|
||||
}
|
||||
log = logging.NewDefaultLeveledLoggerForScope("", logLevel, os.Stdout)
|
||||
|
||||
if *addrStrPtr == "stun.voipgate.com:3478" {
|
||||
if err := mappingTests(*addrStrPtr); err != nil {
|
||||
NatMappingBehavior = "inconclusive" // my changes
|
||||
log.Warn("NAT mapping behavior: inconclusive")
|
||||
model.Log = logging.NewDefaultLeveledLoggerForScope("", logLevel, os.Stdout)
|
||||
if model.AddrStr != "stun.voipgate.com:3478" {
|
||||
if err := stuncheck.MappingTests(model.AddrStr); err != nil {
|
||||
model.NatMappingBehavior = "inconclusive" // my changes
|
||||
model.Log.Warn("NAT mapping behavior: inconclusive")
|
||||
}
|
||||
if err := filteringTests(*addrStrPtr); err != nil {
|
||||
NatFilteringBehavior = "inconclusive" // my changes
|
||||
log.Warn("NAT filtering behavior: inconclusive")
|
||||
if err := stuncheck.FilteringTests(model.AddrStr); err != nil {
|
||||
model.NatFilteringBehavior = "inconclusive" // my changes
|
||||
model.Log.Warn("NAT filtering behavior: inconclusive")
|
||||
}
|
||||
} else {
|
||||
addrStrPtrList := []string{
|
||||
@@ -98,21 +55,21 @@ func main() {
|
||||
}
|
||||
checkStatus := true
|
||||
for _, addrStr := range addrStrPtrList {
|
||||
err1 := mappingTests(addrStr)
|
||||
err1 := stuncheck.MappingTests(addrStr)
|
||||
if err1 != nil {
|
||||
NatMappingBehavior = "inconclusive"
|
||||
log.Warn("NAT mapping behavior: inconclusive")
|
||||
model.NatMappingBehavior = "inconclusive"
|
||||
model.Log.Warn("NAT mapping behavior: inconclusive")
|
||||
checkStatus = false
|
||||
}
|
||||
err2 := filteringTests(addrStr)
|
||||
err2 := stuncheck.FilteringTests(addrStr)
|
||||
if err2 != nil {
|
||||
NatFilteringBehavior = "inconclusive"
|
||||
log.Warn("NAT filtering behavior: inconclusive")
|
||||
model.NatFilteringBehavior = "inconclusive"
|
||||
model.Log.Warn("NAT filtering behavior: inconclusive")
|
||||
checkStatus = false
|
||||
}
|
||||
if NatMappingBehavior == "inconclusive" || NatFilteringBehavior == "inconclusive" {
|
||||
if model.NatMappingBehavior == "inconclusive" || model.NatFilteringBehavior == "inconclusive" {
|
||||
checkStatus = false
|
||||
} else if NatMappingBehavior != "inconclusive" && NatFilteringBehavior != "inconclusive" {
|
||||
} else if model.NatMappingBehavior != "inconclusive" && model.NatFilteringBehavior != "inconclusive" {
|
||||
checkStatus = true
|
||||
}
|
||||
if checkStatus {
|
||||
@@ -120,294 +77,6 @@ func main() {
|
||||
}
|
||||
}
|
||||
}
|
||||
// my changes start
|
||||
if NatMappingBehavior != "" && NatFilteringBehavior != "" {
|
||||
if NatMappingBehavior == "inconclusive" || NatFilteringBehavior == "inconclusive" {
|
||||
fmt.Println("NAT Type: Inconclusive")
|
||||
} else if NatMappingBehavior == "endpoint independent" && NatFilteringBehavior == "endpoint independent" {
|
||||
fmt.Println("NAT Type: Full Cone")
|
||||
} else if NatMappingBehavior == "endpoint independent" && NatFilteringBehavior == "address dependent" {
|
||||
fmt.Println("NAT Type: Restricted Cone")
|
||||
} else if NatMappingBehavior == "endpoint independent" && NatFilteringBehavior == "address and port dependent" {
|
||||
fmt.Println("NAT Type: Port Restricted Cone")
|
||||
} else if NatMappingBehavior == "address and port dependent" && NatFilteringBehavior == "address and port dependent" {
|
||||
fmt.Println("NAT Type: Symmetric")
|
||||
} else {
|
||||
fmt.Printf("NAT Type: %v[NatMappingBehavior] %v[NatFilteringBehavior]\n", NatMappingBehavior, NatFilteringBehavior)
|
||||
}
|
||||
} else {
|
||||
fmt.Println("NAT Type: Inconclusive")
|
||||
}
|
||||
// my changes end
|
||||
}
|
||||
|
||||
// RFC5780: 4.3. Determining NAT Mapping Behavior
|
||||
func mappingTests(addrStr string) error {
|
||||
mapTestConn, err := connect(addrStr)
|
||||
if err != nil {
|
||||
log.Warnf("Error creating STUN connection: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
// Test I: Regular binding request
|
||||
log.Info("Mapping Test I: Regular binding request")
|
||||
request := stun.MustBuild(stun.TransactionID, stun.BindingRequest)
|
||||
|
||||
resp, err := mapTestConn.roundTrip(request, mapTestConn.RemoteAddr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Parse response message for XOR-MAPPED-ADDRESS and make sure OTHER-ADDRESS valid
|
||||
resps1 := parse(resp)
|
||||
if resps1.xorAddr == nil || resps1.otherAddr == nil {
|
||||
log.Info("Error: NAT discovery feature not supported by this server")
|
||||
return errNoOtherAddress
|
||||
}
|
||||
addr, err := net.ResolveUDPAddr("udp4", resps1.otherAddr.String())
|
||||
if err != nil {
|
||||
log.Infof("Failed resolving OTHER-ADDRESS: %v", resps1.otherAddr)
|
||||
return err
|
||||
}
|
||||
mapTestConn.OtherAddr = addr
|
||||
log.Infof("Received XOR-MAPPED-ADDRESS: %v", resps1.xorAddr)
|
||||
|
||||
// Assert mapping behavior
|
||||
if resps1.xorAddr.String() == mapTestConn.LocalAddr.String() {
|
||||
NatMappingBehavior = "endpoint independent (no NAT)" // my changes
|
||||
log.Warn("=> NAT mapping behavior: endpoint independent (no NAT)")
|
||||
return nil
|
||||
}
|
||||
|
||||
// Test II: Send binding request to the other address but primary port
|
||||
log.Info("Mapping Test II: Send binding request to the other address but primary port")
|
||||
oaddr := *mapTestConn.OtherAddr
|
||||
oaddr.Port = mapTestConn.RemoteAddr.Port
|
||||
resp, err = mapTestConn.roundTrip(request, &oaddr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Assert mapping behavior
|
||||
resps2 := parse(resp)
|
||||
log.Infof("Received XOR-MAPPED-ADDRESS: %v", resps2.xorAddr)
|
||||
if resps2.xorAddr.String() == resps1.xorAddr.String() {
|
||||
NatMappingBehavior = "endpoint independent" // my changes
|
||||
log.Warn("=> NAT mapping behavior: endpoint independent")
|
||||
return nil
|
||||
}
|
||||
|
||||
// Test III: Send binding request to the other address and port
|
||||
log.Info("Mapping Test III: Send binding request to the other address and port")
|
||||
resp, err = mapTestConn.roundTrip(request, mapTestConn.OtherAddr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Assert mapping behavior
|
||||
resps3 := parse(resp)
|
||||
log.Infof("Received XOR-MAPPED-ADDRESS: %v", resps3.xorAddr)
|
||||
if resps3.xorAddr.String() == resps2.xorAddr.String() {
|
||||
NatMappingBehavior = "address dependent" // my changes
|
||||
log.Warn("=> NAT mapping behavior: address dependent")
|
||||
} else {
|
||||
NatMappingBehavior = "address and port dependent" // my changes
|
||||
log.Warn("=> NAT mapping behavior: address and port dependent")
|
||||
}
|
||||
return mapTestConn.Close()
|
||||
}
|
||||
|
||||
// RFC5780: 4.4. Determining NAT Filtering Behavior
|
||||
func filteringTests(addrStr string) error {
|
||||
mapTestConn, err := connect(addrStr)
|
||||
if err != nil {
|
||||
log.Warnf("Error creating STUN connection: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
// Test I: Regular binding request
|
||||
log.Info("Filtering Test I: Regular binding request")
|
||||
request := stun.MustBuild(stun.TransactionID, stun.BindingRequest)
|
||||
|
||||
resp, err := mapTestConn.roundTrip(request, mapTestConn.RemoteAddr)
|
||||
if err != nil || errors.Is(err, errTimedOut) {
|
||||
return err
|
||||
}
|
||||
resps := parse(resp)
|
||||
if resps.xorAddr == nil || resps.otherAddr == nil {
|
||||
log.Warn("Error: NAT discovery feature not supported by this server")
|
||||
return errNoOtherAddress
|
||||
}
|
||||
addr, err := net.ResolveUDPAddr("udp4", resps.otherAddr.String())
|
||||
if err != nil {
|
||||
log.Infof("Failed resolving OTHER-ADDRESS: %v", resps.otherAddr)
|
||||
return err
|
||||
}
|
||||
mapTestConn.OtherAddr = addr
|
||||
|
||||
// Test II: Request to change both IP and port
|
||||
log.Info("Filtering Test II: Request to change both IP and port")
|
||||
request = stun.MustBuild(stun.TransactionID, stun.BindingRequest)
|
||||
request.Add(stun.AttrChangeRequest, []byte{0x00, 0x00, 0x00, 0x06})
|
||||
|
||||
resp, err = mapTestConn.roundTrip(request, mapTestConn.RemoteAddr)
|
||||
if err == nil {
|
||||
parse(resp) // just to print out the resp
|
||||
NatFilteringBehavior = "endpoint independent" // my changes
|
||||
log.Warn("=> NAT filtering behavior: endpoint independent")
|
||||
return nil
|
||||
} else if !errors.Is(err, errTimedOut) {
|
||||
return err // something else went wrong
|
||||
}
|
||||
|
||||
// Test III: Request to change port only
|
||||
log.Info("Filtering Test III: Request to change port only")
|
||||
request = stun.MustBuild(stun.TransactionID, stun.BindingRequest)
|
||||
request.Add(stun.AttrChangeRequest, []byte{0x00, 0x00, 0x00, 0x02})
|
||||
|
||||
resp, err = mapTestConn.roundTrip(request, mapTestConn.RemoteAddr)
|
||||
if err == nil {
|
||||
parse(resp) // just to print out the resp
|
||||
NatFilteringBehavior = "address dependent" // my changes
|
||||
log.Warn("=> NAT filtering behavior: address dependent")
|
||||
} else if errors.Is(err, errTimedOut) {
|
||||
NatFilteringBehavior = "address and port dependent" // my changes
|
||||
log.Warn("=> NAT filtering behavior: address and port dependent")
|
||||
}
|
||||
|
||||
return mapTestConn.Close()
|
||||
}
|
||||
|
||||
// Parse a STUN message
|
||||
func parse(msg *stun.Message) (ret struct {
|
||||
xorAddr *stun.XORMappedAddress
|
||||
otherAddr *stun.OtherAddress
|
||||
respOrigin *stun.ResponseOrigin
|
||||
mappedAddr *stun.MappedAddress
|
||||
software *stun.Software
|
||||
},
|
||||
) {
|
||||
ret.mappedAddr = &stun.MappedAddress{}
|
||||
ret.xorAddr = &stun.XORMappedAddress{}
|
||||
ret.respOrigin = &stun.ResponseOrigin{}
|
||||
ret.otherAddr = &stun.OtherAddress{}
|
||||
ret.software = &stun.Software{}
|
||||
if ret.xorAddr.GetFrom(msg) != nil {
|
||||
ret.xorAddr = nil
|
||||
}
|
||||
if ret.otherAddr.GetFrom(msg) != nil {
|
||||
ret.otherAddr = nil
|
||||
}
|
||||
if ret.respOrigin.GetFrom(msg) != nil {
|
||||
ret.respOrigin = nil
|
||||
}
|
||||
if ret.mappedAddr.GetFrom(msg) != nil {
|
||||
ret.mappedAddr = nil
|
||||
}
|
||||
if ret.software.GetFrom(msg) != nil {
|
||||
ret.software = nil
|
||||
}
|
||||
log.Debugf("%v", msg)
|
||||
log.Debugf("\tMAPPED-ADDRESS: %v", ret.mappedAddr)
|
||||
log.Debugf("\tXOR-MAPPED-ADDRESS: %v", ret.xorAddr)
|
||||
log.Debugf("\tRESPONSE-ORIGIN: %v", ret.respOrigin)
|
||||
log.Debugf("\tOTHER-ADDRESS: %v", ret.otherAddr)
|
||||
log.Debugf("\tSOFTWARE: %v", ret.software)
|
||||
for _, attr := range msg.Attributes {
|
||||
switch attr.Type {
|
||||
case
|
||||
stun.AttrXORMappedAddress,
|
||||
stun.AttrOtherAddress,
|
||||
stun.AttrResponseOrigin,
|
||||
stun.AttrMappedAddress,
|
||||
stun.AttrSoftware:
|
||||
break //nolint:staticcheck
|
||||
default:
|
||||
log.Debugf("\t%v (l=%v)", attr, attr.Length)
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// Given an address string, returns a StunServerConn
|
||||
func connect(addrStr string) (*stunServerConn, error) {
|
||||
log.Infof("Connecting to STUN server: %s", addrStr)
|
||||
addr, err := net.ResolveUDPAddr("udp4", addrStr)
|
||||
if err != nil {
|
||||
log.Warnf("Error resolving address: %s", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c, err := net.ListenUDP("udp4", nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.Infof("Local address: %s", c.LocalAddr())
|
||||
log.Infof("Remote address: %s", addr.String())
|
||||
|
||||
mChan := listen(c)
|
||||
|
||||
return &stunServerConn{
|
||||
conn: c,
|
||||
LocalAddr: c.LocalAddr(),
|
||||
RemoteAddr: addr,
|
||||
messageChan: mChan,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Send request and wait for response or timeout
|
||||
func (c *stunServerConn) roundTrip(msg *stun.Message, addr net.Addr) (*stun.Message, error) {
|
||||
_ = msg.NewTransactionID()
|
||||
log.Infof("Sending to %v: (%v bytes)", addr, msg.Length+messageHeaderSize)
|
||||
log.Debugf("%v", msg)
|
||||
for _, attr := range msg.Attributes {
|
||||
log.Debugf("\t%v (l=%v)", attr, attr.Length)
|
||||
}
|
||||
_, err := c.conn.WriteTo(msg.Raw, addr)
|
||||
if err != nil {
|
||||
log.Warnf("Error sending request to %v", addr)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Wait for response or timeout
|
||||
select {
|
||||
case m, ok := <-c.messageChan:
|
||||
if !ok {
|
||||
return nil, errResponseMessage
|
||||
}
|
||||
return m, nil
|
||||
case <-time.After(time.Duration(*timeoutPtr) * time.Second):
|
||||
log.Infof("Timed out waiting for response from server %v", addr)
|
||||
return nil, errTimedOut
|
||||
}
|
||||
}
|
||||
|
||||
// taken from https://github.com/pion/stun/blob/master/cmd/stun-traversal/main.go
|
||||
func listen(conn *net.UDPConn) (messages chan *stun.Message) {
|
||||
messages = make(chan *stun.Message)
|
||||
go func() {
|
||||
for {
|
||||
buf := make([]byte, 1024)
|
||||
|
||||
n, addr, err := conn.ReadFromUDP(buf)
|
||||
if err != nil {
|
||||
close(messages)
|
||||
return
|
||||
}
|
||||
log.Infof("Response from %v: (%v bytes)", addr, n)
|
||||
buf = buf[:n]
|
||||
|
||||
m := new(stun.Message)
|
||||
m.Raw = buf
|
||||
err = m.Decode()
|
||||
if err != nil {
|
||||
log.Infof("Error decoding message: %v", err)
|
||||
close(messages)
|
||||
return
|
||||
}
|
||||
|
||||
messages <- m
|
||||
}
|
||||
}()
|
||||
return
|
||||
res := stuncheck.CheckType()
|
||||
fmt.Printf("NAT Type: %s", res)
|
||||
}
|
||||
|
Reference in New Issue
Block a user