예제 #1
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
}
예제 #2
0
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."))
	}
}
예제 #3
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
}