From f46ae94b8440bc1d3803a10f52789d49dbd28e5a Mon Sep 17 00:00:00 2001 From: Cong Ding Date: Sun, 19 Jul 2026 20:19:28 +0800 Subject: [PATCH 1/3] Improve project documentation and security policy --- README.md | 226 ++++++++++++++++++++++++++++++++++++++-------------- SECURITY.md | 81 +++++++++++++++---- 2 files changed, 235 insertions(+), 72 deletions(-) diff --git a/README.md b/README.md index 9628e24..bdab0f5 100644 --- a/README.md +++ b/README.md @@ -1,89 +1,199 @@ -go-stun -======= +# go-stun + +[![Go Reference](https://pkg.go.dev/badge/github.com/ccding/go-stun/stun.svg)](https://pkg.go.dev/github.com/ccding/go-stun/stun) +[![Tests](https://github.com/ccding/go-stun/actions/workflows/go.yml/badge.svg)](https://github.com/ccding/go-stun/actions/workflows/go.yml) +[![License](https://img.shields.io/badge/license-Apache%202.0-red.svg)](LICENSE) + +`go-stun` is a Go library and command-line client for STUN over UDP. It can +discover the public IP address and port assigned to a UDP socket and, when the +server supports the required probes, classify the client's NAT behavior. -[![License](https://img.shields.io/badge/License-Apache%202.0-red.svg)](https://opensource.org/licenses/Apache-2.0) -[![GoDoc](https://godoc.org/github.com/ccding/go-stun?status.svg)](http://godoc.org/github.com/ccding/go-stun/stun) -[![Go Report Card](https://goreportcard.com/badge/github.com/ccding/go-stun)](https://goreportcard.com/report/github.com/ccding/go-stun) +STUN is one building block for NAT traversal. This project does not implement a +complete UDP hole-punching, ICE, or TURN solution. -go-stun is a STUN (RFC 3489, 5389) client implementation in golang -(a.k.a. UDP hole punching). +## Features -[RFC 3489](https://tools.ietf.org/html/rfc3489): -STUN - Simple Traversal of User Datagram Protocol (UDP) -Through Network Address Translators (NATs) +- Discover a socket's public (server-reflexive) IP address and port. +- 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]. +- 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. -[RFC 5389](https://tools.ietf.org/html/rfc5389): -Session Traversal Utilities for NAT (STUN) +## Install the command -### Use the Command Line Tool +With Go 1.16 or newer: -Simply run these commands (if you have installed golang and set `$GOPATH`) +```console +go install github.com/ccding/go-stun@latest ``` -go get github.com/ccding/go-stun + +Ensure your Go binary directory (usually `$GOBIN` or `$GOPATH/bin`) is on +`PATH`, then run: + +```console go-stun ``` -or clone this repo and run these commands -``` -go build + +To build from a source checkout instead: + +```console +git clone https://github.com/ccding/go-stun.git +cd go-stun +go build . ./go-stun ``` -You will get the output like -``` + +## Command-line usage + +Running `go-stun` with no options uses the default server and an automatically +selected local address. Example output: + +```text NAT Type: Full cone NAT External IP Family: 1 -External IP: 166.111.4.100 -External Port: 23009 +External IP: 203.0.113.10 +External Port: 54321 ``` -You can use `-s` flag to use another STUN server, and use `-v` to work on -verbose mode. - -Most public STUN servers, including Google's and Cloudflare's, support basic -Binding requests but not the alternate-address tests needed to classify NAT -behavior. With those servers the client returns `NATUnknown`, a non-nil mapped -address, and a nil error. Full NAT classification requires a server with -RFC 3489 classic NAT-discovery support or RFC 5780 behavior discovery. -```bash -> ./go-stun --help -Usage of ./go-stun: - -b Enable NAT behavior test mode - -i string - The ip on which to bind requests, set to empty will use default - -legacy - Enable compatibility with RFC 3489-only STUN servers - -p int - The port on which to bind requests, set to 0 to pick a random port - -s string - STUN server address (default "stunserver2025.stunprotocol.org:3478") - -v int - Verbose level (0: none, 1: verbose, 2: double verbose, 3: triple verbose) + +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. | +| `-legacy` | Omit modern optional attributes for RFC 3489-only servers. | +| `-v level` | Set verbosity from `0` (quiet) to `3`; use `2` or higher to include packet dumps. | + +Use `go-stun -h` to see the current defaults. For example: + +```console +go-stun -s stun.example.com:3478 +go-stun -s stun.example.com:3478 -b +go-stun -v 1 ``` -### Use the Library +## Use the library + +Add the package to a Go module: -The library `github.com/ccding/go-stun/stun` is extremely easy to use -- just -one line of code. +```console +go get github.com/ccding/go-stun/stun +``` + +Then create a client and call `Discover`: ```go -import "github.com/ccding/go-stun/stun" +package main + +import ( + "fmt" + "log" + + "github.com/ccding/go-stun/stun" +) func main() { - nat, host, err := stun.NewClient().Discover() + 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) + } } ``` -Modern Binding requests include SOFTWARE and FINGERPRINT attributes. For an -RFC 3489-only server, enable compatibility mode before discovery. Compatibility -mode is disabled by default, preserving the request format used by earlier -go-stun releases: +If no server is configured, the client uses `stun.DefaultServerAddr`. Other +configuration methods include `SetServerHost`, `SetLocalIP`, `SetLocalPort`, +`SetSoftwareName`, and the verbosity setters. See the [package reference] for +the complete API. + +### 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. + +Call `BehaviorTest` (or use the CLI's `-b` option) for RFC 5780 mapping and +filtering tests: + +```go +behavior, err := client.BehaviorTest() +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 behavior != nil { + fmt.Println("Mapping behavior:", behavior.MappingType) + fmt.Println("Filtering behavior:", behavior.FilteringType) +} +``` + +This snippet requires the standard library's `errors` package. A non-nil +behavior result may contain partial results when a later probe fails. + +### 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: ```go client := stun.NewClient() client.SetRFC3489Compatibility(true) -nat, host, err := client.Discover() ``` -UDP requests retain the RFC 3489 retransmission schedule used by the classic -NAT-discovery algorithm: nine sends starting at 100 ms, doubling to a 1.6 s -cap. This favors legacy behavior over RFC 5389's newer recommended defaults. +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. + +## Security + +Report suspected vulnerabilities privately by following the +[security policy](SECURITY.md). Please do not publish vulnerability details in +a GitHub issue. + +## Development + +Run the test suite and static checks before submitting changes: + +```console +go test ./... +go vet ./... +``` + +## License + +`go-stun` is available under the [Apache License 2.0](LICENSE). -More details please go to `main.go` and [GoDoc](http://godoc.org/github.com/ccding/go-stun/stun) +[package reference]: https://pkg.go.dev/github.com/ccding/go-stun/stun +[RFC 3489]: https://www.rfc-editor.org/rfc/rfc3489.html +[RFC 5389]: https://www.rfc-editor.org/rfc/rfc5389.html +[RFC 5780]: https://www.rfc-editor.org/rfc/rfc5780.html diff --git a/SECURITY.md b/SECURITY.md index 034e848..e3b9ae8 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -1,21 +1,74 @@ # Security Policy -## Supported Versions +## Supported versions -Use this section to tell people about which versions of your project are -currently being supported with security updates. +Security fixes are provided on a best-effort basis for the current default +branch and the latest tagged release. Fixes are not normally backported to +older releases, so users should upgrade to the newest available version. -| Version | Supported | -| ------- | ------------------ | -| 5.1.x | :white_check_mark: | -| 5.0.x | :x: | -| 4.0.x | :white_check_mark: | -| < 4.0 | :x: | +| Version | Supported | +| --- | --- | +| Current default branch | Yes | +| Latest tagged release | Yes | +| Older releases | No | -## Reporting a Vulnerability +## Reporting a vulnerability -Use this section to tell people how to report a vulnerability. +Please report suspected vulnerabilities through GitHub's private +[Report a vulnerability] form. Do not disclose vulnerability details in a +public issue, pull request, or discussion before a coordinated fix is +available. -Tell them where to go, how often they can expect to get an update on a -reported vulnerability, what to expect if the vulnerability is accepted or -declined, etc. +Include as much of the following information as possible: + +- The affected version, tag, or commit. +- The operating system, Go version, and relevant network configuration. +- A description of the issue, its security impact, and a realistic attack + scenario. +- Minimal reproduction steps, test code, or a sanitized packet capture. +- Any known mitigations or suggested fixes. + +Remove credentials and redact unrelated personal or network data from the +report. If a report needs large or sensitive attachments, start with a short +private report so the maintainers can arrange an appropriate transfer method. + +## What to expect + +The maintainers aim to acknowledge a report within seven days. After an +initial assessment, they will confirm whether the issue is accepted, request +additional information, or explain why it is not considered a vulnerability. +Response and remediation times depend on severity and maintainer availability. + +For accepted vulnerabilities, the maintainers will work with the reporter on +a fix and coordinated disclosure. When appropriate, the resolution may include +a new release, a GitHub Security Advisory, and credit for the reporter. Please +allow time for users to upgrade before publishing technical details. + +## Scope + +Security-relevant reports include, but are not limited to: + +- Crashes or excessive resource consumption caused by untrusted STUN packets. +- Acceptance of spoofed, malformed, or incorrectly correlated responses. +- Validation bypasses that produce unsafe behavior in the library or CLI. +- Vulnerabilities in dependencies or repository automation that directly + affect users of `go-stun`. + +The following are generally not security vulnerabilities by themselves: + +- Disclosure of the public mapped address, which is the intended purpose of + STUN. +- An unavailable or misconfigured third-party STUN server. +- An unknown or inaccurate NAT classification caused by a server that does not + support the required RFC 3489 or RFC 5780 discovery probes. +- Issues that affect only unsupported releases and are already fixed in the + latest version. + +## Safe testing + +Test only systems and networks you own or have explicit permission to assess. +Avoid disrupting public STUN services, accessing other users' data, or +performing tests that could degrade availability. Stop testing and report the +issue if you encounter sensitive data or cause unexpected impact. + +[Report a vulnerability]: https://github.com/ccding/go-stun/security/advisories/new From 25ff7a413767a7a4c06b737b3f39cb6c8e2bd08b Mon Sep 17 00:00:00 2001 From: Cong Ding Date: Sun, 19 Jul 2026 20:24:53 +0800 Subject: [PATCH 2/3] Clarify behavior example import --- README.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index bdab0f5..7044df0 100644 --- a/README.md +++ b/README.md @@ -128,8 +128,8 @@ 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. -Call `BehaviorTest` (or use the CLI's `-b` option) for RFC 5780 mapping and -filtering tests: +Add `"errors"` to the import block, then call `BehaviorTest` (or use the CLI's +`-b` option) for RFC 5780 mapping and filtering tests: ```go behavior, err := client.BehaviorTest() @@ -145,8 +145,7 @@ if behavior != nil { } ``` -This snippet requires the standard library's `errors` package. A non-nil -behavior result may contain partial results when a later probe fails. +A non-nil behavior result may contain partial results when a later probe fails. ### RFC 3489 compatibility From 58c16207aa9a26f0ebfc08bfc31c57fed14a9fc7 Mon Sep 17 00:00:00 2001 From: Cong Ding Date: Sun, 19 Jul 2026 20:46:48 +0800 Subject: [PATCH 3/3] docs: address independent review feedback --- .travis.yml | 13 ------------- README.md | 47 ++++++++++++++++++++++++++++++++++++----------- SECURITY.md | 17 +++++++++-------- 3 files changed, 45 insertions(+), 32 deletions(-) delete mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 3331b8d..0000000 --- a/.travis.yml +++ /dev/null @@ -1,13 +0,0 @@ -language: go -arch: - - amd64 - - ppc64le -go: - - 1.14.x - - tip -script: - - go build ./... - - go test -v ./... - - go test -race -coverprofile=coverage.txt -covermode=atomic ./... -after_success: - - bash <(curl -s https://codecov.io/bash) diff --git a/README.md b/README.md index 7044df0..c702ce4 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # go-stun -[![Go Reference](https://pkg.go.dev/badge/github.com/ccding/go-stun/stun.svg)](https://pkg.go.dev/github.com/ccding/go-stun/stun) +[![Go Reference (latest release)](https://pkg.go.dev/badge/github.com/ccding/go-stun/stun.svg)](https://pkg.go.dev/github.com/ccding/go-stun/stun) [![Tests](https://github.com/ccding/go-stun/actions/workflows/go.yml/badge.svg)](https://github.com/ccding/go-stun/actions/workflows/go.yml) [![License](https://img.shields.io/badge/license-Apache%202.0-red.svg)](LICENSE) @@ -25,12 +25,17 @@ complete UDP hole-punching, ICE, or TURN solution. ## Install the command -With Go 1.16 or newer: +This README documents the current `master` branch. With Go 1.16 or newer, +install that branch explicitly until the next release is tagged: ```console -go install github.com/ccding/go-stun@latest +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: @@ -71,7 +76,7 @@ Available options: | `-p port` | Bind requests to a local port; `0` selects an available port. | | `-b` | Run RFC 5780 mapping and filtering behavior tests. | | `-legacy` | Omit modern optional attributes for RFC 3489-only servers. | -| `-v level` | Set verbosity from `0` (quiet) to `3`; use `2` or higher to include packet dumps. | +| `-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: @@ -86,7 +91,7 @@ go-stun -v 1 Add the package to a Go module: ```console -go get github.com/ccding/go-stun/stun +go get github.com/ccding/go-stun/stun@master ``` Then create a client and call `Discover`: @@ -118,8 +123,15 @@ func main() { If no server is configured, the client uses `stun.DefaultServerAddr`. Other configuration methods include `SetServerHost`, `SetLocalIP`, `SetLocalPort`, -`SetSoftwareName`, and the verbosity setters. See the [package reference] for -the complete API. +`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. + +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 @@ -139,8 +151,10 @@ if errors.Is(err, stun.ErrBehaviorDiscoveryUnsupported) { fmt.Println("Behavior test failed:", err) } -if behavior != nil { +if behavior != nil && behavior.MappingType != stun.BehaviorTypeUnknown { fmt.Println("Mapping behavior:", behavior.MappingType) +} +if behavior != nil && behavior.FilteringType != stun.BehaviorTypeUnknown { fmt.Println("Filtering behavior:", behavior.FilteringType) } ``` @@ -181,18 +195,29 @@ a GitHub issue. ## Development -Run the test suite and static checks before submitting changes: +Run the checks used by CI before submitting changes: ```console -go test ./... -go vet ./... +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](LICENSE). [package reference]: https://pkg.go.dev/github.com/ccding/go-stun/stun +[Go CI workflow]: .github/workflows/go.yml [RFC 3489]: https://www.rfc-editor.org/rfc/rfc3489.html [RFC 5389]: https://www.rfc-editor.org/rfc/rfc5389.html [RFC 5780]: https://www.rfc-editor.org/rfc/rfc5780.html diff --git a/SECURITY.md b/SECURITY.md index e3b9ae8..2ec0bf0 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -2,15 +2,16 @@ ## Supported versions -Security fixes are provided on a best-effort basis for the current default -branch and the latest tagged release. Fixes are not normally backported to -older releases, so users should upgrade to the newest available version. +Security fixes land on the current default branch and are published in a new +tagged release. Existing tags are not patched in place, so users should upgrade +to a release containing the fix or, until one is available, to a reviewed +commit on the default branch. -| Version | Supported | +| Version | Security updates | | --- | --- | | Current default branch | Yes | -| Latest tagged release | Yes | -| Older releases | No | +| Latest tagged release | Fixes shipped as a new release | +| Older tagged releases | No | ## Reporting a vulnerability @@ -61,8 +62,8 @@ The following are generally not security vulnerabilities by themselves: - An unavailable or misconfigured third-party STUN server. - An unknown or inaccurate NAT classification caused by a server that does not support the required RFC 3489 or RFC 5780 discovery probes. -- Issues that affect only unsupported releases and are already fixed in the - latest version. +- Issues that affect only unsupported releases and are already fixed in a + newer supported version. ## Safe testing