From 1e796f7d29bb6749d0fc09b7cbd15a862d78e882 Mon Sep 17 00:00:00 2001 From: Cong Ding Date: Sun, 18 Aug 2013 00:17:46 +0200 Subject: [PATCH] init commit --- .gitignore | 22 ++++++ LICENSE | 191 ++++++++++++++++++++++++++++++++++++++++++++++ README.md | 16 ++++ example.go | 56 ++++++++++++++ stun/attribute.go | 79 +++++++++++++++++++ stun/client.go | 62 +++++++++++++++ stun/const.go | 146 +++++++++++++++++++++++++++++++++++ stun/discover.go | 171 +++++++++++++++++++++++++++++++++++++++++ stun/host.go | 44 +++++++++++ stun/packet.go | 144 ++++++++++++++++++++++++++++++++++ 10 files changed, 931 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 example.go create mode 100644 stun/attribute.go create mode 100644 stun/client.go create mode 100644 stun/const.go create mode 100644 stun/discover.go create mode 100644 stun/host.go create mode 100644 stun/packet.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0026861 --- /dev/null +++ b/.gitignore @@ -0,0 +1,22 @@ +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..37ec93a --- /dev/null +++ b/LICENSE @@ -0,0 +1,191 @@ +Apache License +Version 2.0, January 2004 +http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + +"License" shall mean the terms and conditions for use, reproduction, and +distribution as defined by Sections 1 through 9 of this document. + +"Licensor" shall mean the copyright owner or entity authorized by the copyright +owner that is granting the License. + +"Legal Entity" shall mean the union of the acting entity and all other entities +that control, are controlled by, or are under common control with that entity. +For the purposes of this definition, "control" means (i) the power, direct or +indirect, to cause the direction or management of such entity, whether by +contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the +outstanding shares, or (iii) beneficial ownership of such entity. + +"You" (or "Your") shall mean an individual or Legal Entity exercising +permissions granted by this License. + +"Source" form shall mean the preferred form for making modifications, including +but not limited to software source code, documentation source, and configuration +files. + +"Object" form shall mean any form resulting from mechanical transformation or +translation of a Source form, including but not limited to compiled object code, +generated documentation, and conversions to other media types. + +"Work" shall mean the work of authorship, whether in Source or Object form, made +available under the License, as indicated by a copyright notice that is included +in or attached to the work (an example is provided in the Appendix below). + +"Derivative Works" shall mean any work, whether in Source or Object form, that +is based on (or derived from) the Work and for which the editorial revisions, +annotations, elaborations, or other modifications represent, as a whole, an +original work of authorship. For the purposes of this License, Derivative Works +shall not include works that remain separable from, or merely link (or bind by +name) to the interfaces of, the Work and Derivative Works thereof. + +"Contribution" shall mean any work of authorship, including the original version +of the Work and any modifications or additions to that Work or Derivative Works +thereof, that is intentionally submitted to Licensor for inclusion in the Work +by the copyright owner or by an individual or Legal Entity authorized to submit +on behalf of the copyright owner. For the purposes of this definition, +"submitted" means any form of electronic, verbal, or written communication sent +to the Licensor or its representatives, including but not limited to +communication on electronic mailing lists, source code control systems, and +issue tracking systems that are managed by, or on behalf of, the Licensor for +the purpose of discussing and improving the Work, but excluding communication +that is conspicuously marked or otherwise designated in writing by the copyright +owner as "Not a Contribution." + +"Contributor" shall mean Licensor and any individual or Legal Entity on behalf +of whom a Contribution has been received by Licensor and subsequently +incorporated within the Work. + +2. Grant of Copyright License. + +Subject to the terms and conditions of this License, each Contributor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable copyright license to reproduce, prepare Derivative Works of, +publicly display, publicly perform, sublicense, and distribute the Work and such +Derivative Works in Source or Object form. + +3. Grant of Patent License. + +Subject to the terms and conditions of this License, each Contributor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable (except as stated in this section) patent license to make, have +made, use, offer to sell, sell, import, and otherwise transfer the Work, where +such license applies only to those patent claims licensable by such Contributor +that are necessarily infringed by their Contribution(s) alone or by combination +of their Contribution(s) with the Work to which such Contribution(s) was +submitted. If You institute patent litigation against any entity (including a +cross-claim or counterclaim in a lawsuit) alleging that the Work or a +Contribution incorporated within the Work constitutes direct or contributory +patent infringement, then any patent licenses granted to You under this License +for that Work shall terminate as of the date such litigation is filed. + +4. Redistribution. + +You may reproduce and distribute copies of the Work or Derivative Works thereof +in any medium, with or without modifications, and in Source or Object form, +provided that You meet the following conditions: + +You must give any other recipients of the Work or Derivative Works a copy of +this License; and +You must cause any modified files to carry prominent notices stating that You +changed the files; and +You must retain, in the Source form of any Derivative Works that You distribute, +all copyright, patent, trademark, and attribution notices from the Source form +of the Work, excluding those notices that do not pertain to any part of the +Derivative Works; and +If the Work includes a "NOTICE" text file as part of its distribution, then any +Derivative Works that You distribute must include a readable copy of the +attribution notices contained within such NOTICE file, excluding those notices +that do not pertain to any part of the Derivative Works, in at least one of the +following places: within a NOTICE text file distributed as part of the +Derivative Works; within the Source form or documentation, if provided along +with the Derivative Works; or, within a display generated by the Derivative +Works, if and wherever such third-party notices normally appear. The contents of +the NOTICE file are for informational purposes only and do not modify the +License. You may add Your own attribution notices within Derivative Works that +You distribute, alongside or as an addendum to the NOTICE text from the Work, +provided that such additional attribution notices cannot be construed as +modifying the License. +You may add Your own copyright statement to Your modifications and may provide +additional or different license terms and conditions for use, reproduction, or +distribution of Your modifications, or for any such Derivative Works as a whole, +provided Your use, reproduction, and distribution of the Work otherwise complies +with the conditions stated in this License. + +5. Submission of Contributions. + +Unless You explicitly state otherwise, any Contribution intentionally submitted +for inclusion in the Work by You to the Licensor shall be under the terms and +conditions of this License, without any additional terms or conditions. +Notwithstanding the above, nothing herein shall supersede or modify the terms of +any separate license agreement you may have executed with Licensor regarding +such Contributions. + +6. Trademarks. + +This License does not grant permission to use the trade names, trademarks, +service marks, or product names of the Licensor, except as required for +reasonable and customary use in describing the origin of the Work and +reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. + +Unless required by applicable law or agreed to in writing, Licensor provides the +Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, +including, without limitation, any warranties or conditions of TITLE, +NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are +solely responsible for determining the appropriateness of using or +redistributing the Work and assume any risks associated with Your exercise of +permissions under this License. + +8. Limitation of Liability. + +In no event and under no legal theory, whether in tort (including negligence), +contract, or otherwise, unless required by applicable law (such as deliberate +and grossly negligent acts) or agreed to in writing, shall any Contributor be +liable to You for damages, including any direct, indirect, special, incidental, +or consequential damages of any character arising as a result of this License or +out of the use or inability to use the Work (including but not limited to +damages for loss of goodwill, work stoppage, computer failure or malfunction, or +any and all other commercial damages or losses), even if such Contributor has +been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. + +While redistributing the Work or Derivative Works thereof, You may choose to +offer, and charge a fee for, acceptance of support, warranty, indemnity, or +other liability obligations and/or rights consistent with this License. However, +in accepting such obligations, You may act only on Your own behalf and on Your +sole responsibility, not on behalf of any other Contributor, and only if You +agree to indemnify, defend, and hold each Contributor harmless for any liability +incurred by, or claims asserted against, such Contributor by reason of your +accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work + +To apply the Apache License to your work, attach the following boilerplate +notice, with the fields enclosed by brackets "[]" replaced with your own +identifying information. (Don't include the brackets!) The text should be +enclosed in the appropriate comment syntax for the file format. We also +recommend that a file or class name and description of purpose be included on +the same "printed page" as the copyright notice for easier identification within +third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md new file mode 100644 index 0000000..b58e278 --- /dev/null +++ b/README.md @@ -0,0 +1,16 @@ +go-stun +======= + +go-stun is a STUN (RFC 3489, 5389) client implementation in golang. + +It is extremely easy to use -- just one line of code. + +```go +import "github.com/ccding/go-stun/stun" + +func main() { + nat, host, err := stun.Discover() +} +``` + +More details please go to `example.go`. diff --git a/example.go b/example.go new file mode 100644 index 0000000..6b797f1 --- /dev/null +++ b/example.go @@ -0,0 +1,56 @@ +// Copyright 2013, Cong Ding. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// author: Cong Ding +// +package main + +import ( + "fmt" + "github.com/ccding/go-stun/stun" +) + +func main() { + nat, host, err := stun.Discover() + if err != nil { + fmt.Println(err) + } + + switch nat { + case stun.NAT_ERROR: + fmt.Println("Test failed") + case stun.NAT_UNKNOWN: + fmt.Println("Unexpected response from the STUN server") + case stun.NAT_BLOCKED: + fmt.Println("UDP is blocked") + case stun.NAT_FULL: + fmt.Println("Full cone NAT") + case stun.NAT_SYMETRIC: + fmt.Println("Symetric NAT") + case stun.NAT_RESTRICTED: + fmt.Println("Restricted NAT") + case stun.NAT_PORT_RESTRICTED: + fmt.Println("Port restricted NAT") + case stun.NAT_NONE: + fmt.Println("Not behind a NAT") + case stun.NAT_SYMETRIC_UDP_FIREWALL: + fmt.Println("Symetric UDP firewall") + } + + if host != nil { + fmt.Println(host.Family()) + fmt.Println(host.Ip()) + fmt.Println(host.Port()) + } +} diff --git a/stun/attribute.go b/stun/attribute.go new file mode 100644 index 0000000..58cbf12 --- /dev/null +++ b/stun/attribute.go @@ -0,0 +1,79 @@ +// Copyright 2013, Cong Ding. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// author: Cong Ding +// +package stun + +import ( + "encoding/binary" + "hash/crc32" + "net" +) + +type attribute struct { + types uint16 + length uint16 + value []byte +} + +func newAttribute(types uint16, value []byte) *attribute { + a := new(attribute) + a.types = types + a.value = padding(value) + a.length = uint16(len(a.value)) + return a +} + +func newFingerprintAttribute(packet *packet) *attribute { + crc := crc32.ChecksumIEEE(packet.bytes()) ^ FINGERPRINT + buf := make([]byte, 4) + binary.BigEndian.PutUint32(buf, crc) + return newAttribute(ATTRIBUTE_FINGERPRINT, buf) +} + +func newSoftwareAttribute(packet *packet, name string) *attribute { + return newAttribute(ATTRIBUTE_SOFTWARE, []byte(name)) +} + +func newChangeReqAttribute(packet *packet, changeIp bool, changePort bool) *attribute { + value := make([]byte, 4) + if changeIp { + value[3] |= 0x04 + } + if changePort { + value[3] |= 0x02 + } + return newAttribute(ATTRIBUTE_CHANGE_REQUEST, value) +} + +func (v *attribute) xorMappedAddr() *Host { + cookie := make([]byte, 4) + binary.BigEndian.PutUint32(cookie, MAGIC_COOKIE) + xorIp := make([]byte, 16) + for i := 0; i < len(v.value)-4; i++ { + xorIp[i] = v.value[i+4] ^ cookie[i] + } + port := binary.BigEndian.Uint16(v.value[2:4]) + return &Host{binary.BigEndian.Uint16(v.value[0:2]), + net.IP(xorIp).String(), port ^ (MAGIC_COOKIE >> 32)} +} + +func (v *attribute) address() *Host { + h := new(Host) + h.family = binary.BigEndian.Uint16(v.value[0:2]) + h.port = binary.BigEndian.Uint16(v.value[2:4]) + h.ip = net.IP(v.value[4:]).String() + return h +} diff --git a/stun/client.go b/stun/client.go new file mode 100644 index 0000000..63b8f2d --- /dev/null +++ b/stun/client.go @@ -0,0 +1,62 @@ +// Copyright 2013, Cong Ding. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// author: Cong Ding +// +package stun + +import ( + "errors" + "net" + "strconv" +) + +var ( + serverAddr string + softwareName string +) + +func init() { + SetSoftwareName(DefaultSoftwareName) +} + +func SetServerHost(host string, port int) error { + ips, err := net.LookupHost(host) + if err != nil { + return err + } + if len(ips) == 0 { + return errors.New("Failed to get IP address of " + host) + } + serverAddr = net.JoinHostPort(ips[0], strconv.Itoa(port)) + return nil +} + +func SetServerAddr(address string) { + serverAddr = address +} + +func SetSoftwareName(name string) { + softwareName = name +} + +func Discover() (int, *Host, error) { + if serverAddr == "" { + err := SetServerHost(DefaultServerHost, DefaultServerPort) + if err != nil { + return NAT_ERROR, nil, err + } + } + return discover() +} diff --git a/stun/const.go b/stun/const.go new file mode 100644 index 0000000..46bf215 --- /dev/null +++ b/stun/const.go @@ -0,0 +1,146 @@ +// Copyright 2013, Cong Ding. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// author: Cong Ding +// +package stun + +const ( + DefaultServerHost = "provserver.televolution.net" + DefaultServerPort = 3478 + DefaultSoftwareName = "StunClient" +) + +const ( + MAGIC_COOKIE = 0x2112A442 + FINGERPRINT = 0x5354554e +) +const ( + NAT_ERROR = iota + NAT_UNKNOWN + NAT_NONE + NAT_BLOCKED + NAT_FULL + NAT_SYMETRIC + NAT_RESTRICTED + NAT_PORT_RESTRICTED + NAT_SYMETRIC_UDP_FIREWALL +) + +const ( + ERROR_TRY_ALTERNATE = 300 + ERROR_BAD_REQUEST = 400 + ERROR_UNAUTHORIZED = 401 + ERROR_UNASSIGNED_402 = 402 + ERROR_FORBIDDEN = 403 + ERROR_UNKNOWN_ATTRIBUTE = 420 + ERROR_ALLOCATION_MISMATCH = 437 + ERROR_STALE_NONCE = 438 + ERROR_UNASSIGNED_439 = 439 + ERROR_ADDRESS_FAMILY_NOT_SUPPORTED = 440 + ERROR_WRONG_CREDENTIALS = 441 + ERROR_UNSUPPORTED_TRANSPORT_PROTOCOL = 442 + ERROR_PEER_ADDRESS_FAMILY_MISMATCH = 443 + ERROR_CONNECTION_ALREADY_EXISTS = 446 + ERROR_CONNECTION_TIMEOUT_OR_FAILURE = 447 + ERROR_ALLOCATION_QUOTA_REACHED = 486 + ERROR_ROLE_CONFLICT = 487 + ERROR_SERVER_ERROR = 500 + ERROR_INSUFFICIENT_CAPACITY = 508 +) +const ( + ATTRIBUTE_FAMILY_IPV4 = 0x01 + ATTRIBUTE_FAMILY_IPV6 = 0x02 +) + +const ( + ATTRIBUTE_MAPPED_ADDRESS = 0x0001 + ATTRIBUTE_RESPONSE_ADDRESS = 0x0002 + ATTRIBUTE_CHANGE_REQUEST = 0x0003 + ATTRIBUTE_SOURCE_ADDRESS = 0x0004 + ATTRIBUTE_CHANGED_ADDRESS = 0x0005 + ATTRIBUTE_USERNAME = 0x0006 + ATTRIBUTE_PASSWORD = 0x0007 + ATTRIBUTE_MESSAGE_INTEGRITY = 0x0008 + ATTRIBUTE_ERROR_CODE = 0x0009 + ATTRIBUTE_UNKNOWN_ATTRIBUTES = 0x000A + ATTRIBUTE_REFLECTED_FROM = 0x000B + ATTRIBUTE_CHANNEL_NUMBER = 0x000C + ATTRIBUTE_LIFETIME = 0x000D + ATTRIBUTE_BANDWIDTH = 0x0010 + ATTRIBUTE_XOR_PEER_ADDRESS = 0x0012 + ATTRIBUTE_DATA = 0x0013 + ATTRIBUTE_REALM = 0x0014 + ATTRIBUTE_NONCE = 0x0015 + ATTRIBUTE_XOR_RELAYED_ADDRESS = 0x0016 + ATTRIBUTE_REQUESTED_ADDRESS_FAMILY = 0x0017 + ATTRIBUTE_EVEN_PORT = 0x0018 + ATTRIBUTE_REQUESTED_TRANSPORT = 0x0019 + ATTRIBUTE_DONT_FRAGMENT = 0x001A + ATTRIBUTE_XOR_MAPPED_ADDRESS = 0x0020 + ATTRIBUTE_TIMER_VAL = 0x0021 + ATTRIBUTE_RESERVATION_TOKEN = 0x0022 + ATTRIBUTE_PRIORITY = 0x0024 + ATTRIBUTE_USE_CANDIDATE = 0x0025 + ATTRIBUTE_PADDING = 0x0026 + ATTRIBUTE_RESPONSE_PORT = 0x0027 + ATTRIBUTE_CONNECTION_ID = 0x002A + ATTRIBUTE_XOR_MAPPED_ADDRESS_EXP = 0x8020 + ATTRIBUTE_SOFTWARE = 0x8022 + ATTRIBUTE_ALTERNATE_SERVER = 0x8023 + ATTRIBUTE_CACHE_TIMEOUT = 0x8027 + ATTRIBUTE_FINGERPRINT = 0x8028 + ATTRIBUTE_ICE_CONTROLLED = 0x8029 + ATTRIBUTE_ICE_CONTROLLING = 0x802A + ATTRIBUTE_RESPONSE_ORIGIN = 0x802B + ATTRIBUTE_OTHER_ADDRESS = 0x802C + ATTRIBUTE_ECN_CHECK_STUN = 0x802D + ATTRIBUTE_CISCO_FLOWDATA = 0xC000 +) + +const ( + TYPE_BINDING_REQUEST = 0x0001 + TYPE_BINDING_RESPONSE = 0x0101 + TYPE_BINDING_ERROR_RESPONSE = 0x0111 + TYPE_SHARED_SECRET_REQUEST = 0x0002 + TYPE_SHARED_SECRET_RESPONSE = 0x0102 + TYPE_SHARED_ERROR_RESPONSE = 0x0112 + TYPE_ALLOCATE = 0x0003 + TYPE_ALLOCATE_RESPONSE = 0x0103 + TYPE_ALLOCATE_ERROR_RESPONSE = 0x0113 + TYPE_REFRESH = 0x0004 + TYPE_REFRESH_RESPONSE = 0x0104 + TYPE_REFRESH_ERROR_RESPONSE = 0x0114 + TYPE_SEND = 0x0006 + TYPE_SEND_RESPONSE = 0x0106 + TYPE_SEND_ERROR_RESPONSE = 0x0116 + TYPE_DATA = 0x0007 + TYPE_DATA_RESPONSE = 0x0107 + TYPE_DATA_ERROR_RESPONSE = 0x0117 + TYPE_CREATE_PERMISIION = 0x0008 + TYPE_CREATE_PERMISIION_RESPONSE = 0x0108 + TYPE_CREATE_PERMISIION_ERROR_RESPONSE = 0x0118 + TYPE_CHANNEL_BINDING = 0x0009 + TYPE_CHANNEL_BINDING_RESPONSE = 0x0109 + TYPE_CHANNEL_BINDING_ERROR_RESPONSE = 0x0119 + TYPE_CONNECT = 0x000A + TYPE_CONNECT_RESPONSE = 0x010A + TYPE_CONNECT_ERROR_RESPONSE = 0x011A + TYPE_CONNECTION_BIND = 0x000B + TYPE_CONNECTION_BIND_RESPONSE = 0x010B + TYPE_CONNECTION_BIND_ERROR_RESPONSE = 0x011B + TYPE_CONNECTION_ATTEMPT = 0x000C + TYPE_CONNECTION_ATTEMPT_RESPONSE = 0x010C + TYPE_CONNECTION_ATTEMPT_ERROR_RESPONSE = 0x011C +) diff --git a/stun/discover.go b/stun/discover.go new file mode 100644 index 0000000..b4108d6 --- /dev/null +++ b/stun/discover.go @@ -0,0 +1,171 @@ +// Copyright 2013, Cong Ding. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// author: Cong Ding +// +package stun + +import ( + "errors" + "net" +) + +// padding the length of the byte slice to multiple of 4 +func padding(b []byte) []byte { + l := uint16(len(b)) + return append(b, make([]byte, align(l)-l)...) +} + +// align the uint16 number to the smallest multiple of 4, which is larger than +// or equal to the uint16 number +func align(l uint16) uint16 { + return (l + 3) & 0xfffc +} + +func sendBindingReq(destAddr string) (*packet, string, error) { + connection, err := net.Dial("udp", destAddr) + if err != nil { + return nil, "", err + } + + packet := newPacket() + packet.types = TYPE_BINDING_REQUEST + attribute := newSoftwareAttribute(packet, DefaultSoftwareName) + packet.addAttribute(*attribute) + attribute = newFingerprintAttribute(packet) + packet.addAttribute(*attribute) + + localAddr := connection.LocalAddr().String() + packet, err = packet.send(connection) + if err != nil { + return nil, "", err + } + + err = connection.Close() + return packet, localAddr, err +} + +func sendChangeReq(changeIp bool, changePort bool) (*packet, error) { + connection, err := net.Dial("udp", serverAddr) + if err != nil { + return nil, err + } + + // construct packet + packet := newPacket() + packet.types = TYPE_BINDING_REQUEST + attribute := newSoftwareAttribute(packet, DefaultSoftwareName) + packet.addAttribute(*attribute) + attribute = newChangeReqAttribute(packet, changeIp, changePort) + packet.addAttribute(*attribute) + attribute = newFingerprintAttribute(packet) + packet.addAttribute(*attribute) + + packet, err = packet.send(connection) + if err != nil { + return nil, err + } + + err = connection.Close() + return packet, err +} + +func test1(destAddr string) (*packet, string, bool, *Host, error) { + packet, localAddr, err := sendBindingReq(destAddr) + if err != nil { + return nil, "", false, nil, err + } + if packet == nil { + return nil, "", false, nil, nil + } + + hm := packet.xorMappedAddr() + // rfc 3489 doesn't require the server return xor mapped address + if hm == nil { + hm = packet.mappedAddr() + if hm == nil { + return nil, "", false, nil, errors.New("No mapped address") + } + } + + hc := packet.changedAddr() + if hc == nil { + return nil, "", false, nil, errors.New("No changed address") + } + changeAddr := hc.Transport() + identical := localAddr == hm.Transport() + + return packet, changeAddr, identical, hm, nil +} + +func test2() (*packet, error) { + return sendChangeReq(true, true) +} + +func test3() (*packet, error) { + return sendChangeReq(false, true) +} + +// follow rfc 3489 and 5389 +func discover() (int, *Host, error) { + packet, changeAddr, identical, host, err := test1(serverAddr) + if err != nil { + return NAT_ERROR, nil, err + } + if packet == nil { + return NAT_BLOCKED, nil, err + } + if identical { + packet, err = test2() + if err != nil { + return NAT_ERROR, host, err + } + if packet != nil { + return NAT_NONE, host, nil + } + return NAT_SYMETRIC_UDP_FIREWALL, host, nil + } else { + packet, err = test2() + if err != nil { + return NAT_ERROR, host, err + } + if packet != nil { + return NAT_FULL, host, nil + } else { + packet, _, identical, _, err := test1(changeAddr) + if err != nil { + return NAT_ERROR, host, err + } + if packet == nil { + // It should be NAT_BLOCKED, but will be + // detected in the first step. So this will + // never happen. + return NAT_UNKNOWN, host, nil + } + if identical { + packet, err = test3() + if err != nil { + return NAT_ERROR, host, err + } + if packet == nil { + return NAT_PORT_RESTRICTED, host, nil + } else { + return NAT_RESTRICTED, host, nil + } + } else { + return NAT_SYMETRIC, host, nil + } + } + } +} diff --git a/stun/host.go b/stun/host.go new file mode 100644 index 0000000..4d3287b --- /dev/null +++ b/stun/host.go @@ -0,0 +1,44 @@ +// Copyright 2013, Cong Ding. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// author: Cong Ding +// +package stun + +import ( + "net" + "strconv" +) + +type Host struct { + family uint16 + ip string + port uint16 +} + +func (h *Host) Family() uint16 { + return h.family +} + +func (h *Host) Ip() string { + return h.ip +} + +func (h *Host) Port() uint16 { + return h.port +} + +func (h *Host) Transport() string { + return net.JoinHostPort(h.ip, strconv.Itoa(int(h.port))) +} diff --git a/stun/packet.go b/stun/packet.go new file mode 100644 index 0000000..dd3942d --- /dev/null +++ b/stun/packet.go @@ -0,0 +1,144 @@ +// Copyright 2013, Cong Ding. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// author: Cong Ding +// +package stun + +import ( + "encoding/binary" + "net" + "time" +) + +type packet struct { + types uint16 + length uint16 + cookie uint32 + id []byte // 12 bytes + attributes []attribute +} + +func newPacket() *packet { + v := new(packet) + v.id = make([]byte, 12) + v.attributes = make([]attribute, 0, 10) + v.cookie = MAGIC_COOKIE + v.length = 0 + return v +} + +func newPacketFromBytes(b []byte) *packet { + packet := newPacket() + packet.types = binary.BigEndian.Uint16(b[0:2]) + packet.length = binary.BigEndian.Uint16(b[2:4]) + packet.cookie = binary.BigEndian.Uint32(b[4:8]) + packet.id = b[8:20] + + for pos := uint16(20); pos < uint16(len(b)); { + types := binary.BigEndian.Uint16(b[pos : pos+2]) + length := binary.BigEndian.Uint16(b[pos+2 : pos+4]) + value := b[pos+4 : pos+4+length] + attribute := newAttribute(types, value) + packet.addAttribute(*attribute) + pos += align(length) + 4 + } + + return packet +} + +func (v *packet) addAttribute(a attribute) { + v.attributes = append(v.attributes, a) + v.length += align(a.length) + 4 +} + +func (v *packet) bytes() []byte { + b := make([]byte, 8) + binary.BigEndian.PutUint16(b[0:2], v.types) + binary.BigEndian.PutUint16(b[2:4], v.length) + binary.BigEndian.PutUint32(b[4:8], v.cookie) + b = append(b, v.id...) + + for _, a := range v.attributes { + buf := make([]byte, 2) + binary.BigEndian.PutUint16(buf, a.types) + b = append(b, buf...) + binary.BigEndian.PutUint16(buf, a.length) + b = append(b, buf...) + b = append(b, a.value...) + } + return b +} + +func (v *packet) mappedAddr() *Host { + for _, a := range v.attributes { + if a.types == ATTRIBUTE_MAPPED_ADDRESS { + h := a.address() + return h + } + } + return nil +} + +func (v *packet) changedAddr() *Host { + for _, a := range v.attributes { + if a.types == ATTRIBUTE_CHANGED_ADDRESS { + h := a.address() + return h + } + } + return nil +} + +func (v *packet) xorMappedAddr() *Host { + for _, a := range v.attributes { + if (a.types == ATTRIBUTE_XOR_MAPPED_ADDRESS) || (a.types == ATTRIBUTE_XOR_MAPPED_ADDRESS_EXP) { + h := a.xorMappedAddr() + return h + } + } + return nil +} + +// RFC 3489: Clients SHOULD retransmit the request starting with an interval +// of 100ms, doubling every retransmit until the interval reaches 1.6s. +// Retransmissions continue with intervals of 1.6s until a response is +// received, or a total of 9 requests have been sent. +func (packet *packet) send(conn net.Conn) (*packet, error) { + timeout := 100 + + for i := 0; i < 9; i++ { + l, err := conn.Write(packet.bytes()) + if err != nil { + return nil, err + } + + conn.SetReadDeadline(time.Now().Add(time.Duration(timeout) * time.Millisecond)) + if timeout < 1600 { + timeout *= 2 + } + + b := make([]byte, 1024) + l, err = conn.Read(b) + if err == nil { + return newPacketFromBytes(b[0:l]), nil + } else { + if !err.(net.Error).Timeout() { + return nil, err + } + } + } + + return nil, nil +}