// Perform Test III. // RFC 3489: In test III, the client sends a Binding Request with only the "change port" flag set. // // OUTPUT // - A boolean that indicates wether the client received a response or not. // - The error flag. func CientTest3() (testResponse, error) { var err error var r requestResponse var response testResponse var info test2Info if verbosity > 0 { tools.AddText(output, fmt.Sprintf("%s", "Test III.\n")) } r, err = ClientSendChangeRequest(false) response.request = r response.extra = info if nil != err { return response, err } return response, nil }
// This function sends a given request and returns the received packet. // // INPUT // - in_connexion: connexion to use. // - in_request: the request to send. // // OUTPUT // - The receive STUN packet. // - A flag that indicates whether the client received a response or not. // + true: the client received a response. // + false: the client did not receive any response // - The error flag. func SendRequest(in_connexion net.Conn, in_request StunPacket) (StunPacket, bool, error) { var rcv_packet StunPacket var request_timeout int = 100 var retries_count int = 0 sent := false for { var err error var count int var b []byte = make([]byte, 1000, 1000) // Dump the packet. if (verbosity > 0) && !sent { tools.AddText(output, fmt.Sprintf("Sending REQUEST to \"%s\"\n\n%s\n", in_connexion.RemoteAddr(), Bytes2String(in_request.ToBytes(), 4))) tools.AddText(output, fmt.Sprintf("%s\n", in_request.String(4))) sent = true } // Send the packet. count, err = in_connexion.Write(in_request.ToBytes()) if err != nil { return rcv_packet, false, errors.New(fmt.Sprintf("Can not send STUN UDP packet to server: %s", err)) } if len(in_request.ToBytes()) != count { return rcv_packet, false, errors.New(fmt.Sprintf("Can not send STUN UDP packet to server: The number of bytes sent is not valid.")) } // RFC 3489: Wait for a response. // 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. in_connexion.SetReadDeadline(time.Now().Add(time.Duration(request_timeout) * time.Millisecond)) if request_timeout < 1600 { request_timeout *= 2 } else { retries_count++ } // Wait for a response. count, err = in_connexion.Read(b) if err != nil { if err.(net.Error).Timeout() { // See http://golang.org/src/pkg/net/timeout_test.go?h=Timeout%28%29 if retries_count >= 9 { break } if verbosity > 0 { tools.AddText(output, fmt.Sprintf("%sTimeout (%04d ms) exceeded, retry...", strings.Repeat(" ", 4), request_timeout)) } continue } return rcv_packet, false, errors.New(fmt.Sprintf("Error while reading packet: %s", err)) } // For nice output. if (verbosity > 0) && (retries_count > 0) { tools.AddText(output, "\n") } // Build the packet from the list of bytes. rcv_packet, err = FromBytes(b[0:count]) if nil != err { // The packet is not valid. if verbosity > 0 { tools.AddText(output, fmt.Sprintf("%sThe received packet is not valid. Continue.", strings.Repeat(" ", 4))) } continue } if verbosity > 0 { tools.AddText(output, fmt.Sprintf("Received\n\n%s\n", Bytes2String(rcv_packet.ToBytes(), 4))) tools.AddText(output, fmt.Sprintf("%s\n", rcv_packet.String(4))) } // OK, a valid response has been received. return rcv_packet, true, nil } // No valid packet has been received. if verbosity > 0 { tools.AddText(output, fmt.Sprintf("")) } return rcv_packet, false, nil }
// 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 }
// 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 }