Skip to content

Commit

Permalink
Add behavior test mode. (#42)
Browse files Browse the repository at this point in the history
./go-stun -b -s stun.stunprotocol.org:3478
Mapping Behavior: EndpointIndependent
Filtering Behavior: AddressAndPortDependent
Normal NAT Type: Port Restricted cone NAT
  • Loading branch information
kevinlemonra authored Jan 26, 2022
1 parent 4e5e57c commit 877ebaf
Show file tree
Hide file tree
Showing 5 changed files with 210 additions and 0 deletions.
21 changes: 21 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (

func main() {
var serverAddr = flag.String("s", stun.DefaultServerAddr, "STUN server address")
var b = flag.Bool("b", false, "NAT behavior test mode")
var v = flag.Bool("v", false, "verbose mode")
var vv = flag.Bool("vv", false, "double verbose mode (includes -v)")
var vvv = flag.Bool("vvv", false, "triple verbose mode (includes -v and -vv)")
Expand All @@ -35,6 +36,12 @@ func main() {
// Non verbose mode will be used by default unless we call SetVerbose(true) or SetVVerbose(true).
client.SetVerbose(*v || *vv || *vvv)
client.SetVVerbose(*vv || *vvv)

if *b {
behaviorTest(client)
return
}

// Discover the NAT and return the result.
nat, host, err := client.Discover()
if err != nil {
Expand All @@ -49,3 +56,17 @@ func main() {
fmt.Println("External Port:", host.Port())
}
}

func behaviorTest(c *stun.Client) {
natBehavior, err := c.BehaviorTest()
if err != nil {
fmt.Println(err)
}

if natBehavior != nil {
fmt.Println(" Mapping Behavior:", natBehavior.MappingType)
fmt.Println("Filtering Behavior:", natBehavior.FilteringType)
fmt.Println(" Normal NAT Type:", natBehavior.NormalType())
}
return
}
21 changes: 21 additions & 0 deletions stun/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,27 @@ func (c *Client) Discover() (NATType, *Host, error) {
return c.discover(conn, serverUDPAddr)
}

func (c *Client) BehaviorTest() (*NATBehavior, error) {
if c.serverAddr == "" {
c.SetServerAddr(DefaultServerAddr)
}
serverUDPAddr, err := net.ResolveUDPAddr("udp", c.serverAddr)
if err != nil {
return nil, err
}
// Use the connection passed to the client if it is not nil, otherwise
// create a connection and close it at the end.
conn := c.conn
if conn == nil {
conn, err = net.ListenUDP("udp", nil)
if err != nil {
return nil, err
}
defer conn.Close()
}
return c.behaviorTest(conn, serverUDPAddr)
}

// Keepalive sends and receives a bind request, which ensures the mapping stays open
// Only applicable when client was created with a connection.
func (c *Client) Keepalive() (*Host, error) {
Expand Down
45 changes: 45 additions & 0 deletions stun/const.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,14 @@ const (
// NATType is the type of NAT described by int.
type NATType int

// NAT behavior type
type BehaviorType int

type NATBehavior struct {
MappingType BehaviorType
FilteringType BehaviorType
}

// NAT types.
const (
NATError NATType = iota
Expand All @@ -46,7 +54,16 @@ const (
NATSymmetricUDPFirewall = SymmetricUDPFirewall
)

const (
BehaviorTypeUnknown BehaviorType = iota
BehaviorTypeEndpoint
BehaviorTypeAddr
BehaviorTypeAddrAndPort
)

var natStr map[NATType]string
var natBehaviorTypeStr map[BehaviorType]string
var natNormalTypeStr map[NATBehavior]string

func init() {
natStr = map[NATType]string{
Expand All @@ -60,6 +77,20 @@ func init() {
NATNone: "Not behind a NAT",
SymmetricUDPFirewall: "Symmetric UDP firewall",
}

natBehaviorTypeStr = map[BehaviorType]string{
BehaviorTypeEndpoint: "EndpointIndependent",
BehaviorTypeAddr: "AddressDependent",
BehaviorTypeAddrAndPort: "AddressAndPortDependent",
}

// Defined in RFC 3489
natNormalTypeStr = map[NATBehavior]string{
NATBehavior{BehaviorTypeEndpoint, BehaviorTypeEndpoint}: "Full cone NAT",
NATBehavior{BehaviorTypeEndpoint, BehaviorTypeAddr}: "Restricted cone NAT",
NATBehavior{BehaviorTypeEndpoint, BehaviorTypeAddrAndPort}: "Port Restricted cone NAT",
NATBehavior{BehaviorTypeAddrAndPort, BehaviorTypeAddrAndPort}: "Symmetric NAT",
}
}

func (nat NATType) String() string {
Expand All @@ -69,6 +100,20 @@ func (nat NATType) String() string {
return "Unknown"
}

func (natBhType BehaviorType) String() string {
if s, ok := natBehaviorTypeStr[natBhType]; ok {
return s
}
return "Unknown"
}

func (natBehavior NATBehavior) NormalType() string {
if s, ok := natNormalTypeStr[natBehavior]; ok {
return s
}
return "Undefined"
}

const (
errorTryAlternate = 300
errorBadRequest = 400
Expand Down
87 changes: 87 additions & 0 deletions stun/discover.go
Original file line number Diff line number Diff line change
Expand Up @@ -164,3 +164,90 @@ func (c *Client) discover(conn net.PacketConn, addr *net.UDPAddr) (NATType, *Hos
}
return NATSymmetric, mappedAddr, nil
}

func (c *Client) behaviorTest(conn net.PacketConn, addr *net.UDPAddr) (*NATBehavior, error) {
natBehavior := &NATBehavior{}

// Test1 ->(IP1,port1)
// Perform test to check if it is under NAT.
c.logger.Debugln("Do Test1")
resp1, err := c.test(conn, addr)
if err != nil {
return nil, err
}
// identical used to check if it is open Internet or not.
if resp1.identical {
return nil, errors.New("Not behind a NAT.")
}
// use otherAddr or changedAddr
otherAddr := resp1.otherAddr
if otherAddr == nil {
if resp1.changedAddr != nil {
otherAddr = resp1.changedAddr
} else {
return nil, errors.New("Server error: no other address and changed address.")
}
}

// Test2 ->(IP2,port1)
// Perform test to see if mapping to the same IP and port when
// send to another IP.
c.logger.Debugln("Do Test2")
tmpAddr := &net.UDPAddr{IP: net.ParseIP(otherAddr.IP()), Port: addr.Port}
resp2, err := c.test(conn, tmpAddr)
if err != nil {
return nil, err
}
if resp2.mappedAddr.IP() == resp1.mappedAddr.IP() &&
resp2.mappedAddr.Port() == resp1.mappedAddr.Port() {
natBehavior.MappingType = BehaviorTypeEndpoint
}

// Test3 ->(IP2,port2)
// Perform test to see if mapping to the same IP and port when
// send to another port.
if natBehavior.MappingType == BehaviorTypeUnknown {
c.logger.Debugln("Do Test3")
tmpAddr.Port = int(otherAddr.Port())
resp3, err := c.test(conn, tmpAddr)
if err != nil {
return nil, err
}
if resp3.mappedAddr.IP() == resp2.mappedAddr.IP() &&
resp3.mappedAddr.Port() == resp2.mappedAddr.Port() {
natBehavior.MappingType = BehaviorTypeAddr
} else {
natBehavior.MappingType = BehaviorTypeAddrAndPort
}
}

// Test4 ->(IP1,port1) (IP2,port2)->
// Perform test to see if the client can receive packet sent from
// another IP and port.
c.logger.Debugln("Do Test4")
resp4, err := c.testChangeBoth(conn, addr)
if err != nil {
return natBehavior, err
}
if resp4 != nil {
natBehavior.FilteringType = BehaviorTypeEndpoint
}

// Test5 ->(IP1,port1) (IP1,port2)->
// Perform test to see if the client can receive packet sent from
// another port.
if natBehavior.FilteringType == BehaviorTypeUnknown {
c.logger.Debugln("Do Test5")
resp5, err := c.testChangePort(conn, addr)
if err != nil {
return natBehavior, err
}
if resp5 != nil {
natBehavior.FilteringType = BehaviorTypeAddr
} else {
natBehavior.FilteringType = BehaviorTypeAddrAndPort
}
}

return natBehavior, nil
}
36 changes: 36 additions & 0 deletions stun/tests.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,45 @@
package stun

import (
"errors"
"net"
)

func (c *Client) sendWithLog(conn net.PacketConn, addr *net.UDPAddr, changeIP bool, changePort bool) (*response, error) {
c.logger.Debugln("Send To:", addr)
resp, err := c.sendBindingReq(conn, addr, changeIP, changePort)
if err != nil {
return nil, err
}
c.logger.Debugln("Received:", resp)
if resp == nil && changeIP == false && changePort == false {
return nil, errors.New("NAT blocked.")
}
if resp != nil && !addrCompare(resp.serverAddr, addr, changeIP, changePort) {
return nil, errors.New("Server error: response IP/port")
}
return resp, err
}

// Make sure IP and port have or haven't change
func addrCompare(host *Host, addr *net.UDPAddr, IPChange, portChange bool) bool {
isIPChange := host.IP() != addr.IP.String()
isPortChange := host.Port() != uint16(addr.Port)
return isIPChange == IPChange && isPortChange == portChange
}

func (c *Client) test(conn net.PacketConn, addr *net.UDPAddr) (*response, error) {
return c.sendWithLog(conn, addr, false, false)
}

func (c *Client) testChangePort(conn net.PacketConn, addr *net.UDPAddr) (*response, error) {
return c.sendWithLog(conn, addr, false, true)
}

func (c *Client) testChangeBoth(conn net.PacketConn, addr *net.UDPAddr) (*response, error) {
return c.sendWithLog(conn, addr, true, true)
}

func (c *Client) test1(conn net.PacketConn, addr net.Addr) (*response, error) {
return c.sendBindingReq(conn, addr, false, false)
}
Expand Down

0 comments on commit 877ebaf

Please sign in to comment.