├── .gitignore ├── README.md ├── stun ├── doc.go ├── host.go ├── client.go ├── multi_client.go ├── attribute.go ├── packet.go ├── discover.go └── const.go ├── example.go └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | go-stun 2 | ======= 3 | 4 | go-stun is a STUN (RFC 3489, 5389) client implementation in golang. 5 | 6 | It is extremely easy to use -- just one line of code. 7 | 8 | ```go 9 | import "github.com/ccding/go-stun/stun" 10 | 11 | func main() { 12 | nat, host, err := stun.Discover() 13 | } 14 | ``` 15 | 16 | More details please go to `example.go`. 17 | -------------------------------------------------------------------------------- /stun/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013, Cong Ding. All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | // author: Cong Ding 16 | 17 | // go-stun is a STUN (RFC 3489 and RFC 5389) client implementation in golang. 18 | // 19 | // It is extremely easy to use -- just one line of code. 20 | // 21 | // nat, host, err := stun.Discover() 22 | // 23 | // More details please go to `example.go`. 24 | package stun 25 | -------------------------------------------------------------------------------- /stun/host.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013, Cong Ding. All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | // author: Cong Ding 16 | 17 | package stun 18 | 19 | import ( 20 | "net" 21 | "strconv" 22 | ) 23 | 24 | type Host struct { 25 | family uint16 26 | ip string 27 | port uint16 28 | } 29 | 30 | func (h *Host) Family() uint16 { 31 | return h.family 32 | } 33 | 34 | func (h *Host) Ip() string { 35 | return h.ip 36 | } 37 | 38 | func (h *Host) Port() uint16 { 39 | return h.port 40 | } 41 | 42 | func (h *Host) TransportAddr() string { 43 | return net.JoinHostPort(h.ip, strconv.Itoa(int(h.port))) 44 | } 45 | -------------------------------------------------------------------------------- /stun/client.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013, Cong Ding. All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | // author: Cong Ding 16 | 17 | package stun 18 | 19 | import ( 20 | "golang.org/x/net/context" 21 | ) 22 | 23 | // Client holds all STUN server details. 24 | type Client struct { 25 | ServerAddr string // Address of the STUN server 26 | SoftwareName string // Name of the Client software (defaults to 'StunClient') 27 | } 28 | 29 | // DefaultClient for global methods 30 | var DefaultClient = Client{ 31 | ServerAddr: "stun1.voiceeclipse.net:3478", 32 | } 33 | 34 | // Discover contacts the STUN server and gets the response of NAT type, host 35 | // for UDP punching 36 | func Discover(ctx context.Context) (int, *Host, error) { 37 | return DefaultClient.Discover(ctx) 38 | } 39 | 40 | // Discover contacts the STUN server and gets the response of NAT type, host 41 | // for UDP punching 42 | func (client *Client) Discover(ctx context.Context) (int, *Host, error) { 43 | if ctx == nil { 44 | ctx = context.Background() 45 | } 46 | if client.SoftwareName == "" { 47 | client.SoftwareName = "StunClient" 48 | } 49 | return client.discover(ctx) 50 | } 51 | -------------------------------------------------------------------------------- /example.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013, Cong Ding. All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | // author: Cong Ding 16 | 17 | package main 18 | 19 | import ( 20 | "fmt" 21 | "time" 22 | 23 | "github.com/fd/go-stun/stun" 24 | "golang.org/x/net/context" 25 | ) 26 | 27 | func main() { 28 | ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) 29 | defer cancel() 30 | 31 | mc := stun.MultiClient{ 32 | ServerAddrs: []string{ 33 | "stun.l.google.com:19302", 34 | "stun1.l.google.com:19302", 35 | "stun2.l.google.com:19302", 36 | "stun3.l.google.com:19302", 37 | "stun4.l.google.com:19302", 38 | "stun01.sipphone.com:3478", 39 | "stun.ekiga.net:3478", 40 | "stun.fwdnet.net:3478", 41 | "stun.ideasip.com:3478", 42 | "stun.iptel.org:3478", 43 | "stun.rixtelecom.se:3478", 44 | "stun.schlund.de:3478", 45 | "stunserver.org:3478", 46 | "stun.softjoys.com:3478", 47 | "stun.voiparound.com:3478", 48 | "stun.voipbuster.com:3478", 49 | "stun.voipstunt.com:3478", 50 | "stun.voxgratia.org:3478", 51 | "stun.xten.com:3478", 52 | }, 53 | } 54 | 55 | nat, host, err := mc.Discover(ctx) 56 | if err != nil { 57 | fmt.Println(err) 58 | } 59 | 60 | switch nat { 61 | case stun.NAT_ERROR: 62 | fmt.Println("Test failed") 63 | case stun.NAT_UNKNOWN: 64 | fmt.Println("Unexpected response from the STUN server") 65 | case stun.NAT_BLOCKED: 66 | fmt.Println("UDP is blocked") 67 | case stun.NAT_FULL: 68 | fmt.Println("Full cone NAT") 69 | case stun.NAT_SYMETRIC: 70 | fmt.Println("Symetric NAT") 71 | case stun.NAT_RESTRICTED: 72 | fmt.Println("Restricted NAT") 73 | case stun.NAT_PORT_RESTRICTED: 74 | fmt.Println("Port restricted NAT") 75 | case stun.NAT_NONE: 76 | fmt.Println("Not behind a NAT") 77 | case stun.NAT_SYMETRIC_UDP_FIREWALL: 78 | fmt.Println("Symetric UDP firewall") 79 | } 80 | 81 | if host != nil { 82 | fmt.Println(host.Family()) 83 | fmt.Println(host.Ip()) 84 | fmt.Println(host.Port()) 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /stun/multi_client.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013, Simon Menke. All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | // author: Simon Menke 16 | 17 | package stun 18 | 19 | import ( 20 | "errors" 21 | 22 | "golang.org/x/net/context" 23 | ) 24 | 25 | // MultiClient will try to discover the NET using multiple stun servers. 26 | type MultiClient struct { 27 | ServerAddrs []string // Addresses of the STUN servers 28 | SoftwareName string // Name of the Client software (defaults to 'StunClient') 29 | } 30 | 31 | // Discover contacts the STUN servers and gets the response of NAT type, host 32 | // for UDP punching 33 | func (mc *MultiClient) Discover(ctx context.Context) (int, *Host, error) { 34 | if ctx == nil { 35 | ctx = context.Background() 36 | } 37 | 38 | if mc.SoftwareName == "" { 39 | mc.SoftwareName = "StunClient" 40 | } 41 | 42 | ctx, cancel := context.WithCancel(ctx) 43 | defer cancel() 44 | 45 | out := make(chan multiResponse, len(mc.ServerAddrs)) 46 | 47 | for _, addr := range mc.ServerAddrs { 48 | go mc.discoverSingle(ctx, addr, out) 49 | } 50 | 51 | var ( 52 | lastResponse multiResponse 53 | gotResponse bool 54 | ) 55 | 56 | for i, l := 0, len(mc.ServerAddrs); i < l; i++ { 57 | select { 58 | 59 | case <-ctx.Done(): 60 | return NAT_ERROR, nil, ctx.Err() 61 | 62 | case res := <-out: 63 | if res.Err == nil && res.Type != NAT_UNKNOWN && res.Type != NAT_BLOCKED { 64 | return res.Type, res.Host, res.Err 65 | } 66 | lastResponse = res 67 | gotResponse = true 68 | 69 | } 70 | } 71 | 72 | if gotResponse { 73 | return lastResponse.Type, lastResponse.Host, lastResponse.Err 74 | } 75 | 76 | return NAT_ERROR, nil, errors.New("stun: no response") 77 | } 78 | 79 | type multiResponse struct { 80 | Type int 81 | Host *Host 82 | Err error 83 | } 84 | 85 | func (mc *MultiClient) discoverSingle(ctx context.Context, addr string, out chan<- multiResponse) { 86 | var client = Client{addr, mc.SoftwareName} 87 | typ, host, err := client.Discover(ctx) 88 | out <- multiResponse{typ, host, err} 89 | } 90 | -------------------------------------------------------------------------------- /stun/attribute.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013, Cong Ding. All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | // author: Cong Ding 16 | 17 | package stun 18 | 19 | import ( 20 | "encoding/binary" 21 | "hash/crc32" 22 | "net" 23 | ) 24 | 25 | type attribute struct { 26 | types uint16 27 | length uint16 28 | value []byte 29 | } 30 | 31 | func newAttribute(types uint16, value []byte) *attribute { 32 | a := new(attribute) 33 | a.types = types 34 | a.value = padding(value) 35 | a.length = uint16(len(a.value)) 36 | return a 37 | } 38 | 39 | func newFingerprintAttribute(packet *packet) *attribute { 40 | crc := crc32.ChecksumIEEE(packet.bytes()) ^ fingerprint 41 | buf := make([]byte, 4) 42 | binary.BigEndian.PutUint32(buf, crc) 43 | return newAttribute(attribute_fingerprint, buf) 44 | } 45 | 46 | func newSoftwareAttribute(packet *packet, name string) *attribute { 47 | return newAttribute(attribute_SOFTWARE, []byte(name)) 48 | } 49 | 50 | func newChangeReqAttribute(packet *packet, changeIP bool, changePort bool) *attribute { 51 | value := make([]byte, 4) 52 | if changeIP { 53 | value[3] |= 0x04 54 | } 55 | if changePort { 56 | value[3] |= 0x02 57 | } 58 | return newAttribute(attribute_CHANGE_REQUEST, value) 59 | } 60 | 61 | func (v *attribute) xorMappedAddr() *Host { 62 | cookie := make([]byte, 4) 63 | binary.BigEndian.PutUint32(cookie, magicCookie) 64 | xorIP := make([]byte, 16) 65 | for i := 0; i < len(v.value)-4; i++ { 66 | xorIP[i] = v.value[i+4] ^ cookie[i] 67 | } 68 | family := binary.BigEndian.Uint16(v.value[0:2]) 69 | port := binary.BigEndian.Uint16(v.value[2:4]) 70 | 71 | // Truncate if IPv4, otherwise net.IP sometimes renders it as an IPv6 address. 72 | if family == attribute_FAMILY_IPV4 { 73 | xorIP = xorIP[:4] 74 | } 75 | 76 | return &Host{family, net.IP(xorIP).String(), port ^ (magicCookie >> 32)} 77 | } 78 | 79 | func (v *attribute) address() *Host { 80 | h := new(Host) 81 | h.family = binary.BigEndian.Uint16(v.value[0:2]) 82 | h.port = binary.BigEndian.Uint16(v.value[2:4]) 83 | 84 | // Truncate if IPv4, otherwise net.IP sometimes renders it as an IPv6 address. 85 | if h.family == attribute_FAMILY_IPV4 { 86 | v.value = v.value[:8] 87 | } 88 | h.ip = net.IP(v.value[4:]).String() 89 | return h 90 | } 91 | -------------------------------------------------------------------------------- /stun/packet.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013, Cong Ding. All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | // author: Cong Ding 16 | 17 | package stun 18 | 19 | import ( 20 | "encoding/binary" 21 | "net" 22 | "time" 23 | ) 24 | 25 | type packet struct { 26 | types uint16 27 | length uint16 28 | cookie uint32 29 | id []byte // 12 bytes 30 | attributes []attribute 31 | } 32 | 33 | func newPacket() *packet { 34 | v := new(packet) 35 | v.id = make([]byte, 12) 36 | v.attributes = make([]attribute, 0, 10) 37 | v.cookie = magicCookie 38 | v.length = 0 39 | return v 40 | } 41 | 42 | func newPacketFromBytes(b []byte) *packet { 43 | packet := newPacket() 44 | packet.types = binary.BigEndian.Uint16(b[0:2]) 45 | packet.length = binary.BigEndian.Uint16(b[2:4]) 46 | packet.cookie = binary.BigEndian.Uint32(b[4:8]) 47 | packet.id = b[8:20] 48 | 49 | for pos := uint16(20); pos < uint16(len(b)); { 50 | types := binary.BigEndian.Uint16(b[pos : pos+2]) 51 | length := binary.BigEndian.Uint16(b[pos+2 : pos+4]) 52 | value := b[pos+4 : pos+4+length] 53 | attribute := newAttribute(types, value) 54 | packet.addAttribute(*attribute) 55 | pos += align(length) + 4 56 | } 57 | 58 | return packet 59 | } 60 | 61 | func (v *packet) addAttribute(a attribute) { 62 | v.attributes = append(v.attributes, a) 63 | v.length += align(a.length) + 4 64 | } 65 | 66 | func (v *packet) bytes() []byte { 67 | b := make([]byte, 8) 68 | binary.BigEndian.PutUint16(b[0:2], v.types) 69 | binary.BigEndian.PutUint16(b[2:4], v.length) 70 | binary.BigEndian.PutUint32(b[4:8], v.cookie) 71 | b = append(b, v.id...) 72 | 73 | for _, a := range v.attributes { 74 | buf := make([]byte, 2) 75 | binary.BigEndian.PutUint16(buf, a.types) 76 | b = append(b, buf...) 77 | binary.BigEndian.PutUint16(buf, a.length) 78 | b = append(b, buf...) 79 | b = append(b, a.value...) 80 | } 81 | return b 82 | } 83 | 84 | func (v *packet) mappedAddr() *Host { 85 | for _, a := range v.attributes { 86 | if a.types == attribute_MAPPED_ADDRESS { 87 | h := a.address() 88 | return h 89 | } 90 | } 91 | return nil 92 | } 93 | 94 | func (v *packet) changedAddr() *Host { 95 | for _, a := range v.attributes { 96 | if a.types == attribute_CHANGED_ADDRESS { 97 | h := a.address() 98 | return h 99 | } 100 | } 101 | return nil 102 | } 103 | 104 | func (v *packet) xorMappedAddr() *Host { 105 | for _, a := range v.attributes { 106 | if (a.types == attribute_XOR_MAPPED_ADDRESS) || (a.types == attribute_XOR_MAPPED_ADDRESS_EXP) { 107 | h := a.xorMappedAddr() 108 | return h 109 | } 110 | } 111 | return nil 112 | } 113 | 114 | // RFC 3489: Clients SHOULD retransmit the request starting with an interval 115 | // of 100ms, doubling every retransmit until the interval reaches 1.6s. 116 | // Retransmissions continue with intervals of 1.6s until a response is 117 | // received, or a total of 9 requests have been sent. 118 | func (packet *packet) send(conn net.Conn) (*packet, error) { 119 | timeout := 100 120 | 121 | for i := 0; i < 9; i++ { 122 | l, err := conn.Write(packet.bytes()) 123 | if err != nil { 124 | return nil, err 125 | } 126 | 127 | conn.SetReadDeadline(time.Now().Add(time.Duration(timeout) * time.Millisecond)) 128 | if timeout < 1600 { 129 | timeout *= 2 130 | } 131 | 132 | b := make([]byte, 1024) 133 | l, err = conn.Read(b) 134 | if err == nil { 135 | return newPacketFromBytes(b[0:l]), nil 136 | } 137 | 138 | if !err.(net.Error).Timeout() { 139 | return nil, err 140 | } 141 | } 142 | 143 | return nil, nil 144 | } 145 | -------------------------------------------------------------------------------- /stun/discover.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013, Cong Ding. All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | // author: Cong Ding 16 | 17 | package stun 18 | 19 | import ( 20 | "errors" 21 | "net" 22 | "time" 23 | 24 | "golang.org/x/net/context" 25 | ) 26 | 27 | // padding the length of the byte slice to multiple of 4 28 | func padding(b []byte) []byte { 29 | l := uint16(len(b)) 30 | return append(b, make([]byte, align(l)-l)...) 31 | } 32 | 33 | // align the uint16 number to the smallest multiple of 4, which is larger than 34 | // or equal to the uint16 number 35 | func align(l uint16) uint16 { 36 | return (l + 3) & 0xfffc 37 | } 38 | 39 | func dialWithContext(ctx context.Context, addr string) (net.Conn, error) { 40 | if ctx.Err() != nil { 41 | return nil, ctx.Err() 42 | } 43 | 44 | deadline, hasDeadline := ctx.Deadline() 45 | if !hasDeadline { 46 | return net.Dial("udp", addr) 47 | } 48 | 49 | conn, err := net.DialTimeout("udp", addr, deadline.Sub(time.Now())) 50 | if err != nil { 51 | return nil, err 52 | } 53 | conn.SetDeadline(deadline) 54 | 55 | return conn, nil 56 | } 57 | 58 | func (client *Client) sendBindingReq(ctx context.Context, destAddr string) (*packet, string, error) { 59 | connection, err := dialWithContext(ctx, destAddr) 60 | if err != nil { 61 | return nil, "", err 62 | } 63 | 64 | defer connection.Close() 65 | 66 | packet := newPacket() 67 | packet.types = type_BINDING_REQUEST 68 | attribute := newSoftwareAttribute(packet, client.SoftwareName) 69 | packet.addAttribute(*attribute) 70 | attribute = newFingerprintAttribute(packet) 71 | packet.addAttribute(*attribute) 72 | 73 | localAddr := connection.LocalAddr().String() 74 | packet, err = packet.send(connection) 75 | if err != nil { 76 | return nil, "", err 77 | } 78 | 79 | return packet, localAddr, err 80 | } 81 | 82 | func (client *Client) sendChangeReq(ctx context.Context, changeIP bool, changePort bool) (*packet, error) { 83 | connection, err := dialWithContext(ctx, client.ServerAddr) 84 | if err != nil { 85 | return nil, err 86 | } 87 | 88 | defer connection.Close() 89 | 90 | // construct packet 91 | packet := newPacket() 92 | packet.types = type_BINDING_REQUEST 93 | attribute := newSoftwareAttribute(packet, client.SoftwareName) 94 | packet.addAttribute(*attribute) 95 | attribute = newChangeReqAttribute(packet, changeIP, changePort) 96 | packet.addAttribute(*attribute) 97 | attribute = newFingerprintAttribute(packet) 98 | packet.addAttribute(*attribute) 99 | 100 | packet, err = packet.send(connection) 101 | if err != nil { 102 | return nil, err 103 | } 104 | 105 | return packet, err 106 | } 107 | 108 | func (client *Client) test1(ctx context.Context, destAddr string) (*packet, string, bool, *Host, error) { 109 | packet, localAddr, err := client.sendBindingReq(ctx, destAddr) 110 | if err != nil { 111 | return nil, "", false, nil, err 112 | } 113 | if packet == nil { 114 | return nil, "", false, nil, nil 115 | } 116 | 117 | hm := packet.xorMappedAddr() 118 | // rfc 3489 doesn't require the server return xor mapped address 119 | if hm == nil { 120 | hm = packet.mappedAddr() 121 | if hm == nil { 122 | return nil, "", false, nil, errors.New("No mapped address") 123 | } 124 | } 125 | 126 | hc := packet.changedAddr() 127 | if hc == nil { 128 | return nil, "", false, nil, errors.New("No changed address") 129 | } 130 | changeAddr := hc.TransportAddr() 131 | identical := localAddr == hm.TransportAddr() 132 | 133 | return packet, changeAddr, identical, hm, nil 134 | } 135 | 136 | func (client *Client) test2(ctx context.Context) (*packet, error) { 137 | return client.sendChangeReq(ctx, true, true) 138 | } 139 | 140 | func (client *Client) test3(ctx context.Context) (*packet, error) { 141 | return client.sendChangeReq(ctx, false, true) 142 | } 143 | 144 | // follow rfc 3489 and 5389 145 | func (client *Client) discover(ctx context.Context) (int, *Host, error) { 146 | ctx, cancel := context.WithCancel(ctx) 147 | defer cancel() 148 | 149 | packet, changeAddr, identical, host, err := client.test1(ctx, client.ServerAddr) 150 | if err != nil { 151 | return NAT_ERROR, nil, err 152 | } 153 | if packet == nil { 154 | return NAT_BLOCKED, nil, err 155 | } 156 | 157 | // detect symetric 158 | if identical { 159 | packet, err = client.test2(ctx) 160 | if err != nil { 161 | return NAT_ERROR, host, err 162 | } 163 | if packet != nil { 164 | return NAT_NONE, host, nil 165 | } 166 | return NAT_SYMETRIC_UDP_FIREWALL, host, nil 167 | } 168 | 169 | // detect full nat 170 | packet, err = client.test2(ctx) 171 | if err != nil { 172 | return NAT_ERROR, host, err 173 | } 174 | if packet != nil { 175 | return NAT_FULL, host, nil 176 | } 177 | 178 | packet, _, identical, _, err = client.test1(ctx, changeAddr) 179 | if err != nil { 180 | return NAT_ERROR, host, err 181 | } 182 | if packet == nil { 183 | // It should be NAT_BLOCKED, but will be 184 | // detected in the first step. So this will 185 | // never happen. 186 | return NAT_UNKNOWN, host, nil 187 | } 188 | if identical { 189 | packet, err = client.test3(ctx) 190 | if err != nil { 191 | return NAT_ERROR, host, err 192 | } 193 | if packet == nil { 194 | return NAT_PORT_RESTRICTED, host, nil 195 | } 196 | return NAT_RESTRICTED, host, nil 197 | } 198 | 199 | return NAT_SYMETRIC, host, nil 200 | } 201 | -------------------------------------------------------------------------------- /stun/const.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013, Cong Ding. All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | // author: Cong Ding 16 | 17 | package stun 18 | 19 | const ( 20 | defaultSoftwareName = "StunClient" 21 | ) 22 | 23 | const ( 24 | magicCookie = 0x2112A442 25 | fingerprint = 0x5354554e 26 | ) 27 | const ( 28 | NAT_ERROR = iota 29 | NAT_UNKNOWN 30 | NAT_NONE 31 | NAT_BLOCKED 32 | NAT_FULL 33 | NAT_SYMETRIC 34 | NAT_RESTRICTED 35 | NAT_PORT_RESTRICTED 36 | NAT_SYMETRIC_UDP_FIREWALL 37 | ) 38 | 39 | const ( 40 | error_TRY_ALTERNATE = 300 41 | error_BAD_REQUEST = 400 42 | error_UNAUTHORIZED = 401 43 | error_UNASSIGNED_402 = 402 44 | error_FORBIDDEN = 403 45 | error_UNKNOWN_attribute = 420 46 | error_ALLOCATION_MISMATCH = 437 47 | error_STALE_NONCE = 438 48 | error_UNASSIGNED_439 = 439 49 | error_ADDRESS_FAMILY_NOT_SUPPORTED = 440 50 | error_WRONG_CREDENTIALS = 441 51 | error_UNSUPPORTED_TRANSPORT_PROTOCOL = 442 52 | error_PEER_ADDRESS_FAMILY_MISMATCH = 443 53 | error_CONNECTION_ALREADY_EXISTS = 446 54 | error_CONNECTION_TIMEOUT_OR_FAILURE = 447 55 | error_ALLOCATION_QUOTA_REACHED = 486 56 | error_ROLE_CONFLICT = 487 57 | error_SERVER_error = 500 58 | error_INSUFFICIENT_CAPACITY = 508 59 | ) 60 | const ( 61 | attribute_FAMILY_IPV4 = 0x01 62 | attribute_FAMILY_IPV6 = 0x02 63 | ) 64 | 65 | const ( 66 | attribute_MAPPED_ADDRESS = 0x0001 67 | attribute_RESPONSE_ADDRESS = 0x0002 68 | attribute_CHANGE_REQUEST = 0x0003 69 | attribute_SOURCE_ADDRESS = 0x0004 70 | attribute_CHANGED_ADDRESS = 0x0005 71 | attribute_USERNAME = 0x0006 72 | attribute_PASSWORD = 0x0007 73 | attribute_MESSAGE_INTEGRITY = 0x0008 74 | attribute_error_CODE = 0x0009 75 | attribute_UNKNOWN_attributeS = 0x000A 76 | attribute_REFLECTED_FROM = 0x000B 77 | attribute_CHANNEL_NUMBER = 0x000C 78 | attribute_LIFETIME = 0x000D 79 | attribute_BANDWIDTH = 0x0010 80 | attribute_XOR_PEER_ADDRESS = 0x0012 81 | attribute_DATA = 0x0013 82 | attribute_REALM = 0x0014 83 | attribute_NONCE = 0x0015 84 | attribute_XOR_RELAYED_ADDRESS = 0x0016 85 | attribute_REQUESTED_ADDRESS_FAMILY = 0x0017 86 | attribute_EVEN_PORT = 0x0018 87 | attribute_REQUESTED_TRANSPORT = 0x0019 88 | attribute_DONT_FRAGMENT = 0x001A 89 | attribute_XOR_MAPPED_ADDRESS = 0x0020 90 | attribute_TIMER_VAL = 0x0021 91 | attribute_RESERVATION_TOKEN = 0x0022 92 | attribute_PRIORITY = 0x0024 93 | attribute_USE_CANDIDATE = 0x0025 94 | attribute_PADDING = 0x0026 95 | attribute_RESPONSE_PORT = 0x0027 96 | attribute_CONNECTION_ID = 0x002A 97 | attribute_XOR_MAPPED_ADDRESS_EXP = 0x8020 98 | attribute_SOFTWARE = 0x8022 99 | attribute_ALTERNATE_SERVER = 0x8023 100 | attribute_CACHE_TIMEOUT = 0x8027 101 | attribute_fingerprint = 0x8028 102 | attribute_ICE_CONTROLLED = 0x8029 103 | attribute_ICE_CONTROLLING = 0x802A 104 | attribute_RESPONSE_ORIGIN = 0x802B 105 | attribute_OTHER_ADDRESS = 0x802C 106 | attribute_ECN_CHECK_STUN = 0x802D 107 | attribute_CISCO_FLOWDATA = 0xC000 108 | ) 109 | 110 | const ( 111 | type_BINDING_REQUEST = 0x0001 112 | type_BINDING_RESPONSE = 0x0101 113 | type_BINDING_error_RESPONSE = 0x0111 114 | type_SHARED_SECRET_REQUEST = 0x0002 115 | type_SHARED_SECRET_RESPONSE = 0x0102 116 | type_SHARED_error_RESPONSE = 0x0112 117 | type_ALLOCATE = 0x0003 118 | type_ALLOCATE_RESPONSE = 0x0103 119 | type_ALLOCATE_error_RESPONSE = 0x0113 120 | type_REFRESH = 0x0004 121 | type_REFRESH_RESPONSE = 0x0104 122 | type_REFRESH_error_RESPONSE = 0x0114 123 | type_SEND = 0x0006 124 | type_SEND_RESPONSE = 0x0106 125 | type_SEND_error_RESPONSE = 0x0116 126 | type_DATA = 0x0007 127 | type_DATA_RESPONSE = 0x0107 128 | type_DATA_error_RESPONSE = 0x0117 129 | type_CREATE_PERMISIION = 0x0008 130 | type_CREATE_PERMISIION_RESPONSE = 0x0108 131 | type_CREATE_PERMISIION_error_RESPONSE = 0x0118 132 | type_CHANNEL_BINDING = 0x0009 133 | type_CHANNEL_BINDING_RESPONSE = 0x0109 134 | type_CHANNEL_BINDING_error_RESPONSE = 0x0119 135 | type_CONNECT = 0x000A 136 | type_CONNECT_RESPONSE = 0x010A 137 | type_CONNECT_error_RESPONSE = 0x011A 138 | type_CONNECTION_BIND = 0x000B 139 | type_CONNECTION_BIND_RESPONSE = 0x010B 140 | type_CONNECTION_BIND_error_RESPONSE = 0x011B 141 | type_CONNECTION_ATTEMPT = 0x000C 142 | type_CONNECTION_ATTEMPT_RESPONSE = 0x010C 143 | type_CONNECTION_ATTEMPT_error_RESPONSE = 0x011C 144 | ) 145 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, and 10 | distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by the copyright 13 | owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all other entities 16 | that control, are controlled by, or are under common control with that entity. 17 | For the purposes of this definition, "control" means (i) the power, direct or 18 | indirect, to cause the direction or management of such entity, whether by 19 | contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the 20 | outstanding shares, or (iii) beneficial ownership of such entity. 21 | 22 | "You" (or "Your") shall mean an individual or Legal Entity exercising 23 | permissions granted by this License. 24 | 25 | "Source" form shall mean the preferred form for making modifications, including 26 | but not limited to software source code, documentation source, and configuration 27 | files. 28 | 29 | "Object" form shall mean any form resulting from mechanical transformation or 30 | translation of a Source form, including but not limited to compiled object code, 31 | generated documentation, and conversions to other media types. 32 | 33 | "Work" shall mean the work of authorship, whether in Source or Object form, made 34 | available under the License, as indicated by a copyright notice that is included 35 | in or attached to the work (an example is provided in the Appendix below). 36 | 37 | "Derivative Works" shall mean any work, whether in Source or Object form, that 38 | is based on (or derived from) the Work and for which the editorial revisions, 39 | annotations, elaborations, or other modifications represent, as a whole, an 40 | original work of authorship. For the purposes of this License, Derivative Works 41 | shall not include works that remain separable from, or merely link (or bind by 42 | name) to the interfaces of, the Work and Derivative Works thereof. 43 | 44 | "Contribution" shall mean any work of authorship, including the original version 45 | of the Work and any modifications or additions to that Work or Derivative Works 46 | thereof, that is intentionally submitted to Licensor for inclusion in the Work 47 | by the copyright owner or by an individual or Legal Entity authorized to submit 48 | on behalf of the copyright owner. For the purposes of this definition, 49 | "submitted" means any form of electronic, verbal, or written communication sent 50 | to the Licensor or its representatives, including but not limited to 51 | communication on electronic mailing lists, source code control systems, and 52 | issue tracking systems that are managed by, or on behalf of, the Licensor for 53 | the purpose of discussing and improving the Work, but excluding communication 54 | that is conspicuously marked or otherwise designated in writing by the copyright 55 | owner as "Not a Contribution." 56 | 57 | "Contributor" shall mean Licensor and any individual or Legal Entity on behalf 58 | of whom a Contribution has been received by Licensor and subsequently 59 | incorporated within the Work. 60 | 61 | 2. Grant of Copyright License. 62 | 63 | Subject to the terms and conditions of this License, each Contributor hereby 64 | grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, 65 | irrevocable copyright license to reproduce, prepare Derivative Works of, 66 | publicly display, publicly perform, sublicense, and distribute the Work and such 67 | Derivative Works in Source or Object form. 68 | 69 | 3. Grant of Patent License. 70 | 71 | Subject to the terms and conditions of this License, each Contributor hereby 72 | grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, 73 | irrevocable (except as stated in this section) patent license to make, have 74 | made, use, offer to sell, sell, import, and otherwise transfer the Work, where 75 | such license applies only to those patent claims licensable by such Contributor 76 | that are necessarily infringed by their Contribution(s) alone or by combination 77 | of their Contribution(s) with the Work to which such Contribution(s) was 78 | submitted. If You institute patent litigation against any entity (including a 79 | cross-claim or counterclaim in a lawsuit) alleging that the Work or a 80 | Contribution incorporated within the Work constitutes direct or contributory 81 | patent infringement, then any patent licenses granted to You under this License 82 | for that Work shall terminate as of the date such litigation is filed. 83 | 84 | 4. Redistribution. 85 | 86 | You may reproduce and distribute copies of the Work or Derivative Works thereof 87 | in any medium, with or without modifications, and in Source or Object form, 88 | provided that You meet the following conditions: 89 | 90 | You must give any other recipients of the Work or Derivative Works a copy of 91 | this License; and 92 | You must cause any modified files to carry prominent notices stating that You 93 | changed the files; and 94 | You must retain, in the Source form of any Derivative Works that You distribute, 95 | all copyright, patent, trademark, and attribution notices from the Source form 96 | of the Work, excluding those notices that do not pertain to any part of the 97 | Derivative Works; and 98 | If the Work includes a "NOTICE" text file as part of its distribution, then any 99 | Derivative Works that You distribute must include a readable copy of the 100 | attribution notices contained within such NOTICE file, excluding those notices 101 | that do not pertain to any part of the Derivative Works, in at least one of the 102 | following places: within a NOTICE text file distributed as part of the 103 | Derivative Works; within the Source form or documentation, if provided along 104 | with the Derivative Works; or, within a display generated by the Derivative 105 | Works, if and wherever such third-party notices normally appear. The contents of 106 | the NOTICE file are for informational purposes only and do not modify the 107 | License. You may add Your own attribution notices within Derivative Works that 108 | You distribute, alongside or as an addendum to the NOTICE text from the Work, 109 | provided that such additional attribution notices cannot be construed as 110 | modifying the License. 111 | You may add Your own copyright statement to Your modifications and may provide 112 | additional or different license terms and conditions for use, reproduction, or 113 | distribution of Your modifications, or for any such Derivative Works as a whole, 114 | provided Your use, reproduction, and distribution of the Work otherwise complies 115 | with the conditions stated in this License. 116 | 117 | 5. Submission of Contributions. 118 | 119 | Unless You explicitly state otherwise, any Contribution intentionally submitted 120 | for inclusion in the Work by You to the Licensor shall be under the terms and 121 | conditions of this License, without any additional terms or conditions. 122 | Notwithstanding the above, nothing herein shall supersede or modify the terms of 123 | any separate license agreement you may have executed with Licensor regarding 124 | such Contributions. 125 | 126 | 6. Trademarks. 127 | 128 | This License does not grant permission to use the trade names, trademarks, 129 | service marks, or product names of the Licensor, except as required for 130 | reasonable and customary use in describing the origin of the Work and 131 | reproducing the content of the NOTICE file. 132 | 133 | 7. Disclaimer of Warranty. 134 | 135 | Unless required by applicable law or agreed to in writing, Licensor provides the 136 | Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, 137 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, 138 | including, without limitation, any warranties or conditions of TITLE, 139 | NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are 140 | solely responsible for determining the appropriateness of using or 141 | redistributing the Work and assume any risks associated with Your exercise of 142 | permissions under this License. 143 | 144 | 8. Limitation of Liability. 145 | 146 | In no event and under no legal theory, whether in tort (including negligence), 147 | contract, or otherwise, unless required by applicable law (such as deliberate 148 | and grossly negligent acts) or agreed to in writing, shall any Contributor be 149 | liable to You for damages, including any direct, indirect, special, incidental, 150 | or consequential damages of any character arising as a result of this License or 151 | out of the use or inability to use the Work (including but not limited to 152 | damages for loss of goodwill, work stoppage, computer failure or malfunction, or 153 | any and all other commercial damages or losses), even if such Contributor has 154 | been advised of the possibility of such damages. 155 | 156 | 9. Accepting Warranty or Additional Liability. 157 | 158 | While redistributing the Work or Derivative Works thereof, You may choose to 159 | offer, and charge a fee for, acceptance of support, warranty, indemnity, or 160 | other liability obligations and/or rights consistent with this License. However, 161 | in accepting such obligations, You may act only on Your own behalf and on Your 162 | sole responsibility, not on behalf of any other Contributor, and only if You 163 | agree to indemnify, defend, and hold each Contributor harmless for any liability 164 | incurred by, or claims asserted against, such Contributor by reason of your 165 | accepting any such warranty or additional liability. 166 | 167 | END OF TERMS AND CONDITIONS 168 | 169 | APPENDIX: How to apply the Apache License to your work 170 | 171 | To apply the Apache License to your work, attach the following boilerplate 172 | notice, with the fields enclosed by brackets "[]" replaced with your own 173 | identifying information. (Don't include the brackets!) The text should be 174 | enclosed in the appropriate comment syntax for the file format. We also 175 | recommend that a file or class name and description of purpose be included on 176 | the same "printed page" as the copyright notice for easier identification within 177 | third-party archives. 178 | 179 | Copyright [yyyy] [name of copyright owner] 180 | 181 | Licensed under the Apache License, Version 2.0 (the "License"); 182 | you may not use this file except in compliance with the License. 183 | You may obtain a copy of the License at 184 | 185 | http://www.apache.org/licenses/LICENSE-2.0 186 | 187 | Unless required by applicable law or agreed to in writing, software 188 | distributed under the License is distributed on an "AS IS" BASIS, 189 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 190 | See the License for the specific language governing permissions and 191 | limitations under the License. 192 | --------------------------------------------------------------------------------