fix & add hairpinning test support

This commit is contained in:
bhpike65
2017-07-13 11:56:58 +08:00
parent 768d5cf0cf
commit 9eccfd803b
6 changed files with 179 additions and 216 deletions

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
.idea

View File

@@ -24,6 +24,7 @@ Go implementation of STUN
fmt.Println("Failed to build STUN PP request:", err) fmt.Println("Failed to build STUN PP request:", err)
os.Exit(1) os.Exit(1)
} }
fmt.Println("mapping address: ", resp.Addr.String())
} }
``` ```
@@ -69,13 +70,13 @@ import (
) )
func main() { func main() {
test, _ := nat.NewNATDiscovery("192.168.1.1:0"", "stun.l.google.com:19302", "") res, err := nat.Discovery("192.168.1.1:0", "stun.l.google.com:19302", "")
if err := test.Discovery(); err != nil { if err != nil {
fmt.Println("nat discovery error: ", err.Error()) fmt.Println("nat discovery error: ", err.Error())
os.Exit(-1) os.Exit(-1)
} }
fmt.Printf("nat discovery result:\n%s", test) fmt.Printf("nat discovery result:\n%s", res)
return return
} }
``` ```
@@ -85,6 +86,7 @@ it will output:
localAddress:192.168.1.3:56010, mappingAddress:1.1.1.1:15168 localAddress:192.168.1.3:56010, mappingAddress:1.1.1.1:15168
NAT mapping type: Endpoint-Independent Mapping NAT NAT mapping type: Endpoint-Independent Mapping NAT
NAT filtering type: Endpoint-Independent Filtering NAT NAT filtering type: Endpoint-Independent Filtering NAT
NAT Hairpinning Support: YES
``` ```
# Example Usage # Example Usage
@@ -107,7 +109,7 @@ if you don't have two public IP address in one machine, instead, you can use two
go run server.go -slave -slaveserver 1.1.1.1:12345 -primary-addr 2.2.2.2 -primary-port 3478 -alt-port 3479 go run server.go -slave -slaveserver 1.1.1.1:12345 -primary-addr 2.2.2.2 -primary-port 3478 -alt-port 3479
``` ```
then it will start a tcp server listen on 1.1.1.1:12345, and waits request from master server. then it will start a tcp server listen on 1.1.1.1:12345, and waits request from master server.
you should add iptables rules to filter the packet which isn't come from master server. you should add iptables rules to filter the packet which doesn't come from master server.
2. start master server 2. start master server
@@ -126,6 +128,7 @@ and get the NAT behaviour test result:
localAddress:192.168.1.3:49191, mappingAddress:3.3.3.3:37408 localAddress:192.168.1.3:49191, mappingAddress:3.3.3.3:37408
Address and Port-Dependent Mapping NAT Address and Port-Dependent Mapping NAT
Endpoint-Independent Filtering NAT Endpoint-Independent Filtering NAT
NAT Hairpinning Support: YES
``` ```
# Spec # Spec

View File

@@ -3,9 +3,9 @@ package main
import ( import (
"flag" "flag"
"fmt" "fmt"
"os"
"github.com/bhpike65/go-stun/nat" "github.com/bhpike65/go-stun/nat"
"net" "net"
"os"
) )
var server = flag.String("server", "stun.l.google.com:19302", "STUN server to query") var server = flag.String("server", "stun.l.google.com:19302", "STUN server to query")
@@ -30,16 +30,12 @@ func main() {
} }
} }
test, err := nat.NewNATDiscovery(*local, *server, *altServer) res, err := nat.Discovery(*local, *server, *altServer)
if err != nil { if err != nil {
fmt.Println("create nat test error: ", err.Error())
os.Exit(-1)
}
if err = test.Discovery(); err != nil {
fmt.Println("nat discovery error: ", err.Error()) fmt.Println("nat discovery error: ", err.Error())
os.Exit(-1) os.Exit(-1)
} }
fmt.Printf("nat discovery result:\n%s", test) fmt.Printf("nat discovery result:\n%s", res)
return return
} }

View File

@@ -1,81 +1,80 @@
package nat package nat
import ( import (
"net"
"github.com/bhpike65/go-stun/stun"
"errors" "errors"
"fmt" "fmt"
"github.com/bhpike65/go-stun/stun"
"net"
) )
const ( const (
NAT_TEST_FAILED = -1 NAT_TEST_FAILED = -1
NAT_TYPE_NONAT = iota NAT_TYPE_NONAT = iota
NAT_TYPE_EIM //Endpoint-Independent Mapping NAT NAT_TYPE_EIM //Endpoint-Independent Mapping NAT
NAT_TYPE_ADM //Address-Dependent Mapping NAT NAT_TYPE_ADM //Address-Dependent Mapping NAT
NAT_TYPE_APDM //Address and Port-Dependent Mapping NAT NAT_TYPE_APDM //Address and Port-Dependent Mapping NAT
NAT_TYPE_EIF //Endpoint-Independent Filtering NAT NAT_TYPE_EIF //Endpoint-Independent Filtering NAT
NAT_TYPE_ADF //Address-Dependent Filtering NAT NAT_TYPE_ADF //Address-Dependent Filtering NAT
NAT_TYPE_APDF //Address and Port-Dependent Filtering NAT NAT_TYPE_APDF //Address and Port-Dependent Filtering NAT
) )
type NATBehaviorDiscovery struct { type NATBehaviorDiscovery struct {
Conn *net.UDPConn Local *net.UDPAddr
Local *net.UDPAddr Server *net.UDPAddr
Server *net.UDPAddr AltServer *net.UDPAddr
AltServer *net.UDPAddr LocalAddr string
LocalAddr string MappingAddr string
MappingAddr string MappingType int
MappingType int
FilteringType int FilteringType int
Hairpinning bool
} }
func NewNATDiscovery(localAddr, serverAddr, altServerAddr string) (*NATBehaviorDiscovery, error) {
ret := new(NATBehaviorDiscovery) func Discovery(local, server, altServer string) (*NATBehaviorDiscovery, error) {
var res NATBehaviorDiscovery
var err error var err error
ret.Server, err = net.ResolveUDPAddr("udp", serverAddr) res.Server, err = net.ResolveUDPAddr("udp", server)
if err != nil { if err != nil {
return nil, err return nil, err
} }
ret.Local, err = net.ResolveUDPAddr("udp", localAddr) res.Local, err = net.ResolveUDPAddr("udp", local)
if err != nil { if err != nil {
return nil, err return nil, err
} }
ret.Conn, err = net.ListenUDP("udp", ret.Local) if altServer != "" {
if err != nil { res.AltServer, err = net.ResolveUDPAddr("udp", altServer)
return nil, err
}
ret.LocalAddr = ret.Conn.LocalAddr().String()
if altServerAddr != "" {
ret.AltServer, err = net.ResolveUDPAddr("udp", altServerAddr)
if err != nil { if err != nil {
return nil, err return nil, err
} }
} }
ret.MappingType = NAT_TEST_FAILED
ret.FilteringType = NAT_TEST_FAILED
return ret, nil
}
func (d *NATBehaviorDiscovery) Discovery() error { conn, err := net.ListenUDP("udp", res.Local)
if err != nil {
return nil, err
}
defer conn.Close()
res.LocalAddr = conn.LocalAddr().String()
// testI: NO-NAT? // testI: NO-NAT?
req := stun.NewBindRequest(nil) req := stun.NewBindRequest(nil)
resp, localAddr, err := req.RequestTo(d.Conn, d.Server) resp, localAddr, err := req.RequestTo(conn, res.Server)
if err != nil { if err != nil {
return errors.New(fmt.Sprintf("Failed to build STUN PP request: %s", err.Error())) return &res, errors.New(fmt.Sprintf("Failed to build STUN PP request: %s", err.Error()))
} }
if localAddr.String() == resp.Addr.String() {
d.MappingType = NAT_TYPE_NONAT
return nil
}
primaryPort := d.Server.Port
mappingPP := resp.Addr.String() mappingPP := resp.Addr.String()
d.MappingAddr = mappingPP res.MappingAddr = mappingPP
if localAddr.String() == mappingPP {
res.MappingType = NAT_TYPE_NONAT
return &res, nil
}
primaryPort := res.Server.Port
alternative := resp.OtherAddr alternative := resp.OtherAddr
other := resp.OtherAddr other := resp.OtherAddr
if other == nil && d.AltServer != nil { if other == nil && res.AltServer != nil {
other = d.AltServer other = res.AltServer
} }
if other != nil { if other != nil {
altIp := other.IP altIp := other.IP
@@ -84,66 +83,73 @@ func (d *NATBehaviorDiscovery) Discovery() error {
req = stun.NewBindRequest(nil) req = stun.NewBindRequest(nil)
remoteAP, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", altIp.String(), primaryPort)) remoteAP, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", altIp.String(), primaryPort))
if err != nil { if err != nil {
return errors.New(fmt.Sprintf("resolve AP address failed:%s", err.Error())) return &res, errors.New(fmt.Sprintf("resolve AP address failed:%s", err.Error()))
} }
resp, localAddr, err = req.RequestTo(d.Conn, remoteAP) resp, localAddr, err = req.RequestTo(conn, remoteAP)
if err != nil { if err != nil {
return errors.New(fmt.Sprintf("Failed to build STUN AP request:%s", err.Error())) return &res, errors.New(fmt.Sprintf("Failed to build STUN AP request:%s", err.Error()))
} }
mappingAP := resp.Addr.String() mappingAP := resp.Addr.String()
if mappingPP == mappingAP { if mappingPP == mappingAP {
d.MappingType = NAT_TYPE_EIM res.MappingType = NAT_TYPE_EIM
} else{ } else {
//testIII, send to alternativeIp:alternativePort //testIII, send to alternativeIp:alternativePort
req = stun.NewBindRequest(nil) req = stun.NewBindRequest(nil)
remoteAA, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", altIp.String(), altPort)) remoteAA, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", altIp.String(), altPort))
if err != nil { if err != nil {
return errors.New(fmt.Sprintf("resolve AA address failed:%s", err.Error())) return &res, errors.New(fmt.Sprintf("resolve AA address failed:%s", err.Error()))
} }
resp, localAddr, err = req.RequestTo(d.Conn, remoteAA) resp, localAddr, err = req.RequestTo(conn, remoteAA)
if err != nil { if err != nil {
return errors.New(fmt.Sprintf("Failed to build STUN AA request:%s", err.Error())) return &res, errors.New(fmt.Sprintf("Failed to build STUN AA request:%s", err.Error()))
} }
mappingAA := resp.Addr.String() mappingAA := resp.Addr.String()
if mappingAP == mappingAA { if mappingAP == mappingAA {
d.MappingType = NAT_TYPE_ADM res.MappingType = NAT_TYPE_ADM
} else { } else {
d.MappingType = NAT_TYPE_APDM res.MappingType = NAT_TYPE_APDM
} }
} }
} else { } else {
d.MappingType = NAT_TEST_FAILED res.MappingType = NAT_TEST_FAILED
} }
if alternative == nil { if alternative != nil {
d.FilteringType = NAT_TEST_FAILED //start NAT filter behavior test
return nil //test II
}
//start NAT filter behavior test
//test II
req = stun.NewBindRequest(nil)
req.SetChangeIP(true)
req.SetChangePort(true)
_, _, err = req.RequestTo(d.Conn, d.Server)
if err == nil {
d.FilteringType = NAT_TYPE_EIF
} else {
//test III
req = stun.NewBindRequest(nil) req = stun.NewBindRequest(nil)
req.SetChangeIP(false) req.SetChangeIP(true)
req.SetChangePort(true) req.SetChangePort(true)
req.ValidateSource(fmt.Sprintf("%s:%d", d.Server.IP.String(), alternative.Port)) _, _, err = req.RequestTo(conn, res.Server)
resp, _, err = req.RequestTo(d.Conn, d.Server)
if err == nil { if err == nil {
d.FilteringType = NAT_TYPE_ADF res.FilteringType = NAT_TYPE_EIF
} else if resp != nil {
d.FilteringType = NAT_TEST_FAILED
} else { } else {
d.FilteringType = NAT_TYPE_APDF //test III
req = stun.NewBindRequest(nil)
req.SetChangeIP(false)
req.SetChangePort(true)
req.ValidateSource(fmt.Sprintf("%s:%d", res.Server.IP.String(), alternative.Port))
resp, _, err = req.RequestTo(conn, res.Server)
if err == nil {
res.FilteringType = NAT_TYPE_ADF
} else if resp != nil {
res.FilteringType = NAT_TEST_FAILED
} else {
res.FilteringType = NAT_TYPE_APDF
}
} }
} else {
res.FilteringType = NAT_TEST_FAILED
} }
d.Conn.Close()
return nil //hairpinning support test
req = stun.NewBindRequest(nil)
resp, _, err = req.Request(res.Local.IP.String(), mappingPP)
if err != nil {
res.Hairpinning = true
}
return &res, nil
} }
func (d *NATBehaviorDiscovery) String() string { func (d *NATBehaviorDiscovery) String() string {
@@ -173,5 +179,12 @@ func (d *NATBehaviorDiscovery) String() string {
ret += "NAT filtering type: test failed\n" ret += "NAT filtering type: test failed\n"
} }
} }
if d.Hairpinning {
ret += "NAT Hairpinning Support: YES\n"
} else {
ret += "NAT Hairpinning Support:: NO\n"
}
return ret return ret
} }

View File

@@ -1,29 +1,29 @@
package main package main
import ( import (
"bufio"
"encoding/hex"
"flag" "flag"
"fmt"
"github.com/bhpike65/go-stun/stun"
"io"
"log"
"net" "net"
"os" "os"
"bufio"
"fmt"
"go-stun/stun"
"log"
"io"
"strings" "strings"
"encoding/hex"
) )
const ( const (
typePP = iota // primaryAddr:primaryPort typePP = iota // primaryAddr:primaryPort
typePA // primaryAddr:alterAddr typePA // primaryAddr:alterAddr
typeAP // alterAddr:primaryPort typeAP // alterAddr:primaryPort
typeAA // alterAddr:alterAddr typeAA // alterAddr:alterAddr
typeMax typeMax
) )
var roleSet [typeMax]*net.UDPConn var roleSet [typeMax]*net.UDPConn
var logger *log.Logger var logger *log.Logger
// ./stunserver --primaryAddr 1.1.1.1 --alternativeAddr 2.2.2.2 --primaryPort 3478 --alternativePort 3479 // ./stunserver --primaryAddr 1.1.1.1 --alternativeAddr 2.2.2.2 --primaryPort 3478 --alternativePort 3479
// ./stunserver --slaveserver 2.2.2.2:12345 --primaryAddr 1.1.1.1 --primaryPort 3478 --alternativePort 3479 // ./stunserver --slaveserver 2.2.2.2:12345 --primaryAddr 1.1.1.1 --primaryPort 3478 --alternativePort 3479
@@ -40,7 +40,6 @@ var public = flag.Bool("public", true, "primaryAddr and alternativeAddr must be
var slaveChan chan *string var slaveChan chan *string
var lanNets = []*net.IPNet{ var lanNets = []*net.IPNet{
{net.IPv4(10, 0, 0, 0), net.CIDRMask(8, 32)}, {net.IPv4(10, 0, 0, 0), net.CIDRMask(8, 32)},
{net.IPv4(172, 16, 0, 0), net.CIDRMask(12, 32)}, {net.IPv4(172, 16, 0, 0), net.CIDRMask(12, 32)},
@@ -51,12 +50,12 @@ var lanNets = []*net.IPNet{
func main() { func main() {
flag.Parse() flag.Parse()
logFile, err := os.OpenFile("./slave.log", os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0666) logFile, err := os.OpenFile("./slave.log", os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0666)
if err != nil { if err != nil {
fmt.Println("failed to create slave.log: ", err.Error()) fmt.Println("failed to create slave.log: ", err.Error())
os.Exit(-1) os.Exit(-1)
} }
logger = log.New(logFile,"",log.Llongfile | log.LstdFlags) logger = log.New(logFile, "", log.Llongfile|log.LstdFlags)
if *primaryAddr == "" || *alterAddr == "" { if *primaryAddr == "" || *alterAddr == "" {
addrs, err := net.InterfaceAddrs() addrs, err := net.InterfaceAddrs()
@@ -70,7 +69,7 @@ func main() {
if ipnet.IP.To4() != nil && !lan.Contains(ipnet.IP) { if ipnet.IP.To4() != nil && !lan.Contains(ipnet.IP) {
if *primaryAddr == "" { if *primaryAddr == "" {
*primaryAddr = ipnet.IP.String() *primaryAddr = ipnet.IP.String()
} else if *alterAddr == "" && *primaryAddr != ipnet.IP.String() { } else if *alterAddr == "" && *primaryAddr != ipnet.IP.String() {
*alterAddr = ipnet.IP.String() *alterAddr = ipnet.IP.String()
} else { } else {
break break
@@ -92,11 +91,11 @@ func main() {
} }
} }
roleSet[typePP], err = net.ListenUDP("udp", &net.UDPAddr{IP:net.ParseIP(*primaryAddr), Port:*primaryPort}) roleSet[typePP], err = net.ListenUDP("udp", &net.UDPAddr{IP: net.ParseIP(*primaryAddr), Port: *primaryPort})
if err != nil { if err != nil {
logger.Fatal("listen on PP failed") logger.Fatal("listen on PP failed")
} }
roleSet[typePA], err = net.ListenUDP("udp", &net.UDPAddr{IP:net.ParseIP(*primaryAddr), Port:*alterPort}) roleSet[typePA], err = net.ListenUDP("udp", &net.UDPAddr{IP: net.ParseIP(*primaryAddr), Port: *alterPort})
if err != nil { if err != nil {
logger.Fatal("listen on PA failed") logger.Fatal("listen on PA failed")
} }
@@ -114,7 +113,7 @@ func main() {
} }
slaveChan = make(chan *string, 128) slaveChan = make(chan *string, 128)
go slaveClientWorker(slaveAddr) go slaveClientWorker(slaveAddr)
aaAddr = &net.UDPAddr{IP:slaveAddr.IP, Port:*alterPort} aaAddr = &net.UDPAddr{IP: slaveAddr.IP, Port: *alterPort}
} }
} else if *slaveServer != "" { } else if *slaveServer != "" {
slaveAddr, err := net.ResolveTCPAddr("tcp", *slaveServer) slaveAddr, err := net.ResolveTCPAddr("tcp", *slaveServer)
@@ -128,7 +127,7 @@ func main() {
if err != nil { if err != nil {
logger.Fatalf("alterAddr %s:%d resolve failed", *alterAddr, alterPort) logger.Fatalf("alterAddr %s:%d resolve failed", *alterAddr, alterPort)
} }
roleSet[typeAP], err = net.ListenUDP("udp", &net.UDPAddr{IP:net.ParseIP(*alterAddr), Port:*primaryPort}) roleSet[typeAP], err = net.ListenUDP("udp", &net.UDPAddr{IP: net.ParseIP(*alterAddr), Port: *primaryPort})
if err != nil { if err != nil {
logger.Fatal("listen on PP failed") logger.Fatal("listen on PP failed")
} }
@@ -264,4 +263,3 @@ func slaveProcessRequest(conn net.Conn) {
req.RespondTo(roleSet[typePP], remote, nil) req.RespondTo(roleSet[typePP], remote, nil)
} }
} }

View File

@@ -1,64 +1,64 @@
package stun package stun
import ( import (
"crypto/rand"
"fmt"
"net"
"bytes" "bytes"
"time" "crypto/rand"
"encoding/binary" "encoding/binary"
"io"
"errors" "errors"
"fmt"
"golang.org/x/net/ipv4" "golang.org/x/net/ipv4"
"io"
"net"
"time"
) )
/* /*
0 1 2 3 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 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 | |0 0| STUN Message Type | Message Length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Magic Cookie | | Magic Cookie |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| | | |
| Transaction ID (96 bits) | | Transaction ID (96 bits) |
| | | |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
0 1 0 1
2 3 4 5 6 7 8 9 0 1 2 3 4 5 2 3 4 5 6 7 8 9 0 1 2 3 4 5
+--+--+-+-+-+-+-+-+-+-+-+-+-+-+ +--+--+-+-+-+-+-+-+-+-+-+-+-+-+
|M |M |M|M|M|C|M|M|M|C|M|M|M|M| |M |M |M|M|M|C|M|M|M|C|M|M|M|M|
|11|10|9|8|7|1|6|5|4|0|3|2|1|0| |11|10|9|8|7|1|6|5|4|0|3|2|1|0|
+--+--+-+-+-+-+-+-+-+-+-+-+-+-+ +--+--+-+-+-+-+-+-+-+-+-+-+-+-+
Figure 3: Format of STUN Message Type Field Figure 3: Format of STUN Message Type Field
*/ */
type header struct { type header struct {
Type uint16 Type uint16
Length uint16 Length uint16
Magic uint32 Magic uint32
TransacrtonId [12]byte TransacrtonId [12]byte
} }
type StunMessageReq struct { type StunMessageReq struct {
header header
ChangeIp bool ChangeIp bool
ChangePort bool ChangePort bool
RespSource string RespSource string
//Candidate interface{} //Candidate interface{}
} }
type StunMessageResp struct { type StunMessageResp struct {
header header
Addr *net.UDPAddr Addr *net.UDPAddr
OtherAddr *net.UDPAddr OtherAddr *net.UDPAddr
ErrorCode uint16 ErrorCode uint16
ErrorMsg string ErrorMsg string
} }
type attrHeader struct { type attrHeader struct {
@@ -78,14 +78,14 @@ const (
attrNonce = 0x15 attrNonce = 0x15
attrXorAddress = 0x20 attrXorAddress = 0x20
attrUseCandidate = 0x25 attrUseCandidate = 0x25
attrPadding = 0x26 attrPadding = 0x26
attrResponsePort = 0x27 attrResponsePort = 0x27
// Comprehension optional // Comprehension optional
attrSoftware = 0x8022 attrSoftware = 0x8022
//attrAlternate = 0x8023 //attrAlternate = 0x8023
attrFingerprint = 0x8028 attrFingerprint = 0x8028
attrOtherAddress= 0x802c attrOtherAddress = 0x802c
) )
const ( const (
@@ -107,12 +107,12 @@ const (
const ( const (
attrAddressFieldIpv4 = 1 attrAddressFieldIpv4 = 1
attrAddressFieldIpv6 = 2 attrAddressFieldIpv6 = 2
attrAddressSizeIpv4 = 8 attrAddressSizeIpv4 = 8
attrAddressSizeIpv6 = 20 attrAddressSizeIpv6 = 20
) )
const ( const (
magic = 0x2112a442 magic = 0x2112a442
headerLen = 20 headerLen = 20
) )
@@ -150,8 +150,8 @@ func (req *StunMessageReq) Unmarshal(data []byte) error {
} }
if !typeIsRequest(req.Type) || methodFromMsgType(req.Type) != methodBinding || if !typeIsRequest(req.Type) || methodFromMsgType(req.Type) != methodBinding ||
req.Magic != magic || req.Magic != magic ||
int(req.Length+20) != len(data) { int(req.Length+20) != len(data) {
return errors.New("stun binding get an error format reply") return errors.New("stun binding get an error format reply")
} }
@@ -184,7 +184,7 @@ func (resp *StunMessageResp) Marshal() []byte {
binary.Write(&buf, binary.BigEndian, resp.header) binary.Write(&buf, binary.BigEndian, resp.header)
if resp.Addr.IP.To4() != nil { if resp.Addr.IP.To4() != nil {
binary.Write(&buf, binary.BigEndian, []interface{} { binary.Write(&buf, binary.BigEndian, []interface{}{
uint16(attrAddress), uint16(attrAddress),
uint16(attrAddressSizeIpv4), uint16(attrAddressSizeIpv4),
uint8(0), uint8(0),
@@ -193,18 +193,18 @@ func (resp *StunMessageResp) Marshal() []byte {
resp.Addr.IP.To4(), resp.Addr.IP.To4(),
}) })
binary.Write(&buf, binary.BigEndian, []interface{} { binary.Write(&buf, binary.BigEndian, []interface{}{
uint16(attrXorAddress), uint16(attrXorAddress),
uint16(attrAddressSizeIpv4), uint16(attrAddressSizeIpv4),
uint8(0), uint8(0),
uint8(attrAddressFieldIpv4), uint8(attrAddressFieldIpv4),
uint16(resp.Addr.Port ^ magic>>16), uint16(resp.Addr.Port ^ magic>>16),
}) })
for i, field :=range resp.Addr.IP.To4() { for i, field := range resp.Addr.IP.To4() {
binary.Write(&buf, binary.BigEndian, uint8(field^magicBytes[i])) binary.Write(&buf, binary.BigEndian, uint8(field^magicBytes[i]))
} }
} else { } else {
binary.Write(&buf, binary.BigEndian, []interface{} { binary.Write(&buf, binary.BigEndian, []interface{}{
uint16(attrAddress), uint16(attrAddress),
uint16(attrAddressSizeIpv6), uint16(attrAddressSizeIpv6),
uint8(0), uint8(0),
@@ -212,14 +212,14 @@ func (resp *StunMessageResp) Marshal() []byte {
uint16(resp.Addr.Port), uint16(resp.Addr.Port),
resp.Addr.IP.To16(), resp.Addr.IP.To16(),
}) })
binary.Write(&buf, binary.BigEndian, []interface{} { binary.Write(&buf, binary.BigEndian, []interface{}{
uint16(attrXorAddress), uint16(attrXorAddress),
uint16(attrAddressSizeIpv6), uint16(attrAddressSizeIpv6),
uint8(0), uint8(0),
uint8(attrAddressFieldIpv6), uint8(attrAddressFieldIpv6),
uint16(resp.Addr.Port ^ magic>>16), uint16(resp.Addr.Port ^ magic>>16),
}) })
for i, field :=range resp.Addr.IP.To16() { for i, field := range resp.Addr.IP.To16() {
if i < 4 { if i < 4 {
binary.Write(&buf, binary.BigEndian, uint8(field^magicBytes[i])) binary.Write(&buf, binary.BigEndian, uint8(field^magicBytes[i]))
} else { } else {
@@ -292,7 +292,7 @@ func (resp *StunMessageResp) Unmarshal(data []byte) error {
if err != nil { if err != nil {
return err return err
} }
resp.Addr = &net.UDPAddr{IP:ip, Port:port, Zone:""} resp.Addr = &net.UDPAddr{IP: ip, Port: port, Zone: ""}
} }
case attrXorAddress: case attrXorAddress:
ip, port, err := parseAddress(value) ip, port, err := parseAddress(value)
@@ -303,17 +303,17 @@ func (resp *StunMessageResp) Unmarshal(data []byte) error {
ip[i] ^= data[4+i] ip[i] ^= data[4+i]
} }
port ^= int(binary.BigEndian.Uint16(data[4:])) port ^= int(binary.BigEndian.Uint16(data[4:]))
resp.Addr = &net.UDPAddr{IP:ip, Port:port, Zone:""} resp.Addr = &net.UDPAddr{IP: ip, Port: port, Zone: ""}
haveXor = true haveXor = true
case attrErrCode: case attrErrCode:
resp.ErrorCode = uint16(value[2])*100 + uint16(value[3]) resp.ErrorCode = uint16(value[2])*100 + uint16(value[3])
resp.ErrorMsg = string(value[4:]) resp.ErrorMsg = string(value[4:])
case attrOtherAddress: case attrOtherAddress:
ip, port, err := parseAddress(value) ip, port, err := parseAddress(value)
if err != nil { if err != nil {
return err return err
} }
resp.OtherAddr = &net.UDPAddr{IP:ip, Port:port, Zone:""} resp.OtherAddr = &net.UDPAddr{IP: ip, Port: port, Zone: ""}
default: default:
} }
} }
@@ -337,29 +337,28 @@ func parseAddress(raw []byte) (net.IP, int, error) {
ip := make([]byte, len(raw[4:])) ip := make([]byte, len(raw[4:]))
copy(ip, raw[4:]) copy(ip, raw[4:])
if len(ip) != family { if len(ip) != family {
return nil, 0, errors.New("address parse error") return nil, 0, errors.New("address parse error")
} }
return net.IP(ip), int(port), nil return net.IP(ip), int(port), nil
} }
func getMsgType(class uint8, method uint16) uint16 { func getMsgType(class uint8, method uint16) uint16 {
return (method & 0x0f80) << 2 | (method & 0x0070) << 1 | (method & 0x0f) |(uint16(class) & 0x02) << 7 | (uint16(class) & 0x01) << 4 return (method&0x0f80)<<2 | (method&0x0070)<<1 | (method & 0x0f) | (uint16(class)&0x02)<<7 | (uint16(class)&0x01)<<4
} }
func typeIsRequest(t uint16) bool { func typeIsRequest(t uint16) bool {
return (t& 0x0110) == 0x0000 return (t & 0x0110) == 0x0000
} }
func typeIsSuccessResp(t uint16) bool { func typeIsSuccessResp(t uint16) bool {
return (t& 0x0110) == 0x0100 return (t & 0x0110) == 0x0100
} }
func typeIsErrorResp(t uint16) bool { func typeIsErrorResp(t uint16) bool {
return (t& 0x0110) == 0x0110 return (t & 0x0110) == 0x0110
} }
func methodFromMsgType(t uint16) uint16 { func methodFromMsgType(t uint16) uint16 {
return (t & 0x000f) | ((t & 0x00e0) >>1) | ((t & 0x3E00) >>2) return (t & 0x000f) | ((t & 0x00e0) >> 1) | ((t & 0x3E00) >> 2)
} }
func NewBindRequest(tid []byte) *StunMessageReq { func NewBindRequest(tid []byte) *StunMessageReq {
@@ -403,7 +402,7 @@ func (req *StunMessageReq) RequestTo(conn *net.UDPConn, to *net.UDPAddr) (*StunM
loc, _ := net.ResolveUDPAddr("udp", conn.LocalAddr().String()) loc, _ := net.ResolveUDPAddr("udp", conn.LocalAddr().String())
buf := make([]byte, 1500) buf := make([]byte, 1500)
for retry:=0; retry < 3; retry++ { for retry := 0; retry < 3; retry++ {
_, err := pkConn.WriteTo(req.Marshal(), nil, to) _, err := pkConn.WriteTo(req.Marshal(), nil, to)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
@@ -453,55 +452,8 @@ func (req *StunMessageReq) Request(localAddr, remoteAddr string) (*StunMessageRe
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
defer sock.Close()
pkConn := ipv4.NewPacketConn(sock) return req.RequestTo(sock, remote)
pkConn.SetControlMessage(ipv4.FlagDst, true)
defer pkConn.Close()
if err := pkConn.SetDeadline(time.Now().Add(5 * time.Second)); err != nil {
fmt.Println("Couldn't set the socket timeout:", err)
}
loc, _ := net.ResolveUDPAddr("udp", sock.LocalAddr().String())
buf := make([]byte, 1500)
for retry:=0; retry < 3; retry++ {
_, err = pkConn.WriteTo(req.Marshal(), nil, remote)
if err != nil {
return nil, nil, err
}
n, cm, src, err := pkConn.ReadFrom(buf)
if err != nil {
if err, ok := err.(net.Error); ok && err.Timeout() {
}
return nil, nil, err
}
loc.IP = cm.Dst
var resp StunMessageResp
if err = resp.Unmarshal(buf[:n]); err != nil {
return nil, loc, err
}
if req.RespSource != "" && src.String() != req.RespSource {
return &resp, nil, errors.New("receive packet from unexpected source")
}
if resp.ErrorCode != 0 {
return &resp, loc, errors.New(resp.ErrorMsg)
}
if req.TransacrtonId != resp.TransacrtonId ||
getMsgType(classResonseSuccess, methodBinding) != resp.Type ||
resp.Addr == nil {
return &resp, loc, errors.New("receive error response")
}
return &resp, loc, nil
}
return nil, nil, errors.New("request retry exceeds max times")
} }
func (req *StunMessageReq) RespondTo(conn *net.UDPConn, to *net.UDPAddr, other *net.UDPAddr) error { func (req *StunMessageReq) RespondTo(conn *net.UDPConn, to *net.UDPAddr, other *net.UDPAddr) error {
@@ -516,4 +468,4 @@ func (req *StunMessageReq) RespondTo(conn *net.UDPConn, to *net.UDPAddr, other *
_, err := conn.WriteTo(resp.Marshal(), to) _, err := conn.WriteTo(resp.Marshal(), to)
return err return err
} }