From d0a56bbc2136d0a248ef4c8a64140d2013d5d807 Mon Sep 17 00:00:00 2001 From: Aleksandr Razumov Date: Wed, 2 Oct 2019 00:30:53 +0300 Subject: [PATCH] all: major version bump --- .golangci.yml | 19 +- Dockerfile | 5 - README.md | 32 +- agent.go | 228 ----- agent_test.go | 212 ----- api/check.sh | 2 - api/except.txt | 25 - api/stun1.0.0.txt | 323 ------- api/stun1.14.0.txt | 21 - api/stun1.15.0.txt | 6 - api/stun1.16.0.txt | 9 - api/stun1.17.0.txt | 10 - api/stun1.17.3.txt | 12 - api/stun1.18.0.txt | 1 - api/stun1.18.1.txt | 3 - api/stun1.19.0.txt | 1 - checks.go | 2 +- checks_debug.go | 2 +- client.go | 631 -------------- client_test.go | 1433 -------------------------------- cmd/stun-bench/main.go | 132 --- cmd/stun-client/stun-client.go | 41 - cmd/stun-decode/main.go | 2 +- cmd/stun-multiplex/README.md | 31 - cmd/stun-multiplex/main.go | 190 ----- cmd/stun-traversal/Readme.md | 1 - cmd/stun-traversal/main.go | 191 ----- e2e/Dockerfile | 11 - e2e/capture.sh | 5 - e2e/docker-compose.yml | 22 - e2e/main.go | 130 --- e2e/tcpdump.Dockerfile | 6 - e2e/test.sh | 73 -- e2e/turnserver.conf | 200 ----- go.mod | 4 +- helpers_test.go | 2 +- integrity.go | 2 +- message.go | 21 +- stun.go | 2 + 39 files changed, 38 insertions(+), 4005 deletions(-) delete mode 100644 Dockerfile delete mode 100644 agent.go delete mode 100644 agent_test.go delete mode 100755 api/check.sh delete mode 100644 api/except.txt delete mode 100644 api/stun1.0.0.txt delete mode 100644 api/stun1.14.0.txt delete mode 100644 api/stun1.15.0.txt delete mode 100644 api/stun1.16.0.txt delete mode 100644 api/stun1.17.0.txt delete mode 100644 api/stun1.17.3.txt delete mode 100644 api/stun1.18.0.txt delete mode 100644 api/stun1.18.1.txt delete mode 100644 api/stun1.19.0.txt delete mode 100644 client.go delete mode 100644 client_test.go delete mode 100644 cmd/stun-bench/main.go delete mode 100644 cmd/stun-client/stun-client.go delete mode 100644 cmd/stun-multiplex/README.md delete mode 100644 cmd/stun-multiplex/main.go delete mode 100644 cmd/stun-traversal/Readme.md delete mode 100644 cmd/stun-traversal/main.go delete mode 100644 e2e/Dockerfile delete mode 100644 e2e/capture.sh delete mode 100644 e2e/docker-compose.yml delete mode 100644 e2e/main.go delete mode 100644 e2e/tcpdump.Dockerfile delete mode 100755 e2e/test.sh delete mode 100644 e2e/turnserver.conf diff --git a/.golangci.yml b/.golangci.yml index 5d9665e..e5ca4b6 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -32,8 +32,7 @@ issues: - "`assertHMACSize` - `blocksize` always receives `64`" exclude-rules: - text: "string ``" - linters: - - goconst + linters: [goconst] # Exclude some linters from running on tests files. - path: _test\.go @@ -47,19 +46,16 @@ issues: # Ease some gocritic warnings on test files. - path: _test\.go text: "(unnamedResult|exitAfterDefer|unlambda)" - linters: - - gocritic + linters: [gocritic] # Exclude known linters from partially hard-vendored code, # which is impossible to exclude via "nolint" comments. - path: internal/hmac/ text: "weak cryptographic primitive" - linters: - - gosec + linters: [gosec] - path: internal/hmac/ text: "Write\\` is not checked" - linters: - - errcheck + linters: [errcheck] # Ease linting on benchmarking code. - path: cmd/stun-bench/ @@ -68,13 +64,6 @@ issues: - errcheck - unparam - - path: ^cmd/ - linters: - - gocyclo - - path: ^cmd/ - text: "(unnamedResult|exitAfterDefer)" - linters: - - gocritic - text: evalOrder path: helpers source: "Build" diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index a97989f..0000000 --- a/Dockerfile +++ /dev/null @@ -1,5 +0,0 @@ -FROM golang:1.12 - -COPY . /go/src/gortc.io/stun - -RUN go test gortc.io/stun diff --git a/README.md b/README.md index b1a7491..d0ccf25 100644 --- a/README.md +++ b/README.md @@ -2,15 +2,19 @@ [![GoDoc](https://godoc.org/gortc.io/stun?status.svg)](http://godoc.org/gortc.io/stun) [![codecov](https://codecov.io/gh/gortc/stun/branch/master/graph/badge.svg)](https://codecov.io/gh/gortc/stun) # STUN -Package stun implements Session Traversal Utilities for NAT (STUN) [[RFC5389](https://tools.ietf.org/html/rfc5389)] -protocol and [client](https://godoc.org/gortc.io/stun#Client) with no external dependencies and zero allocations in hot paths. -Client [supports](https://godoc.org/gortc.io/stun#WithRTO) automatic request retransmissions. -Complies to [gortc principles](https://gortc.io/#principles) as core package. +Package stun implements Session Traversal Utilities for NAT (STUN) +[[RFC5389](https://tools.ietf.org/html/rfc5389)] protocol with no +external dependencies and zero allocations in hot paths.Complies to +[gortc principles](https://gortc.io/#principles) as core package. -See [example](https://godoc.org/gortc.io/stun#example-Message) and [stun server](https://github.com/gortc/stund) for simple usage. -Also see [gortc/turn](https://github.com/gortc/turn) for TURN [[RFC5766](https://tools.ietf.org/html/rfc5766)] implementation and -[gortcd](https://github.com/gortc/gortcd) for TURN and STUN server. This repo was merged to [pion/stun](https://github.com/pion/stun) -at version `v1.19.0`. +See [stun server](https://github.com/gortc/stund) for simple usage. Also see +[gortc/turn](https://github.com/gortc/turn) for TURN +[[RFC5766](https://tools.ietf.org/html/rfc5766)] implementation and +[gortcd](https://github.com/gortc/gortcd) for TURN and STUN server. This +repo was merged to [pion/stun](https://github.com/pion/stun) at version +`v1.19.0`. + +Please use `v1` version for stun agent and client. # Example You can get your current IP address from any STUN server by sending @@ -21,7 +25,7 @@ package main import ( "fmt" - "gortc.io/stun" + "gortc.io/stun/v2" ) func main() { @@ -52,16 +56,12 @@ func main() { ## Supported RFCs - [x] [RFC 5389](https://tools.ietf.org/html/rfc5389) — Session Traversal Utilities for NAT - [x] [RFC 5769](https://tools.ietf.org/html/rfc5769) — Test Vectors for STUN -- [x] [RFC 6062](https://tools.ietf.org/html/rfc6062) — TURN extensions for TCP allocations - [x] [RFC 7064](https://tools.ietf.org/html/rfc7064) — STUN URI -- [x] (TLS-over-)TCP client support -- [ ] [ALTERNATE-SERVER](https://tools.ietf.org/html/rfc5389#section-11) support [#48](https://github.com/gortc/stun/issues/48) -- [ ] [RFC 5780](https://tools.ietf.org/html/rfc5780) — NAT Behavior Discovery Using STUN [#49](https://github.com/gortc/stun/issues/49) # Stability [![stability-mature](https://img.shields.io/badge/stability-mature-008000.svg)](https://github.com/mkenney/software-guides/blob/master/STABILITY-BADGES.md#mature) ![GitHub tag](https://img.shields.io/github/tag/gortc/stun.svg) -Package is currently stable, no backward incompatible changes are expected -with exception of critical bugs or security fixes. +The v1 is currently stable, no backward incompatible changes are +expected with exception of critical bugs or security fixes. Additional attributes are unlikely to be implemented in scope of stun package, the only exception is constants for attribute or message types. @@ -71,7 +71,7 @@ RFC 5389 obsoletes RFC 3489, so implementation was ignored by purpose, however, RFC 3489 can be easily implemented as separate package. # Requirements -Go 1.10 is currently supported and tested in CI. Should work on 1.9 and tip. +Go 1.13 or better is required. # Testing Client behavior is tested and verified in many ways: diff --git a/agent.go b/agent.go deleted file mode 100644 index 6a8a473..0000000 --- a/agent.go +++ /dev/null @@ -1,228 +0,0 @@ -package stun - -import ( - "errors" - "sync" - "time" -) - -// NoopHandler just discards any event. -var NoopHandler Handler = func(e Event) {} - -// NewAgent initializes and returns new Agent with provided handler. -// If h is nil, the NoopHandler will be used. -func NewAgent(h Handler) *Agent { - if h == nil { - h = NoopHandler - } - a := &Agent{ - transactions: make(map[transactionID]agentTransaction), - handler: h, - } - return a -} - -// Agent is low-level abstraction over transaction list that -// handles concurrency (all calls are goroutine-safe) and -// time outs (via Collect call). -type Agent struct { - // transactions is map of transactions that are currently - // in progress. Event handling is done in such way when - // transaction is unregistered before agentTransaction access, - // minimizing mux lock and protecting agentTransaction from - // data races via unexpected concurrent access. - transactions map[transactionID]agentTransaction - closed bool // all calls are invalid if true - mux sync.Mutex // protects transactions and closed - handler Handler // handles transactions -} - -// Handler handles state changes of transaction. -// -// Handler is called on transaction state change. -// Usage of e is valid only during call, user must -// copy needed fields explicitly. -type Handler func(e Event) - -// Event is passed to Handler describing the transaction event. -// Do not reuse outside Handler. -type Event struct { - TransactionID [TransactionIDSize]byte - Message *Message - Error error -} - -// agentTransaction represents transaction in progress. -// Concurrent access is invalid. -type agentTransaction struct { - id transactionID - deadline time.Time -} - -var ( - // ErrTransactionStopped indicates that transaction was manually stopped. - ErrTransactionStopped = errors.New("transaction is stopped") - // ErrTransactionNotExists indicates that agent failed to find transaction. - ErrTransactionNotExists = errors.New("transaction not exists") - // ErrTransactionExists indicates that transaction with same id is already - // registered. - ErrTransactionExists = errors.New("transaction exists with same id") -) - -// StopWithError removes transaction from list and calls handler with -// provided error. Can return ErrTransactionNotExists and ErrAgentClosed. -func (a *Agent) StopWithError(id [TransactionIDSize]byte, err error) error { - a.mux.Lock() - if a.closed { - a.mux.Unlock() - return ErrAgentClosed - } - t, exists := a.transactions[id] - delete(a.transactions, id) - h := a.handler - a.mux.Unlock() - if !exists { - return ErrTransactionNotExists - } - h(Event{ - TransactionID: t.id, - Error: err, - }) - return nil -} - -// Stop stops transaction by id with ErrTransactionStopped, blocking -// until handler returns. -func (a *Agent) Stop(id [TransactionIDSize]byte) error { - return a.StopWithError(id, ErrTransactionStopped) -} - -// ErrAgentClosed indicates that agent is in closed state and is unable -// to handle transactions. -var ErrAgentClosed = errors.New("agent is closed") - -// Start registers transaction with provided id and deadline. -// Could return ErrAgentClosed, ErrTransactionExists. -// -// Agent handler is guaranteed to be eventually called. -func (a *Agent) Start(id [TransactionIDSize]byte, deadline time.Time) error { - a.mux.Lock() - defer a.mux.Unlock() - if a.closed { - return ErrAgentClosed - } - _, exists := a.transactions[id] - if exists { - return ErrTransactionExists - } - a.transactions[id] = agentTransaction{ - id: id, - deadline: deadline, - } - return nil -} - -// agentCollectCap is initial capacity for Agent.Collect slices, -// sufficient to make function zero-alloc in most cases. -const agentCollectCap = 100 - -// ErrTransactionTimeOut indicates that transaction has reached deadline. -var ErrTransactionTimeOut = errors.New("transaction is timed out") - -// Collect terminates all transactions that have deadline before provided -// time, blocking until all handlers will process ErrTransactionTimeOut. -// Will return ErrAgentClosed if agent is already closed. -// -// It is safe to call Collect concurrently but makes no sense. -func (a *Agent) Collect(gcTime time.Time) error { - toRemove := make([]transactionID, 0, agentCollectCap) - a.mux.Lock() - if a.closed { - // Doing nothing if agent is closed. - // All transactions should be already closed - // during Close() call. - a.mux.Unlock() - return ErrAgentClosed - } - // Adding all transactions with deadline before gcTime - // to toCall and toRemove slices. - // No allocs if there are less than agentCollectCap - // timed out transactions. - for id, t := range a.transactions { - if t.deadline.Before(gcTime) { - toRemove = append(toRemove, id) - } - } - // Un-registering timed out transactions. - for _, id := range toRemove { - delete(a.transactions, id) - } - // Calling handler does not require locked mutex, - // reducing lock time. - h := a.handler - a.mux.Unlock() - // Sending ErrTransactionTimeOut to handler for all transactions, - // blocking until last one. - event := Event{ - Error: ErrTransactionTimeOut, - } - for _, id := range toRemove { - event.TransactionID = id - h(event) - } - return nil -} - -// Process incoming message, synchronously passing it to handler. -func (a *Agent) Process(m *Message) error { - e := Event{ - TransactionID: m.TransactionID, - Message: m, - } - a.mux.Lock() - if a.closed { - a.mux.Unlock() - return ErrAgentClosed - } - h := a.handler - delete(a.transactions, m.TransactionID) - a.mux.Unlock() - h(e) - return nil -} - -// SetHandler sets agent handler to h. -func (a *Agent) SetHandler(h Handler) error { - a.mux.Lock() - if a.closed { - a.mux.Unlock() - return ErrAgentClosed - } - a.handler = h - a.mux.Unlock() - return nil -} - -// Close terminates all transactions with ErrAgentClosed and renders Agent to -// closed state. -func (a *Agent) Close() error { - e := Event{ - Error: ErrAgentClosed, - } - a.mux.Lock() - if a.closed { - a.mux.Unlock() - return ErrAgentClosed - } - for _, t := range a.transactions { - e.TransactionID = t.id - a.handler(e) - } - a.transactions = nil - a.closed = true - a.handler = nil - a.mux.Unlock() - return nil -} - -type transactionID [TransactionIDSize]byte diff --git a/agent_test.go b/agent_test.go deleted file mode 100644 index 245429a..0000000 --- a/agent_test.go +++ /dev/null @@ -1,212 +0,0 @@ -package stun - -import ( - "testing" - "time" -) - -func TestAgent_ProcessInTransaction(t *testing.T) { - m := New() - a := NewAgent(func(e Event) { - if e.Error != nil { - t.Errorf("got error: %s", e.Error) - } - if !e.Message.Equal(m) { - t.Errorf("%s (got) != %s (expected)", e.Message, m) - } - }) - if err := m.NewTransactionID(); err != nil { - t.Fatal(err) - } - if err := a.Start(m.TransactionID, time.Time{}); err != nil { - t.Fatal(err) - } - if err := a.Process(m); err != nil { - t.Error(err) - } - if err := a.Close(); err != nil { - t.Error(err) - } -} - -func TestAgent_Process(t *testing.T) { - m := New() - a := NewAgent(func(e Event) { - if e.Error != nil { - t.Errorf("got error: %s", e.Error) - } - if !e.Message.Equal(m) { - t.Errorf("%s (got) != %s (expected)", e.Message, m) - } - }) - if err := m.NewTransactionID(); err != nil { - t.Fatal(err) - } - if err := a.Process(m); err != nil { - t.Error(err) - } - if err := a.Close(); err != nil { - t.Error(err) - } - if err := a.Process(m); err != ErrAgentClosed { - t.Errorf("closed agent should return <%s>, but got <%s>", - ErrAgentClosed, err, - ) - } -} - -func TestAgent_Start(t *testing.T) { - a := NewAgent(nil) - id := NewTransactionID() - deadline := time.Now().AddDate(0, 0, 1) - if err := a.Start(id, deadline); err != nil { - t.Errorf("failed to statt transaction: %s", err) - } - if err := a.Start(id, deadline); err != ErrTransactionExists { - t.Errorf("duplicate start should return <%s>, got <%s>", - ErrTransactionExists, err, - ) - } - if err := a.Close(); err != nil { - t.Error(err) - } - id = NewTransactionID() - if err := a.Start(id, deadline); err != ErrAgentClosed { - t.Errorf("start on closed agent should return <%s>, got <%s>", - ErrAgentClosed, err, - ) - } - if err := a.SetHandler(nil); err != ErrAgentClosed { - t.Errorf("SetHandler on closed agent should return <%s>, got <%s>", - ErrAgentClosed, err, - ) - } -} - -func TestAgent_Stop(t *testing.T) { - called := make(chan Event, 1) - a := NewAgent(func(e Event) { - called <- e - }) - if err := a.Stop(transactionID{}); err != ErrTransactionNotExists { - t.Fatalf("unexpected error: %s, should be %s", err, ErrTransactionNotExists) - } - id := NewTransactionID() - timeout := time.Millisecond * 200 - if err := a.Start(id, time.Now().Add(timeout)); err != nil { - t.Fatal(err) - } - if err := a.Stop(id); err != nil { - t.Fatal(err) - } - select { - case e := <-called: - if e.Error != ErrTransactionStopped { - t.Fatalf("unexpected error: %s, should be %s", - e.Error, ErrTransactionStopped, - ) - } - case <-time.After(timeout * 2): - t.Fatal("timed out") - } - if err := a.Close(); err != nil { - t.Fatal(err) - } - if err := a.Close(); err != ErrAgentClosed { - t.Fatalf("a.Close returned %s instead of %s", err, ErrAgentClosed) - } - if err := a.Stop(transactionID{}); err != ErrAgentClosed { - t.Fatalf("unexpected error: %s, should be %s", err, ErrAgentClosed) - } -} - -func TestAgent_GC(t *testing.T) { - a := NewAgent(nil) - shouldTimeOutID := make(map[transactionID]bool) - deadline := time.Date(2027, time.November, 21, - 23, 0, 0, 0, - time.UTC, - ) - gcDeadline := deadline.Add(-time.Second) - deadlineNotGC := gcDeadline.AddDate(0, 0, -1) - a.SetHandler(func(e Event) { - id := e.TransactionID - shouldTimeOut, found := shouldTimeOutID[id] - if !found { - t.Error("unexpected transaction ID") - } - if shouldTimeOut && e.Error != ErrTransactionTimeOut { - t.Errorf("%x should time out, but got %v", id, e.Error) - } - if !shouldTimeOut && e.Error == ErrTransactionTimeOut { - t.Errorf("%x should not time out, but got %v", id, e.Error) - } - }) - for i := 0; i < 5; i++ { - id := NewTransactionID() - shouldTimeOutID[id] = false - if err := a.Start(id, deadline); err != nil { - t.Fatal(err) - } - } - for i := 0; i < 5; i++ { - id := NewTransactionID() - shouldTimeOutID[id] = true - if err := a.Start(id, deadlineNotGC); err != nil { - t.Fatal(err) - } - } - if err := a.Collect(gcDeadline); err != nil { - t.Fatal(err) - } - if err := a.Close(); err != nil { - t.Error(err) - } - if err := a.Collect(gcDeadline); err != ErrAgentClosed { - t.Errorf("should <%s>, but got <%s>", ErrAgentClosed, err) - } -} - -func BenchmarkAgent_GC(b *testing.B) { - a := NewAgent(nil) - deadline := time.Now().AddDate(0, 0, 1) - for i := 0; i < agentCollectCap; i++ { - if err := a.Start(NewTransactionID(), deadline); err != nil { - b.Fatal(err) - } - } - defer func() { - if err := a.Close(); err != nil { - b.Error(err) - } - }() - b.ReportAllocs() - gcDeadline := deadline.Add(-time.Second) - for i := 0; i < b.N; i++ { - if err := a.Collect(gcDeadline); err != nil { - b.Fatal(err) - } - } -} - -func BenchmarkAgent_Process(b *testing.B) { - a := NewAgent(nil) - deadline := time.Now().AddDate(0, 0, 1) - for i := 0; i < 1000; i++ { - if err := a.Start(NewTransactionID(), deadline); err != nil { - b.Fatal(err) - } - } - defer func() { - if err := a.Close(); err != nil { - b.Error(err) - } - }() - b.ReportAllocs() - m := MustBuild(TransactionID) - for i := 0; i < b.N; i++ { - if err := a.Process(m); err != nil { - b.Fatal(err) - } - } -} diff --git a/api/check.sh b/api/check.sh deleted file mode 100755 index 30dd3d9..0000000 --- a/api/check.sh +++ /dev/null @@ -1,2 +0,0 @@ -#!/usr/bin/env bash -api -c $(ls -dm stun*.txt | tr -d ' \n') -except except.txt gortc.io/stun diff --git a/api/except.txt b/api/except.txt deleted file mode 100644 index c69ff07..0000000 --- a/api/except.txt +++ /dev/null @@ -1,25 +0,0 @@ -pkg gortc.io/stun, type Handler interface { HandleEvent } -pkg gortc.io/stun, type Handler interface, HandleEvent(Event) -pkg gortc.io/stun, type HandlerFunc func(Event) -pkg gortc.io/stun, method (HandlerFunc) HandleEvent(Event) -pkg gortc.io/stun, method (*Client) Do(*Message, time.Time, func(Event)) error -pkg gortc.io/stun, method (*Client) Start(*Message, time.Time, Handler) error -pkg gortc.io/stun, method (*Client) Start(*Message, time.Time, Handler) error -pkg gortc.io/stun, type ClientAgent interface { Close, Collect, Process, Start, Stop } -pkg gortc.io/stun, method (*Agent) SetHandler(Handler) error -pkg gortc.io/stun, method (*Agent) Start([12]uint8, time.Time, Handler) error -pkg gortc.io/stun, func NewAgent(AgentOptions) *Agent -pkg gortc.io/stun, type ClientAgent interface, Start([12]uint8, time.Time, Handler) error -pkg gortc.io/stun, var ErrAttrSizeInvalid error -pkg gortc.io/stun, type AgentOptions struct -pkg gortc.io/stun, type AgentOptions struct, Handler Handler -pkg gortc.io/stun, func NewClient(ClientOptions) (*Client, error) -pkg gortc.io/stun, type ClientOptions struct -pkg gortc.io/stun, type ClientOptions struct, Agent ClientAgent -pkg gortc.io/stun, type ClientOptions struct, Clock Clock -pkg gortc.io/stun, type ClientOptions struct, Collector Collector -pkg gortc.io/stun, type ClientOptions struct, Connection Connection -pkg gortc.io/stun, type ClientOptions struct, Handler Handler -pkg gortc.io/stun, type ClientOptions struct, RTO time.Duration -pkg gortc.io/stun, type ClientOptions struct, TimeoutRate time.Duration -pkg gortc.io/stun, const CodeRoleConflict = 478 \ No newline at end of file diff --git a/api/stun1.0.0.txt b/api/stun1.0.0.txt deleted file mode 100644 index ae73e4f..0000000 --- a/api/stun1.0.0.txt +++ /dev/null @@ -1,323 +0,0 @@ -pkg gortc.io/stun, const AttrAlternateServer = 32803 -pkg gortc.io/stun, const AttrAlternateServer AttrType -pkg gortc.io/stun, const AttrChannelNumber = 12 -pkg gortc.io/stun, const AttrChannelNumber AttrType -pkg gortc.io/stun, const AttrData = 19 -pkg gortc.io/stun, const AttrData AttrType -pkg gortc.io/stun, const AttrDontFragment = 26 -pkg gortc.io/stun, const AttrDontFragment AttrType -pkg gortc.io/stun, const AttrErrorCode = 9 -pkg gortc.io/stun, const AttrErrorCode AttrType -pkg gortc.io/stun, const AttrEvenPort = 24 -pkg gortc.io/stun, const AttrEvenPort AttrType -pkg gortc.io/stun, const AttrFingerprint = 32808 -pkg gortc.io/stun, const AttrFingerprint AttrType -pkg gortc.io/stun, const AttrICEControlled = 32809 -pkg gortc.io/stun, const AttrICEControlled AttrType -pkg gortc.io/stun, const AttrICEControlling = 32810 -pkg gortc.io/stun, const AttrICEControlling AttrType -pkg gortc.io/stun, const AttrLifetime = 13 -pkg gortc.io/stun, const AttrLifetime AttrType -pkg gortc.io/stun, const AttrMappedAddress = 1 -pkg gortc.io/stun, const AttrMappedAddress AttrType -pkg gortc.io/stun, const AttrMessageIntegrity = 8 -pkg gortc.io/stun, const AttrMessageIntegrity AttrType -pkg gortc.io/stun, const AttrNonce = 21 -pkg gortc.io/stun, const AttrNonce AttrType -pkg gortc.io/stun, const AttrOrigin = 32815 -pkg gortc.io/stun, const AttrOrigin AttrType -pkg gortc.io/stun, const AttrPriority = 36 -pkg gortc.io/stun, const AttrPriority AttrType -pkg gortc.io/stun, const AttrRealm = 20 -pkg gortc.io/stun, const AttrRealm AttrType -pkg gortc.io/stun, const AttrRequestedTransport = 25 -pkg gortc.io/stun, const AttrRequestedTransport AttrType -pkg gortc.io/stun, const AttrReservationToken = 34 -pkg gortc.io/stun, const AttrReservationToken AttrType -pkg gortc.io/stun, const AttrSoftware = 32802 -pkg gortc.io/stun, const AttrSoftware AttrType -pkg gortc.io/stun, const AttrUnknownAttributes = 10 -pkg gortc.io/stun, const AttrUnknownAttributes AttrType -pkg gortc.io/stun, const AttrUseCandidate = 37 -pkg gortc.io/stun, const AttrUseCandidate AttrType -pkg gortc.io/stun, const AttrUsername = 6 -pkg gortc.io/stun, const AttrUsername AttrType -pkg gortc.io/stun, const AttrXORMappedAddress = 32 -pkg gortc.io/stun, const AttrXORMappedAddress AttrType -pkg gortc.io/stun, const AttrXORPeerAddress = 18 -pkg gortc.io/stun, const AttrXORPeerAddress AttrType -pkg gortc.io/stun, const AttrXORRelayedAddress = 22 -pkg gortc.io/stun, const AttrXORRelayedAddress AttrType -pkg gortc.io/stun, const ClassErrorResponse = 3 -pkg gortc.io/stun, const ClassErrorResponse MessageClass -pkg gortc.io/stun, const ClassIndication = 1 -pkg gortc.io/stun, const ClassIndication MessageClass -pkg gortc.io/stun, const ClassRequest = 0 -pkg gortc.io/stun, const ClassRequest MessageClass -pkg gortc.io/stun, const ClassSuccessResponse = 2 -pkg gortc.io/stun, const ClassSuccessResponse MessageClass -pkg gortc.io/stun, const CodeAllocMismatch = 437 -pkg gortc.io/stun, const CodeAllocMismatch ErrorCode -pkg gortc.io/stun, const CodeAllocQuotaReached = 486 -pkg gortc.io/stun, const CodeAllocQuotaReached ErrorCode -pkg gortc.io/stun, const CodeBadRequest = 400 -pkg gortc.io/stun, const CodeBadRequest ErrorCode -pkg gortc.io/stun, const CodeForbidden = 403 -pkg gortc.io/stun, const CodeForbidden ErrorCode -pkg gortc.io/stun, const CodeInsufficientCapacity = 508 -pkg gortc.io/stun, const CodeInsufficientCapacity ErrorCode -pkg gortc.io/stun, const CodeRoleConflict = 478 -pkg gortc.io/stun, const CodeRoleConflict ErrorCode -pkg gortc.io/stun, const CodeServerError = 500 -pkg gortc.io/stun, const CodeServerError ErrorCode -pkg gortc.io/stun, const CodeStaleNonce = 438 -pkg gortc.io/stun, const CodeStaleNonce ErrorCode -pkg gortc.io/stun, const CodeTryAlternate = 300 -pkg gortc.io/stun, const CodeTryAlternate ErrorCode -pkg gortc.io/stun, const CodeUnauthorised = 401 -pkg gortc.io/stun, const CodeUnauthorised ErrorCode -pkg gortc.io/stun, const CodeUnknownAttribute = 420 -pkg gortc.io/stun, const CodeUnknownAttribute ErrorCode -pkg gortc.io/stun, const CodeUnsupportedTransProto = 442 -pkg gortc.io/stun, const CodeUnsupportedTransProto ErrorCode -pkg gortc.io/stun, const CodeWrongCredentials = 441 -pkg gortc.io/stun, const CodeWrongCredentials ErrorCode -pkg gortc.io/stun, const DefaultPort = 3478 -pkg gortc.io/stun, const DefaultPort ideal-int -pkg gortc.io/stun, const DefaultTLSPort = 5349 -pkg gortc.io/stun, const DefaultTLSPort ideal-int -pkg gortc.io/stun, const MethodAllocate = 3 -pkg gortc.io/stun, const MethodAllocate Method -pkg gortc.io/stun, const MethodBinding = 1 -pkg gortc.io/stun, const MethodBinding Method -pkg gortc.io/stun, const MethodChannelBind = 9 -pkg gortc.io/stun, const MethodChannelBind Method -pkg gortc.io/stun, const MethodCreatePermission = 8 -pkg gortc.io/stun, const MethodCreatePermission Method -pkg gortc.io/stun, const MethodData = 7 -pkg gortc.io/stun, const MethodData Method -pkg gortc.io/stun, const MethodRefresh = 4 -pkg gortc.io/stun, const MethodRefresh Method -pkg gortc.io/stun, const MethodSend = 6 -pkg gortc.io/stun, const MethodSend Method -pkg gortc.io/stun, const TransactionIDSize = 12 -pkg gortc.io/stun, const TransactionIDSize ideal-int -pkg gortc.io/stun, func Build(...Setter) (*Message, error) -pkg gortc.io/stun, func CheckOverflow(AttrType, int, int) error -pkg gortc.io/stun, func CheckSize(AttrType, int, int) error -pkg gortc.io/stun, func Decode([]uint8, *Message) error -pkg gortc.io/stun, func Dial(string, string) (*Client, error) -pkg gortc.io/stun, func FingerprintValue([]uint8) uint32 -pkg gortc.io/stun, func IsAttrSizeInvalid(error) bool -pkg gortc.io/stun, func IsAttrSizeOverflow(error) bool -pkg gortc.io/stun, func IsMessage([]uint8) bool -pkg gortc.io/stun, func MustBuild(...Setter) *Message -pkg gortc.io/stun, func New() *Message -pkg gortc.io/stun, func NewAgent(AgentOptions) *Agent -pkg gortc.io/stun, func NewClient(ClientOptions) (*Client, error) -pkg gortc.io/stun, func NewLongTermIntegrity(string, string, string) MessageIntegrity -pkg gortc.io/stun, func NewNonce(string) Nonce -pkg gortc.io/stun, func NewRealm(string) Realm -pkg gortc.io/stun, func NewShortTermIntegrity(string) MessageIntegrity -pkg gortc.io/stun, func NewSoftware(string) Software -pkg gortc.io/stun, func NewTransactionID() [12]uint8 -pkg gortc.io/stun, func NewTransactionIDSetter([12]uint8) Setter -pkg gortc.io/stun, func NewType(Method, MessageClass) MessageType -pkg gortc.io/stun, func NewUsername(string) Username -pkg gortc.io/stun, method (*Agent) Close() error -pkg gortc.io/stun, method (*Agent) Collect(time.Time) error -pkg gortc.io/stun, method (*Agent) Process(*Message) error -pkg gortc.io/stun, method (*Agent) Start([12]uint8, time.Time, Handler) error -pkg gortc.io/stun, method (*Agent) Stop([12]uint8) error -pkg gortc.io/stun, method (*Agent) StopWithError([12]uint8, error) error -pkg gortc.io/stun, method (*AlternateServer) AddTo(*Message) error -pkg gortc.io/stun, method (*AlternateServer) GetFrom(*Message) error -pkg gortc.io/stun, method (*Client) Close() error -pkg gortc.io/stun, method (*Client) Do(*Message, time.Time, func(Event)) error -pkg gortc.io/stun, method (*Client) Indicate(*Message) error -pkg gortc.io/stun, method (*Client) Start(*Message, time.Time, Handler) error -pkg gortc.io/stun, method (*ErrorCodeAttribute) GetFrom(*Message) error -pkg gortc.io/stun, method (*MappedAddress) AddTo(*Message) error -pkg gortc.io/stun, method (*MappedAddress) GetFrom(*Message) error -pkg gortc.io/stun, method (*Message) Add(AttrType, []uint8) -pkg gortc.io/stun, method (*Message) AddTo(*Message) error -pkg gortc.io/stun, method (*Message) Build(...Setter) error -pkg gortc.io/stun, method (*Message) Check(...Checker) error -pkg gortc.io/stun, method (*Message) CloneTo(*Message) error -pkg gortc.io/stun, method (*Message) Contains(AttrType) bool -pkg gortc.io/stun, method (*Message) Decode() error -pkg gortc.io/stun, method (*Message) Encode() -pkg gortc.io/stun, method (*Message) Equal(*Message) bool -pkg gortc.io/stun, method (*Message) ForEach(AttrType, func(*Message) error) error -pkg gortc.io/stun, method (*Message) Get(AttrType) ([]uint8, error) -pkg gortc.io/stun, method (*Message) NewTransactionID() error -pkg gortc.io/stun, method (*Message) Parse(...Getter) error -pkg gortc.io/stun, method (*Message) ReadFrom(io.Reader) (int64, error) -pkg gortc.io/stun, method (*Message) Reset() -pkg gortc.io/stun, method (*Message) SetType(MessageType) -pkg gortc.io/stun, method (*Message) String() string -pkg gortc.io/stun, method (*Message) Write([]uint8) (int, error) -pkg gortc.io/stun, method (*Message) WriteAttributes() -pkg gortc.io/stun, method (*Message) WriteHeader() -pkg gortc.io/stun, method (*Message) WriteLength() -pkg gortc.io/stun, method (*Message) WriteTo(io.Writer) (int64, error) -pkg gortc.io/stun, method (*Message) WriteTransactionID() -pkg gortc.io/stun, method (*Message) WriteType() -pkg gortc.io/stun, method (*MessageType) ReadValue(uint16) -pkg gortc.io/stun, method (*Nonce) GetFrom(*Message) error -pkg gortc.io/stun, method (*Realm) GetFrom(*Message) error -pkg gortc.io/stun, method (*Software) GetFrom(*Message) error -pkg gortc.io/stun, method (*TextAttribute) GetFromAs(*Message, AttrType) error -pkg gortc.io/stun, method (*UnknownAttributes) GetFrom(*Message) error -pkg gortc.io/stun, method (*Username) GetFrom(*Message) error -pkg gortc.io/stun, method (*XORMappedAddress) GetFrom(*Message) error -pkg gortc.io/stun, method (*XORMappedAddress) GetFromAs(*Message, AttrType) error -pkg gortc.io/stun, method (AttrType) Optional() bool -pkg gortc.io/stun, method (AttrType) Required() bool -pkg gortc.io/stun, method (AttrType) String() string -pkg gortc.io/stun, method (AttrType) Value() uint16 -pkg gortc.io/stun, method (Attributes) Get(AttrType) (RawAttribute, bool) -pkg gortc.io/stun, method (CloseErr) Error() string -pkg gortc.io/stun, method (DecodeErr) Error() string -pkg gortc.io/stun, method (DecodeErr) IsInvalidCookie() bool -pkg gortc.io/stun, method (DecodeErr) IsPlace(DecodeErrPlace) bool -pkg gortc.io/stun, method (DecodeErr) IsPlaceChildren(string) bool -pkg gortc.io/stun, method (DecodeErr) IsPlaceParent(string) bool -pkg gortc.io/stun, method (DecodeErrPlace) String() string -pkg gortc.io/stun, method (ErrorCode) AddTo(*Message) error -pkg gortc.io/stun, method (ErrorCodeAttribute) AddTo(*Message) error -pkg gortc.io/stun, method (ErrorCodeAttribute) String() string -pkg gortc.io/stun, method (FingerprintAttr) AddTo(*Message) error -pkg gortc.io/stun, method (FingerprintAttr) Check(*Message) error -pkg gortc.io/stun, method (HandlerFunc) HandleEvent(Event) -pkg gortc.io/stun, method (MappedAddress) String() string -pkg gortc.io/stun, method (MessageClass) String() string -pkg gortc.io/stun, method (MessageIntegrity) AddTo(*Message) error -pkg gortc.io/stun, method (MessageIntegrity) Check(*Message) error -pkg gortc.io/stun, method (MessageIntegrity) String() string -pkg gortc.io/stun, method (MessageType) AddTo(*Message) error -pkg gortc.io/stun, method (MessageType) String() string -pkg gortc.io/stun, method (MessageType) Value() uint16 -pkg gortc.io/stun, method (Method) String() string -pkg gortc.io/stun, method (Nonce) AddTo(*Message) error -pkg gortc.io/stun, method (Nonce) String() string -pkg gortc.io/stun, method (RawAttribute) Equal(RawAttribute) bool -pkg gortc.io/stun, method (RawAttribute) String() string -pkg gortc.io/stun, method (Realm) AddTo(*Message) error -pkg gortc.io/stun, method (Realm) String() string -pkg gortc.io/stun, method (Software) AddTo(*Message) error -pkg gortc.io/stun, method (Software) String() string -pkg gortc.io/stun, method (StopErr) Error() string -pkg gortc.io/stun, method (TextAttribute) AddToAs(*Message, AttrType, int) error -pkg gortc.io/stun, method (UnknownAttributes) AddTo(*Message) error -pkg gortc.io/stun, method (UnknownAttributes) String() string -pkg gortc.io/stun, method (Username) AddTo(*Message) error -pkg gortc.io/stun, method (Username) String() string -pkg gortc.io/stun, method (XORMappedAddress) AddTo(*Message) error -pkg gortc.io/stun, method (XORMappedAddress) AddToAs(*Message, AttrType) error -pkg gortc.io/stun, method (XORMappedAddress) String() string -pkg gortc.io/stun, type Agent struct -pkg gortc.io/stun, type AgentOptions struct -pkg gortc.io/stun, type AgentOptions struct, Handler Handler -pkg gortc.io/stun, type AlternateServer struct -pkg gortc.io/stun, type AlternateServer struct, IP net.IP -pkg gortc.io/stun, type AlternateServer struct, Port int -pkg gortc.io/stun, type AttrType uint16 -pkg gortc.io/stun, type Attributes []RawAttribute -pkg gortc.io/stun, type Checker interface { Check } -pkg gortc.io/stun, type Checker interface, Check(*Message) error -pkg gortc.io/stun, type Client struct -pkg gortc.io/stun, type ClientAgent interface { Close, Collect, Process, Start, Stop } -pkg gortc.io/stun, type ClientAgent interface, Close() error -pkg gortc.io/stun, type ClientAgent interface, Collect(time.Time) error -pkg gortc.io/stun, type ClientAgent interface, Process(*Message) error -pkg gortc.io/stun, type ClientAgent interface, Start([12]uint8, time.Time, Handler) error -pkg gortc.io/stun, type ClientAgent interface, Stop([12]uint8) error -pkg gortc.io/stun, type ClientOptions struct -pkg gortc.io/stun, type ClientOptions struct, Agent ClientAgent -pkg gortc.io/stun, type ClientOptions struct, Connection Connection -pkg gortc.io/stun, type ClientOptions struct, TimeoutRate time.Duration -pkg gortc.io/stun, type CloseErr struct -pkg gortc.io/stun, type CloseErr struct, AgentErr error -pkg gortc.io/stun, type CloseErr struct, ConnectionErr error -pkg gortc.io/stun, type Connection interface { Close, Read, Write } -pkg gortc.io/stun, type Connection interface, Close() error -pkg gortc.io/stun, type Connection interface, Read([]uint8) (int, error) -pkg gortc.io/stun, type Connection interface, Write([]uint8) (int, error) -pkg gortc.io/stun, type DecodeErr struct -pkg gortc.io/stun, type DecodeErr struct, Message string -pkg gortc.io/stun, type DecodeErr struct, Place DecodeErrPlace -pkg gortc.io/stun, type DecodeErrPlace struct -pkg gortc.io/stun, type DecodeErrPlace struct, Children string -pkg gortc.io/stun, type DecodeErrPlace struct, Parent string -pkg gortc.io/stun, type ErrorCode int -pkg gortc.io/stun, type ErrorCodeAttribute struct -pkg gortc.io/stun, type ErrorCodeAttribute struct, Code ErrorCode -pkg gortc.io/stun, type ErrorCodeAttribute struct, Reason []uint8 -pkg gortc.io/stun, type Event struct -pkg gortc.io/stun, type Event struct, Error error -pkg gortc.io/stun, type Event struct, Message *Message -pkg gortc.io/stun, type FingerprintAttr struct -pkg gortc.io/stun, type Getter interface { GetFrom } -pkg gortc.io/stun, type Getter interface, GetFrom(*Message) error -pkg gortc.io/stun, type Handler interface { HandleEvent } -pkg gortc.io/stun, type Handler interface, HandleEvent(Event) -pkg gortc.io/stun, type HandlerFunc func(Event) -pkg gortc.io/stun, type MappedAddress struct -pkg gortc.io/stun, type MappedAddress struct, IP net.IP -pkg gortc.io/stun, type MappedAddress struct, Port int -pkg gortc.io/stun, type Message struct -pkg gortc.io/stun, type Message struct, Attributes Attributes -pkg gortc.io/stun, type Message struct, Length uint32 -pkg gortc.io/stun, type Message struct, Raw []uint8 -pkg gortc.io/stun, type Message struct, TransactionID [12]uint8 -pkg gortc.io/stun, type Message struct, Type MessageType -pkg gortc.io/stun, type MessageClass uint8 -pkg gortc.io/stun, type MessageIntegrity []uint8 -pkg gortc.io/stun, type MessageType struct -pkg gortc.io/stun, type MessageType struct, Class MessageClass -pkg gortc.io/stun, type MessageType struct, Method Method -pkg gortc.io/stun, type Method uint16 -pkg gortc.io/stun, type Nonce []uint8 -pkg gortc.io/stun, type RawAttribute struct -pkg gortc.io/stun, type RawAttribute struct, Length uint16 -pkg gortc.io/stun, type RawAttribute struct, Type AttrType -pkg gortc.io/stun, type RawAttribute struct, Value []uint8 -pkg gortc.io/stun, type Realm []uint8 -pkg gortc.io/stun, type Setter interface { AddTo } -pkg gortc.io/stun, type Setter interface, AddTo(*Message) error -pkg gortc.io/stun, type Software []uint8 -pkg gortc.io/stun, type StopErr struct -pkg gortc.io/stun, type StopErr struct, Cause error -pkg gortc.io/stun, type StopErr struct, Err error -pkg gortc.io/stun, type TextAttribute []uint8 -pkg gortc.io/stun, type UnknownAttributes []AttrType -pkg gortc.io/stun, type Username []uint8 -pkg gortc.io/stun, type XORMappedAddress struct -pkg gortc.io/stun, type XORMappedAddress struct, IP net.IP -pkg gortc.io/stun, type XORMappedAddress struct, Port int -pkg gortc.io/stun, var BindingError MessageType -pkg gortc.io/stun, var BindingRequest MessageType -pkg gortc.io/stun, var BindingSuccess MessageType -pkg gortc.io/stun, var ErrAgentClosed error -pkg gortc.io/stun, var ErrAttrSizeInvalid error -pkg gortc.io/stun, var ErrAttributeNotFound error -pkg gortc.io/stun, var ErrAttributeSizeInvalid error -pkg gortc.io/stun, var ErrAttributeSizeOverflow error -pkg gortc.io/stun, var ErrBadIPLength error -pkg gortc.io/stun, var ErrBadUnknownAttrsSize error -pkg gortc.io/stun, var ErrClientClosed error -pkg gortc.io/stun, var ErrClientNotInitialized error -pkg gortc.io/stun, var ErrDecodeToNil error -pkg gortc.io/stun, var ErrFingerprintBeforeIntegrity error -pkg gortc.io/stun, var ErrFingerprintMismatch error -pkg gortc.io/stun, var ErrIntegrityMismatch error -pkg gortc.io/stun, var ErrNoConnection error -pkg gortc.io/stun, var ErrNoDefaultReason error -pkg gortc.io/stun, var ErrTransactionExists error -pkg gortc.io/stun, var ErrTransactionNotExists error -pkg gortc.io/stun, var ErrTransactionStopped error -pkg gortc.io/stun, var ErrTransactionTimeOut error -pkg gortc.io/stun, var ErrUnexpectedHeaderEOF error -pkg gortc.io/stun, var Fingerprint FingerprintAttr -pkg gortc.io/stun, var TransactionID Setter diff --git a/api/stun1.14.0.txt b/api/stun1.14.0.txt deleted file mode 100644 index eef9887..0000000 --- a/api/stun1.14.0.txt +++ /dev/null @@ -1,21 +0,0 @@ -pkg gortc.io/stun, type Handler func(Event) -pkg gortc.io/stun, method (*Client) Do(*Message, func(Event)) error -pkg gortc.io/stun, method (*Client) SetRTO(time.Duration) -pkg gortc.io/stun, method (*Client) Start(*Message, Handler) error -pkg gortc.io/stun, type Clock interface { Now } -pkg gortc.io/stun, type Clock interface, Now() time.Time -pkg gortc.io/stun, type Collector interface { Close, Start } -pkg gortc.io/stun, type Collector interface, Close() error -pkg gortc.io/stun, type Collector interface, Start(time.Duration, func(time.Time)) error -pkg gortc.io/stun, type Event struct, TransactionID [12]uint8 -pkg gortc.io/stun, method (*Agent) SetHandler(Handler) error -pkg gortc.io/stun, method (*Agent) Start([12]uint8, time.Time) error -pkg gortc.io/stun, type ClientAgent interface { Close, Collect, Process, SetHandler, Start, Stop } -pkg gortc.io/stun, type ClientAgent interface, SetHandler(Handler) error -pkg gortc.io/stun, type ClientAgent interface, Start([12]uint8, time.Time) error -pkg gortc.io/stun, type ClientOptions struct, Handler Handler -pkg gortc.io/stun, type ClientOptions struct, Clock Clock -pkg gortc.io/stun, type ClientOptions struct, Collector Collector -pkg gortc.io/stun, type ClientOptions struct, RTO time.Duration -pkg gortc.io/stun, func NewAgent(Handler) *Agent -pkg gortc.io/stun, var NoopHandler Handler diff --git a/api/stun1.15.0.txt b/api/stun1.15.0.txt deleted file mode 100644 index 2cd8ca3..0000000 --- a/api/stun1.15.0.txt +++ /dev/null @@ -1,6 +0,0 @@ -pkg gortc.io/stun, const AttrRequestedAddressFamily = 23 -pkg gortc.io/stun, const AttrRequestedAddressFamily AttrType -pkg gortc.io/stun, const CodeAddrFamilyNotSupported = 440 -pkg gortc.io/stun, const CodeAddrFamilyNotSupported ErrorCode -pkg gortc.io/stun, const CodePeerAddrFamilyMismatch = 443 -pkg gortc.io/stun, const CodePeerAddrFamilyMismatch ErrorCode diff --git a/api/stun1.16.0.txt b/api/stun1.16.0.txt deleted file mode 100644 index b1858da..0000000 --- a/api/stun1.16.0.txt +++ /dev/null @@ -1,9 +0,0 @@ -pkg gortc.io/stun, func NewClient(Connection, ...ClientOption) (*Client, error) -pkg gortc.io/stun, func WithAgent(ClientAgent) ClientOption -pkg gortc.io/stun, func WithClock(Clock) ClientOption -pkg gortc.io/stun, func WithCollector(Collector) ClientOption -pkg gortc.io/stun, func WithHandler(Handler) ClientOption -pkg gortc.io/stun, func WithRTO(time.Duration) ClientOption -pkg gortc.io/stun, func WithTimeoutRate(time.Duration) ClientOption -pkg gortc.io/stun, func WithNoRetransmit(*Client) -pkg gortc.io/stun, type ClientOption func(*Client) diff --git a/api/stun1.17.0.txt b/api/stun1.17.0.txt deleted file mode 100644 index 7fb8b22..0000000 --- a/api/stun1.17.0.txt +++ /dev/null @@ -1,10 +0,0 @@ -pkg gortc.io/stun, const Scheme = "stun" -pkg gortc.io/stun, const Scheme ideal-string -pkg gortc.io/stun, const SchemeSecure = "stuns" -pkg gortc.io/stun, const SchemeSecure ideal-string -pkg gortc.io/stun, func ParseURI(string) (URI, error) -pkg gortc.io/stun, method (URI) String() string -pkg gortc.io/stun, type URI struct -pkg gortc.io/stun, type URI struct, Host string -pkg gortc.io/stun, type URI struct, Port int -pkg gortc.io/stun, type URI struct, Scheme string diff --git a/api/stun1.17.3.txt b/api/stun1.17.3.txt deleted file mode 100644 index 46d4e30..0000000 --- a/api/stun1.17.3.txt +++ /dev/null @@ -1,12 +0,0 @@ -pkg gortc.io/stun, const AttrConnectionID = 42 -pkg gortc.io/stun, const AttrConnectionID AttrType -pkg gortc.io/stun, const CodeConnAlreadyExists = 446 -pkg gortc.io/stun, const CodeConnAlreadyExists ErrorCode -pkg gortc.io/stun, const CodeConnTimeoutOrFailure = 447 -pkg gortc.io/stun, const CodeConnTimeoutOrFailure ErrorCode -pkg gortc.io/stun, const MethodConnect = 10 -pkg gortc.io/stun, const MethodConnect Method -pkg gortc.io/stun, const MethodConnectionAttempt = 12 -pkg gortc.io/stun, const MethodConnectionAttempt Method -pkg gortc.io/stun, const MethodConnectionBind = 11 -pkg gortc.io/stun, const MethodConnectionBind Method diff --git a/api/stun1.18.0.txt b/api/stun1.18.0.txt deleted file mode 100644 index 6bb4328..0000000 --- a/api/stun1.18.0.txt +++ /dev/null @@ -1 +0,0 @@ -pkg gortc.io/stun, method (RawAttribute) AddTo(*Message) error diff --git a/api/stun1.18.1.txt b/api/stun1.18.1.txt deleted file mode 100644 index af2a5d3..0000000 --- a/api/stun1.18.1.txt +++ /dev/null @@ -1,3 +0,0 @@ -pkg gortc.io/stun, const CodeRoleConflict = 487 -pkg gortc.io/stun, const CodeUnauthorized = 401 -pkg gortc.io/stun, const CodeUnauthorized ErrorCode diff --git a/api/stun1.19.0.txt b/api/stun1.19.0.txt deleted file mode 100644 index 83e359f..0000000 --- a/api/stun1.19.0.txt +++ /dev/null @@ -1 +0,0 @@ -pkg gortc.io/stun, var WithNoConnClose ClientOption diff --git a/checks.go b/checks.go index 7b610c5..0c27f93 100644 --- a/checks.go +++ b/checks.go @@ -2,7 +2,7 @@ package stun -import "gortc.io/stun/internal/hmac" +import "gortc.io/stun/v2/internal/hmac" // CheckSize returns ErrAttrSizeInvalid if got is not equal to expected. func CheckSize(_ AttrType, got, expected int) error { diff --git a/checks_debug.go b/checks_debug.go index 3bd10ee..51d20dd 100644 --- a/checks_debug.go +++ b/checks_debug.go @@ -2,7 +2,7 @@ package stun -import "gortc.io/stun/internal/hmac" +import "gortc.io/stun/v2/internal/hmac" // CheckSize returns *AttrLengthError if got is not equal to expected. func CheckSize(a AttrType, got, expected int) error { diff --git a/client.go b/client.go deleted file mode 100644 index 9374e96..0000000 --- a/client.go +++ /dev/null @@ -1,631 +0,0 @@ -package stun - -import ( - "errors" - "fmt" - "io" - "log" - "net" - "runtime" - "sync" - "sync/atomic" - "time" -) - -// Dial connects to the address on the named network and then -// initializes Client on that connection, returning error if any. -func Dial(network, address string) (*Client, error) { - conn, err := net.Dial(network, address) - if err != nil { - return nil, err - } - return NewClient(conn) -} - -// ErrNoConnection means that ClientOptions.Connection is nil. -var ErrNoConnection = errors.New("no connection provided") - -// ClientOption sets some client option. -type ClientOption func(c *Client) - -// WithHandler sets client handler which is called if Agent emits the Event -// with TransactionID that is not currently registered by Client. -// Useful for handling Data indications from TURN server. -func WithHandler(h Handler) ClientOption { - return func(c *Client) { - c.handler = h - } -} - -// WithRTO sets client RTO as defined in STUN RFC. -func WithRTO(rto time.Duration) ClientOption { - return func(c *Client) { - c.rto = int64(rto) - } -} - -// WithClock sets Clock of client, the source of current time. -// Also clock is passed to default collector if set. -func WithClock(clock Clock) ClientOption { - return func(c *Client) { - c.clock = clock - } -} - -// WithTimeoutRate sets RTO timer minimum resolution. -func WithTimeoutRate(d time.Duration) ClientOption { - return func(c *Client) { - c.rtoRate = d - } -} - -// WithAgent sets client STUN agent. -// -// Defaults to agent implementation in current package, -// see agent.go. -func WithAgent(a ClientAgent) ClientOption { - return func(c *Client) { - c.a = a - } -} - -// WithCollector rests client timeout collector, the implementation -// of ticker which calls function on each tick. -func WithCollector(coll Collector) ClientOption { - return func(c *Client) { - c.collector = coll - } -} - -// WithNoConnClose prevents client from closing underlying connection when -// the Close() method is called. -var WithNoConnClose ClientOption = func(c *Client) { - c.closeConn = false -} - -// WithNoRetransmit disables retransmissions and sets RTO to -// defaultMaxAttempts * defaultRTO which will be effectively time out -// if not set. -// -// Useful for TCP connections where transport handles RTO. -func WithNoRetransmit(c *Client) { - c.maxAttempts = 0 - if c.rto == 0 { - c.rto = defaultMaxAttempts * int64(defaultRTO) - } -} - -const ( - defaultTimeoutRate = time.Millisecond * 5 - defaultRTO = time.Millisecond * 300 - defaultMaxAttempts = 7 -) - -// NewClient initializes new Client from provided options, -// starting internal goroutines and using default options fields -// if necessary. Call Close method after using Client to close conn and -// release resources. -// -// The conn will be closed on Close call. Use WithNoConnClose option to -// prevent that. -// -// Note that user should handle the protocol multiplexing, client does not -// provide any API for it, so if you need to read application data, wrap the -// connection with your (de-)multiplexer and pass the wrapper as conn. -func NewClient(conn Connection, options ...ClientOption) (*Client, error) { - c := &Client{ - close: make(chan struct{}), - c: conn, - clock: systemClock, - rto: int64(defaultRTO), - rtoRate: defaultTimeoutRate, - t: make(map[transactionID]*clientTransaction, 100), - maxAttempts: defaultMaxAttempts, - closeConn: true, - } - for _, o := range options { - o(c) - } - if c.c == nil { - return nil, ErrNoConnection - } - if c.a == nil { - c.a = NewAgent(nil) - } - if err := c.a.SetHandler(c.handleAgentCallback); err != nil { - return nil, err - } - if c.collector == nil { - c.collector = &tickerCollector{ - close: make(chan struct{}), - clock: c.clock, - } - } - if err := c.collector.Start(c.rtoRate, func(t time.Time) { - closedOrPanic(c.a.Collect(t)) - }); err != nil { - return nil, err - } - c.wg.Add(1) - go c.readUntilClosed() - runtime.SetFinalizer(c, clientFinalizer) - return c, nil -} - -func clientFinalizer(c *Client) { - if c == nil { - return - } - err := c.Close() - if err == ErrClientClosed { - return - } - if err == nil { - log.Println("client: called finalizer on non-closed client") - return - } - log.Println("client: called finalizer on non-closed client:", err) -} - -// Connection wraps Reader, Writer and Closer interfaces. -type Connection interface { - io.Reader - io.Writer - io.Closer -} - -// ClientAgent is Agent implementation that is used by Client to -// process transactions. -type ClientAgent interface { - Process(*Message) error - Close() error - Start(id [TransactionIDSize]byte, deadline time.Time) error - Stop(id [TransactionIDSize]byte) error - Collect(time.Time) error - SetHandler(h Handler) error -} - -// Client simulates "connection" to STUN server. -type Client struct { - rto int64 // time.Duration - a ClientAgent - c Connection - close chan struct{} - rtoRate time.Duration - maxAttempts int32 - closed bool - closeConn bool // should call c.Close() while closing - wg sync.WaitGroup - clock Clock - handler Handler - collector Collector - t map[transactionID]*clientTransaction - - // mux guards closed and t - mux sync.RWMutex -} - -// clientTransaction represents transaction in progress. -// If transaction is succeed or failed, f will be called -// provided by event. -// Concurrent access is invalid. -type clientTransaction struct { - id transactionID - attempt int32 - calls int32 - h Handler - start time.Time - rto time.Duration - raw []byte -} - -func (t *clientTransaction) handle(e Event) { - if atomic.AddInt32(&t.calls, 1) == 1 { - t.h(e) - } -} - -var clientTransactionPool = &sync.Pool{ - New: func() interface{} { - return &clientTransaction{ - raw: make([]byte, 1500), - } - }, -} - -func acquireClientTransaction() *clientTransaction { - return clientTransactionPool.Get().(*clientTransaction) -} - -func putClientTransaction(t *clientTransaction) { - t.raw = t.raw[:0] - t.start = time.Time{} - t.attempt = 0 - t.id = transactionID{} - clientTransactionPool.Put(t) -} - -func (t *clientTransaction) nextTimeout(now time.Time) time.Time { - return now.Add(time.Duration(t.attempt+1) * t.rto) -} - -// start registers transaction. -// -// Could return ErrClientClosed, ErrTransactionExists. -func (c *Client) start(t *clientTransaction) error { - c.mux.Lock() - defer c.mux.Unlock() - if c.closed { - return ErrClientClosed - } - _, exists := c.t[t.id] - if exists { - return ErrTransactionExists - } - c.t[t.id] = t - return nil -} - -// Clock abstracts the source of current time. -type Clock interface { - Now() time.Time -} - -type systemClockService struct{} - -func (systemClockService) Now() time.Time { return time.Now() } - -var systemClock = systemClockService{} - -// SetRTO sets current RTO value. -func (c *Client) SetRTO(rto time.Duration) { - atomic.StoreInt64(&c.rto, int64(rto)) -} - -// StopErr occurs when Client fails to stop transaction while -// processing error. -type StopErr struct { - Err error // value returned by Stop() - Cause error // error that caused Stop() call -} - -func (e StopErr) Error() string { - return fmt.Sprintf("error while stopping due to %s: %s", sprintErr(e.Cause), sprintErr(e.Err)) -} - -// CloseErr indicates client close failure. -type CloseErr struct { - AgentErr error - ConnectionErr error -} - -func sprintErr(err error) string { - if err == nil { - return "" - } - return err.Error() -} - -func (c CloseErr) Error() string { - return fmt.Sprintf("failed to close: %s (connection), %s (agent)", sprintErr(c.ConnectionErr), sprintErr(c.AgentErr)) -} - -func (c *Client) readUntilClosed() { - defer c.wg.Done() - m := new(Message) - m.Raw = make([]byte, 1024) - for { - select { - case <-c.close: - return - default: - } - _, err := m.ReadFrom(c.c) - if err == nil { - if pErr := c.a.Process(m); pErr == ErrAgentClosed { - return - } - } - } -} - -func closedOrPanic(err error) { - if err == nil || err == ErrAgentClosed { - return - } - panic(err) -} - -type tickerCollector struct { - close chan struct{} - wg sync.WaitGroup - clock Clock -} - -// Collector calls function f with constant rate. -// -// The simple Collector is ticker which calls function on each tick. -type Collector interface { - Start(rate time.Duration, f func(now time.Time)) error - Close() error -} - -func (a *tickerCollector) Start(rate time.Duration, f func(now time.Time)) error { - t := time.NewTicker(rate) - a.wg.Add(1) - go func() { - defer a.wg.Done() - for { - select { - case <-a.close: - t.Stop() - return - case <-t.C: - f(a.clock.Now()) - } - } - }() - return nil -} - -func (a *tickerCollector) Close() error { - close(a.close) - a.wg.Wait() - return nil -} - -// ErrClientClosed indicates that client is closed. -var ErrClientClosed = errors.New("client is closed") - -// Close stops internal connection and agent, returning CloseErr on error. -func (c *Client) Close() error { - if err := c.checkInit(); err != nil { - return err - } - c.mux.Lock() - if c.closed { - c.mux.Unlock() - return ErrClientClosed - } - c.closed = true - c.mux.Unlock() - if closeErr := c.collector.Close(); closeErr != nil { - return closeErr - } - var connErr error - agentErr := c.a.Close() - if c.closeConn { - connErr = c.c.Close() - } - close(c.close) - c.wg.Wait() - if agentErr == nil && connErr == nil { - return nil - } - return CloseErr{ - AgentErr: agentErr, - ConnectionErr: connErr, - } -} - -// Indicate sends indication m to server. Shorthand to Start call -// with zero deadline and callback. -func (c *Client) Indicate(m *Message) error { - return c.Start(m, nil) -} - -// callbackWaitHandler blocks on wait() call until callback is called. -type callbackWaitHandler struct { - handler Handler - callback func(event Event) - cond *sync.Cond - processed bool -} - -func (s *callbackWaitHandler) HandleEvent(e Event) { - s.cond.L.Lock() - if s.callback == nil { - panic("s.callback is nil") - } - s.callback(e) - s.processed = true - s.cond.Broadcast() - s.cond.L.Unlock() -} - -func (s *callbackWaitHandler) wait() { - s.cond.L.Lock() - for !s.processed { - s.cond.Wait() - } - s.processed = false - s.callback = nil - s.cond.L.Unlock() -} - -func (s *callbackWaitHandler) setCallback(f func(event Event)) { - if f == nil { - panic("f is nil") - } - s.cond.L.Lock() - s.callback = f - if s.handler == nil { - s.handler = s.HandleEvent - } - s.cond.L.Unlock() -} - -var callbackWaitHandlerPool = sync.Pool{ - New: func() interface{} { - return &callbackWaitHandler{ - cond: sync.NewCond(new(sync.Mutex)), - } - }, -} - -// ErrClientNotInitialized means that client connection or agent is nil. -var ErrClientNotInitialized = errors.New("client not initialized") - -func (c *Client) checkInit() error { - if c == nil || c.c == nil || c.a == nil || c.close == nil { - return ErrClientNotInitialized - } - return nil -} - -// Do is Start wrapper that waits until callback is called. If no callback -// provided, Indicate is called instead. -// -// Do has cpu overhead due to blocking, see BenchmarkClient_Do. -// Use Start method for less overhead. -func (c *Client) Do(m *Message, f func(Event)) error { - if err := c.checkInit(); err != nil { - return err - } - if f == nil { - return c.Indicate(m) - } - h := callbackWaitHandlerPool.Get().(*callbackWaitHandler) - h.setCallback(f) - defer func() { - callbackWaitHandlerPool.Put(h) - }() - if err := c.Start(m, h.handler); err != nil { - return err - } - h.wait() - return nil -} - -func (c *Client) delete(id transactionID) { - c.mux.Lock() - if c.t != nil { - delete(c.t, id) - } - c.mux.Unlock() -} - -type buffer struct { - buf []byte -} - -var bufferPool = &sync.Pool{ - New: func() interface{} { - return &buffer{buf: make([]byte, 2048)} - }, -} - -func (c *Client) handleAgentCallback(e Event) { - c.mux.Lock() - if c.closed { - c.mux.Unlock() - return - } - t, found := c.t[e.TransactionID] - if found { - delete(c.t, t.id) - } - c.mux.Unlock() - if !found { - if c.handler != nil && e.Error != ErrTransactionStopped { - c.handler(e) - } - // Ignoring. - return - } - if atomic.LoadInt32(&c.maxAttempts) <= t.attempt || e.Error == nil { - // Transaction completed. - t.handle(e) - putClientTransaction(t) - return - } - // Doing re-transmission. - t.attempt++ - b := bufferPool.Get().(*buffer) - b.buf = b.buf[:copy(b.buf[:cap(b.buf)], t.raw)] - defer bufferPool.Put(b) - var ( - now = c.clock.Now() - timeOut = t.nextTimeout(now) - id = t.id - ) - // Starting client transaction. - if startErr := c.start(t); startErr != nil { - c.delete(id) - e.Error = startErr - t.handle(e) - putClientTransaction(t) - return - } - // Starting agent transaction. - if startErr := c.a.Start(id, timeOut); startErr != nil { - c.delete(id) - e.Error = startErr - t.handle(e) - putClientTransaction(t) - return - } - // Writing message to connection again. - _, writeErr := c.c.Write(b.buf) - if writeErr != nil { - c.delete(id) - e.Error = writeErr - // Stopping agent transaction instead of waiting until it's deadline. - // This will call handleAgentCallback with "ErrTransactionStopped" error - // which will be ignored. - if stopErr := c.a.Stop(id); stopErr != nil { - // Failed to stop agent transaction. Wrapping the error in StopError. - e.Error = StopErr{ - Err: stopErr, - Cause: writeErr, - } - } - t.handle(e) - putClientTransaction(t) - return - } -} - -// Start starts transaction (if h set) and writes message to server, handler -// is called asynchronously. -func (c *Client) Start(m *Message, h Handler) error { - if err := c.checkInit(); err != nil { - return err - } - c.mux.RLock() - closed := c.closed - c.mux.RUnlock() - if closed { - return ErrClientClosed - } - if h != nil { - // Starting transaction only if h is set. Useful for indications. - t := acquireClientTransaction() - t.id = m.TransactionID - t.start = c.clock.Now() - t.h = h - t.rto = time.Duration(atomic.LoadInt64(&c.rto)) - t.attempt = 0 - t.raw = append(t.raw[:0], m.Raw...) - t.calls = 0 - d := t.nextTimeout(t.start) - if err := c.start(t); err != nil { - return err - } - if err := c.a.Start(m.TransactionID, d); err != nil { - return err - } - } - _, err := m.WriteTo(c.c) - if err != nil && h != nil { - c.delete(m.TransactionID) - // Stopping transaction instead of waiting until deadline. - if stopErr := c.a.Stop(m.TransactionID); stopErr != nil { - return StopErr{ - Err: stopErr, - Cause: err, - } - } - } - return err -} diff --git a/client_test.go b/client_test.go deleted file mode 100644 index 829e365..0000000 --- a/client_test.go +++ /dev/null @@ -1,1433 +0,0 @@ -package stun - -import ( - "bufio" - "bytes" - "errors" - "fmt" - "io" - "log" - "net" - "os" - "sync" - "testing" - "time" -) - -type TestAgent struct { - h Handler - e chan Event -} - -func (n *TestAgent) SetHandler(h Handler) error { - n.h = h - return nil -} - -func (n *TestAgent) Close() error { - close(n.e) - return nil -} - -func (TestAgent) Collect(time.Time) error { return nil } - -func (TestAgent) Process(m *Message) error { return nil } - -func (n *TestAgent) Start(id [TransactionIDSize]byte, deadline time.Time) error { - n.e <- Event{ - TransactionID: id, - } - return nil -} - -func (n *TestAgent) Stop([TransactionIDSize]byte) error { - return nil -} - -type noopConnection struct{} - -func (noopConnection) Write(b []byte) (int, error) { - return len(b), nil -} - -func (noopConnection) Read(b []byte) (int, error) { - time.Sleep(time.Millisecond) - return 0, io.EOF -} - -func (noopConnection) Close() error { - return nil -} - -func BenchmarkClient_Do(b *testing.B) { - b.ReportAllocs() - agent := &TestAgent{ - e: make(chan Event, 1000), - } - client, err := NewClient(noopConnection{}, - WithAgent(agent), - ) - if err != nil { - log.Fatal(err) - } - defer client.Close() - noopF := func(event Event) { - // pass - } - b.RunParallel(func(pb *testing.PB) { - go func() { - for e := range agent.e { - agent.h(e) - } - }() - m := New() - m.NewTransactionID() - m.Encode() - for pb.Next() { - if err := client.Do(m, noopF); err != nil { - b.Error(err) - } - } - }) -} - -type testConnection struct { - write func([]byte) (int, error) - read func([]byte) (int, error) - close func() error - b []byte - stopped bool - stoppedMux sync.Mutex -} - -func (t *testConnection) Write(b []byte) (int, error) { - return t.write(b) -} - -func (t *testConnection) Close() error { - if t.close != nil { - return t.close() - } - t.stoppedMux.Lock() - defer t.stoppedMux.Unlock() - if t.stopped { - return errors.New("already stopped") - } - t.stopped = true - return nil -} - -func (t *testConnection) Read(b []byte) (int, error) { - t.stoppedMux.Lock() - defer t.stoppedMux.Unlock() - if t.stopped { - return 0, io.EOF - } - if t.read != nil { - return t.read(b) - } - return copy(b, t.b), nil -} - -func TestClosedOrPanic(t *testing.T) { - closedOrPanic(nil) - closedOrPanic(ErrAgentClosed) - func() { - defer func() { - r := recover() - if r != io.EOF { - t.Error(r) - } - }() - closedOrPanic(io.EOF) - }() -} - -func TestClient_Start(t *testing.T) { - response := MustBuild(TransactionID, BindingSuccess) - response.Encode() - write := make(chan struct{}, 1) - read := make(chan struct{}, 1) - conn := &testConnection{ - b: response.Raw, - read: func(i []byte) (int, error) { - t.Log("waiting for read") - select { - case <-read: - t.Log("reading") - copy(i, response.Raw) - return len(response.Raw), nil - case <-time.After(time.Millisecond * 10): - return 0, errors.New("read timed out") - } - }, - write: func(bytes []byte) (int, error) { - t.Log("waiting for write") - select { - case <-write: - t.Log("writing") - return len(bytes), nil - case <-time.After(time.Millisecond * 10): - return 0, errors.New("write timed out") - } - }, - } - c, err := NewClient(conn) - if err != nil { - log.Fatal(err) - } - defer func() { - if err := c.Close(); err != nil { - t.Error(err) - } - if err := c.Close(); err == nil { - t.Error("second close should fail") - } - if err := c.Do(MustBuild(TransactionID), nil); err == nil { - t.Error("Do after Close should fail") - } - }() - m := MustBuild(response, BindingRequest) - t.Log("init") - got := make(chan struct{}) - write <- struct{}{} - t.Log("starting the first transaction") - if err := c.Start(m, func(event Event) { - t.Log("got first transaction callback") - if event.Error != nil { - t.Error(event.Error) - } - got <- struct{}{} - }); err != nil { - t.Error(err) - } - t.Log("starting the second transaction") - if err := c.Start(m, func(e Event) { - t.Error("should not be called") - }); err != ErrTransactionExists { - t.Errorf("unexpected error %v", err) - } - read <- struct{}{} - select { - case <-got: - // pass - case <-time.After(time.Millisecond * 10): - t.Error("timed out") - } -} - -func TestClient_Do(t *testing.T) { - response := MustBuild(TransactionID, BindingSuccess) - response.Encode() - conn := &testConnection{ - b: response.Raw, - write: func(bytes []byte) (int, error) { - return len(bytes), nil - }, - } - c, err := NewClient(conn) - if err != nil { - log.Fatal(err) - } - defer func() { - if err := c.Close(); err != nil { - t.Error(err) - } - if err := c.Close(); err == nil { - t.Error("second close should fail") - } - if err := c.Do(MustBuild(TransactionID), nil); err == nil { - t.Error("Do after Close should fail") - } - }() - m := MustBuild( - NewTransactionIDSetter(response.TransactionID), - ) - if err := c.Do(m, func(event Event) { - if event.Error != nil { - t.Error(event.Error) - } - }); err != nil { - t.Error(err) - } - m = MustBuild(TransactionID) - if err := c.Do(m, nil); err != nil { - t.Error(err) - } -} - -func TestCloseErr_Error(t *testing.T) { - for id, c := range []struct { - Err CloseErr - Out string - }{ - {CloseErr{}, "failed to close: (connection), (agent)"}, - {CloseErr{ - AgentErr: io.ErrUnexpectedEOF, - }, "failed to close: (connection), unexpected EOF (agent)"}, - {CloseErr{ - ConnectionErr: io.ErrUnexpectedEOF, - }, "failed to close: unexpected EOF (connection), (agent)"}, - } { - if out := c.Err.Error(); out != c.Out { - t.Errorf("[%d]: Error(%#v) %q (got) != %q (expected)", - id, c.Err, out, c.Out, - ) - } - } -} - -func TestStopErr_Error(t *testing.T) { - for id, c := range []struct { - Err StopErr - Out string - }{ - {StopErr{}, "error while stopping due to : "}, - {StopErr{ - Err: io.ErrUnexpectedEOF, - }, "error while stopping due to : unexpected EOF"}, - {StopErr{ - Cause: io.ErrUnexpectedEOF, - }, "error while stopping due to unexpected EOF: "}, - } { - if out := c.Err.Error(); out != c.Out { - t.Errorf("[%d]: Error(%#v) %q (got) != %q (expected)", - id, c.Err, out, c.Out, - ) - } - } -} - -type errorAgent struct { - startErr error - stopErr error - closeErr error - setHandlerError error -} - -func (a errorAgent) SetHandler(h Handler) error { return a.setHandlerError } - -func (a errorAgent) Close() error { return a.closeErr } - -func (errorAgent) Collect(time.Time) error { return nil } - -func (errorAgent) Process(m *Message) error { return nil } - -func (a errorAgent) Start(id [TransactionIDSize]byte, deadline time.Time) error { - return a.startErr -} - -func (a errorAgent) Stop([TransactionIDSize]byte) error { - return a.stopErr -} - -func TestClientAgentError(t *testing.T) { - response := MustBuild(TransactionID, BindingSuccess) - response.Encode() - conn := &testConnection{ - b: response.Raw, - write: func(bytes []byte) (int, error) { - return len(bytes), nil - }, - } - c, err := NewClient(conn, - WithAgent(errorAgent{ - startErr: io.ErrUnexpectedEOF, - }), - ) - if err != nil { - log.Fatal(err) - } - defer func() { - if err := c.Close(); err != nil { - t.Error(err) - } - }() - m := MustBuild(NewTransactionIDSetter(response.TransactionID)) - if err := c.Do(m, nil); err != nil { - t.Error(err) - } - if err := c.Do(m, func(event Event) { - if event.Error == nil { - t.Error("error expected") - } - }); err != io.ErrUnexpectedEOF { - t.Error("error expected") - } -} - -func TestClientConnErr(t *testing.T) { - conn := &testConnection{ - write: func(bytes []byte) (int, error) { - return 0, io.ErrClosedPipe - }, - } - c, err := NewClient(conn) - if err != nil { - log.Fatal(err) - } - defer func() { - if err := c.Close(); err != nil { - t.Error(err) - } - }() - m := MustBuild(TransactionID) - if err := c.Do(m, nil); err == nil { - t.Error("error expected") - } - if err := c.Do(m, NoopHandler); err == nil { - t.Error("error expected") - } -} - -func TestClientConnErrStopErr(t *testing.T) { - conn := &testConnection{ - write: func(bytes []byte) (int, error) { - return 0, io.ErrClosedPipe - }, - } - c, err := NewClient(conn, - WithAgent(errorAgent{ - stopErr: io.ErrUnexpectedEOF, - }), - ) - if err != nil { - log.Fatal(err) - } - defer func() { - if err := c.Close(); err != nil { - t.Error(err) - } - }() - m := MustBuild(TransactionID) - if err := c.Do(m, NoopHandler); err == nil { - t.Error("error expected") - } -} - -func TestCallbackWaitHandler_setCallback(t *testing.T) { - c := callbackWaitHandler{} - defer func() { - if err := recover(); err == nil { - t.Error("should panic") - } - }() - c.setCallback(nil) -} - -func TestCallbackWaitHandler_HandleEvent(t *testing.T) { - c := &callbackWaitHandler{ - cond: sync.NewCond(new(sync.Mutex)), - } - defer func() { - if err := recover(); err == nil { - t.Error("should panic") - } - }() - c.HandleEvent(Event{}) -} - -func TestNewClientNoConnection(t *testing.T) { - c, err := NewClient(nil) - if c != nil { - t.Error("c should be nil") - } - if err != ErrNoConnection { - t.Error("bad error") - } -} - -func TestDial(t *testing.T) { - c, err := Dial("udp4", "localhost:3458") - if err != nil { - t.Fatal(err) - } - defer func() { - if err = c.Close(); err != nil { - t.Error(err) - } - }() -} - -func TestDialError(t *testing.T) { - _, err := Dial("bad?network", "?????") - if err == nil { - t.Fatal("error expected") - } -} -func TestClientCloseErr(t *testing.T) { - response := MustBuild(TransactionID, BindingSuccess) - response.Encode() - conn := &testConnection{ - b: response.Raw, - write: func(bytes []byte) (int, error) { - return len(bytes), nil - }, - } - c, err := NewClient(conn, - WithAgent(errorAgent{ - closeErr: io.ErrUnexpectedEOF, - }), - ) - if err != nil { - log.Fatal(err) - } - defer func() { - if err, ok := c.Close().(CloseErr); !ok || err.AgentErr != io.ErrUnexpectedEOF { - t.Error("unexpected close err") - } - }() -} - -func TestWithNoConnClose(t *testing.T) { - response := MustBuild(TransactionID, BindingSuccess) - response.Encode() - closeErr := errors.New("close error") - conn := &testConnection{ - b: response.Raw, - close: func() error { - return closeErr - }, - } - c, err := NewClient(conn, - WithAgent(errorAgent{ - closeErr: nil, - }), - WithNoConnClose, - ) - if err != nil { - log.Fatal(err) - } - if err := c.Close(); err != nil { - t.Error("unexpected non-nil error") - } -} - -type gcWaitAgent struct { - gc chan struct{} -} - -func (a *gcWaitAgent) SetHandler(h Handler) error { - return nil -} - -func (a *gcWaitAgent) Stop(id [TransactionIDSize]byte) error { - return nil -} - -func (a *gcWaitAgent) Close() error { - close(a.gc) - return nil -} - -func (a *gcWaitAgent) Collect(time.Time) error { - a.gc <- struct{}{} - return nil -} - -func (a *gcWaitAgent) Process(m *Message) error { - return nil -} - -func (a *gcWaitAgent) Start(id [TransactionIDSize]byte, deadline time.Time) error { - return nil -} - -func TestClientGC(t *testing.T) { - response := MustBuild(TransactionID, BindingSuccess) - response.Encode() - conn := &testConnection{ - b: response.Raw, - write: func(bytes []byte) (int, error) { - return len(bytes), nil - }, - } - agent := &gcWaitAgent{ - gc: make(chan struct{}), - } - c, err := NewClient(conn, - WithAgent(agent), - WithTimeoutRate(time.Millisecond), - ) - if err != nil { - log.Fatal(err) - } - defer func() { - if err = c.Close(); err != nil { - t.Error(err) - } - }() - select { - case <-agent.gc: - case <-time.After(time.Millisecond * 200): - t.Error("timed out") - } -} - -func TestClientCheckInit(t *testing.T) { - if err := (&Client{}).Indicate(nil); err != ErrClientNotInitialized { - t.Error("unexpected error") - } - if err := (&Client{}).Do(nil, nil); err != ErrClientNotInitialized { - t.Error("unexpected error") - } -} - -func captureLog() (*bytes.Buffer, func()) { - var buf bytes.Buffer - log.SetOutput(&buf) - f := log.Flags() - log.SetFlags(0) - return &buf, func() { - log.SetFlags(f) - log.SetOutput(os.Stderr) - } -} - -func TestClientFinalizer(t *testing.T) { - buf, stopCapture := captureLog() - defer stopCapture() - clientFinalizer(nil) // should not panic - clientFinalizer(&Client{}) - conn := &testConnection{ - write: func(bytes []byte) (int, error) { - return 0, io.ErrClosedPipe - }, - } - c, err := NewClient(conn) - if err != nil { - log.Fatal(err) - } - clientFinalizer(c) - clientFinalizer(c) - response := MustBuild(TransactionID, BindingSuccess) - response.Encode() - conn = &testConnection{ - b: response.Raw, - write: func(bytes []byte) (int, error) { - return len(bytes), nil - }, - } - c, err = NewClient(conn, - WithAgent(errorAgent{ - closeErr: io.ErrUnexpectedEOF, - }), - ) - if err != nil { - log.Fatal(err) - } - clientFinalizer(c) - reader := bufio.NewScanner(buf) - var lines int - var expectedLines = []string{ - "client: called finalizer on non-closed client: client not initialized", - "client: called finalizer on non-closed client", - "client: called finalizer on non-closed client: failed to close: " + - " (connection), unexpected EOF (agent)", - } - for reader.Scan() { - if reader.Text() != expectedLines[lines] { - t.Error(reader.Text(), "!=", expectedLines[lines]) - } - lines++ - } - if reader.Err() != nil { - t.Error(err) - } - if lines != 3 { - t.Error("incorrect count of log lines:", lines) - } -} - -func TestCallbackWaitHandler(t *testing.T) { - h := callbackWaitHandlerPool.Get().(*callbackWaitHandler) - for i := 0; i < 100; i++ { - h.setCallback(func(event Event) {}) - go func() { - time.Sleep(time.Microsecond * 100) - h.HandleEvent(Event{}) - }() - h.wait() - } -} - -type manualCollector struct { - f func(t time.Time) -} - -func (m *manualCollector) Collect(t time.Time) { - m.f(t) -} - -func (m *manualCollector) Start(rate time.Duration, f func(t time.Time)) error { - m.f = f - return nil -} - -func (m *manualCollector) Close() error { - return nil -} - -type manualClock struct { - mux sync.Mutex - current time.Time -} - -func (m *manualClock) Add(d time.Duration) time.Time { - m.mux.Lock() - v := m.current.Add(d) - m.current = v - m.mux.Unlock() - return v -} - -func (m *manualClock) Now() time.Time { - m.mux.Lock() - defer m.mux.Unlock() - return m.current -} - -type manualAgent struct { - start func(id [TransactionIDSize]byte, deadline time.Time) error - stop func(id [TransactionIDSize]byte) error - process func(m *Message) error - h Handler -} - -func (n *manualAgent) SetHandler(h Handler) error { - n.h = h - return nil -} - -func (n *manualAgent) Close() error { - return nil -} - -func (manualAgent) Collect(time.Time) error { return nil } - -func (n *manualAgent) Process(m *Message) error { - if n.process != nil { - return n.process(m) - } - return nil -} - -func (n *manualAgent) Start(id [TransactionIDSize]byte, deadline time.Time) error { - return n.start(id, deadline) -} - -func (n *manualAgent) Stop(id [TransactionIDSize]byte) error { - if n.stop != nil { - return n.stop(id) - } - return nil -} - -func TestClientRetransmission(t *testing.T) { - response := MustBuild(TransactionID, BindingSuccess) - response.Encode() - connL, connR := net.Pipe() - defer connL.Close() - collector := new(manualCollector) - clock := &manualClock{current: time.Now()} - agent := &manualAgent{} - attempt := 0 - agent.start = func(id [TransactionIDSize]byte, deadline time.Time) error { - if attempt == 0 { - attempt++ - go agent.h(Event{ - TransactionID: id, - Error: ErrTransactionTimeOut, - }) - } else { - go agent.h(Event{ - TransactionID: id, - Message: response, - }) - } - return nil - } - c, err := NewClient(connR, - WithAgent(agent), - WithClock(clock), - WithCollector(collector), - WithRTO(time.Millisecond), - ) - if err != nil { - t.Fatal(err) - } - c.SetRTO(time.Second) - gotReads := make(chan struct{}) - go func() { - buf := make([]byte, 1500) - readN, readErr := connL.Read(buf) - if readErr != nil { - t.Error(readErr) - } - if !IsMessage(buf[:readN]) { - t.Error("should be STUN") - } - readN, readErr = connL.Read(buf) - if readErr != nil { - t.Error(readErr) - } - if !IsMessage(buf[:readN]) { - t.Error("should be STUN") - } - gotReads <- struct{}{} - }() - if doErr := c.Do(MustBuild(response, BindingRequest), func(event Event) { - if event.Error != nil { - t.Error("failed") - } - }); doErr != nil { - t.Fatal(doErr) - } - <-gotReads -} - -func testClientDoConcurrent(t *testing.T, concurrency int) { - response := MustBuild(TransactionID, BindingSuccess) - response.Encode() - connL, connR := net.Pipe() - defer connL.Close() - collector := new(manualCollector) - clock := &manualClock{current: time.Now()} - agent := &manualAgent{} - agent.start = func(id [TransactionIDSize]byte, deadline time.Time) error { - go agent.h(Event{ - TransactionID: id, - Message: response, - }) - return nil - } - c, err := NewClient(connR, - WithAgent(agent), - WithClock(clock), - WithCollector(collector), - ) - if err != nil { - t.Fatal(err) - } - c.SetRTO(time.Second) - conns := new(sync.WaitGroup) - wg := new(sync.WaitGroup) - for i := 0; i < concurrency; i++ { - conns.Add(1) - go func() { - defer conns.Done() - buf := make([]byte, 1500) - for { - readN, readErr := connL.Read(buf) - if readErr != nil { - if readErr == io.EOF { - break - } - t.Error(readErr) - } - if !IsMessage(buf[:readN]) { - t.Error("should be STUN") - } - } - }() - wg.Add(1) - go func() { - defer wg.Done() - if doErr := c.Do(MustBuild(TransactionID, BindingRequest), func(event Event) { - if event.Error != nil { - t.Error("failed") - } - }); doErr != nil { - t.Error(doErr) - } - }() - } - wg.Wait() - if connErr := connR.Close(); connErr != nil { - t.Error(connErr) - } - conns.Wait() -} - -func TestClient_DoConcurrent(t *testing.T) { - t.Parallel() - for _, concurrency := range []int{ - 1, 5, 10, 25, 100, 500, - } { - t.Run(fmt.Sprintf("%d", concurrency), func(t *testing.T) { - testClientDoConcurrent(t, concurrency) - }) - } -} - -type errorCollector struct { - startError error - closeError error -} - -func (c errorCollector) Start(rate time.Duration, f func(now time.Time)) error { - return c.startError -} - -func (c errorCollector) Close() error { return c.closeError } - -func TestNewClient(t *testing.T) { - t.Run("SetCallbackError", func(t *testing.T) { - setHandlerError := errors.New("set handler error") - if _, createErr := NewClient(noopConnection{}, - WithAgent(&errorAgent{ - setHandlerError: setHandlerError, - }), - ); createErr != setHandlerError { - t.Errorf("unexpected error returned: %v", createErr) - } - }) - t.Run("CollectorStartError", func(t *testing.T) { - startError := errors.New("start error") - if _, createErr := NewClient(noopConnection{}, - WithAgent(&TestAgent{}), - WithCollector(errorCollector{ - startError: startError, - }), - ); createErr != startError { - t.Errorf("unexpected error returned: %v", createErr) - } - }) -} - -func TestClient_Close(t *testing.T) { - t.Run("CollectorCloseError", func(t *testing.T) { - closeErr := errors.New("start error") - c, createErr := NewClient(noopConnection{}, - WithCollector(errorCollector{ - closeError: closeErr, - }), - WithAgent(&TestAgent{}), - ) - if createErr != nil { - t.Errorf("unexpected create error returned: %v", createErr) - } - gotCloseErr := c.Close() - if gotCloseErr != closeErr { - t.Errorf("unexpected close error returned: %v", gotCloseErr) - } - }) -} - -func TestClientDefaultHandler(t *testing.T) { - a := &TestAgent{ - e: make(chan Event), - } - id := NewTransactionID() - handlerCalled := make(chan struct{}) - called := false - c, createErr := NewClient(noopConnection{}, - WithAgent(a), - WithHandler(func(e Event) { - if called { - t.Error("should not be called twice") - } - called = true - if e.TransactionID != id { - t.Error("wrong transaction ID") - } - handlerCalled <- struct{}{} - }), - ) - if createErr != nil { - t.Fatal(createErr) - } - go func() { - a.h(Event{ - TransactionID: id, - }) - }() - select { - case <-handlerCalled: - // pass - case <-time.After(time.Millisecond * 100): - t.Fatal("timed out") - } - if closeErr := c.Close(); closeErr != nil { - t.Error(closeErr) - } - // Handler call should be ignored. - a.h(Event{}) -} - -func TestClientClosedStart(t *testing.T) { - a := &TestAgent{ - e: make(chan Event), - } - c, createErr := NewClient(noopConnection{}, - WithAgent(a), - ) - if createErr != nil { - t.Fatal(createErr) - } - if closeErr := c.Close(); closeErr != nil { - t.Error(closeErr) - } - if startErr := c.start(&clientTransaction{}); startErr != ErrClientClosed { - t.Error("should error") - } -} - -func TestWithNoRetransmit(t *testing.T) { - response := MustBuild(TransactionID, BindingSuccess) - response.Encode() - connL, connR := net.Pipe() - defer connL.Close() - collector := new(manualCollector) - clock := &manualClock{current: time.Now()} - agent := &manualAgent{} - attempt := 0 - agent.start = func(id [TransactionIDSize]byte, deadline time.Time) error { - if attempt == 0 { - attempt++ - go agent.h(Event{ - TransactionID: id, - Error: ErrTransactionTimeOut, - }) - } else { - t.Error("there should be no second attempt") - go agent.h(Event{ - TransactionID: id, - Error: ErrTransactionTimeOut, - }) - } - return nil - } - c, err := NewClient(connR, - WithAgent(agent), - WithClock(clock), - WithCollector(collector), - WithRTO(0), - WithNoRetransmit, - ) - if err != nil { - t.Fatal(err) - } - gotReads := make(chan struct{}) - go func() { - buf := make([]byte, 1500) - readN, readErr := connL.Read(buf) - if readErr != nil { - t.Error(readErr) - } - if !IsMessage(buf[:readN]) { - t.Error("should be STUN") - } - gotReads <- struct{}{} - }() - if doErr := c.Do(MustBuild(response, BindingRequest), func(event Event) { - if event.Error != ErrTransactionTimeOut { - t.Error("unexpected error") - } - }); doErr != nil { - t.Fatal(err) - } - <-gotReads -} - -type callbackClock func() time.Time - -func (c callbackClock) Now() time.Time { - return c() -} - -func TestClientRTOStartErr(t *testing.T) { - response := MustBuild(TransactionID, BindingSuccess) - response.Encode() - connL, connR := net.Pipe() - defer connL.Close() - collector := new(manualCollector) - shouldWait := false - shouldWaitMux := new(sync.RWMutex) - clockWait := make(chan struct{}) - clockLocked := make(chan struct{}) - clock := callbackClock(func() time.Time { - shouldWaitMux.RLock() - waiting := shouldWait - t.Log("waiting:", waiting) - time.Sleep(time.Millisecond * 100) - shouldWaitMux.RUnlock() - if waiting { - t.Log("clock waiting for log ack") - clockLocked <- struct{}{} - t.Log("clock waiting for unlock") - <-clockWait - t.Log("clock returned after waiting") - } else { - t.Log("clock returned") - } - return time.Now() - }) - agent := &manualAgent{} - attempt := 0 - gotReads := make(chan struct{}) - var ( - c *Client - startClientErr error - ) - agent.start = func(id [TransactionIDSize]byte, deadline time.Time) error { - t.Log("start", attempt) - if attempt == 0 { - attempt++ - go agent.h(Event{ - TransactionID: id, - Error: ErrTransactionTimeOut, - }) - } else { - go func() { - <-gotReads - shouldWaitMux.Lock() - shouldWait = true - shouldWaitMux.Unlock() - go agent.h(Event{ - TransactionID: id, - Error: ErrTransactionTimeOut, - }) - t.Log("clock locked") - <-clockLocked - t.Log("closing client") - if closeErr := c.Close(); closeErr != nil { - t.Error(closeErr) - } - t.Log("client closed, unlocking clock") - clockWait <- struct{}{} - t.Log("clock unlocked") - }() - } - return nil - } - c, startClientErr = NewClient(connR, - WithAgent(agent), - WithClock(clock), - WithCollector(collector), - WithRTO(time.Millisecond), - ) - if startClientErr != nil { - t.Fatal(startClientErr) - } - go func() { - buf := make([]byte, 1500) - readN, readErr := connL.Read(buf) - if readErr != nil { - t.Error(readErr) - } - if !IsMessage(buf[:readN]) { - t.Error("should be STUN") - } - readN, readErr = connL.Read(buf) - if readErr != nil { - t.Error(readErr) - } - if !IsMessage(buf[:readN]) { - t.Error("should be STUN") - } - gotReads <- struct{}{} - }() - t.Log("starting") - done := make(chan struct{}) - go func() { - if doErr := c.Do(MustBuild(response, BindingRequest), func(event Event) { - if event.Error != ErrClientClosed { - t.Error(event.Error) - } - }); doErr != nil { - t.Error(doErr) - } - done <- struct{}{} - }() - select { - case <-done: - // ok - case <-time.After(time.Second * 5): - t.Error("timeout") - } -} - -func TestClientRTOWriteErr(t *testing.T) { - response := MustBuild(TransactionID, BindingSuccess) - response.Encode() - connL, connR := net.Pipe() - defer connL.Close() - collector := new(manualCollector) - shouldWait := false - shouldWaitMux := new(sync.RWMutex) - clockWait := make(chan struct{}) - clockLocked := make(chan struct{}) - clock := callbackClock(func() time.Time { - shouldWaitMux.RLock() - waiting := shouldWait - t.Log("waiting:", waiting) - time.Sleep(time.Millisecond * 100) - shouldWaitMux.RUnlock() - if waiting { - t.Log("clock waiting for log ack") - clockLocked <- struct{}{} - t.Log("clock waiting for unlock") - <-clockWait - t.Log("clock returned after waiting") - } else { - t.Log("clock returned") - } - return time.Now() - }) - agent := &manualAgent{} - attempt := 0 - gotReads := make(chan struct{}) - var ( - c *Client - startClientErr error - ) - agentStopErr := errors.New("agent dont want to stop") - agent.stop = func(id [TransactionIDSize]byte) error { - return agentStopErr - } - agent.start = func(id [TransactionIDSize]byte, deadline time.Time) error { - t.Log("start", attempt) - if attempt == 0 { - attempt++ - go agent.h(Event{ - TransactionID: id, - Error: ErrTransactionTimeOut, - }) - } else { - go func() { - <-gotReads - shouldWaitMux.Lock() - shouldWait = true - shouldWaitMux.Unlock() - go agent.h(Event{ - TransactionID: id, - Error: ErrTransactionTimeOut, - }) - t.Log("clock locked") - <-clockLocked - t.Log("closing connection") - connL.Close() - t.Log("connection closed, unlocking clock") - clockWait <- struct{}{} - t.Log("clock unlocked") - }() - } - return nil - } - c, startClientErr = NewClient(connR, - WithAgent(agent), - WithClock(clock), - WithCollector(collector), - WithRTO(time.Millisecond), - ) - if startClientErr != nil { - t.Fatal(startClientErr) - } - go func() { - buf := make([]byte, 1500) - readN, readErr := connL.Read(buf) - if readErr != nil { - t.Error(readErr) - } - if !IsMessage(buf[:readN]) { - t.Error("should be STUN") - } - readN, readErr = connL.Read(buf) - if readErr != nil { - t.Error(readErr) - } - if !IsMessage(buf[:readN]) { - t.Error("should be STUN") - } - gotReads <- struct{}{} - }() - t.Log("starting") - done := make(chan struct{}) - go func() { - if doErr := c.Do(MustBuild(response, BindingRequest), func(event Event) { - if e, ok := event.Error.(StopErr); !ok { - t.Error(event.Error) - } else { - if e.Err != agentStopErr { - t.Error("incorrect agent error") - } - if e.Cause != io.ErrClosedPipe { - t.Error("incorrect connection error") - } - } - }); doErr != nil { - t.Error(doErr) - } - done <- struct{}{} - }() - select { - case <-done: - // ok - case <-time.After(time.Second * 5): - t.Error("timeout") - } -} - -func TestClientRTOAgentErr(t *testing.T) { - response := MustBuild(TransactionID, BindingSuccess) - response.Encode() - connL, connR := net.Pipe() - defer connL.Close() - collector := new(manualCollector) - clock := callbackClock(time.Now) - agent := &manualAgent{} - attempt := 0 - gotReads := make(chan struct{}) - var ( - c *Client - startClientErr error - ) - agentStartErr := errors.New("start refused") - agent.start = func(id [TransactionIDSize]byte, deadline time.Time) error { - t.Log("start", attempt) - if attempt == 0 { - attempt++ - go agent.h(Event{ - TransactionID: id, - Error: ErrTransactionTimeOut, - }) - } else { - return agentStartErr - } - return nil - } - c, startClientErr = NewClient(connR, - WithAgent(agent), - WithClock(clock), - WithCollector(collector), - WithRTO(time.Millisecond), - ) - if startClientErr != nil { - t.Fatal(startClientErr) - } - go func() { - buf := make([]byte, 1500) - readN, readErr := connL.Read(buf) - if readErr != nil { - t.Error(readErr) - } - if !IsMessage(buf[:readN]) { - t.Error("should be STUN") - } - gotReads <- struct{}{} - }() - t.Log("starting") - if doErr := c.Do(MustBuild(response, BindingRequest), func(event Event) { - if event.Error != agentStartErr { - t.Error(event.Error) - } - }); doErr != nil { - t.Error(doErr) - } - select { - case <-gotReads: - // ok - case <-time.After(time.Second * 5): - t.Error("reads timeout") - } -} - -func TestClient_HandleProcessError(t *testing.T) { - response := MustBuild(TransactionID, BindingSuccess) - response.Encode() - connL, connR := net.Pipe() - defer connL.Close() - collector := new(manualCollector) - clock := callbackClock(time.Now) - agent := &manualAgent{} - gotWrites := make(chan struct{}) - processCalled := make(chan struct{}, 1) - agent.process = func(m *Message) error { - processCalled <- struct{}{} - return ErrAgentClosed - } - c, startClientErr := NewClient(connR, - WithAgent(agent), - WithClock(clock), - WithCollector(collector), - WithRTO(time.Millisecond), - ) - if startClientErr != nil { - t.Fatal(startClientErr) - } - go func() { - _, readErr := connL.Write(response.Raw) - if readErr != nil { - t.Error(readErr) - } - gotWrites <- struct{}{} - }() - t.Log("starting") - select { - case <-gotWrites: - // ok - case <-time.After(time.Second * 5): - t.Error("reads timeout") - } - if closeErr := c.Close(); closeErr != nil { - t.Error(closeErr) - } -} - -func TestClientImmediateTimeout(t *testing.T) { - response := MustBuild(TransactionID, BindingSuccess) - connL, connR := net.Pipe() - defer connL.Close() - collector := new(manualCollector) - clock := &manualClock{current: time.Now()} - rto := time.Second * 1 - agent := &manualAgent{} - attempt := 0 - agent.start = func(id [TransactionIDSize]byte, deadline time.Time) error { - if attempt == 0 { - if deadline.Before(clock.current.Add(rto / 2)) { - t.Error("deadline too fast") - } - attempt++ - go agent.h(Event{ - TransactionID: id, - Message: response, - }) - } else { - t.Error("there should be no second attempt") - go agent.h(Event{ - TransactionID: id, - Error: ErrTransactionTimeOut, - }) - } - return nil - } - c, err := NewClient(connR, - WithAgent(agent), - WithClock(clock), - WithCollector(collector), - WithRTO(rto), - ) - if err != nil { - t.Fatal(err) - } - gotReads := make(chan struct{}) - go func() { - buf := make([]byte, 1500) - readN, readErr := connL.Read(buf) - if readErr != nil { - t.Error(readErr) - } - if !IsMessage(buf[:readN]) { - t.Error("should be STUN") - } - gotReads <- struct{}{} - }() - c.Start(MustBuild(response, BindingRequest), func(e Event) { - if e.Error == ErrTransactionTimeOut { - t.Error("unexpected error") - } - }) - <-gotReads -} diff --git a/cmd/stun-bench/main.go b/cmd/stun-bench/main.go deleted file mode 100644 index bd662ce..0000000 --- a/cmd/stun-bench/main.go +++ /dev/null @@ -1,132 +0,0 @@ -package main - -import ( - "context" - "crypto/rand" - "flag" - "fmt" - "log" - mathRand "math/rand" - "net" - "os" - "os/signal" - "runtime" - "runtime/pprof" - "sync/atomic" - "time" - - "gortc.io/stun" -) - -var ( - workers = flag.Int("w", runtime.GOMAXPROCS(0), "concurrent workers") - addr = flag.String("addr", fmt.Sprintf("localhost"), "target address") - port = flag.Int("port", stun.DefaultPort, "target port") - duration = flag.Duration("d", time.Minute, "benchmark duration") - network = flag.String("net", "udp", "protocol to use (udp, tcp)") - cpuProfile = flag.String("cpuprofile", "", "file output of pprof cpu profile") - memProfile = flag.String("memprofile", "", "file output of pprof memory profile") - realRand = flag.Bool("crypt", false, "use crypto/rand as random source") -) - -func main() { - flag.Parse() - signals := make(chan os.Signal, 1) - signal.Notify(signals, os.Interrupt) - start := time.Now() - var ( - request int64 - requestOK int64 - requestErr int64 - ) - if *cpuProfile != "" { - f, createErr := os.Create(*cpuProfile) - if createErr != nil { - log.Fatalln("failed to create cpu profile output file:", createErr) - } - if pprofErr := pprof.StartCPUProfile(f); pprofErr != nil { - log.Fatalln("failed to start pprof cpu profiling:", pprofErr) - } - defer func() { - pprof.StopCPUProfile() - if closeErr := f.Close(); closeErr != nil { - log.Println("failed to close cpu profile output file:", closeErr) - } else { - fmt.Println("saved cpu profile to", *cpuProfile) - } - }() - } - if *memProfile != "" { - f, createErr := os.Create(*memProfile) - if createErr != nil { - log.Fatalln("failed to create memory profile output file:", createErr) - } - defer func() { - if pprofErr := pprof.Lookup("heap").WriteTo(f, 1); pprofErr != nil { - log.Fatalln("failed to write pprof memory profiling:", pprofErr) - } - if closeErr := f.Close(); closeErr != nil { - log.Println("failed to close memory profile output file:", closeErr) - } else { - fmt.Println("saved memory profile to", *memProfile) - } - }() - } - ctx, cancel := context.WithTimeout(context.Background(), *duration) - go func() { - for sig := range signals { - fmt.Println("stopping on", sig) - cancel() - } - }() - if *realRand { - fmt.Println("using crypto/rand as random source for transaction id") - } - for i := 0; i < *workers; i++ { - wConn, connErr := net.Dial(*network, fmt.Sprintf("%s:%d", *addr, *port)) - if connErr != nil { - log.Fatalln("failed to dial:", wConn) - } - c, clientErr := stun.NewClient(wConn) - if clientErr != nil { - log.Fatalln("failed to create client:", clientErr) - } - go func(client *stun.Client) { - req := stun.New() - for { - if *realRand { - rand.Read(req.TransactionID[:]) - } else { - mathRand.Read(req.TransactionID[:]) - } - req.Type = stun.BindingRequest - req.WriteHeader() - atomic.AddInt64(&request, 1) - if doErr := c.Do(req, func(event stun.Event) { - if event.Error != nil { - if event.Error != stun.ErrTransactionTimeOut { - log.Println("event.Error error:", event.Error) - } - atomic.AddInt64(&requestErr, 1) - return - } - atomic.AddInt64(&requestOK, 1) - }); doErr != nil { - if doErr != stun.ErrTransactionExists { - log.Println("Do() error:", doErr) - } - atomic.AddInt64(&requestErr, 1) - } - } - }(c) - } - fmt.Println("workers started") - <-ctx.Done() - stop := time.Now() - rps := int(float64(atomic.LoadInt64(&requestOK)) / stop.Sub(start).Seconds()) - fmt.Println("rps:", rps) - if reqErr := atomic.LoadInt64(&requestErr); requestErr != 0 { - fmt.Println("errors:", reqErr) - } - fmt.Println("total:", atomic.LoadInt64(&request)) -} diff --git a/cmd/stun-client/stun-client.go b/cmd/stun-client/stun-client.go deleted file mode 100644 index d17b285..0000000 --- a/cmd/stun-client/stun-client.go +++ /dev/null @@ -1,41 +0,0 @@ -package main - -import ( - "flag" - "fmt" - "log" - "os" - - "gortc.io/stun" -) - -func main() { - flag.Usage = func() { - fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0]) - fmt.Fprintln(os.Stderr, os.Args[0], "stun.l.google.com:19302") - } - flag.Parse() - addr := flag.Arg(0) - if addr == "" { - addr = "stun.l.google.com:19302" - } - c, err := stun.Dial("udp", addr) - if err != nil { - log.Fatal("dial:", err) - } - if err = c.Do(stun.MustBuild(stun.TransactionID, stun.BindingRequest), func(res stun.Event) { - if res.Error != nil { - log.Fatalln(err) - } - var xorAddr stun.XORMappedAddress - if getErr := xorAddr.GetFrom(res.Message); getErr != nil { - log.Fatalln(getErr) - } - fmt.Println(xorAddr) - }); err != nil { - log.Fatal("do:", err) - } - if err := c.Close(); err != nil { - log.Fatalln(err) - } -} diff --git a/cmd/stun-decode/main.go b/cmd/stun-decode/main.go index b3fb3bd..6126745 100644 --- a/cmd/stun-decode/main.go +++ b/cmd/stun-decode/main.go @@ -7,7 +7,7 @@ import ( "log" "os" - "gortc.io/stun" + "gortc.io/stun/v2" ) func main() { diff --git a/cmd/stun-multiplex/README.md b/cmd/stun-multiplex/README.md deleted file mode 100644 index df93faf..0000000 --- a/cmd/stun-multiplex/README.md +++ /dev/null @@ -1,31 +0,0 @@ -# Multiplex - -An example of doing UDP connection multiplexing -that splits incoming UDP packets to two streams, "STUN Data" and "Application Data". - -Usage: -```sh -$ go get github.com/gortc/stun/cmd/stun-multiplex -``` - -On "server": -```sh -$ stun-multiplex -local addr: 0.0.0.0:34690 stun server addr: 64.233.161.127:19302 -public addr: 123.131.100.200:34690 -Acting as server. Use following command to connect: -stun-multiplex 123.131.100.200:34690 -``` - -On "client": -```sh -$ stun-multiplex 123.131.100.200:34690 -local addr: 0.0.0.0:37551 stun server addr: 66.102.1.127:19302 -public addr: 159.69.13.15:37551 -Acting as client. Connecting to 123.131.100.200:34690 -Writing 123.131.100.200:34690 -demultiplex: [123.131.100.200:34690]: Hello peer -Got response from 123.131.100.200:34690: Hello peer -``` - -On "server" you will see `demultiplex: [159.69.13.15:37551]: Hello peer` message. \ No newline at end of file diff --git a/cmd/stun-multiplex/main.go b/cmd/stun-multiplex/main.go deleted file mode 100644 index b01e85c..0000000 --- a/cmd/stun-multiplex/main.go +++ /dev/null @@ -1,190 +0,0 @@ -// Command stun-multiplex is example of doing UDP connection multiplexing -// that splits incoming UDP packets to two streams, "STUN Data" and -// "Application Data". -package main - -import ( - "flag" - "fmt" - "io" - "net" - "os" - "os/signal" - "syscall" - "time" - - "gortc.io/stun" -) - -func copyAddr(dst *stun.XORMappedAddress, src stun.XORMappedAddress) { - dst.IP = append(dst.IP, src.IP...) - dst.Port = src.Port -} - -func keepAlive(c *stun.Client) { - // Keep-alive for NAT binding. - t := time.NewTicker(time.Second * 5) - for range t.C { - if err := c.Do(stun.MustBuild(stun.TransactionID, stun.BindingRequest), func(res stun.Event) { - if res.Error != nil { - panic(res.Error) - } - }); err != nil { - panic(err) - } - } -} - -type message struct { - text string - addr net.Addr -} - -func demultiplex(conn *net.UDPConn, stunConn io.Writer, messages chan message) { - buf := make([]byte, 1024) - for { - n, raddr, err := conn.ReadFrom(buf) - if err != nil { - panic(err) - } - // De-multiplexing incoming packets. - if stun.IsMessage(buf[:n]) { - // If buf looks like STUN message, send it to STUN client connection. - if _, err = stunConn.Write(buf[:n]); err != nil { - panic(err) - } - } else { - // If not, it is application data. - fmt.Printf("demultiplex: [%s]: %s\n", raddr, buf[:n]) - messages <- message{ - text: string(buf[:n]), - addr: raddr, - } - } - } -} - -func multiplex(conn *net.UDPConn, stunAddr net.Addr, stunConn io.Reader) { - // Sending all data from stun client to stun server. - buf := make([]byte, 1024) - for { - n, err := stunConn.Read(buf) - if err != nil { - panic(err) - } - if _, err = conn.WriteTo(buf[:n], stunAddr); err != nil { - panic(err) - } - } -} - -var stunServer = flag.String("stun", "stun.l.google.com:19302", "STUN Server to use") - -func main() { - flag.Parse() - // Allocating local UDP socket that will be used both for STUN and - // our application data. - addr, err := net.ResolveUDPAddr("udp4", "0.0.0.0:0") - if err != nil { - panic(err) - } - conn, err := net.ListenUDP("udp4", addr) - if err != nil { - panic(err) - } - // Resolving STUN Server address. - stunAddr, err := net.ResolveUDPAddr("udp4", *stunServer) - if err != nil { - panic(err) - } - fmt.Println("local addr:", conn.LocalAddr(), "stun server addr:", stunAddr) - stunL, stunR := net.Pipe() - c, err := stun.NewClient(stunR) - if err != nil { - panic(err) - } - // Starting multiplexing (writing back STUN messages) with de-multiplexing - // (passing STUN messages to STUN client and processing application - // data separately). - // - // stunL and stunR are virtual connections, see net.Pipe for reference. - messages := make(chan message) - go demultiplex(conn, stunL, messages) - go multiplex(conn, stunAddr, stunL) - - // Getting our "real" IP address from STUN Server. - // This will create a NAT binding on your provider/router NAT Server, - // and the STUN server will return allocated public IP for that binding. - // - // This can fail if your NAT Server is strict and will use separate ports - // for application data and STUN - var gotAddr stun.XORMappedAddress - if err = c.Do(stun.MustBuild(stun.TransactionID, stun.BindingRequest), func(res stun.Event) { - if res.Error != nil { - panic(res.Error) - } - var xorAddr stun.XORMappedAddress - if getErr := xorAddr.GetFrom(res.Message); getErr != nil { - panic(getErr) - } - copyAddr(&gotAddr, xorAddr) - }); err != nil { - panic(err) - } - fmt.Println("public addr:", gotAddr) - - // Keep-alive is needed to keep our NAT port allocated. - // Any ping-pong will work, but we are just making binding requests. - // Note that STUN Server is not mandatory for keep alive, application - // data will keep alive that binding too. - go keepAlive(c) - - notify := make(chan os.Signal, 1) - signal.Notify(notify, os.Interrupt, syscall.SIGTERM) - if flag.Arg(0) == "" { - fmt.Println("Acting as server. Use following command to connect:") - fmt.Println(os.Args[0], gotAddr) - for { - select { - case m := <-messages: - if _, err = conn.WriteTo([]byte(m.text), m.addr); err != nil { - panic(err) - } - case <-notify: - fmt.Println("\rStopping") - return - } - } - } else { - peerAddr, err := net.ResolveUDPAddr("udp4", flag.Arg(0)) - if err != nil { - panic(err) - } - fmt.Println("Acting as client. Connecting to", peerAddr) - msg := "Hello peer" - sendMsg := func() { - fmt.Println("Writing", peerAddr) - if _, err = conn.WriteTo([]byte(msg), peerAddr); err != nil { - panic(err) - } - } - sendMsg() - deadline := time.After(time.Second * 10) - for { - select { - case <-deadline: - fmt.Println("Failed to connect: deadline reached.") - os.Exit(2) - case <-time.After(time.Second): - // Retry. - sendMsg() - case m := <-messages: - fmt.Printf("Got response from %s: %s\n", m.addr, m.text) - return - case <-notify: - fmt.Println("\rStopping") - return - } - } - } -} diff --git a/cmd/stun-traversal/Readme.md b/cmd/stun-traversal/Readme.md deleted file mode 100644 index f746d84..0000000 --- a/cmd/stun-traversal/Readme.md +++ /dev/null @@ -1 +0,0 @@ -stun-traversal is a small NAT traversal proof of concept using package gortc/stun. Peer exchange is done manually using stdin. \ No newline at end of file diff --git a/cmd/stun-traversal/main.go b/cmd/stun-traversal/main.go deleted file mode 100644 index 8dee571..0000000 --- a/cmd/stun-traversal/main.go +++ /dev/null @@ -1,191 +0,0 @@ -package main - -import ( - "bufio" - "flag" - "fmt" - "log" - "net" - "os" - "strings" - "time" - - "gortc.io/stun" -) - -var ( - server = flag.String("server", fmt.Sprintf("gortc.io:3478"), "Stun server address") -) - -const ( - udp = "udp4" - pingMsg = "ping" - pongMsg = "pong" - timeoutMillis = 500 -) - -func main() { - flag.Parse() - - srvAddr, err := net.ResolveUDPAddr(udp, *server) - if err != nil { - log.Fatalln("resolve serveraddr:", err) - } - - conn, err := net.ListenUDP(udp, nil) - if err != nil { - log.Fatalln("dial:", err) - } - - defer conn.Close() - - log.Printf("Listening on %s\n", conn.LocalAddr()) - - var publicAddr stun.XORMappedAddress - var peerAddr *net.UDPAddr - - messageChan := listen(conn) - var peerAddrChan <-chan string - - keepalive := time.Tick(timeoutMillis * time.Millisecond) - keepaliveMsg := pingMsg - - var quit <-chan time.Time - - gotPong := false - sentPong := false - - for { - select { - case message, ok := <-messageChan: - if !ok { - return - } - - switch { - case string(message) == pingMsg: - keepaliveMsg = pongMsg - - case string(message) == pongMsg: - if !gotPong { - log.Println("Received pong message.") - } - - // One client may skip sending ping if it receives - // a ping message before knowning the peer address. - keepaliveMsg = pongMsg - - gotPong = true - - case stun.IsMessage(message): - m := new(stun.Message) - m.Raw = message - decErr := m.Decode() - if decErr != nil { - log.Println("decode:", decErr) - break - } - var xorAddr stun.XORMappedAddress - if getErr := xorAddr.GetFrom(m); getErr != nil { - log.Println("getFrom:", getErr) - break - } - - if publicAddr.String() != xorAddr.String() { - log.Printf("My public address: %s\n", xorAddr) - publicAddr = xorAddr - - peerAddrChan = getPeerAddr() - } - - default: - log.Fatalln("unknown message", message) - } - - case peerStr := <-peerAddrChan: - peerAddr, err = net.ResolveUDPAddr(udp, peerStr) - if err != nil { - log.Fatalln("resolve peeraddr:", err) - } - - case <-keepalive: - // Keep NAT binding alive using STUN server or the peer once it's known - if peerAddr == nil { - err = sendBindingRequest(conn, srvAddr) - } else { - err = sendStr(keepaliveMsg, conn, peerAddr) - if keepaliveMsg == pongMsg { - sentPong = true - } - } - - if err != nil { - log.Fatalln("keepalive:", err) - } - - case <-quit: - conn.Close() - } - - if quit == nil && gotPong && sentPong { - log.Println("Success! Quitting in two seconds.") - quit = time.After(2 * time.Second) - } - } -} - -func getPeerAddr() <-chan string { - result := make(chan string) - - go func() { - reader := bufio.NewReader(os.Stdin) - log.Println("Enter remote peer address:") - peer, _ := reader.ReadString('\n') - result <- strings.Trim(peer, " \r\n") - }() - - return result -} - -func listen(conn *net.UDPConn) <-chan []byte { - messages := make(chan []byte) - go func() { - for { - buf := make([]byte, 1024) - - n, _, err := conn.ReadFromUDP(buf) - if err != nil { - close(messages) - return - } - buf = buf[:n] - - messages <- buf - } - }() - return messages -} - -func sendBindingRequest(conn *net.UDPConn, addr *net.UDPAddr) error { - m := stun.MustBuild(stun.TransactionID, stun.BindingRequest) - - err := send(m.Raw, conn, addr) - if err != nil { - return fmt.Errorf("binding: %v", err) - } - - return nil -} - -func send(msg []byte, conn *net.UDPConn, addr *net.UDPAddr) error { - _, err := conn.WriteToUDP(msg, addr) - if err != nil { - return fmt.Errorf("send: %v", err) - } - - return nil -} - -func sendStr(msg string, conn *net.UDPConn, addr *net.UDPAddr) error { - return send([]byte(msg), conn, addr) -} diff --git a/e2e/Dockerfile b/e2e/Dockerfile deleted file mode 100644 index 8867624..0000000 --- a/e2e/Dockerfile +++ /dev/null @@ -1,11 +0,0 @@ -ARG CI_GO_VERSION -FROM golang:${CI_GO_VERSION} - -ADD . /go/src/gortc.io/stun - -WORKDIR /go/src/gortc.io/stun/e2e - -RUN go install . - -CMD ["e2e"] - diff --git a/e2e/capture.sh b/e2e/capture.sh deleted file mode 100644 index 7c9cb68..0000000 --- a/e2e/capture.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/usr/bin/env bash -echo "net: $INTERFACE $SUBNET" -tcpdump -U -v -i $INTERFACE \ - src net $SUBNET and dst net $SUBNET \ - -w /root/dump/dump.pcap diff --git a/e2e/docker-compose.yml b/e2e/docker-compose.yml deleted file mode 100644 index 8657951..0000000 --- a/e2e/docker-compose.yml +++ /dev/null @@ -1,22 +0,0 @@ -version: '3' - -services: - stun-client: - depends_on: - - stun-server - links: - - stun-server - build: - context: .. - dockerfile: e2e/Dockerfile - args: - CI_GO_VERSION: ${CI_GO_VERSION} - stun-server: - image: gortc/coturn - volumes: - - ./turnserver.conf:/etc/turnserver.conf - -networks: - default: - external: - name: stun_e2e_coturn diff --git a/e2e/main.go b/e2e/main.go deleted file mode 100644 index fcfb314..0000000 --- a/e2e/main.go +++ /dev/null @@ -1,130 +0,0 @@ -package main - -import ( - "fmt" - "log" - "net" - "strings" - "time" - - "gortc.io/stun" -) - -func test(network string) { - addr := resolve(network) - fmt.Println("START", strings.ToUpper(addr.Network())) - var ( - nonce stun.Nonce - realm stun.Realm - ) - const ( - username = "user" - password = "secret" - ) - conn, err := net.Dial(addr.Network(), addr.String()) - if err != nil { - log.Fatalln("failed to dial conn:", err) - } - var options []stun.ClientOption - if network == "tcp" { - // Switching to "NO-RTO" mode. - fmt.Println("using WithNoRetransmit for TCP") - options = append(options, stun.WithNoRetransmit) - } - client, err := stun.NewClient(conn, options...) - if err != nil { - log.Fatal(err) - } - // First request should error. - request, err := stun.Build(stun.BindingRequest, stun.TransactionID, stun.Fingerprint) - if err != nil { - log.Fatalln("failed to build:", err) - } - if err = client.Do(request, func(event stun.Event) { - if event.Error != nil { - log.Fatalln("got event with error:", event.Error) - } - response := event.Message - if response.Type != stun.BindingError { - log.Fatalln("bad message", response) - } - var errCode stun.ErrorCodeAttribute - if codeErr := errCode.GetFrom(response); codeErr != nil { - log.Fatalln("failed to get error code:", codeErr) - } - if errCode.Code != stun.CodeUnauthorized { - log.Fatalln("unexpected error code:", errCode) - } - if parseErr := response.Parse(&nonce, &realm); parseErr != nil { - log.Fatalln("failed to parse:", parseErr) - } - fmt.Println("Got nonce", nonce, "and realm", realm) - }); err != nil { - log.Fatalln("failed to Do:", err) - } - - // Authenticating and sending second request. - request, err = stun.Build(stun.TransactionID, stun.BindingRequest, - stun.NewUsername(username), nonce, realm, - stun.NewLongTermIntegrity(username, realm.String(), password), - stun.Fingerprint, - ) - if err != nil { - log.Fatalln(err) - } - if err = client.Do(request, func(event stun.Event) { - if event.Error != nil { - log.Fatalln("got event with error:", event.Error) - } - response := event.Message - if response.Type != stun.BindingSuccess { - var errCode stun.ErrorCodeAttribute - if codeErr := errCode.GetFrom(response); codeErr != nil { - log.Fatalln("failed to get error code:", codeErr) - } - log.Fatalln("bad message", response, errCode) - } - var xorMapped stun.XORMappedAddress - if err = response.Parse(&xorMapped); err != nil { - log.Fatalln("failed to parse xor mapped address:", err) - } - if conn.LocalAddr().String() != xorMapped.String() { - log.Fatalln(conn.LocalAddr(), "!=", xorMapped) - } - fmt.Println("OK", response, "GOT", xorMapped) - }); err != nil { - log.Fatalln("failed to Do:", err) - } - if err := client.Close(); err != nil { - log.Fatalln("failed to close client:", err) - } - fmt.Println("OK", strings.ToUpper(addr.Network())) -} - -func resolve(network string) net.Addr { - addr := fmt.Sprintf("%s:%d", "stun-server", stun.DefaultPort) - var ( - resolved net.Addr - resolveErr error - ) - for i := 0; i < 10; i++ { - switch network { - case "udp": - resolved, resolveErr = net.ResolveUDPAddr("udp", addr) - case "tcp": - resolved, resolveErr = net.ResolveTCPAddr("tcp", addr) - default: - panic("unknown network") - } - if resolveErr == nil { - return resolved - } - time.Sleep(time.Millisecond * 300 * time.Duration(i)) - } - panic(resolveErr) -} - -func main() { - test("udp") - test("tcp") -} diff --git a/e2e/tcpdump.Dockerfile b/e2e/tcpdump.Dockerfile deleted file mode 100644 index cb577dd..0000000 --- a/e2e/tcpdump.Dockerfile +++ /dev/null @@ -1,6 +0,0 @@ -FROM ubuntu -RUN apt-get update && apt-get install -y tcpdump -RUN apt-get install net-tools -y - -ADD capture.sh /root/capture.sh -ENTRYPOINT ["/bin/bash", "/root/capture.sh"] diff --git a/e2e/test.sh b/e2e/test.sh deleted file mode 100755 index 81b18b0..0000000 --- a/e2e/test.sh +++ /dev/null @@ -1,73 +0,0 @@ -#!/usr/bin/env bash - -export CURRENT_GO_VERSION=$(echo -n "$(go version)" | grep -o 'go1\.[0-9|\.]*' || true) -CURRENT_GO_VERSION=${CURRENT_GO_VERSION#go} -GO_VERSION=${GO_VERSION:-$CURRENT_GO_VERSION} - -# set golang version from env -export CI_GO_VERSION="${GO_VERSION:-latest}" - -# define some colors to use for output -RED='\033[0;31m' -GREEN='\033[0;32m' -NC='\033[0m' - -printf "${GREEN}Go version \"${CI_GO_VERSION}\"${NC}\n" - -# kill and remove any running containers -cleanup () { - docker stop ci_stun-tcpdump - docker rm -f ci_stun-tcpdump - docker-compose -p ci kill - docker-compose -p ci rm -f - docker network rm stun_e2e_coturn -} - -# catch unexpected failures, do cleanup and output an error message -trap 'cleanup ; printf "${RED}Tests Failed For Unexpected Reasons${NC}\n"'\ - HUP INT QUIT PIPE TERM - -# PREPARING NETWORK CAPTURE -docker network create stun_e2e_coturn --internal -docker build -t gortc/tcpdump -f tcpdump.Dockerfile . - -NETWORK_ID=`docker network inspect stun_e2e_coturn -f "{{.Id}}"` -NETWORK_SUBNET=`docker network inspect stun_e2e_coturn -f "{{(index .IPAM.Config 0).Subnet}}"` -CAPTURE_INTERFACE="br-${NETWORK_ID:0:12}" - -echo "will capture traffic on $CAPTURE_INTERFACE$" - -docker run -e INTERFACE=${CAPTURE_INTERFACE} -e SUBNET=${NETWORK_SUBNET} -d \ - -v $(pwd):/root/dump \ - --name ci_stun-tcpdump --net=host gortc/tcpdump - - -# build and run the composed services -docker-compose -p ci build && docker-compose -p ci up -d -if [ $? -ne 0 ] ; then - printf "${RED}Docker Compose Failed${NC}\n" - exit -1 -fi - -# wait for the test service to complete and grab the exit code -TEST_EXIT_CODE=`docker wait ci_stun-client_1` - -docker logs ci_stun-client_1 &> log-client.txt -docker logs ci_stun-server_1 &> log-server.txt -docker logs ci_stun-tcpdump &> log-tcpdump.txt - -# output the logs for the test (for clarity) -cat log-client.txt - -# inspect the output of the test and display respective message -if [ -z ${TEST_EXIT_CODE+x} ] || [ "$TEST_EXIT_CODE" -ne 0 ] ; then - printf "${RED}Tests Failed${NC} - Exit Code: $TEST_EXIT_CODE\n" -else - printf "${GREEN}Tests Passed${NC}\n" -fi - -# call the cleanup function -cleanup - -# exit the script with the same code as the test service code -exit ${TEST_EXIT_CODE} diff --git a/e2e/turnserver.conf b/e2e/turnserver.conf deleted file mode 100644 index d8ffdd9..0000000 --- a/e2e/turnserver.conf +++ /dev/null @@ -1,200 +0,0 @@ -# Coturn TURN SERVER configuration file - -Verbose -fingerprint - -# Uncomment to use long-term credential mechanism. -# By default no credentials mechanism is used (any user allowed). -# -lt-cred-mech - -# This option is opposite to lt-cred-mech. -# (TURN Server with no-auth option allows anonymous access). -# If neither option is defined, and no users are defined, -# then no-auth is default. If at least one user is defined, -# in this file or in command line or in usersdb file, then -# lt-cred-mech is default. -# -#no-auth - -# 'Static' user accounts for long term credentials mechanism, only. -# This option cannot be used with TURN REST API. -# 'Static' user accounts are NOT dynamically checked by the turnserver process, -# so that they can NOT be changed while the turnserver is running. -# -#user=username1:key1 -#user=username2:key2 -# OR: -#user=username1:password1 -#user=username2:password2 -# -# Keys must be generated by turnadmin utility. The key value depends -# on user name, realm, and password: -# -# Example: -# $ turnadmin -k -u ninefingers -r north.gov -p youhavetoberealistic -# Output: 0xbc807ee29df3c9ffa736523fb2c4e8ee -# ('0x' in the beginning of the key is what differentiates the key from -# password. If it has 0x then it is a key, otherwise it is a password). -# -# The corresponding user account entry in the config file will be: -# -#user=ninefingers:0xbc807ee29df3c9ffa736523fb2c4e8ee -# Or, equivalently, with open clear password (less secure): -#user=ninefingers:youhavetoberealistic -# - -user=user:secret -realm=realm - -# Uncomment if no UDP client listener is desired. -# By default UDP client listener is always started. -# -#no-udp - -# Uncomment if no TCP client listener is desired. -# By default TCP client listener is always started. -# -# no-tcp - -# Uncomment if no TLS client listener is desired. -# By default TLS client listener is always started. -# -no-tls - -# Uncomment if no DTLS client listener is desired. -# By default DTLS client listener is always started. -# -no-dtls - -# Uncomment if no UDP relay endpoints are allowed. -# By default UDP relay endpoints are enabled (like in RFC 5766). -# -#no-udp-relay - -# Uncomment if no TCP relay endpoints are allowed. -# By default TCP relay endpoints are enabled (like in RFC 6062). -# -no-tcp-relay - -# Uncomment if extra security is desired, -# with nonce value having limited lifetime (600 secs). -# By default, the nonce value is unique for a session, -# but it has unlimited lifetime. With this option, -# the nonce lifetime is limited to 600 seconds, after that -# the client will get 438 error and will have to re-authenticate itself. -# -stale-nonce - -# Certificate file. -# Use an absolute path or path relative to the -# configuration file. -# -#cert=/usr/local/etc/turn_server_cert.pem - -# Private key file. -# Use an absolute path or path relative to the -# configuration file. -# Use PEM file format. -# -#pkey=/usr/local/etc/turn_server_pkey.pem - -# Private key file password, if it is in encoded format. -# This option has no default value. -# -#pkey-pwd=... - -# Allowed OpenSSL cipher list for TLS/DTLS connections. -# Default value is "DEFAULT". -# -#cipher-list="DEFAULT" - -# CA file in OpenSSL format. -# Forces TURN server to verify the client SSL certificates. -# By default it is not set: there is no default value and the client -# certificate is not checked. -# -# Example: -#CA-file=/etc/ssh/id_rsa.cert - -# Curve name for EC ciphers, if supported by OpenSSL -# library (TLS and DTLS). The default value is prime256v1, -# if pre-OpenSSL 1.0.2 is used. With OpenSSL 1.0.2+, -# an optimal curve will be automatically calculated, if not defined -# by this option. -# -#ec-curve-name=prime256v1 - -# Use 566 bits predefined DH TLS key. Default size of the key is 1066. -# -#dh566 - -# Use 2066 bits predefined DH TLS key. Default size of the key is 1066. -# -#dh2066 - -# Use custom DH TLS key, stored in PEM format in the file. -# Flags --dh566 and --dh2066 are ignored when the DH key is taken from a file. -# -#dh-file= - -# Flag to prevent stdout log messages. -# By default, all log messages are going to both stdout and to -# the configured log file. With this option everything will be -# going to the configured log only (unless the log file itself is stdout). -# -#no-stdout-log - -# Option to set the log file name. -# By default, the turnserver tries to open a log file in -# /var/log, /var/tmp, /tmp and current directories directories -# (which open operation succeeds first that file will be used). -# With this option you can set the definite log file name. -# The special names are "stdout" and "-" - they will force everything -# to the stdout. Also, the "syslog" name will force everything to -# the system log (syslog). -# In the runtime, the logfile can be reset with the SIGHUP signal -# to the turnserver process. -# -log-file=stdout - -# Option to redirect all log output into system log (syslog). -# -#syslog - -# Option to suppress TURN functionality, only STUN requests will be processed. -# Run as STUN server only, all TURN requests will be ignored. -# By default, this option is NOT set. -# -stun-only - -# Option to suppress STUN functionality, only TURN requests will be processed. -# Run as TURN server only, all STUN requests will be ignored. -# By default, this option is NOT set. -# -#no-stun - -# Require authentication of the STUN Binding request. -# By default, the clients are allowed anonymous access to the STUN Binding functionality. -# -secure-stun - -# Mobility with ICE (MICE) specs support. -# -#mobility - -# User name to run the process. After the initialization, the turnserver process -# will make an attempt to change the current user ID to that user. -# -#proc-user= - -# Group name to run the process. After the initialization, the turnserver process -# will make an attempt to change the current group ID to that group. -# -#proc-group= - -# Turn OFF the CLI support. -# By default it is always ON. -# See also options cli-ip and cli-port. -# -no-cli diff --git a/go.mod b/go.mod index 7e01552..0aa0d03 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,3 @@ -module gortc.io/stun +module gortc.io/stun/v2 -go 1.12 +go 1.13 diff --git a/helpers_test.go b/helpers_test.go index 96480d3..2e28dc7 100644 --- a/helpers_test.go +++ b/helpers_test.go @@ -5,7 +5,7 @@ import ( "fmt" "testing" - "gortc.io/stun/internal/testutil" + "gortc.io/stun/v2/internal/testutil" ) func BenchmarkBuildOverhead(b *testing.B) { diff --git a/integrity.go b/integrity.go index 4ed4a7f..e51b59f 100644 --- a/integrity.go +++ b/integrity.go @@ -7,7 +7,7 @@ import ( "fmt" "strings" - "gortc.io/stun/internal/hmac" + "gortc.io/stun/v2/internal/hmac" ) // separator for credentials. diff --git a/message.go b/message.go index 8fbe588..feb2257 100644 --- a/message.go +++ b/message.go @@ -397,10 +397,10 @@ type MessageClass byte // Possible values for message class in STUN Message Type. const ( - ClassRequest MessageClass = 0x00 // 0b00 - ClassIndication MessageClass = 0x01 // 0b01 - ClassSuccessResponse MessageClass = 0x02 // 0b10 - ClassErrorResponse MessageClass = 0x03 // 0b11 + ClassRequest MessageClass = 0b00 + ClassIndication MessageClass = 0b01 + ClassSuccessResponse MessageClass = 0b10 + ClassErrorResponse MessageClass = 0b11 ) // Common STUN message types. @@ -494,18 +494,15 @@ func NewType(method Method, class MessageClass) MessageType { } const ( - methodABits = 0xf // 0b0000000000001111 - methodBBits = 0x70 // 0b0000000001110000 - methodDBits = 0xf80 // 0b0000111110000000 + methodABits = 0b0000000000001111 + methodBBits = 0b0000000001110000 + methodDBits = 0b0000111110000000 methodBShift = 1 methodDShift = 2 - firstBit = 0x1 - secondBit = 0x2 - - c0Bit = firstBit - c1Bit = secondBit + c0Bit = 0b01 + c1Bit = 0b10 classC0Shift = 4 classC1Shift = 7 diff --git a/stun.go b/stun.go index b9be735..9622baf 100644 --- a/stun.go +++ b/stun.go @@ -49,3 +49,5 @@ func (transactionIDSetter) AddTo(m *Message) error { // TransactionID is Setter for m.TransactionID. var TransactionID Setter = transactionIDSetter{} + +type transactionID [TransactionIDSize]byte