Skip to content

Latest commit

 

History

143 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

go-stun

Go Reference (latest release) Tests License

go-stun is a Go library and command-line client for STUN over UDP and TCP. It can discover the public IP address and port assigned to a socket and, for UDP when the server supports the required probes, classify the client's NAT behavior.

STUN is one building block for NAT traversal. This project does not implement a complete UDP hole-punching, ICE, or TURN solution.

Features

  • Discover a socket's public (server-reflexive) IP address and port.
  • Perform basic STUN Binding transactions over UDP or TCP.
  • Send RFC 5389 Binding requests with SOFTWARE and FINGERPRINT attributes.
  • Perform classic NAT type discovery based on RFC 3489.
  • Test NAT mapping and filtering behavior as described by RFC 5780.
  • Report the external address and observed port preservation in behavior mode.
  • Select the STUN server and local IP address or port.
  • Reuse an existing net.PacketConn from library code.
  • Communicate with RFC 3489-only servers through an explicit compatibility mode.

Install the command

This README documents the current master branch. With Go 1.16 or newer, install that branch explicitly until the next release is tagged:

go install github.com/ccding/go-stun@master

The newest tag, v0.1.5, predates RFC 3489 compatibility mode, the -legacy flag, and ErrBehaviorDiscoveryUnsupported. Use @latest only if you need the older released interface.

Ensure your Go binary directory (usually $GOBIN or $GOPATH/bin) is on PATH, then run:

go-stun

To build from a source checkout instead:

git clone https://github.com/ccding/go-stun.git
cd go-stun
go build .
./go-stun

Command-line usage

Running go-stun with no options uses the default server and an automatically selected local address. Example output:

NAT Type: Full cone NAT
External IP Family: 1
External IP: 203.0.113.10
External Port: 54321

The values depend on the network and server. Address family 1 denotes IPv4; 2 denotes IPv6.

Available options:

Option Description
-s host:port Use a specific STUN server.
-i ip Bind requests to a local IP address.
-p port Bind requests to a local port; 0 selects an available port.
-b Run RFC 5780 mapping and filtering behavior tests, and report the external address and observed port preservation.
-legacy Omit modern optional attributes for RFC 3489-only servers.
-t transport Select udp (the default) or tcp. TCP performs basic Binding only.
-v level Set verbosity to 0 (quiet), 1 (protocol trace), or 2/3 (also dump packets in hex); values above 3 are rejected.

Use go-stun -h to see the current defaults. For example:

go-stun -s stun.example.com:3478
go-stun -s stun.example.com:3478 -t tcp
go-stun -s stun.example.com:3478 -b
go-stun -v 1

Behavior mode (-b) also reports the address from its initial Binding response:

External IP Family: 1
External IP: 203.0.113.10
External Port: 54321
Port Preservation: true
  Mapping Behavior: EndpointIndependent
Filtering Behavior: EndpointIndependent
   Normal NAT Type: Full cone NAT

Port Preservation compares the external port with the socket's actual local port, including the port selected automatically with -p 0. It describes the initial mapping to this server; other destinations or later connections may use different ports. The address and port-preservation result remain available if the server cannot support behavior discovery or a later probe fails. The port-preservation line is omitted when the local port cannot be determined.

Use the library

Add the package to a Go module:

go get github.com/ccding/go-stun/stun@master

Then create a client and call Discover:

package main

import (
	"fmt"
	"log"

	"github.com/ccding/go-stun/stun"
)

func main() {
	client := stun.NewClient()

	natType, mappedAddr, err := client.Discover()
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println("NAT type:", natType)
	if mappedAddr != nil {
		fmt.Println("Mapped address:", mappedAddr)
	}
}

If no server is configured, the client uses stun.DefaultServerAddr. Other configuration methods include SetServerHost, SetLocalIP, SetLocalPort, SetSoftwareName, and the verbosity setters. For an existing socket, use stun.NewClientWithConnection(conn) with a net.PacketConn created by an applicable net.Listen* function; the caller remains responsible for closing the connection, and Keepalive can refresh its mapping.

For a basic Binding transaction over TCP, call DiscoverTCP:

mappedAddr, err := client.DiscoverTCP()

DiscoverTCP opens a TCP connection for the transaction and closes it before returning. Because a reflexive TCP address remains useful only while its connection is open, applications that need to retain the mapping should dial the server themselves and use NewClientWithTCPConnection. The caller owns that connection and can call DiscoverTCP again to refresh the mapping. Use SetTCPTimeout to replace the RFC 8489 default response timeout of 39.5 seconds. When DiscoverTCP opens the connection itself, the same duration also bounds connection establishment independently.

Run go doc github.com/ccding/go-stun/stun for documentation matching the version in your module. The linked package reference shows the latest tagged release and will not include master-only symbols until a new release is published.

NAT behavior discovery

Discover first performs a standard Binding request. If that succeeds but the server does not provide a usable alternate address, it returns stun.NATUnknown, the mapped address, and a nil error. The mapped address is still valid even though the NAT type could not be determined.

Add "errors" to the import block, then call BehaviorTestWithDetails (or use the CLI's -b option) for RFC 5780 mapping and filtering tests together with the external address and port-preservation observation:

result, err := client.BehaviorTestWithDetails()
if errors.Is(err, stun.ErrBehaviorDiscoveryUnsupported) {
	fmt.Println("The server does not support behavior discovery")
} else if err != nil {
	fmt.Println("Behavior test failed:", err)
}

if result != nil && result.MappingType != stun.BehaviorTypeUnknown {
	fmt.Println("Mapping behavior:", result.MappingType)
}
if result != nil && result.FilteringType != stun.BehaviorTypeUnknown {
	fmt.Println("Filtering behavior:", result.FilteringType)
}
if result != nil && result.MappedAddress != nil {
	fmt.Println("Mapped address:", result.MappedAddress)
}
if result != nil && result.PortPreservation != nil {
	fmt.Println("Port preservation:", *result.PortPreservation)
}

A non-nil behavior result may contain partial results when a later probe fails. MappedAddress contains the initial Binding response's address, and PortPreservation is nil when the local port is unavailable. Both observations use the same socket as the behavior probes; no separate discovery call is needed.

BehaviorTest retains its existing *NATBehavior return value and three-field structure, so existing struct literals, equality comparisons, and map keys keep working. BehaviorTestWithDetails returns *NATBehaviorResult, which embeds that classification as result.NATBehavior and adds the observations. Use result.NATBehavior when comparing classifications.

RFC 3489 compatibility

Normal requests include SOFTWARE and FINGERPRINT attributes. Some legacy RFC 3489 servers reject those attributes; enable compatibility mode for such a server:

client := stun.NewClient()
client.SetRFC3489Compatibility(true)

The equivalent command-line option is -legacy.

Server requirements and limitations

A successful STUN Binding request only requires the server to return a mapped address. NAT classification additionally requires an alternate IP address and port, advertised through RFC 3489's CHANGED-ADDRESS or RFC 5780's OTHER-ADDRESS attribute. Many public STUN servers support Binding but do not support these discovery probes; NAT type unavailable is therefore an expected result with those servers.

UDP requests use the RFC 3489 retransmission schedule: nine sends beginning at 100 ms, doubling up to a 1.6-second interval. A timed-out probe can consequently take several seconds. TCP requests rely on TCP reliability and are not retransmitted at the STUN layer.

Security

Report suspected vulnerabilities privately by following the security policy. Please do not publish vulnerability details in a GitHub issue.

Development

Run the checks used by CI before submitting changes:

git ls-files -z -- '*.go' | xargs -0 gofmt -l
go mod tidy
go mod verify
go vet -mod=readonly ./...
staticcheck -checks=all ./...
go test -mod=readonly -race -shuffle=on -covermode=atomic -coverprofile=coverage.out ./...
govulncheck -test ./...

The formatting command should produce no output, and go mod tidy should not change go.mod or go.sum. CI also requires at least 84% statement coverage. See the Go CI workflow for the pinned Go and tool versions and the exact checks.

License

go-stun is available under the Apache License 2.0.

About

A go implementation of the STUN client (RFC 3489 and RFC 5389)

Topics

Resources

Security policy

Stars

723 stars

Watchers

17 watching

Forks

Releases

Packages

Used by

Contributors

Languages