Example #1
0
// 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
}
Example #2
0
// 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
}
Example #3
0
// 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
}
Example #4
0
// 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
}