// Perform the discovery process. // See RFC 3489, section "Discovery Process". // // OUTPUT // - The type of NAT we are behind from. // - The error flag. func ClientDiscover() (int, error) { var err error var changer_transport string var test1_response, test2_response, test3_response testResponse // RFC 3489: The client begins by initiating test I. If this test yields no // response, the client knows right away that it is not capable of UDP // connectivity. If the test produces a response, the client examines // the MAPPED-ADDRESS attribute. If this address and port are the same // as the local IP address and port of the socket used to send the // request, the client knows that it is not natted. It executes test II. /// ---------- /// TEST I (a) /// ---------- test1_response, err = ClientTest1(nil) if nil != err { return STUN_NAT_ERROR, err } if !test1_response.request.response { if verbosity > 0 { tools.AddText(output, fmt.Sprintf("% -25s%s", "Result:", "Got no response for test I.")) tools.AddText(output, fmt.Sprintf("% -25s%s", "Conclusion:", "UDP is blocked.")) } return STUN_NAT_BLOCKED, err } // Save "changed transport address" for later test. // Please note that some servers don't set this attribute. if test1_response.extra.(test1Info).changed_address_found { if verbosity > 0 { tools.AddText(output, fmt.Sprintf("% -25s: %s", "Change IP", test1_response.extra.(test1Info).changed_ip)) tools.AddText(output, fmt.Sprintf("% -25s: %d", "Change port", int(test1_response.extra.(test1Info).changed_port))) } changer_transport, err = tools.MakeTransportAddress(test1_response.extra.(test1Info).changed_ip, int(test1_response.extra.(test1Info).changed_port)) if nil != err { return STUN_NAT_ERROR, err } } else { if verbosity > 0 { tools.AddText(output, fmt.Sprintf("% -25s: %s", "Result", "The response does not contain any \"changed\" address.")) tools.AddText(output, fmt.Sprintf("% -25s: %s", "Conclusion", "The only thing we can say is that we are behind a NAT.\n")) } return STUN_NAT_UNKNOWN, nil } if !test1_response.extra.(test1Info).identical { // Test I (a): The local transport address is different than the mapped transport address. // RFC 3489: In the event that the IP address and port of the socket did not match // the MAPPED-ADDRESS attribute in the response to test I, the client // knows that it is behind a NAT. It performs test II. /// ----------- /// TEST II (a) /// ----------- if verbosity > 0 { tools.AddText(output, fmt.Sprintf("% -25s: %s", "Result", "Got a response for test I. Test I is not OK.")) tools.AddText(output, fmt.Sprintf("% -25s: %s", "Conclusion", "We are behind a NAT.\n")) } test2_response, err = CientTest2() if nil != err { return STUN_NAT_ERROR, err } if !test2_response.request.response { // Test II (a): We did not receive any valid response from the server. // RFC 3489: If no response is received, it performs test I again, but this time, // does so to the address and port from the CHANGED-ADDRESS attribute // from the response to test I. if verbosity > 0 { tools.AddText(output, fmt.Sprintf("% -25s: %s", "Result", "Got no response for test II. Test II is not OK.")) tools.AddText(output, fmt.Sprintf("% -25s: %s \"%s\"\n", "Conclusion", "Perform Test I again. This time, server's transport address is", changer_transport)) } /// ---------- /// TEST I (b) /// ---------- test1_response, err = ClientTest1(&changer_transport) if nil != err { return STUN_NAT_ERROR, err } if !test1_response.request.response { // No response from the server. This should not happend. if verbosity > 0 { tools.AddText(output, fmt.Sprintf("% -25s: %s", "Result", "Got no response for test I. This is unexpected!")) tools.AddText(output, fmt.Sprintf("% -25s: %s", "Conclusion", "The only thing we can say is that we are behind a NAT.\n")) } return STUN_NAT_UNKNOWN, nil } if !test1_response.extra.(test1Info).identical { // Test I (b) if verbosity > 0 { tools.AddText(output, fmt.Sprintf("% -25s: %s", "Result", "Got a response for test I. Test I is not OK.")) tools.AddText(output, fmt.Sprintf("% -25s: %s", "Conclusion", "We are behind a symetric NAT.\n")) } return STUN_NAT_SYMETRIC, nil } else { // Test I (b) if verbosity > 0 { tools.AddText(output, fmt.Sprintf("% -25s: %s", "Result", "Got a response for test I. Test I is OK.\n")) tools.AddText(output, fmt.Sprintf("% -25s: %s", "Conclusion", "Perform Test III.\n")) } /// -------- /// TEST III /// -------- test3_response, err = CientTest3() if nil != err { return STUN_NAT_ERROR, err } if !test3_response.request.response { if verbosity > 0 { tools.AddText(output, fmt.Sprintf("% -25s: %s", "Result", "Got no response for test III.")) tools.AddText(output, fmt.Sprintf("% -25s: %s", "Conclusion", "We are behind a \"port sestricted\" NAT.\n")) } return STUN_NAT_PORT_RESTRICTED, nil } else { if verbosity > 0 { tools.AddText(output, fmt.Sprintf("% -25s: %s", "Result", "Got a response for test III.")) tools.AddText(output, fmt.Sprintf("% -25s: %s", "Conclusion", "We are behind a \"restricted\" NAT.\n")) } return STUN_NAT_RESTRICTED, nil } // End of branch. } } else { // TEST II (a) : We received a valid response from the server. // RFC 3489: If a response is received, the client knows that it is behind a \"full-cone\" NAT. if verbosity > 0 { tools.AddText(output, fmt.Sprintf("% -25s: %s", "Result", "Test II is OK.")) tools.AddText(output, fmt.Sprintf("% -25s: %s", "Conclusion", "We are behind a \"full cone\" NAT.\n")) } return STUN_NAT_FULL_CONE, nil } return 0, nil } else { // Test I (a): The local transport address is identical to the mapped transport address. // RFC 3489: If this address and port are the same // as the local IP address and port of the socket used to send the // request, the client knows that it is not natted. It executes test II. if verbosity > 0 { tools.AddText(output, fmt.Sprintf("% -25s: %s", "Result", "Got a response for test 1. Test I is OK. Addresses are the same.\n")) tools.AddText(output, fmt.Sprintf("% -25s: %s", "Conclusion", "We are *not* behind a NAT.")) } /// ----------- /// TEST II (b) /// ----------- // If a response is received, the client knows that it has open access // to the Internet (or, at least, its behind a firewall that behaves // like a full-cone NAT, but without the translation). If no response // is received, the client knows its behind a symmetric UDP firewall. test2_response, err = CientTest2() if nil != err { return STUN_NAT_ERROR, err } if test2_response.request.response { // if verbosity > 0 { tools.AddText(output, fmt.Sprintf("% -25s: %s", "Result", "Got a response for test II.\n")) tools.AddText(output, fmt.Sprintf("% -25s: %s", "Conclusion", "We are *not* behind a NAT.")) } return STUN_NAT_NO_NAT, nil } if verbosity > 0 { tools.AddText(output, fmt.Sprintf("% -25s: %s", "Result", "Got no response for test II.\n")) tools.AddText(output, fmt.Sprintf("% -25s: %s", "Conclusion", "We are behind a symmetric UDP firewall.")) } return STUN_NAT_SYMETRIC_UDP_FIREWALL, nil } return 2, nil }
func main() { var err error var serverHost *string = flag.String("host", "", "Host name for the STUN server.") var serverPort *int = flag.Int("port", 3478, "Pot number for the host server.") var verbosityLevel *int = flag.Int("verbose", 0, "Verbosity level.") var ips []string var ip string var nat int // Parse the command line. flag.Parse() if "" == *serverHost { fmt.Println("ERROR: You must specify the host name of the STUN server (option -host).") os.Exit(1) } // Lookup the host name. ips, err = net.LookupHost(*serverHost) if nil != err { fmt.Println(fmt.Sprintf("ERROR: %s", err)) os.Exit(1) } if 0 == len(ips) { fmt.Println(fmt.Sprintf("ERROR: Can not lookup host \"%s\".", *serverHost)) os.Exit(1) } fmt.Println(fmt.Sprintf("% -15s: %s", "Host", *serverHost)) fmt.Println(fmt.Sprintf("% -15s: %d", "Port", *serverPort)) fmt.Println(fmt.Sprintf("% -15s: IP%d = %s", "IPs", 0, ips[0])) for i := 1; i < len(ips); i++ { fmt.Println(fmt.Sprintf("% -15s: IP%d = %s", " ", i, ips[i])) } fmt.Println("\n\n") if len(ips) > 1 { fmt.Println(fmt.Sprintf("The given host name is associated to %d IP addresses.", len(ips))) fmt.Println(fmt.Sprintf("Which one should I use?")) for { var response string var idx int fmt.Println(fmt.Sprintf("\nPlease, enter an integer between 0 (for IP0) and %d (for IP%d).", len(ips)-1, len(ips))) fmt.Scanln(&response) idx, err = strconv.Atoi(response) if nil != err { fmt.Println(fmt.Sprintf("The given value (%s) is not valid.", response)) continue } if (idx < 0) || (idx >= len(ips)) { fmt.Println(fmt.Sprintf("The given value (%d) is not valid.", idx)) continue } ip, err = tools.MakeTransportAddress(ips[idx], *serverPort) _ = err break } } else { ip, err = tools.MakeTransportAddress(ips[0], *serverPort) _ = err } fmt.Println(fmt.Sprintf("\nUsing transport address \"%s\".\n", ip)) // Perform discovery. stun.ClientInit(ip) stun.ActivateOutput(*verbosityLevel, nil) nat, err = stun.ClientDiscover() if nil != err { fmt.Println(fmt.Sprintf("An error occured: %s", err)) os.Exit(1) } // Print result. fmt.Println("\n\nCONCLUSION\n") switch nat { case stun.STUN_NAT_ERROR: fmt.Println(fmt.Sprintf("Test failed: %s", err)) case stun.STUN_NAT_BLOCKED: fmt.Println(fmt.Sprintf("UDP is blocked.")) case stun.STUN_NAT_UNKNOWN: fmt.Println(fmt.Sprintf("Unexpected response from the STUN server. All we can say is that we are behind a NAT.")) case stun.STUN_NAT_FULL_CONE: fmt.Println(fmt.Sprintf("We are behind a full cone NAT.")) case stun.STUN_NAT_SYMETRIC: fmt.Println(fmt.Sprintf("We are behind a symetric NAT.")) case stun.STUN_NAT_RESTRICTED: fmt.Println(fmt.Sprintf("We are behind a restricted NAT.")) case stun.STUN_NAT_PORT_RESTRICTED: fmt.Println(fmt.Sprintf("We are behind a port restricted NAT.")) case stun.STUN_NAT_NO_NAT: fmt.Println(fmt.Sprintf("We are not behind a NAT.")) case stun.STUN_NAT_SYMETRIC_UDP_FIREWALL: fmt.Println(fmt.Sprintf("We are behind a symetric UDP firewall.")) } }
// Perform Test I. // RFC 3489: In test I, the client sends a // STUN Binding Request to a server, without any flags set in the // CHANGE-REQUEST attribute, and without the RESPONSE-ADDRESS attribute. // This causes the server to send the response back to the address and // port that the request came from. // // INPUT // - in_destination_address: this string represents the transport address of the request's destination. // This value should be written: "IP:Port" (IPV4) or "[IP]:Port" (IPV6). // If the value of this parameter is nil, then the default server's transport address will be used. // Note: The default server's transport address is the one defined through the call to the function "ClientInit()". // // OUTPUT // - The response. // - The error flag. func ClientTest1(in_destination_address *string) (testResponse, error) { var err, err_mapped error var ip_mapped, ip_xored_mapped string var family_mapped, family_xored_mapped, port_mapped, port_xored_mapped uint16 var response testResponse var info test1Info var found bool if verbosity > 0 { tools.AddText(output, fmt.Sprintf("%s", "Test I\n")) } response.request, err = ClientSendBinding(in_destination_address) if nil != err { return response, err } if !response.request.response { return response, nil } // Extracts the mapped address and the XORED mapped address. // Note: Some STUN servers don't set the XORED mapped address (RFC 3489 does not define XORED mapped IP address). // Therefore, we consider that no XORED mapped address is not an error. _ = family_mapped // Really not used found, family_mapped, ip_mapped, port_mapped, err = response.request.packet.GetMappedAddress() if (nil != err) || (!found) { return response, err } found, family_xored_mapped, ip_xored_mapped, port_xored_mapped, err = response.request.packet.GetXorMappedAddress() if verbosity > 0 { tools.AddText(output, fmt.Sprintf("% -25s: %s:%d", "Mapped address", ip_mapped, port_mapped)) if nil == err_mapped && found { if verbosity > 0 { tools.AddText(output, fmt.Sprintf("% -25s: %s:%d", "Xored mapped address", ip_xored_mapped, port_xored_mapped)) } } else { if verbosity > 0 { tools.AddText(output, fmt.Sprintf("% -25s: %s", "Xored mapped address", "No xored mapped address given")) } } } if (ip_mapped != ip_xored_mapped) && (found) { ip_mapped = ip_xored_mapped port_mapped = port_xored_mapped family_mapped = family_xored_mapped } ip_mapped, err = tools.MakeTransportAddress(ip_mapped, int(port_mapped)) if nil != err { return response, err } // Extracts the transport address "CHANGED-ADDRESS". // Some servers don't set the attribute "CHANGED-ADDRESS". // So we consider that the lake of this attribute is not an error. info.changed_address_found, info.changed_address_family, info.changed_ip, info.changed_port, err = response.request.packet.GetChangedAddress() if nil != err { return response, err } // Compare local IP with mapped IP. if verbosity > 0 { tools.AddText(output, fmt.Sprintf("% -25s: %s", "Local address", response.request.transport_local)) } info.identical = response.request.transport_local == ip_mapped response.extra = info return response, nil }