├── .gitignore ├── README.md ├── client.go ├── nat └── nat.go ├── server.go └── stun └── stun.go /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # go-stun 2 | 3 | Go implementation of STUN 4 | 5 | # stun 6 | 7 | ## stun client 8 | 9 | ```go 10 | package main 11 | 12 | import ( 13 | "github.com/bhpike65/go-stun/stun" 14 | "fmt" 15 | "os" 16 | ) 17 | 18 | func main() { 19 | req := stun.NewBindRequest(nil) 20 | req.SetChangeIP(true) 21 | req.SetChangePort(true) 22 | resp, localAddr, err := req.Request("192.168.1.3"", "stun.l.google.com:19302") 23 | if err != nil { 24 | fmt.Println("Failed to build STUN PP request:", err) 25 | os.Exit(1) 26 | } 27 | fmt.Println("mapping address: ", resp.Addr.String()) 28 | } 29 | ``` 30 | 31 | ## stun server 32 | 33 | ```go 34 | package main 35 | 36 | import ( 37 | "github.com/bhpike65/go-stun/stun" 38 | "fmt" 39 | ) 40 | 41 | func main() { 42 | addr, _ := net.ResolveUDPAddr("udp", "1.1.1.1:3478") 43 | conn, _ := net.ListenUDP("udp", addr) 44 | buf := make([]byte, 1500) 45 | for { 46 | n, remote, err := conn.ReadFromUDP(buf) 47 | 48 | var req stun.StunMessageReq 49 | if err = req.Unmarshal(buf[:n]); err != nil { 50 | continue 51 | } 52 | if err = req.RespondTo(conn, remote, other); err != nil { 53 | continue 54 | } 55 | } 56 | } 57 | ``` 58 | 59 | # NAT Behaviour Discovery 60 | 61 | ```go 62 | package main 63 | 64 | import ( 65 | "flag" 66 | "fmt" 67 | "os" 68 | "github.com/bhpike65/go-stun/nat" 69 | "net" 70 | ) 71 | 72 | func main() { 73 | res, err := nat.Discovery("192.168.1.1:0", "stun.l.google.com:19302", "") 74 | if err != nil { 75 | fmt.Println("nat discovery error: ", err.Error()) 76 | os.Exit(-1) 77 | } 78 | 79 | fmt.Printf("nat discovery result:\n%s", res) 80 | return 81 | } 82 | ``` 83 | 84 | it will output: 85 | ```text 86 | localAddress:192.168.1.3:56010, mappingAddress:1.1.1.1:15168 87 | NAT mapping type: Endpoint-Independent Mapping NAT 88 | NAT filtering type: Endpoint-Independent Filtering NAT 89 | NAT Hairpinning Support: YES 90 | ``` 91 | 92 | # Example Usage 93 | 94 | ## server 95 | ```sh 96 | go run ./server.go -primary-addr 1.1.1.1 -primary-port 3478 -alt-address 2.2.2.2 -alt-port 3479 97 | ``` 98 | or you can let the program auto select the public IP from the interface 99 | 100 | ```sh 101 | go run ./server.go -public 102 | ``` 103 | 104 | ## slave server 105 | if you don't have two public IP address in one machine, instead, you can use two machine and specify one as slave server. 106 | 107 | 1. start slave server 108 | ```sh 109 | go run server.go -slave -slaveserver 1.1.1.1:12345 -primary-addr 2.2.2.2 -primary-port 3478 -alt-port 3479 110 | ``` 111 | then it will start a tcp server listen on 1.1.1.1:12345, and waits request from master server. 112 | you should add iptables rules to filter the packet which doesn't come from master server. 113 | 114 | 115 | 2. start master server 116 | ```sh 117 | go run server.go -slaveserver 1.1.1.1:12345 -primary-addr 1.1.1.1 -primary-port 3478 -alt-port 3479 118 | ``` 119 | if master don't have alt-addr public IP, and the ChangeIP Bit in Bonding Request is set, then it will let slaveserver to reply to it. 120 | slaveserver and master server should have the same primary-port and alt-port 121 | 122 | ## client 123 | ```sh 124 | go run client.go -server 1.1.1.1:3478 -alt-server 2.2.2.2:3479 125 | ``` 126 | and get the NAT behaviour test result: 127 | ```text 128 | localAddress:192.168.1.3:49191, mappingAddress:3.3.3.3:37408 129 | Address and Port-Dependent Mapping NAT 130 | Endpoint-Independent Filtering NAT 131 | NAT Hairpinning Support: YES 132 | ``` 133 | 134 | # Spec 135 | - [RFC 4787: NAT](https://tools.ietf.org/html/rfc787) 136 | - [RFC 5389: STUN](https://tools.ietf.org/html/rfc5389) 137 | - [RFC 5780: NAT Behaviour Discovery](https://tools.ietf.org/html/rfc5780) 138 | -------------------------------------------------------------------------------- /client.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "github.com/bhpike65/go-stun/nat" 7 | "net" 8 | "os" 9 | ) 10 | 11 | var server = flag.String("server", "stun.l.google.com:19302", "STUN server to query") 12 | var altServer = flag.String("alt-server", "", "alternative STUN server to query") 13 | var local = flag.String("local", "", "local ip:port to use") 14 | 15 | func main() { 16 | flag.Parse() 17 | 18 | if *local == "" { 19 | addrs, err := net.InterfaceAddrs() 20 | if err != nil { 21 | fmt.Println("get interface addrs error: ", err.Error()) 22 | os.Exit(-1) 23 | } 24 | for _, a := range addrs { 25 | if ipnet, ok := a.(*net.IPNet); ok && !ipnet.IP.IsLoopback() { 26 | if ipnet.IP.To4() != nil { 27 | *local = ipnet.IP.String() + ":0" 28 | } 29 | } 30 | } 31 | } 32 | 33 | res, err := nat.Discovery(*local, *server, *altServer) 34 | if err != nil { 35 | fmt.Println("nat discovery error: ", err.Error()) 36 | os.Exit(-1) 37 | } 38 | 39 | fmt.Printf("nat discovery result:\n%s", res) 40 | return 41 | } 42 | -------------------------------------------------------------------------------- /nat/nat.go: -------------------------------------------------------------------------------- 1 | package nat 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "github.com/bhpike65/go-stun/stun" 7 | "net" 8 | ) 9 | 10 | const ( 11 | NAT_TEST_FAILED = -1 12 | NAT_TYPE_NONAT = iota 13 | NAT_TYPE_EIM //Endpoint-Independent Mapping NAT 14 | NAT_TYPE_ADM //Address-Dependent Mapping NAT 15 | NAT_TYPE_APDM //Address and Port-Dependent Mapping NAT 16 | NAT_TYPE_EIF //Endpoint-Independent Filtering NAT 17 | NAT_TYPE_ADF //Address-Dependent Filtering NAT 18 | NAT_TYPE_APDF //Address and Port-Dependent Filtering NAT 19 | ) 20 | 21 | type NATBehaviorDiscovery struct { 22 | Local *net.UDPAddr 23 | Server *net.UDPAddr 24 | AltServer *net.UDPAddr 25 | LocalAddr string 26 | MappingAddr string 27 | MappingType int 28 | FilteringType int 29 | Hairpinning bool 30 | } 31 | 32 | 33 | func Discovery(local, server, altServer string) (*NATBehaviorDiscovery, error) { 34 | var res NATBehaviorDiscovery 35 | var err error 36 | res.Server, err = net.ResolveUDPAddr("udp", server) 37 | if err != nil { 38 | return nil, err 39 | } 40 | res.Local, err = net.ResolveUDPAddr("udp", local) 41 | if err != nil { 42 | return nil, err 43 | } 44 | if altServer != "" { 45 | res.AltServer, err = net.ResolveUDPAddr("udp", altServer) 46 | if err != nil { 47 | return nil, err 48 | } 49 | } 50 | 51 | conn, err := net.ListenUDP("udp", res.Local) 52 | if err != nil { 53 | return nil, err 54 | } 55 | defer conn.Close() 56 | res.LocalAddr = conn.LocalAddr().String() 57 | 58 | // testI: NO-NAT? 59 | req := stun.NewBindRequest(nil) 60 | resp, localAddr, err := req.RequestTo(conn, res.Server) 61 | if err != nil { 62 | return &res, errors.New(fmt.Sprintf("Failed to build STUN PP request: %s", err.Error())) 63 | } 64 | 65 | mappingPP := resp.Addr.String() 66 | res.MappingAddr = mappingPP 67 | 68 | if localAddr.String() == mappingPP { 69 | res.MappingType = NAT_TYPE_NONAT 70 | return &res, nil 71 | } 72 | primaryPort := res.Server.Port 73 | alternative := resp.OtherAddr 74 | other := resp.OtherAddr 75 | 76 | if other == nil && res.AltServer != nil { 77 | other = res.AltServer 78 | } 79 | if other != nil { 80 | altIp := other.IP 81 | altPort := other.Port 82 | // testII, send to alternativeIp:primaryPort 83 | req = stun.NewBindRequest(nil) 84 | remoteAP, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", altIp.String(), primaryPort)) 85 | if err != nil { 86 | return &res, errors.New(fmt.Sprintf("resolve AP address failed:%s", err.Error())) 87 | } 88 | resp, localAddr, err = req.RequestTo(conn, remoteAP) 89 | if err != nil { 90 | return &res, errors.New(fmt.Sprintf("Failed to build STUN AP request:%s", err.Error())) 91 | } 92 | mappingAP := resp.Addr.String() 93 | if mappingPP == mappingAP { 94 | res.MappingType = NAT_TYPE_EIM 95 | } else { 96 | //testIII, send to alternativeIp:alternativePort 97 | req = stun.NewBindRequest(nil) 98 | remoteAA, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", altIp.String(), altPort)) 99 | if err != nil { 100 | return &res, errors.New(fmt.Sprintf("resolve AA address failed:%s", err.Error())) 101 | } 102 | resp, localAddr, err = req.RequestTo(conn, remoteAA) 103 | if err != nil { 104 | return &res, errors.New(fmt.Sprintf("Failed to build STUN AA request:%s", err.Error())) 105 | } 106 | mappingAA := resp.Addr.String() 107 | if mappingAP == mappingAA { 108 | res.MappingType = NAT_TYPE_ADM 109 | } else { 110 | res.MappingType = NAT_TYPE_APDM 111 | } 112 | } 113 | } else { 114 | res.MappingType = NAT_TEST_FAILED 115 | } 116 | 117 | if alternative != nil { 118 | //start NAT filter behavior test 119 | //test II 120 | req = stun.NewBindRequest(nil) 121 | req.SetChangeIP(true) 122 | req.SetChangePort(true) 123 | _, _, err = req.RequestTo(conn, res.Server) 124 | if err == nil { 125 | res.FilteringType = NAT_TYPE_EIF 126 | } else { 127 | //test III 128 | req = stun.NewBindRequest(nil) 129 | req.SetChangeIP(false) 130 | req.SetChangePort(true) 131 | req.ValidateSource(fmt.Sprintf("%s:%d", res.Server.IP.String(), alternative.Port)) 132 | resp, _, err = req.RequestTo(conn, res.Server) 133 | if err == nil { 134 | res.FilteringType = NAT_TYPE_ADF 135 | } else if resp != nil { 136 | res.FilteringType = NAT_TEST_FAILED 137 | } else { 138 | res.FilteringType = NAT_TYPE_APDF 139 | } 140 | } 141 | } else { 142 | res.FilteringType = NAT_TEST_FAILED 143 | } 144 | 145 | //hairpinning support test 146 | req = stun.NewBindRequest(nil) 147 | _, _, err = req.Request(res.Local.IP.String()+":0", mappingPP) 148 | if err == nil { 149 | res.Hairpinning = true 150 | } 151 | 152 | return &res, nil 153 | } 154 | 155 | func (d *NATBehaviorDiscovery) String() string { 156 | ret := fmt.Sprintf("localAddress:%s, mappingAddress:%s\n", d.LocalAddr, d.MappingAddr) 157 | switch d.MappingType { 158 | case NAT_TYPE_NONAT: 159 | ret += "NAT type: No NAT\n" 160 | case NAT_TYPE_EIM: 161 | ret += "NAT mapping type: Endpoint-Independent Mapping NAT\n" 162 | case NAT_TYPE_ADM: 163 | ret += "NAT mapping type: Address-Dependent Mapping NAT\n" 164 | case NAT_TYPE_APDM: 165 | ret += "NAT mapping type: Address and Port-Dependent Mapping NAT\n" 166 | case NAT_TEST_FAILED: 167 | ret += "NAT mapping type: test failed\n" 168 | } 169 | 170 | if d.MappingType != NAT_TYPE_NONAT { 171 | switch d.FilteringType { 172 | case NAT_TYPE_EIF: 173 | ret += "NAT filtering type: Endpoint-Independent Filtering NAT\n" 174 | case NAT_TYPE_ADF: 175 | ret += "NAT filtering type: Address-Dependent Filtering NAT\n" 176 | case NAT_TYPE_APDF: 177 | ret += "NAT filtering type: Address and Port-Dependent Filtering NAT\n" 178 | case NAT_TEST_FAILED: 179 | ret += "NAT filtering type: test failed\n" 180 | } 181 | } 182 | 183 | if d.Hairpinning { 184 | ret += "NAT Hairpinning Support: YES\n" 185 | } else { 186 | ret += "NAT Hairpinning Support:: NO\n" 187 | } 188 | 189 | return ret 190 | } 191 | -------------------------------------------------------------------------------- /server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "encoding/hex" 6 | "flag" 7 | "fmt" 8 | "github.com/bhpike65/go-stun/stun" 9 | "io" 10 | "log" 11 | "net" 12 | "os" 13 | "strings" 14 | ) 15 | 16 | const ( 17 | typePP = iota // primaryAddr:primaryPort 18 | typePA // primaryAddr:alterAddr 19 | typeAP // alterAddr:primaryPort 20 | typeAA // alterAddr:alterAddr 21 | typeMax 22 | ) 23 | 24 | var roleSet [typeMax]*net.UDPConn 25 | 26 | var logger *log.Logger 27 | 28 | // ./stunserver --primaryAddr 1.1.1.1 --alternativeAddr 2.2.2.2 --primaryPort 3478 --alternativePort 3479 29 | // ./stunserver --slaveserver 2.2.2.2:12345 --primaryAddr 1.1.1.1 --primaryPort 3478 --alternativePort 3479 30 | // ./stunserver --slave --slaveserver 2.2.2.2:12345 --primaryPort 3478 --alternativePort 3479 31 | 32 | var primaryAddr = flag.String("primary-addr", "", "STUN server primary address") 33 | var alterAddr = flag.String("alt-addr", "", "STUN server alternative address") 34 | var primaryPort = flag.Int("primary-port", 3478, "primary port") 35 | var alterPort = flag.Int("alt-port", 3479, "alternative port") 36 | var slaveServer = flag.String("slaveserver", "", "slave STUN server which has alternative Ip") 37 | 38 | var isSlave = flag.Bool("slave", false, "this is a slave stun server") 39 | var public = flag.Bool("public", true, "primaryAddr and alternativeAddr must be public ip address") 40 | 41 | var slaveChan chan *string 42 | 43 | var lanNets = []*net.IPNet{ 44 | {net.IPv4(10, 0, 0, 0), net.CIDRMask(8, 32)}, 45 | {net.IPv4(172, 16, 0, 0), net.CIDRMask(12, 32)}, 46 | {net.IPv4(192, 168, 0, 0), net.CIDRMask(16, 32)}, 47 | {net.ParseIP("fc00"), net.CIDRMask(7, 128)}, 48 | } 49 | 50 | func main() { 51 | flag.Parse() 52 | 53 | logFile, err := os.OpenFile("./slave.log", os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0666) 54 | if err != nil { 55 | fmt.Println("failed to create slave.log: ", err.Error()) 56 | os.Exit(-1) 57 | } 58 | logger = log.New(logFile, "", log.Llongfile|log.LstdFlags) 59 | 60 | if *primaryAddr == "" || *alterAddr == "" { 61 | addrs, err := net.InterfaceAddrs() 62 | if err != nil { 63 | logger.Fatal("get interface addrs error: ", err.Error()) 64 | } 65 | for _, a := range addrs { 66 | if ipnet, ok := a.(*net.IPNet); ok && !ipnet.IP.IsLoopback() { 67 | if *public { 68 | for _, lan := range lanNets { 69 | if ipnet.IP.To4() != nil && !lan.Contains(ipnet.IP) { 70 | if *primaryAddr == "" { 71 | *primaryAddr = ipnet.IP.String() 72 | } else if *alterAddr == "" && *primaryAddr != ipnet.IP.String() { 73 | *alterAddr = ipnet.IP.String() 74 | } else { 75 | break 76 | } 77 | } 78 | } 79 | } else { 80 | if ipnet.IP.To4() != nil { 81 | if *primaryAddr == "" { 82 | *primaryAddr = ipnet.IP.String() 83 | } else if *alterAddr == "" { 84 | *alterAddr = ipnet.IP.String() 85 | } else { 86 | break 87 | } 88 | } 89 | } 90 | } 91 | } 92 | } 93 | 94 | roleSet[typePP], err = net.ListenUDP("udp", &net.UDPAddr{IP: net.ParseIP(*primaryAddr), Port: *primaryPort}) 95 | if err != nil { 96 | logger.Fatal("listen on PP failed") 97 | } 98 | roleSet[typePA], err = net.ListenUDP("udp", &net.UDPAddr{IP: net.ParseIP(*primaryAddr), Port: *alterPort}) 99 | if err != nil { 100 | logger.Fatal("listen on PA failed") 101 | } 102 | 103 | if *isSlave && *alterAddr != "" { 104 | *alterAddr = "" 105 | } 106 | var aaAddr *net.UDPAddr 107 | if *alterAddr == "" { 108 | if *isSlave == false { 109 | if *slaveServer != "" { 110 | slaveAddr, err := net.ResolveTCPAddr("tcp", *slaveServer) 111 | if err != nil { 112 | logger.Fatal("slave server resolve failed") 113 | } 114 | slaveChan = make(chan *string, 128) 115 | go slaveClientWorker(slaveAddr) 116 | aaAddr = &net.UDPAddr{IP: slaveAddr.IP, Port: *alterPort} 117 | } 118 | } else if *slaveServer != "" { 119 | slaveAddr, err := net.ResolveTCPAddr("tcp", *slaveServer) 120 | if err != nil { 121 | logger.Fatal("slave server resolve failed") 122 | } 123 | go slaveWorker(slaveAddr) 124 | } 125 | } else { 126 | aaAddr, err = net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", *alterAddr, *alterPort)) 127 | if err != nil { 128 | logger.Fatalf("alterAddr %s:%d resolve failed", *alterAddr, alterPort) 129 | } 130 | roleSet[typeAP], err = net.ListenUDP("udp", &net.UDPAddr{IP: net.ParseIP(*alterAddr), Port: *primaryPort}) 131 | if err != nil { 132 | logger.Fatal("listen on PP failed") 133 | } 134 | roleSet[typeAA], err = net.ListenUDP("udp", aaAddr) 135 | if err != nil { 136 | logger.Fatal("listen on PA failed") 137 | } 138 | 139 | go startStunServer(typeAP, roleSet[typeAP], nil) 140 | go startStunServer(typeAA, roleSet[typeAA], nil) 141 | } 142 | 143 | go startStunServer(typePA, roleSet[typePA], nil) 144 | startStunServer(typePP, roleSet[typePP], aaAddr) 145 | } 146 | 147 | func startStunServer(role int, conn *net.UDPConn, other *net.UDPAddr) { 148 | buf := make([]byte, 1500) 149 | for { 150 | n, remote, err := conn.ReadFromUDP(buf) 151 | if err != nil { 152 | logger.Println("receive Error: ", err) 153 | } 154 | var req stun.StunMessageReq 155 | if err = req.Unmarshal(buf[:n]); err != nil { 156 | logger.Println("receive error req: ", err.Error()) 157 | continue 158 | } 159 | otherRole := role 160 | if req.ChangeIp { 161 | otherRole ^= 0x02 162 | } 163 | if req.ChangePort { 164 | otherRole ^= 0x01 165 | } 166 | if otherRole != role { 167 | if slaveChan != nil { 168 | info := fmt.Sprintf("%s|%x\n", remote.String(), req.TransacrtonId) 169 | go sendToSlave(&info) 170 | continue 171 | } else if *alterAddr != "" && roleSet[otherRole] != nil { 172 | if err = req.RespondTo(roleSet[otherRole], remote, nil); err != nil { 173 | logger.Printf("respond to %s failed %s", remote, err.Error()) 174 | } 175 | } else { 176 | //ignore 177 | continue 178 | } 179 | } else { 180 | if err = req.RespondTo(conn, remote, other); err != nil { 181 | logger.Printf("respond to %s failed %s", remote, err.Error()) 182 | } 183 | } 184 | } 185 | } 186 | 187 | func sendToSlave(info *string) { 188 | //ip:port|transactionId\n 189 | slaveChan <- info 190 | } 191 | 192 | func slaveClientWorker(slaveServer *net.TCPAddr) { 193 | 194 | for { 195 | conn, err := net.DialTCP("tcp", nil, slaveServer) 196 | if err != nil { 197 | logger.Fatal("Dial slave server failed:", err.Error()) 198 | } 199 | 200 | for { 201 | data := <-slaveChan 202 | conn.SetNoDelay(true) 203 | _, err = conn.Write([]byte(*data)) 204 | if err != nil { 205 | fmt.Println("Write to slave server failed:", err.Error()) 206 | conn.Close() 207 | break 208 | } 209 | } 210 | } 211 | } 212 | 213 | func slaveWorker(slaveServer *net.TCPAddr) { 214 | l, err := net.ListenTCP("tcp", slaveServer) 215 | if err != nil { 216 | logger.Fatal("slave tcp listen error: ", err.Error()) 217 | os.Exit(-1) 218 | } 219 | defer l.Close() 220 | 221 | for { 222 | conn, err := l.AcceptTCP() 223 | if err != nil { 224 | logger.Fatal("slave accept error:", err.Error()) 225 | continue 226 | } 227 | go slaveProcessRequest(conn) 228 | } 229 | } 230 | 231 | func slaveProcessRequest(conn net.Conn) { 232 | defer conn.Close() 233 | 234 | reader := bufio.NewReaderSize(conn, 128) 235 | for { 236 | data, err := reader.ReadString('\n') 237 | if err == io.EOF { 238 | break 239 | } 240 | if err != nil { 241 | logger.Println("read from tcp socket failed:", err.Error()) 242 | break 243 | } 244 | data = strings.TrimRight(data, "\n") 245 | logger.Println("slave get: ", data) 246 | infos := strings.Split(data, "|") 247 | if len(infos) != 2 { 248 | logger.Print("receive error slave data: ", data) 249 | continue 250 | } 251 | addr := infos[0] 252 | tid, err := hex.DecodeString(infos[1]) 253 | if err != nil || len(tid) != 12 { 254 | logger.Print("receive error slave data: ", data) 255 | continue 256 | } 257 | remote, err := net.ResolveUDPAddr("udp", addr) 258 | if err != nil { 259 | logger.Print("receive error slave data: ", data) 260 | continue 261 | } 262 | req := stun.NewBindRequest(tid) 263 | req.RespondTo(roleSet[typePP], remote, nil) 264 | } 265 | } 266 | -------------------------------------------------------------------------------- /stun/stun.go: -------------------------------------------------------------------------------- 1 | package stun 2 | 3 | import ( 4 | "bytes" 5 | "crypto/rand" 6 | "encoding/binary" 7 | "errors" 8 | "fmt" 9 | "golang.org/x/net/ipv4" 10 | "io" 11 | "net" 12 | "time" 13 | ) 14 | 15 | /* 16 | 0 1 2 3 17 | 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 18 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 19 | |0 0| STUN Message Type | Message Length | 20 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 21 | | Magic Cookie | 22 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 23 | | | 24 | | Transaction ID (96 bits) | 25 | | | 26 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 27 | 28 | 29 | 0 1 30 | 2 3 4 5 6 7 8 9 0 1 2 3 4 5 31 | 32 | +--+--+-+-+-+-+-+-+-+-+-+-+-+-+ 33 | |M |M |M|M|M|C|M|M|M|C|M|M|M|M| 34 | |11|10|9|8|7|1|6|5|4|0|3|2|1|0| 35 | +--+--+-+-+-+-+-+-+-+-+-+-+-+-+ 36 | 37 | Figure 3: Format of STUN Message Type Field 38 | */ 39 | 40 | type header struct { 41 | Type uint16 42 | Length uint16 43 | Magic uint32 44 | TransacrtonId [12]byte 45 | } 46 | 47 | type StunMessageReq struct { 48 | header 49 | 50 | ChangeIp bool 51 | ChangePort bool 52 | RespSource string 53 | //Candidate interface{} 54 | } 55 | 56 | type StunMessageResp struct { 57 | header 58 | Addr *net.UDPAddr 59 | OtherAddr *net.UDPAddr 60 | ErrorCode uint16 61 | ErrorMsg string 62 | } 63 | 64 | type attrHeader struct { 65 | Type uint16 66 | Length uint16 67 | } 68 | 69 | const ( 70 | // Comprehension required 71 | attrAddress = 0x01 72 | attrChangeRequest = 0x03 73 | attrUsername = 0x06 74 | attrIntegrity = 0x08 75 | attrErrCode = 0x09 76 | attrUnknownAttrs = 0x0A 77 | attrRealm = 0x14 78 | attrNonce = 0x15 79 | attrXorAddress = 0x20 80 | attrUseCandidate = 0x25 81 | attrPadding = 0x26 82 | attrResponsePort = 0x27 83 | 84 | // Comprehension optional 85 | attrSoftware = 0x8022 86 | //attrAlternate = 0x8023 87 | attrFingerprint = 0x8028 88 | attrOtherAddress = 0x802c 89 | ) 90 | 91 | const ( 92 | errTryAlternate = 300 93 | errBadRequest = 400 94 | errUnauthorized = 401 95 | errUnknownAttribute = 420 96 | errStaleNonce = 438 97 | errServerInternal = 500 98 | ) 99 | const ( 100 | classRequest = iota 101 | classIndication 102 | classResonseSuccess 103 | classError 104 | methodBinding = 1 105 | ) 106 | 107 | const ( 108 | attrAddressFieldIpv4 = 1 109 | attrAddressFieldIpv6 = 2 110 | attrAddressSizeIpv4 = 8 111 | attrAddressSizeIpv6 = 20 112 | ) 113 | 114 | const ( 115 | magic = 0x2112a442 116 | 117 | headerLen = 20 118 | ) 119 | 120 | var ( 121 | magicBytes = []byte{0x21, 0x12, 0xa4, 0x42} 122 | ) 123 | 124 | func changeReqestValue(changeIp, changePort bool) uint32 { 125 | var v uint32 126 | if changeIp { 127 | v |= 0x04 128 | } 129 | if changePort { 130 | v |= 0x02 131 | } 132 | return v 133 | } 134 | 135 | func (req *StunMessageReq) Marshal() []byte { 136 | var buf bytes.Buffer 137 | binary.Write(&buf, binary.BigEndian, req.header) 138 | binary.Write(&buf, binary.BigEndian, []interface{}{ 139 | uint16(attrChangeRequest), 140 | uint16(4), 141 | changeReqestValue(req.ChangeIp, req.ChangePort), 142 | }) 143 | 144 | return buf.Bytes() 145 | } 146 | 147 | func (req *StunMessageReq) Unmarshal(data []byte) error { 148 | if err := binary.Read(bytes.NewBuffer(data[:headerLen]), binary.BigEndian, &req.header); err != nil { 149 | return err 150 | } 151 | 152 | if !typeIsRequest(req.Type) || methodFromMsgType(req.Type) != methodBinding || 153 | req.Magic != magic || 154 | int(req.Length+20) != len(data) { 155 | return errors.New("stun binding get an error format reply") 156 | } 157 | 158 | attrReader := bytes.NewBuffer(data[headerLen:]) 159 | for { 160 | if attrReader.Len() == 0 { 161 | break 162 | } 163 | var ahdr attrHeader 164 | if err := binary.Read(attrReader, binary.BigEndian, &ahdr); err != nil { 165 | return err 166 | } 167 | 168 | value := attrReader.Next(int(ahdr.Length)) 169 | if ahdr.Length%4 != 0 { 170 | attrReader.Next(int(4 - ahdr.Length%4)) 171 | } 172 | 173 | switch ahdr.Type { 174 | case attrChangeRequest: 175 | req.ChangeIp = (binary.BigEndian.Uint32(value) & 0x04) != 0 176 | req.ChangePort = (binary.BigEndian.Uint32(value) & 0x02) != 0 177 | } 178 | } 179 | return nil 180 | } 181 | 182 | func (resp *StunMessageResp) Marshal() []byte { 183 | var buf bytes.Buffer 184 | 185 | binary.Write(&buf, binary.BigEndian, resp.header) 186 | if resp.Addr.IP.To4() != nil { 187 | binary.Write(&buf, binary.BigEndian, []interface{}{ 188 | uint16(attrAddress), 189 | uint16(attrAddressSizeIpv4), 190 | uint8(0), 191 | uint8(attrAddressFieldIpv4), 192 | uint16(resp.Addr.Port), 193 | resp.Addr.IP.To4(), 194 | }) 195 | 196 | binary.Write(&buf, binary.BigEndian, []interface{}{ 197 | uint16(attrXorAddress), 198 | uint16(attrAddressSizeIpv4), 199 | uint8(0), 200 | uint8(attrAddressFieldIpv4), 201 | uint16(resp.Addr.Port ^ magic>>16), 202 | }) 203 | for i, field := range resp.Addr.IP.To4() { 204 | binary.Write(&buf, binary.BigEndian, uint8(field^magicBytes[i])) 205 | } 206 | } else { 207 | binary.Write(&buf, binary.BigEndian, []interface{}{ 208 | uint16(attrAddress), 209 | uint16(attrAddressSizeIpv6), 210 | uint8(0), 211 | uint8(attrAddressFieldIpv6), 212 | uint16(resp.Addr.Port), 213 | resp.Addr.IP.To16(), 214 | }) 215 | binary.Write(&buf, binary.BigEndian, []interface{}{ 216 | uint16(attrXorAddress), 217 | uint16(attrAddressSizeIpv6), 218 | uint8(0), 219 | uint8(attrAddressFieldIpv6), 220 | uint16(resp.Addr.Port ^ magic>>16), 221 | }) 222 | for i, field := range resp.Addr.IP.To16() { 223 | if i < 4 { 224 | binary.Write(&buf, binary.BigEndian, uint8(field^magicBytes[i])) 225 | } else { 226 | binary.Write(&buf, binary.BigEndian, uint8(field^resp.TransacrtonId[i])) 227 | } 228 | } 229 | } 230 | 231 | if resp.OtherAddr != nil { 232 | if resp.OtherAddr.IP.To4() != nil { 233 | binary.Write(&buf, binary.BigEndian, []interface{}{ 234 | uint16(attrOtherAddress), 235 | uint16(attrAddressSizeIpv4), 236 | uint8(0), 237 | uint8(attrAddressFieldIpv4), 238 | uint16(resp.OtherAddr.Port), 239 | resp.OtherAddr.IP.To4(), 240 | }) 241 | } else { 242 | binary.Write(&buf, binary.BigEndian, []interface{}{ 243 | uint16(attrOtherAddress), 244 | uint16(attrAddressSizeIpv6), 245 | uint8(0), 246 | uint8(attrAddressFieldIpv6), 247 | uint16(resp.OtherAddr.Port), 248 | resp.OtherAddr.IP.To16(), 249 | }) 250 | } 251 | } 252 | 253 | resp.Length = uint16(len(buf.Bytes())) - 20 254 | buf.Bytes()[2] = byte(resp.Length >> 8) 255 | buf.Bytes()[3] = byte(resp.Length) 256 | 257 | return buf.Bytes() 258 | } 259 | 260 | func (resp *StunMessageResp) Unmarshal(data []byte) error { 261 | if err := binary.Read(bytes.NewBuffer(data[:headerLen]), binary.BigEndian, &resp.header); err != nil { 262 | return err 263 | } 264 | 265 | if !typeIsSuccessResp(resp.Type) || int(resp.Length+20) != len(data) || resp.Magic != magic { 266 | fmt.Println(resp.header) 267 | fmt.Println(typeIsSuccessResp(resp.Type), resp.Length, len(data)) 268 | return errors.New("stun binding get an error format reply") 269 | } 270 | 271 | attrReader := bytes.NewBuffer(data[headerLen:]) 272 | var haveXor bool 273 | for { 274 | if attrReader.Len() == 0 { 275 | break 276 | } 277 | 278 | var ahdr attrHeader 279 | if err := binary.Read(attrReader, binary.BigEndian, &ahdr); err != nil { 280 | return err 281 | } 282 | 283 | value := attrReader.Next(int(ahdr.Length)) 284 | if ahdr.Length%4 != 0 { 285 | attrReader.Next(int(4 - ahdr.Length%4)) 286 | } 287 | 288 | switch ahdr.Type { 289 | case attrAddress: 290 | if !haveXor { 291 | ip, port, err := parseAddress(value) 292 | if err != nil { 293 | return err 294 | } 295 | resp.Addr = &net.UDPAddr{IP: ip, Port: port, Zone: ""} 296 | } 297 | case attrXorAddress: 298 | ip, port, err := parseAddress(value) 299 | if err != nil { 300 | return err 301 | } 302 | for i := range ip { 303 | ip[i] ^= data[4+i] 304 | } 305 | port ^= int(binary.BigEndian.Uint16(data[4:])) 306 | resp.Addr = &net.UDPAddr{IP: ip, Port: port, Zone: ""} 307 | haveXor = true 308 | case attrErrCode: 309 | resp.ErrorCode = uint16(value[2])*100 + uint16(value[3]) 310 | resp.ErrorMsg = string(value[4:]) 311 | case attrOtherAddress: 312 | ip, port, err := parseAddress(value) 313 | if err != nil { 314 | return err 315 | } 316 | resp.OtherAddr = &net.UDPAddr{IP: ip, Port: port, Zone: ""} 317 | default: 318 | } 319 | } 320 | return nil 321 | } 322 | 323 | func parseAddress(raw []byte) (net.IP, int, error) { 324 | if len(raw) != 8 && len(raw) != 20 { 325 | return nil, 0, errors.New("address parse error") 326 | } 327 | var family int 328 | switch int(raw[1]) { 329 | case 1: 330 | family = 4 331 | case 2: 332 | family = 16 333 | default: 334 | return nil, 0, errors.New("address parse error") 335 | } 336 | port := binary.BigEndian.Uint16(raw[2:]) 337 | ip := make([]byte, len(raw[4:])) 338 | copy(ip, raw[4:]) 339 | if len(ip) != family { 340 | return nil, 0, errors.New("address parse error") 341 | } 342 | return net.IP(ip), int(port), nil 343 | } 344 | 345 | func getMsgType(class uint8, method uint16) uint16 { 346 | return (method&0x0f80)<<2 | (method&0x0070)<<1 | (method & 0x0f) | (uint16(class)&0x02)<<7 | (uint16(class)&0x01)<<4 347 | } 348 | 349 | func typeIsRequest(t uint16) bool { 350 | return (t & 0x0110) == 0x0000 351 | } 352 | 353 | func typeIsSuccessResp(t uint16) bool { 354 | return (t & 0x0110) == 0x0100 355 | } 356 | func typeIsErrorResp(t uint16) bool { 357 | return (t & 0x0110) == 0x0110 358 | } 359 | 360 | func methodFromMsgType(t uint16) uint16 { 361 | return (t & 0x000f) | ((t & 0x00e0) >> 1) | ((t & 0x3E00) >> 2) 362 | } 363 | 364 | func NewBindRequest(tid []byte) *StunMessageReq { 365 | var req StunMessageReq 366 | 367 | if tid == nil { 368 | tid = make([]byte, 12) 369 | _, err := io.ReadFull(rand.Reader, tid) 370 | if err != nil { 371 | return nil 372 | } 373 | } 374 | copy(req.TransacrtonId[:], tid) 375 | 376 | req.Type = getMsgType(classRequest, methodBinding) 377 | req.Length = 0 378 | req.Magic = magic 379 | 380 | return &req 381 | } 382 | 383 | func (req *StunMessageReq) SetChangeIP(on bool) { 384 | req.ChangeIp = on 385 | } 386 | func (req *StunMessageReq) SetChangePort(on bool) { 387 | req.ChangePort = on 388 | } 389 | 390 | func (req *StunMessageReq) ValidateSource(souce string) { 391 | req.RespSource = souce 392 | } 393 | 394 | func (req *StunMessageReq) RequestTo(conn *net.UDPConn, to *net.UDPAddr) (*StunMessageResp, *net.UDPAddr, error) { 395 | pkConn := ipv4.NewPacketConn(conn) 396 | pkConn.SetControlMessage(ipv4.FlagDst, true) 397 | 398 | if err := pkConn.SetDeadline(time.Now().Add(5 * time.Second)); err != nil { 399 | fmt.Println("Couldn't set the socket timeout:", err) 400 | } 401 | 402 | loc, _ := net.ResolveUDPAddr("udp", conn.LocalAddr().String()) 403 | 404 | buf := make([]byte, 1500) 405 | for retry := 0; retry < 3; retry++ { 406 | _, err := pkConn.WriteTo(req.Marshal(), nil, to) 407 | if err != nil { 408 | return nil, nil, err 409 | } 410 | 411 | n, cm, src, err := pkConn.ReadFrom(buf) 412 | if err != nil { 413 | if err, ok := err.(net.Error); ok && err.Timeout() { 414 | 415 | } 416 | return nil, nil, err 417 | } 418 | loc.IP = cm.Dst 419 | 420 | var resp StunMessageResp 421 | if err = resp.Unmarshal(buf[:n]); err != nil { 422 | return nil, loc, err 423 | } 424 | if req.RespSource != "" && src.String() != req.RespSource { 425 | return &resp, nil, errors.New("receive packet from unexpected source") 426 | } 427 | if resp.ErrorCode != 0 { 428 | return &resp, loc, errors.New(resp.ErrorMsg) 429 | } 430 | if req.TransacrtonId != resp.TransacrtonId || 431 | getMsgType(classResonseSuccess, methodBinding) != resp.Type || 432 | resp.Addr == nil { 433 | return &resp, loc, errors.New("receive error response") 434 | } 435 | return &resp, loc, nil 436 | } 437 | 438 | return nil, nil, errors.New("request retry exceeds max times") 439 | } 440 | 441 | func (req *StunMessageReq) Request(localAddr, remoteAddr string) (*StunMessageResp, *net.UDPAddr, error) { 442 | remote, err := net.ResolveUDPAddr("udp", remoteAddr) 443 | if err != nil { 444 | return nil, nil, err 445 | } 446 | local, err := net.ResolveUDPAddr("udp", localAddr) 447 | if err != nil { 448 | return nil, nil, err 449 | } 450 | 451 | sock, err := net.ListenUDP("udp", local) 452 | if err != nil { 453 | return nil, nil, err 454 | } 455 | defer sock.Close() 456 | return req.RequestTo(sock, remote) 457 | } 458 | 459 | func (req *StunMessageReq) RespondTo(conn *net.UDPConn, to *net.UDPAddr, other *net.UDPAddr) error { 460 | var resp StunMessageResp 461 | 462 | resp.TransacrtonId = req.TransacrtonId 463 | resp.Type = getMsgType(classResonseSuccess, methodBinding) 464 | resp.Length = 0 465 | resp.Magic = magic 466 | resp.Addr = to 467 | resp.OtherAddr = other 468 | 469 | _, err := conn.WriteTo(resp.Marshal(), to) 470 | return err 471 | } 472 | --------------------------------------------------------------------------------