mirror of
https://github.com/netbirdio/ice.git
synced 2026-05-22 17:10:58 -07:00
Merge upstream/master
This commit is contained in:
@@ -0,0 +1,20 @@
|
||||
#
|
||||
# DO NOT EDIT THIS FILE
|
||||
#
|
||||
# It is automatically copied from https://github.com/pion/.goassets repository.
|
||||
# If this repository should have package specific CI config,
|
||||
# remove the repository name from .goassets/.github/workflows/assets-sync.yml.
|
||||
#
|
||||
# If you want to update the shared CI config, send a PR to
|
||||
# https://github.com/pion/.goassets instead of this repository.
|
||||
#
|
||||
# SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
name: API
|
||||
on:
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
check:
|
||||
uses: pion/.goassets/.github/workflows/api.reusable.yml@master
|
||||
@@ -21,4 +21,4 @@ jobs:
|
||||
release:
|
||||
uses: pion/.goassets/.github/workflows/release.reusable.yml@master
|
||||
with:
|
||||
go-version: '1.20' # auto-update/latest-go-version
|
||||
go-version: "1.24" # auto-update/latest-go-version
|
||||
|
||||
@@ -23,16 +23,17 @@ jobs:
|
||||
uses: pion/.goassets/.github/workflows/test.reusable.yml@master
|
||||
strategy:
|
||||
matrix:
|
||||
go: ['1.21', '1.20'] # auto-update/supported-go-version-list
|
||||
go: ["1.24", "1.23"] # auto-update/supported-go-version-list
|
||||
fail-fast: false
|
||||
with:
|
||||
go-version: ${{ matrix.go }}
|
||||
secrets: inherit
|
||||
|
||||
test-i386:
|
||||
uses: pion/.goassets/.github/workflows/test-i386.reusable.yml@master
|
||||
strategy:
|
||||
matrix:
|
||||
go: ['1.21', '1.20'] # auto-update/supported-go-version-list
|
||||
go: ["1.24", "1.23"] # auto-update/supported-go-version-list
|
||||
fail-fast: false
|
||||
with:
|
||||
go-version: ${{ matrix.go }}
|
||||
@@ -40,4 +41,5 @@ jobs:
|
||||
test-wasm:
|
||||
uses: pion/.goassets/.github/workflows/test-wasm.reusable.yml@master
|
||||
with:
|
||||
go-version: '1.20' # auto-update/latest-go-version
|
||||
go-version: "1.24" # auto-update/latest-go-version
|
||||
secrets: inherit
|
||||
|
||||
@@ -22,4 +22,4 @@ jobs:
|
||||
tidy:
|
||||
uses: pion/.goassets/.github/workflows/tidy-check.reusable.yml@master
|
||||
with:
|
||||
go-version: '1.21' # auto-update/latest-go-version
|
||||
go-version: "1.24" # auto-update/latest-go-version
|
||||
|
||||
+90
-67
@@ -1,101 +1,71 @@
|
||||
# SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
linters-settings:
|
||||
govet:
|
||||
check-shadowing: true
|
||||
misspell:
|
||||
locale: US
|
||||
exhaustive:
|
||||
default-signifies-exhaustive: true
|
||||
gomodguard:
|
||||
blocked:
|
||||
modules:
|
||||
- github.com/pkg/errors:
|
||||
recommendations:
|
||||
- errors
|
||||
forbidigo:
|
||||
forbid:
|
||||
- ^fmt.Print(f|ln)?$
|
||||
- ^log.(Panic|Fatal|Print)(f|ln)?$
|
||||
- ^os.Exit$
|
||||
- ^panic$
|
||||
- ^print(ln)?$
|
||||
|
||||
version: "2"
|
||||
linters:
|
||||
enable:
|
||||
- asciicheck # Simple linter to check that your code does not contain non-ASCII identifiers
|
||||
- bidichk # Checks for dangerous unicode character sequences
|
||||
- bodyclose # checks whether HTTP response body is closed successfully
|
||||
- containedctx # containedctx is a linter that detects struct contained context.Context field
|
||||
- contextcheck # check the function whether use a non-inherited context
|
||||
- cyclop # checks function and package cyclomatic complexity
|
||||
- decorder # check declaration order and count of types, constants, variables and functions
|
||||
- dogsled # Checks assignments with too many blank identifiers (e.g. x, _, _, _, := f())
|
||||
- dupl # Tool for code clone detection
|
||||
- durationcheck # check for two durations multiplied together
|
||||
- err113 # Golang linter to check the errors handling expressions
|
||||
- errcheck # Errcheck is a program for checking for unchecked errors in go programs. These unchecked errors can be critical bugs in some cases
|
||||
- errchkjson # Checks types passed to the json encoding functions. Reports unsupported types and optionally reports occations, where the check for the returned error can be omitted.
|
||||
- errname # Checks that sentinel errors are prefixed with the `Err` and error types are suffixed with the `Error`.
|
||||
- errorlint # errorlint is a linter for that can be used to find code that will cause problems with the error wrapping scheme introduced in Go 1.13.
|
||||
- exhaustive # check exhaustiveness of enum switch statements
|
||||
- exportloopref # checks for pointers to enclosing loop variables
|
||||
- forbidigo # Forbids identifiers
|
||||
- forcetypeassert # finds forced type assertions
|
||||
- gci # Gci control golang package import order and make it always deterministic.
|
||||
- gochecknoglobals # Checks that no globals are present in Go code
|
||||
- gochecknoinits # Checks that no init functions are present in Go code
|
||||
- gocognit # Computes and checks the cognitive complexity of functions
|
||||
- goconst # Finds repeated strings that could be replaced by a constant
|
||||
- gocritic # The most opinionated Go source code linter
|
||||
- gocyclo # Computes and checks the cyclomatic complexity of functions
|
||||
- godot # Check if comments end in a period
|
||||
- godox # Tool for detection of FIXME, TODO and other comment keywords
|
||||
- goerr113 # Golang linter to check the errors handling expressions
|
||||
- gofmt # Gofmt checks whether code was gofmt-ed. By default this tool runs with -s option to check for code simplification
|
||||
- gofumpt # Gofumpt checks whether code was gofumpt-ed.
|
||||
- goheader # Checks is file header matches to pattern
|
||||
- goimports # Goimports does everything that gofmt does. Additionally it checks unused imports
|
||||
- gomoddirectives # Manage the use of 'replace', 'retract', and 'excludes' directives in go.mod.
|
||||
- gomodguard # Allow and block list linter for direct Go module dependencies. This is different from depguard where there are different block types for example version constraints and module recommendations.
|
||||
- goprintffuncname # Checks that printf-like functions are named with `f` at the end
|
||||
- gosec # Inspects source code for security problems
|
||||
- gosimple # Linter for Go source code that specializes in simplifying a code
|
||||
- govet # Vet examines Go source code and reports suspicious constructs, such as Printf calls whose arguments do not align with the format string
|
||||
- grouper # An analyzer to analyze expression groups.
|
||||
- importas # Enforces consistent import aliases
|
||||
- ineffassign # Detects when assignments to existing variables are not used
|
||||
- lll # Reports long lines
|
||||
- maintidx # maintidx measures the maintainability index of each function.
|
||||
- makezero # Finds slice declarations with non-zero initial length
|
||||
- misspell # Finds commonly misspelled English words in comments
|
||||
- nakedret # Finds naked returns in functions greater than a specified function length
|
||||
- nestif # Reports deeply nested if statements
|
||||
- nilerr # Finds the code that returns nil even if it checks that the error is not nil.
|
||||
- nilnil # Checks that there is no simultaneous return of `nil` error and an invalid value.
|
||||
- nlreturn # nlreturn checks for a new line before return and branch statements to increase code clarity
|
||||
- noctx # noctx finds sending http request without context.Context
|
||||
- predeclared # find code that shadows one of Go's predeclared identifiers
|
||||
- revive # golint replacement, finds style mistakes
|
||||
- staticcheck # Staticcheck is a go vet on steroids, applying a ton of static analysis checks
|
||||
- stylecheck # Stylecheck is a replacement for golint
|
||||
- tagliatelle # Checks the struct tags.
|
||||
- tenv # tenv is analyzer that detects using os.Setenv instead of t.Setenv since Go1.17
|
||||
- tparallel # tparallel detects inappropriate usage of t.Parallel() method in your Go test codes
|
||||
- typecheck # Like the front-end of a Go compiler, parses and type-checks Go code
|
||||
- thelper # thelper detects golang test helpers without t.Helper() call and checks the consistency of test helpers
|
||||
- unconvert # Remove unnecessary type conversions
|
||||
- unparam # Reports unused function parameters
|
||||
- unused # Checks Go code for unused constants, variables, functions and types
|
||||
- varnamelen # checks that the length of a variable's name matches its scope
|
||||
- wastedassign # wastedassign finds wasted assignment statements
|
||||
- whitespace # Tool for detection of leading and trailing whitespace
|
||||
disable:
|
||||
- depguard # Go linter that checks if package imports are in a list of acceptable packages
|
||||
- containedctx # containedctx is a linter that detects struct contained context.Context field
|
||||
- cyclop # checks function and package cyclomatic complexity
|
||||
- exhaustivestruct # Checks if all struct's fields are initialized
|
||||
- funlen # Tool for detection of long functions
|
||||
- gocyclo # Computes and checks the cyclomatic complexity of functions
|
||||
- godot # Check if comments end in a period
|
||||
- gomnd # An analyzer to detect magic numbers.
|
||||
- ifshort # Checks that your code uses short syntax for if-statements whenever possible
|
||||
- gochecknoinits # Checks that no init functions are present in Go code
|
||||
- gomodguard # Allow and block list linter for direct Go module dependencies. This is different from depguard where there are different block types for example version constraints and module recommendations.
|
||||
- interfacebloat # A linter that checks length of interface.
|
||||
- ireturn # Accept Interfaces, Return Concrete Types
|
||||
- lll # Reports long lines
|
||||
- maintidx # maintidx measures the maintainability index of each function.
|
||||
- makezero # Finds slice declarations with non-zero initial length
|
||||
- maligned # Tool to detect Go structs that would take less memory if their fields were sorted
|
||||
- nakedret # Finds naked returns in functions greater than a specified function length
|
||||
- nestif # Reports deeply nested if statements
|
||||
- nlreturn # nlreturn checks for a new line before return and branch statements to increase code clarity
|
||||
- mnd # An analyzer to detect magic numbers
|
||||
- nolintlint # Reports ill-formed or insufficient nolint directives
|
||||
- paralleltest # paralleltest detects missing usage of t.Parallel() method in your Go test
|
||||
- prealloc # Finds slice declarations that could potentially be preallocated
|
||||
@@ -103,24 +73,77 @@ linters:
|
||||
- rowserrcheck # checks whether Err of rows is checked successfully
|
||||
- sqlclosecheck # Checks that sql.Rows and sql.Stmt are closed.
|
||||
- testpackage # linter that makes you use a separate _test package
|
||||
- thelper # thelper detects golang test helpers without t.Helper() call and checks the consistency of test helpers
|
||||
- varnamelen # checks that the length of a variable's name matches its scope
|
||||
- tparallel # tparallel detects inappropriate usage of t.Parallel() method in your Go test codes
|
||||
- wrapcheck # Checks that errors returned from external packages are wrapped
|
||||
- wsl # Whitespace Linter - Forces you to use empty lines!
|
||||
|
||||
issues:
|
||||
exclude-use-default: false
|
||||
exclude-rules:
|
||||
# Allow complex tests and examples, better to be self contained
|
||||
- path: (examples|main\.go|_test\.go)
|
||||
linters:
|
||||
- forbidigo
|
||||
- gocognit
|
||||
|
||||
# Allow forbidden identifiers in CLI commands
|
||||
- path: cmd
|
||||
linters:
|
||||
- forbidigo
|
||||
|
||||
run:
|
||||
skip-dirs-use-default: false
|
||||
settings:
|
||||
staticcheck:
|
||||
checks:
|
||||
- all
|
||||
# "could remove embedded field", to keep it explicit!
|
||||
- -QF1008
|
||||
# "could use tagged switch on enum", Cases conflicts with exhaustive!
|
||||
- -QF1003
|
||||
exhaustive:
|
||||
default-signifies-exhaustive: true
|
||||
forbidigo:
|
||||
forbid:
|
||||
- pattern: ^fmt.Print(f|ln)?$
|
||||
- pattern: ^log.(Panic|Fatal|Print)(f|ln)?$
|
||||
- pattern: ^os.Exit$
|
||||
- pattern: ^panic$
|
||||
- pattern: ^print(ln)?$
|
||||
- pattern: ^testing.T.(Error|Errorf|Fatal|Fatalf|Fail|FailNow)$
|
||||
pkg: ^testing$
|
||||
msg: use testify/assert instead
|
||||
analyze-types: true
|
||||
gomodguard:
|
||||
blocked:
|
||||
modules:
|
||||
- github.com/pkg/errors:
|
||||
recommendations:
|
||||
- errors
|
||||
govet:
|
||||
enable:
|
||||
- shadow
|
||||
revive:
|
||||
rules:
|
||||
# Prefer 'any' type alias over 'interface{}' for Go 1.18+ compatibility
|
||||
- name: use-any
|
||||
severity: warning
|
||||
disabled: false
|
||||
misspell:
|
||||
locale: US
|
||||
varnamelen:
|
||||
max-distance: 12
|
||||
min-name-length: 2
|
||||
ignore-type-assert-ok: true
|
||||
ignore-map-index-ok: true
|
||||
ignore-chan-recv-ok: true
|
||||
ignore-decls:
|
||||
- i int
|
||||
- n int
|
||||
- w io.Writer
|
||||
- r io.Reader
|
||||
- b []byte
|
||||
exclusions:
|
||||
generated: lax
|
||||
rules:
|
||||
- linters:
|
||||
- forbidigo
|
||||
- gocognit
|
||||
path: (examples|main\.go)
|
||||
- linters:
|
||||
- gocognit
|
||||
path: _test\.go
|
||||
- linters:
|
||||
- forbidigo
|
||||
path: cmd
|
||||
formatters:
|
||||
enable:
|
||||
- gci
|
||||
- gofmt
|
||||
- gofumpt
|
||||
- goimports
|
||||
exclusions:
|
||||
generated: lax
|
||||
|
||||
+2
-2
@@ -2,10 +2,10 @@ Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
|
||||
Upstream-Name: Pion
|
||||
Source: https://github.com/pion/
|
||||
|
||||
Files: README.md DESIGN.md **/README.md AUTHORS.txt renovate.json go.mod go.sum **/go.mod **/go.sum .eslintrc.json package.json examples/examples.json
|
||||
Files: README.md DESIGN.md **/README.md AUTHORS.txt renovate.json go.mod go.sum **/go.mod **/go.sum .eslintrc.json package.json examples.json sfu-ws/flutter/.gitignore sfu-ws/flutter/pubspec.yaml c-data-channels/webrtc.h examples/examples.json yarn.lock
|
||||
Copyright: 2023 The Pion community <https://pion.ly>
|
||||
License: MIT
|
||||
|
||||
Files: testdata/fuzz/* **/testdata/fuzz/* api/*.txt
|
||||
Files: testdata/seed/* testdata/fuzz/* **/testdata/fuzz/* api/*.txt
|
||||
Copyright: 2023 The Pion community <https://pion.ly>
|
||||
License: CC0-1.0
|
||||
|
||||
@@ -6,10 +6,10 @@
|
||||
<h4 align="center">A Go implementation of ICE</h4>
|
||||
<p align="center">
|
||||
<a href="https://pion.ly"><img src="https://img.shields.io/badge/pion-ice-gray.svg?longCache=true&colorB=brightgreen" alt="Pion ICE"></a>
|
||||
<a href="http://gophers.slack.com/messages/pion"><img src="https://img.shields.io/badge/join-us%20on%20slack-gray.svg?longCache=true&logo=slack&colorB=brightgreen" alt="Slack Widget"></a>
|
||||
<a href="https://discord.gg/PngbdqpFbt"><img src="https://img.shields.io/badge/join-us%20on%20discord-gray.svg?longCache=true&logo=discord&colorB=brightblue" alt="join us on Discord"></a> <a href="https://bsky.app/profile/pion.ly"><img src="https://img.shields.io/badge/follow-us%20on%20bluesky-gray.svg?longCache=true&logo=bluesky&colorB=brightblue" alt="Follow us on Bluesky"></a>
|
||||
<br>
|
||||
<img alt="GitHub Workflow Status" src="https://img.shields.io/github/actions/workflow/status/pion/ice/test.yaml">
|
||||
<a href="https://pkg.go.dev/github.com/pion/ice/v3"><img src="https://pkg.go.dev/badge/github.com/pion/ice/v3.svg" alt="Go Reference"></a>
|
||||
<a href="https://pkg.go.dev/github.com/pion/ice/v4"><img src="https://pkg.go.dev/badge/github.com/pion/ice/v4.svg" alt="Go Reference"></a>
|
||||
<a href="https://codecov.io/gh/pion/ice"><img src="https://codecov.io/gh/pion/ice/branch/master/graph/badge.svg" alt="Coverage Status"></a>
|
||||
<a href="https://goreportcard.com/report/github.com/pion/ice"><img src="https://goreportcard.com/badge/github.com/pion/ice" alt="Go Report Card"></a>
|
||||
<a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="License: MIT"></a>
|
||||
@@ -20,9 +20,9 @@
|
||||
The library is used as a part of our WebRTC implementation. Please refer to that [roadmap](https://github.com/pion/webrtc/issues/9) to track our major milestones.
|
||||
|
||||
### Community
|
||||
Pion has an active community on the [Slack](https://pion.ly/slack).
|
||||
Pion has an active community on the [Discord](https://discord.gg/PngbdqpFbt).
|
||||
|
||||
Follow the [Pion Twitter](https://twitter.com/_pion) for project updates and important WebRTC news.
|
||||
Follow the [Pion Bluesky](https://bsky.app/profile/pion.ly) or [Pion Twitter](https://twitter.com/_pion) for project updates and important WebRTC news.
|
||||
|
||||
We are always looking to support **your projects**. Please reach out if you have something to build!
|
||||
If you need commercial support or don't want to use public methods you can contact us at [team@pion.ly](mailto:team@pion.ly)
|
||||
|
||||
+32
-13
@@ -7,6 +7,7 @@ import (
|
||||
"context"
|
||||
"io"
|
||||
"net"
|
||||
"net/netip"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
@@ -17,10 +18,15 @@ import (
|
||||
type activeTCPConn struct {
|
||||
readBuffer, writeBuffer *packetio.Buffer
|
||||
localAddr, remoteAddr atomic.Value
|
||||
closed int32
|
||||
closed atomic.Bool
|
||||
}
|
||||
|
||||
func newActiveTCPConn(ctx context.Context, localAddress, remoteAddress string, log logging.LeveledLogger) (a *activeTCPConn) {
|
||||
func newActiveTCPConn(
|
||||
ctx context.Context,
|
||||
localAddress string,
|
||||
remoteAddress netip.AddrPort,
|
||||
log logging.LeveledLogger,
|
||||
) (a *activeTCPConn) {
|
||||
a = &activeTCPConn{
|
||||
readBuffer: packetio.NewBuffer(),
|
||||
writeBuffer: packetio.NewBuffer(),
|
||||
@@ -28,40 +34,43 @@ func newActiveTCPConn(ctx context.Context, localAddress, remoteAddress string, l
|
||||
|
||||
laddr, err := getTCPAddrOnInterface(localAddress)
|
||||
if err != nil {
|
||||
atomic.StoreInt32(&a.closed, 1)
|
||||
a.closed.Store(true)
|
||||
log.Infof("Failed to dial TCP address %s: %v", remoteAddress, err)
|
||||
return
|
||||
|
||||
return a
|
||||
}
|
||||
a.localAddr.Store(laddr)
|
||||
|
||||
go func() {
|
||||
defer func() {
|
||||
atomic.StoreInt32(&a.closed, 1)
|
||||
a.closed.Store(true)
|
||||
}()
|
||||
|
||||
dialer := &net.Dialer{
|
||||
LocalAddr: laddr,
|
||||
}
|
||||
conn, err := dialer.DialContext(ctx, "tcp", remoteAddress)
|
||||
conn, err := dialer.DialContext(ctx, "tcp", remoteAddress.String())
|
||||
if err != nil {
|
||||
log.Infof("Failed to dial TCP address %s: %v", remoteAddress, err)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
a.remoteAddr.Store(conn.RemoteAddr())
|
||||
|
||||
go func() {
|
||||
buff := make([]byte, receiveMTU)
|
||||
|
||||
for atomic.LoadInt32(&a.closed) == 0 {
|
||||
for !a.closed.Load() {
|
||||
n, err := readStreamingPacket(conn, buff)
|
||||
if err != nil {
|
||||
log.Infof("Failed to read streaming packet: %s", err)
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
if _, err := a.readBuffer.Write(buff[:n]); err != nil {
|
||||
log.Infof("Failed to write to buffer: %s", err)
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -69,15 +78,17 @@ func newActiveTCPConn(ctx context.Context, localAddress, remoteAddress string, l
|
||||
|
||||
buff := make([]byte, receiveMTU)
|
||||
|
||||
for atomic.LoadInt32(&a.closed) == 0 {
|
||||
for !a.closed.Load() {
|
||||
n, err := a.writeBuffer.Read(buff)
|
||||
if err != nil {
|
||||
log.Infof("Failed to read from buffer: %s", err)
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
if _, err = writeStreamingPacket(conn, buff[:n]); err != nil {
|
||||
log.Infof("Failed to write streaming packet: %s", err)
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -91,17 +102,19 @@ func newActiveTCPConn(ctx context.Context, localAddress, remoteAddress string, l
|
||||
}
|
||||
|
||||
func (a *activeTCPConn) ReadFrom(buff []byte) (n int, srcAddr net.Addr, err error) {
|
||||
if atomic.LoadInt32(&a.closed) == 1 {
|
||||
if a.closed.Load() {
|
||||
return 0, nil, io.ErrClosedPipe
|
||||
}
|
||||
|
||||
srcAddr = a.RemoteAddr()
|
||||
n, err = a.readBuffer.Read(buff)
|
||||
// RemoteAddr is assuredly set *after* we can read from the buffer
|
||||
srcAddr = a.RemoteAddr()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (a *activeTCPConn) WriteTo(buff []byte, _ net.Addr) (n int, err error) {
|
||||
if atomic.LoadInt32(&a.closed) == 1 {
|
||||
if a.closed.Load() {
|
||||
return 0, io.ErrClosedPipe
|
||||
}
|
||||
|
||||
@@ -109,9 +122,10 @@ func (a *activeTCPConn) WriteTo(buff []byte, _ net.Addr) (n int, err error) {
|
||||
}
|
||||
|
||||
func (a *activeTCPConn) Close() error {
|
||||
atomic.StoreInt32(&a.closed, 1)
|
||||
a.closed.Store(true)
|
||||
_ = a.readBuffer.Close()
|
||||
_ = a.writeBuffer.Close()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -123,6 +137,11 @@ func (a *activeTCPConn) LocalAddr() net.Addr {
|
||||
return &net.TCPAddr{}
|
||||
}
|
||||
|
||||
// RemoteAddr returns the remote address of the connection which is only
|
||||
// set once a background goroutine has successfully dialed. That means
|
||||
// this may return ":0" for the address prior to that happening. If this
|
||||
// becomes an issue, we can introduce a synchronization point between Dial
|
||||
// and these methods.
|
||||
func (a *activeTCPConn) RemoteAddr() net.Addr {
|
||||
if v, ok := a.remoteAddr.Load().(*net.TCPAddr); ok {
|
||||
return v
|
||||
|
||||
+145
-63
@@ -7,47 +7,54 @@
|
||||
package ice
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/netip"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/pion/logging"
|
||||
"github.com/pion/transport/v3/stdnet"
|
||||
"github.com/pion/transport/v3/test"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func getLocalIPAddress(t *testing.T, networkType NetworkType) net.IP {
|
||||
func getLocalIPAddress(t *testing.T, networkType NetworkType) netip.Addr {
|
||||
t.Helper()
|
||||
|
||||
net, err := stdnet.NewNet()
|
||||
require.NoError(t, err)
|
||||
localIPs, err := localInterfaces(net, nil, nil, []NetworkType{networkType}, false)
|
||||
_, localAddrs, err := localInterfaces(net, problematicNetworkInterfaces, nil, []NetworkType{networkType}, false)
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, localIPs)
|
||||
return localIPs[0]
|
||||
require.NotEmpty(t, localAddrs)
|
||||
|
||||
return localAddrs[0]
|
||||
}
|
||||
|
||||
func ipv6Available(t *testing.T) bool {
|
||||
t.Helper()
|
||||
|
||||
net, err := stdnet.NewNet()
|
||||
require.NoError(t, err)
|
||||
localIPs, err := localInterfaces(net, nil, nil, []NetworkType{NetworkTypeTCP6}, false)
|
||||
_, localAddrs, err := localInterfaces(net, problematicNetworkInterfaces, nil, []NetworkType{NetworkTypeTCP6}, false)
|
||||
require.NoError(t, err)
|
||||
return len(localIPs) > 0
|
||||
|
||||
return len(localAddrs) > 0
|
||||
}
|
||||
|
||||
func TestActiveTCP(t *testing.T) {
|
||||
report := test.CheckRoutines(t)
|
||||
defer report()
|
||||
defer test.CheckRoutines(t)()
|
||||
|
||||
lim := test.TimeOut(time.Second * 5)
|
||||
defer lim.Stop()
|
||||
defer test.TimeOut(time.Second * 5).Stop()
|
||||
|
||||
const listenPort = 7686
|
||||
type testCase struct {
|
||||
name string
|
||||
networkTypes []NetworkType
|
||||
listenIPAddress net.IP
|
||||
listenIPAddress netip.Addr
|
||||
selectedPairNetworkType string
|
||||
useMDNS bool
|
||||
}
|
||||
|
||||
testCases := []testCase{
|
||||
@@ -72,25 +79,30 @@ func TestActiveTCP(t *testing.T) {
|
||||
networkTypes: []NetworkType{NetworkTypeTCP6},
|
||||
listenIPAddress: getLocalIPAddress(t, NetworkTypeTCP6),
|
||||
selectedPairNetworkType: tcp,
|
||||
// if we don't use mDNS, we will very likely be filtering out location tracked ips.
|
||||
useMDNS: true,
|
||||
},
|
||||
testCase{
|
||||
name: "UDP is preferred over TCP6", // This fails some time
|
||||
name: "UDP is preferred over TCP6",
|
||||
networkTypes: supportedNetworkTypes(),
|
||||
listenIPAddress: getLocalIPAddress(t, NetworkTypeTCP6),
|
||||
selectedPairNetworkType: udp,
|
||||
// if we don't use mDNS, we will very likely be filtering out location tracked ips.
|
||||
useMDNS: true,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
r := require.New(t)
|
||||
req := require.New(t)
|
||||
|
||||
listener, err := net.ListenTCP("tcp", &net.TCPAddr{
|
||||
IP: testCase.listenIPAddress,
|
||||
IP: testCase.listenIPAddress.AsSlice(),
|
||||
Port: listenPort,
|
||||
Zone: testCase.listenIPAddress.Zone(),
|
||||
})
|
||||
r.NoError(err)
|
||||
req.NoError(err)
|
||||
defer func() {
|
||||
_ = listener.Close()
|
||||
}()
|
||||
@@ -107,104 +119,174 @@ func TestActiveTCP(t *testing.T) {
|
||||
_ = tcpMux.Close()
|
||||
}()
|
||||
|
||||
r.NotNil(tcpMux.LocalAddr(), "tcpMux.LocalAddr() is nil")
|
||||
req.NotNil(tcpMux.LocalAddr(), "tcpMux.LocalAddr() is nil")
|
||||
|
||||
hostAcceptanceMinWait := 100 * time.Millisecond
|
||||
passiveAgent, err := NewAgent(&AgentConfig{
|
||||
cfg := &AgentConfig{
|
||||
TCPMux: tcpMux,
|
||||
CandidateTypes: []CandidateType{CandidateTypeHost},
|
||||
NetworkTypes: testCase.networkTypes,
|
||||
LoggerFactory: loggerFactory,
|
||||
IncludeLoopback: true,
|
||||
HostAcceptanceMinWait: &hostAcceptanceMinWait,
|
||||
})
|
||||
r.NoError(err)
|
||||
r.NotNil(passiveAgent)
|
||||
InterfaceFilter: problematicNetworkInterfaces,
|
||||
}
|
||||
if testCase.useMDNS {
|
||||
cfg.MulticastDNSMode = MulticastDNSModeQueryAndGather
|
||||
}
|
||||
passiveAgent, err := NewAgent(cfg)
|
||||
req.NoError(err)
|
||||
req.NotNil(passiveAgent)
|
||||
|
||||
activeAgent, err := NewAgent(&AgentConfig{
|
||||
CandidateTypes: []CandidateType{CandidateTypeHost},
|
||||
NetworkTypes: testCase.networkTypes,
|
||||
LoggerFactory: loggerFactory,
|
||||
HostAcceptanceMinWait: &hostAcceptanceMinWait,
|
||||
InterfaceFilter: problematicNetworkInterfaces,
|
||||
})
|
||||
r.NoError(err)
|
||||
r.NotNil(activeAgent)
|
||||
req.NoError(err)
|
||||
req.NotNil(activeAgent)
|
||||
|
||||
passiveAgentConn, activeAgenConn := connect(passiveAgent, activeAgent)
|
||||
r.NotNil(passiveAgentConn)
|
||||
r.NotNil(activeAgenConn)
|
||||
passiveAgentConn, activeAgenConn := connect(t, passiveAgent, activeAgent)
|
||||
req.NotNil(passiveAgentConn)
|
||||
req.NotNil(activeAgenConn)
|
||||
|
||||
defer func() {
|
||||
req.NoError(activeAgenConn.Close())
|
||||
req.NoError(passiveAgentConn.Close())
|
||||
}()
|
||||
|
||||
pair := passiveAgent.getSelectedPair()
|
||||
r.NotNil(pair)
|
||||
r.Equal(testCase.selectedPairNetworkType, pair.Local.NetworkType().NetworkShort())
|
||||
req.NotNil(pair)
|
||||
req.Equal(testCase.selectedPairNetworkType, pair.Local.NetworkType().NetworkShort())
|
||||
|
||||
foo := []byte("foo")
|
||||
_, err = passiveAgentConn.Write(foo)
|
||||
r.NoError(err)
|
||||
req.NoError(err)
|
||||
|
||||
buffer := make([]byte, 1024)
|
||||
n, err := activeAgenConn.Read(buffer)
|
||||
r.NoError(err)
|
||||
r.Equal(foo, buffer[:n])
|
||||
req.NoError(err)
|
||||
req.Equal(foo, buffer[:n])
|
||||
|
||||
bar := []byte("bar")
|
||||
_, err = activeAgenConn.Write(bar)
|
||||
r.NoError(err)
|
||||
req.NoError(err)
|
||||
|
||||
n, err = passiveAgentConn.Read(buffer)
|
||||
r.NoError(err)
|
||||
r.Equal(bar, buffer[:n])
|
||||
|
||||
r.NoError(activeAgenConn.Close())
|
||||
r.NoError(passiveAgentConn.Close())
|
||||
req.NoError(err)
|
||||
req.Equal(bar, buffer[:n])
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Assert that Active TCP connectivity isn't established inside
|
||||
// the main thread of the Agent
|
||||
// Assert that Active TCP connectivity isn't established inside.
|
||||
// the main thread of the Agent.
|
||||
func TestActiveTCP_NonBlocking(t *testing.T) {
|
||||
report := test.CheckRoutines(t)
|
||||
defer report()
|
||||
defer test.CheckRoutines(t)()
|
||||
|
||||
lim := test.TimeOut(time.Second * 5)
|
||||
defer lim.Stop()
|
||||
defer test.TimeOut(time.Second * 5).Stop()
|
||||
|
||||
cfg := &AgentConfig{
|
||||
NetworkTypes: supportedNetworkTypes(),
|
||||
NetworkTypes: supportedNetworkTypes(),
|
||||
InterfaceFilter: problematicNetworkInterfaces,
|
||||
}
|
||||
|
||||
aAgent, err := NewAgent(cfg)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
defer func() {
|
||||
require.NoError(t, aAgent.Close())
|
||||
}()
|
||||
|
||||
bAgent, err := NewAgent(cfg)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
isConnected := make(chan interface{})
|
||||
defer func() {
|
||||
require.NoError(t, bAgent.Close())
|
||||
}()
|
||||
|
||||
isConnected := make(chan any)
|
||||
err = aAgent.OnConnectionStateChange(func(c ConnectionState) {
|
||||
if c == ConnectionStateConnected {
|
||||
close(isConnected)
|
||||
}
|
||||
})
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
// Add a invalid ice-tcp candidate to each
|
||||
invalidCandidate, err := UnmarshalCandidate("1052353102 1 tcp 1675624447 192.0.2.1 8080 typ host tcptype passive")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assert.NoError(t, aAgent.AddRemoteCandidate(invalidCandidate))
|
||||
assert.NoError(t, bAgent.AddRemoteCandidate(invalidCandidate))
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, aAgent.AddRemoteCandidate(invalidCandidate))
|
||||
require.NoError(t, bAgent.AddRemoteCandidate(invalidCandidate))
|
||||
|
||||
connect(aAgent, bAgent)
|
||||
connect(t, aAgent, bAgent)
|
||||
|
||||
<-isConnected
|
||||
assert.NoError(t, aAgent.Close())
|
||||
assert.NoError(t, bAgent.Close())
|
||||
}
|
||||
|
||||
// Assert that we ignore remote TCP candidates when running a UDP Only Agent.
|
||||
func TestActiveTCP_Respect_NetworkTypes(t *testing.T) {
|
||||
defer test.CheckRoutines(t)()
|
||||
defer test.TimeOut(time.Second * 5).Stop()
|
||||
|
||||
tcpListener, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
require.NoError(t, err)
|
||||
|
||||
_, port, err := net.SplitHostPort(tcpListener.Addr().String())
|
||||
require.NoError(t, err)
|
||||
|
||||
var incomingTCPCount uint64
|
||||
go func() {
|
||||
for {
|
||||
conn, listenErr := tcpListener.Accept()
|
||||
if listenErr != nil {
|
||||
return
|
||||
}
|
||||
|
||||
require.NoError(t, conn.Close())
|
||||
atomic.AddUint64(&incomingTCPCount, ^uint64(0))
|
||||
}
|
||||
}()
|
||||
|
||||
cfg := &AgentConfig{
|
||||
NetworkTypes: []NetworkType{NetworkTypeUDP4, NetworkTypeUDP6, NetworkTypeTCP6},
|
||||
InterfaceFilter: problematicNetworkInterfaces,
|
||||
IncludeLoopback: true,
|
||||
}
|
||||
|
||||
aAgent, err := NewAgent(cfg)
|
||||
require.NoError(t, err)
|
||||
|
||||
defer func() {
|
||||
require.NoError(t, aAgent.Close())
|
||||
}()
|
||||
|
||||
bAgent, err := NewAgent(cfg)
|
||||
require.NoError(t, err)
|
||||
|
||||
defer func() {
|
||||
require.NoError(t, bAgent.Close())
|
||||
}()
|
||||
|
||||
isConnected := make(chan any)
|
||||
err = aAgent.OnConnectionStateChange(func(c ConnectionState) {
|
||||
if c == ConnectionStateConnected {
|
||||
close(isConnected)
|
||||
}
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
invalidCandidate, err := UnmarshalCandidate(
|
||||
fmt.Sprintf("1052353102 1 tcp 1675624447 127.0.0.1 %s typ host tcptype passive", port),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, aAgent.AddRemoteCandidate(invalidCandidate))
|
||||
require.NoError(t, bAgent.AddRemoteCandidate(invalidCandidate))
|
||||
|
||||
connect(t, aAgent, bAgent)
|
||||
|
||||
<-isConnected
|
||||
require.NoError(t, tcpListener.Close())
|
||||
require.Equal(t, uint64(0), atomic.LoadUint64(&incomingTCPCount))
|
||||
}
|
||||
|
||||
@@ -4,52 +4,133 @@
|
||||
package ice
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/netip"
|
||||
)
|
||||
|
||||
func parseMulticastAnswerAddr(in net.Addr) (net.IP, bool) {
|
||||
func addrWithOptionalZone(addr netip.Addr, zone string) netip.Addr {
|
||||
if zone == "" {
|
||||
return addr
|
||||
}
|
||||
if addr.Is6() && (addr.IsLinkLocalUnicast() || addr.IsLinkLocalMulticast()) {
|
||||
return addr.WithZone(zone)
|
||||
}
|
||||
|
||||
return addr
|
||||
}
|
||||
|
||||
// parseAddrFromIface should only be used when it's known the address belongs to that interface.
|
||||
// e.g. it's LocalAddress on a listener.
|
||||
func parseAddrFromIface(in net.Addr, ifcName string) (netip.Addr, int, NetworkType, error) {
|
||||
addr, port, nt, err := parseAddr(in)
|
||||
if err != nil {
|
||||
return netip.Addr{}, 0, 0, err
|
||||
}
|
||||
if _, ok := in.(*net.IPNet); ok {
|
||||
// net.IPNet does not have a Zone but we provide it from the interface
|
||||
addr = addrWithOptionalZone(addr, ifcName)
|
||||
}
|
||||
|
||||
return addr, port, nt, nil
|
||||
}
|
||||
|
||||
func parseAddr(in net.Addr) (netip.Addr, int, NetworkType, error) { //nolint:cyclop
|
||||
switch addr := in.(type) {
|
||||
case *net.IPNet:
|
||||
ipAddr, err := ipAddrToNetIP(addr.IP, "")
|
||||
if err != nil {
|
||||
return netip.Addr{}, 0, 0, err
|
||||
}
|
||||
|
||||
return ipAddr, 0, 0, nil
|
||||
case *net.IPAddr:
|
||||
return addr.IP, true
|
||||
ipAddr, err := ipAddrToNetIP(addr.IP, addr.Zone)
|
||||
if err != nil {
|
||||
return netip.Addr{}, 0, 0, err
|
||||
}
|
||||
|
||||
return ipAddr, 0, 0, nil
|
||||
case *net.UDPAddr:
|
||||
return addr.IP, true
|
||||
ipAddr, err := ipAddrToNetIP(addr.IP, addr.Zone)
|
||||
if err != nil {
|
||||
return netip.Addr{}, 0, 0, err
|
||||
}
|
||||
var nt NetworkType
|
||||
if ipAddr.Is4() {
|
||||
nt = NetworkTypeUDP4
|
||||
} else {
|
||||
nt = NetworkTypeUDP6
|
||||
}
|
||||
|
||||
return ipAddr, addr.Port, nt, nil
|
||||
case *net.TCPAddr:
|
||||
return addr.IP, true
|
||||
ipAddr, err := ipAddrToNetIP(addr.IP, addr.Zone)
|
||||
if err != nil {
|
||||
return netip.Addr{}, 0, 0, err
|
||||
}
|
||||
var nt NetworkType
|
||||
if ipAddr.Is4() {
|
||||
nt = NetworkTypeTCP4
|
||||
} else {
|
||||
nt = NetworkTypeTCP6
|
||||
}
|
||||
|
||||
return ipAddr, addr.Port, nt, nil
|
||||
default:
|
||||
return netip.Addr{}, 0, 0, addrParseError{in}
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func parseAddr(in net.Addr) (net.IP, int, NetworkType, bool) {
|
||||
switch addr := in.(type) {
|
||||
case *net.UDPAddr:
|
||||
return addr.IP, addr.Port, NetworkTypeUDP4, true
|
||||
case *net.TCPAddr:
|
||||
return addr.IP, addr.Port, NetworkTypeTCP4, true
|
||||
}
|
||||
return nil, 0, 0, false
|
||||
type addrParseError struct {
|
||||
addr net.Addr
|
||||
}
|
||||
|
||||
func createAddr(network NetworkType, ip net.IP, port int) net.Addr {
|
||||
func (e addrParseError) Error() string {
|
||||
return fmt.Sprintf("do not know how to parse address type %T", e.addr)
|
||||
}
|
||||
|
||||
type ipConvertError struct {
|
||||
ip []byte
|
||||
}
|
||||
|
||||
func (e ipConvertError) Error() string {
|
||||
return fmt.Sprintf("failed to convert IP '%s' to netip.Addr", e.ip)
|
||||
}
|
||||
|
||||
func ipAddrToNetIP(ip []byte, zone string) (netip.Addr, error) {
|
||||
netIPAddr, ok := netip.AddrFromSlice(ip)
|
||||
if !ok {
|
||||
return netip.Addr{}, ipConvertError{ip}
|
||||
}
|
||||
// we'd rather have an IPv4-mapped IPv6 become IPv4 so that it is usable.
|
||||
netIPAddr = netIPAddr.Unmap()
|
||||
netIPAddr = addrWithOptionalZone(netIPAddr, zone)
|
||||
|
||||
return netIPAddr, nil
|
||||
}
|
||||
|
||||
func createAddr(network NetworkType, ip netip.Addr, port int) net.Addr {
|
||||
switch {
|
||||
case network.IsTCP():
|
||||
return &net.TCPAddr{IP: ip, Port: port}
|
||||
return &net.TCPAddr{IP: ip.AsSlice(), Port: port, Zone: ip.Zone()}
|
||||
default:
|
||||
return &net.UDPAddr{IP: ip, Port: port}
|
||||
return &net.UDPAddr{IP: ip.AsSlice(), Port: port, Zone: ip.Zone()}
|
||||
}
|
||||
}
|
||||
|
||||
func addrEqual(a, b net.Addr) bool {
|
||||
aIP, aPort, aType, aOk := parseAddr(a)
|
||||
if !aOk {
|
||||
aIP, aPort, aType, aErr := parseAddr(a)
|
||||
if aErr != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
bIP, bPort, bType, bOk := parseAddr(b)
|
||||
if !bOk {
|
||||
bIP, bPort, bType, bErr := parseAddr(b)
|
||||
if bErr != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return aType == bType && aIP.Equal(bIP) && aPort == bPort
|
||||
return aType == bType && aIP.Compare(bIP) == 0 && aPort == bPort
|
||||
}
|
||||
|
||||
// AddrPort is an IP and a port number.
|
||||
@@ -60,12 +141,13 @@ func toAddrPort(addr net.Addr) AddrPort {
|
||||
switch addr := addr.(type) {
|
||||
case *net.UDPAddr:
|
||||
copy(ap[:16], addr.IP.To16())
|
||||
ap[16] = uint8(addr.Port >> 8)
|
||||
ap[17] = uint8(addr.Port)
|
||||
ap[16] = uint8(addr.Port >> 8) //nolint:gosec // G115 false positive
|
||||
ap[17] = uint8(addr.Port) //nolint:gosec // G115 false positive
|
||||
case *net.TCPAddr:
|
||||
copy(ap[:16], addr.IP.To16())
|
||||
ap[16] = uint8(addr.Port >> 8)
|
||||
ap[17] = uint8(addr.Port)
|
||||
ap[16] = uint8(addr.Port >> 8) //nolint:gosec // G115 false positive
|
||||
ap[17] = uint8(addr.Port) //nolint:gosec // G115 false positive
|
||||
}
|
||||
|
||||
return ap
|
||||
}
|
||||
|
||||
+80
-52
@@ -8,47 +8,50 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/pion/logging"
|
||||
"github.com/pion/stun/v2"
|
||||
"github.com/pion/stun/v3"
|
||||
"github.com/pion/transport/v3"
|
||||
"golang.org/x/net/proxy"
|
||||
)
|
||||
|
||||
const (
|
||||
// defaultCheckInterval is the interval at which the agent performs candidate checks in the connecting phase
|
||||
// defaultCheckInterval is the interval at which the agent performs candidate checks in the connecting phase.
|
||||
defaultCheckInterval = 200 * time.Millisecond
|
||||
|
||||
// keepaliveInterval used to keep candidates alive
|
||||
// keepaliveInterval used to keep candidates alive.
|
||||
defaultKeepaliveInterval = 2 * time.Second
|
||||
|
||||
// defaultDisconnectedTimeout is the default time till an Agent transitions disconnected
|
||||
// defaultDisconnectedTimeout is the default time till an Agent transitions disconnected.
|
||||
defaultDisconnectedTimeout = 5 * time.Second
|
||||
|
||||
// defaultFailedTimeout is the default time till an Agent transitions to failed after disconnected
|
||||
// defaultFailedTimeout is the default time till an Agent transitions to failed after disconnected.
|
||||
defaultFailedTimeout = 25 * time.Second
|
||||
|
||||
// defaultHostAcceptanceMinWait is the wait time before nominating a host candidate
|
||||
// defaultHostAcceptanceMinWait is the wait time before nominating a host candidate.
|
||||
defaultHostAcceptanceMinWait = 0
|
||||
|
||||
// defaultSrflxAcceptanceMinWait is the wait time before nominating a srflx candidate
|
||||
// defaultSrflxAcceptanceMinWait is the wait time before nominating a srflx candidate.
|
||||
defaultSrflxAcceptanceMinWait = 500 * time.Millisecond
|
||||
|
||||
// defaultPrflxAcceptanceMinWait is the wait time before nominating a prflx candidate
|
||||
// defaultPrflxAcceptanceMinWait is the wait time before nominating a prflx candidate.
|
||||
defaultPrflxAcceptanceMinWait = 1000 * time.Millisecond
|
||||
|
||||
// defaultRelayAcceptanceMinWait is the wait time before nominating a relay candidate
|
||||
// defaultRelayAcceptanceMinWait is the wait time before nominating a relay candidate.
|
||||
defaultRelayAcceptanceMinWait = 2000 * time.Millisecond
|
||||
|
||||
// defaultMaxBindingRequests is the maximum number of binding requests before considering a pair failed
|
||||
// defaultSTUNGatherTimeout is the wait time for STUN responses.
|
||||
defaultSTUNGatherTimeout = 5 * time.Second
|
||||
|
||||
// defaultMaxBindingRequests is the maximum number of binding requests before considering a pair failed.
|
||||
defaultMaxBindingRequests = 7
|
||||
|
||||
// TCPPriorityOffset is a number which is subtracted from the default (UDP) candidate type preference
|
||||
// for host, srflx and prfx candidate types.
|
||||
defaultTCPPriorityOffset = 27
|
||||
|
||||
// maxBufferSize is the number of bytes that can be buffered before we start to error
|
||||
// maxBufferSize is the number of bytes that can be buffered before we start to error.
|
||||
maxBufferSize = 1000 * 1000 // 1MB
|
||||
|
||||
// maxBindingRequestTimeout is the wait time before binding requests can be deleted
|
||||
// maxBindingRequestTimeout is the wait time before binding requests can be deleted.
|
||||
maxBindingRequestTimeout = 4000 * time.Millisecond
|
||||
)
|
||||
|
||||
@@ -57,7 +60,7 @@ func defaultCandidateTypes() []CandidateType {
|
||||
}
|
||||
|
||||
// AgentConfig collects the arguments to ice.Agent construction into
|
||||
// a single structure, for future-proofness of the interface
|
||||
// a single structure, for future-proofness of the interface.
|
||||
type AgentConfig struct {
|
||||
Urls []*stun.URI
|
||||
|
||||
@@ -88,7 +91,7 @@ type AgentConfig struct {
|
||||
|
||||
// KeepaliveInterval determines how often should we send ICE
|
||||
// keepalives (should be less then connectiontimeout above)
|
||||
// when this is nil, it defaults to 10 seconds.
|
||||
// when this is nil, it defaults to 2 seconds.
|
||||
// A keepalive interval of 0 means we never send keepalive packets
|
||||
KeepaliveInterval *time.Duration
|
||||
|
||||
@@ -130,12 +133,14 @@ type AgentConfig struct {
|
||||
|
||||
// HostAcceptanceMinWait specify a minimum wait time before selecting host candidates
|
||||
HostAcceptanceMinWait *time.Duration
|
||||
// HostAcceptanceMinWait specify a minimum wait time before selecting srflx candidates
|
||||
// SrflxAcceptanceMinWait specify a minimum wait time before selecting srflx candidates
|
||||
SrflxAcceptanceMinWait *time.Duration
|
||||
// HostAcceptanceMinWait specify a minimum wait time before selecting prflx candidates
|
||||
// PrflxAcceptanceMinWait specify a minimum wait time before selecting prflx candidates
|
||||
PrflxAcceptanceMinWait *time.Duration
|
||||
// HostAcceptanceMinWait specify a minimum wait time before selecting relay candidates
|
||||
// RelayAcceptanceMinWait specify a minimum wait time before selecting relay candidates
|
||||
RelayAcceptanceMinWait *time.Duration
|
||||
// STUNGatherTimeout specify a minimum wait time for STUN responses
|
||||
STUNGatherTimeout *time.Duration
|
||||
|
||||
// Net is the our abstracted network interface for internal development purpose only
|
||||
// (see https://github.com/pion/transport)
|
||||
@@ -143,11 +148,11 @@ type AgentConfig struct {
|
||||
|
||||
// InterfaceFilter is a function that you can use in order to whitelist or blacklist
|
||||
// the interfaces which are used to gather ICE candidates.
|
||||
InterfaceFilter func(string) bool
|
||||
InterfaceFilter func(string) (keep bool)
|
||||
|
||||
// IPFilter is a function that you can use in order to whitelist or blacklist
|
||||
// the ips which are used to gather ICE candidates.
|
||||
IPFilter func(net.IP) bool
|
||||
IPFilter func(net.IP) (keep bool)
|
||||
|
||||
// InsecureSkipVerify controls if self-signed certificates are accepted when connecting
|
||||
// to TURN servers via TLS or DTLS
|
||||
@@ -188,105 +193,127 @@ type AgentConfig struct {
|
||||
// DisableActiveTCP can be used to disable Active TCP candidates. Otherwise when TCP is enabled
|
||||
// Active TCP candidates will be created when a new passive TCP remote candidate is added.
|
||||
DisableActiveTCP bool
|
||||
|
||||
// BindingRequestHandler allows applications to perform logic on incoming STUN Binding Requests
|
||||
// This was implemented to allow users to
|
||||
// * Log incoming Binding Requests for debugging
|
||||
// * Implement draft-thatcher-ice-renomination
|
||||
// * Implement custom CandidatePair switching logic
|
||||
BindingRequestHandler func(m *stun.Message, local, remote Candidate, pair *CandidatePair) bool
|
||||
|
||||
// EnableUseCandidateCheckPriority can be used to enable checking for equal or higher priority to
|
||||
// switch selected candidate pair if the peer requests USE-CANDIDATE and agent is a lite agent.
|
||||
// This is disabled by default, i. e. when peer requests USE-CANDIDATE, the selected pair will be
|
||||
// switched to that irrespective of relative priority between current selected pair
|
||||
// and priority of the pair being switched to.
|
||||
EnableUseCandidateCheckPriority bool
|
||||
}
|
||||
|
||||
// initWithDefaults populates an agent and falls back to defaults if fields are unset
|
||||
func (config *AgentConfig) initWithDefaults(a *Agent) {
|
||||
// initWithDefaults populates an agent and falls back to defaults if fields are unset.
|
||||
func (config *AgentConfig) initWithDefaults(agent *Agent) { //nolint:cyclop
|
||||
if config.MaxBindingRequests == nil {
|
||||
a.maxBindingRequests = defaultMaxBindingRequests
|
||||
agent.maxBindingRequests = defaultMaxBindingRequests
|
||||
} else {
|
||||
a.maxBindingRequests = *config.MaxBindingRequests
|
||||
agent.maxBindingRequests = *config.MaxBindingRequests
|
||||
}
|
||||
|
||||
if config.HostAcceptanceMinWait == nil {
|
||||
a.hostAcceptanceMinWait = defaultHostAcceptanceMinWait
|
||||
agent.hostAcceptanceMinWait = defaultHostAcceptanceMinWait
|
||||
} else {
|
||||
a.hostAcceptanceMinWait = *config.HostAcceptanceMinWait
|
||||
agent.hostAcceptanceMinWait = *config.HostAcceptanceMinWait
|
||||
}
|
||||
|
||||
if config.SrflxAcceptanceMinWait == nil {
|
||||
a.srflxAcceptanceMinWait = defaultSrflxAcceptanceMinWait
|
||||
agent.srflxAcceptanceMinWait = defaultSrflxAcceptanceMinWait
|
||||
} else {
|
||||
a.srflxAcceptanceMinWait = *config.SrflxAcceptanceMinWait
|
||||
agent.srflxAcceptanceMinWait = *config.SrflxAcceptanceMinWait
|
||||
}
|
||||
|
||||
if config.PrflxAcceptanceMinWait == nil {
|
||||
a.prflxAcceptanceMinWait = defaultPrflxAcceptanceMinWait
|
||||
agent.prflxAcceptanceMinWait = defaultPrflxAcceptanceMinWait
|
||||
} else {
|
||||
a.prflxAcceptanceMinWait = *config.PrflxAcceptanceMinWait
|
||||
agent.prflxAcceptanceMinWait = *config.PrflxAcceptanceMinWait
|
||||
}
|
||||
|
||||
if config.RelayAcceptanceMinWait == nil {
|
||||
a.relayAcceptanceMinWait = defaultRelayAcceptanceMinWait
|
||||
agent.relayAcceptanceMinWait = defaultRelayAcceptanceMinWait
|
||||
} else {
|
||||
a.relayAcceptanceMinWait = *config.RelayAcceptanceMinWait
|
||||
agent.relayAcceptanceMinWait = *config.RelayAcceptanceMinWait
|
||||
}
|
||||
|
||||
if config.STUNGatherTimeout == nil {
|
||||
agent.stunGatherTimeout = defaultSTUNGatherTimeout
|
||||
} else {
|
||||
agent.stunGatherTimeout = *config.STUNGatherTimeout
|
||||
}
|
||||
|
||||
if config.TCPPriorityOffset == nil {
|
||||
a.tcpPriorityOffset = defaultTCPPriorityOffset
|
||||
agent.tcpPriorityOffset = defaultTCPPriorityOffset
|
||||
} else {
|
||||
a.tcpPriorityOffset = *config.TCPPriorityOffset
|
||||
agent.tcpPriorityOffset = *config.TCPPriorityOffset
|
||||
}
|
||||
|
||||
if config.DisconnectedTimeout == nil {
|
||||
a.disconnectedTimeout = defaultDisconnectedTimeout
|
||||
agent.disconnectedTimeout = defaultDisconnectedTimeout
|
||||
} else {
|
||||
a.disconnectedTimeout = *config.DisconnectedTimeout
|
||||
agent.disconnectedTimeout = *config.DisconnectedTimeout
|
||||
}
|
||||
|
||||
if config.FailedTimeout == nil {
|
||||
a.failedTimeout = defaultFailedTimeout
|
||||
agent.failedTimeout = defaultFailedTimeout
|
||||
} else {
|
||||
a.failedTimeout = *config.FailedTimeout
|
||||
agent.failedTimeout = *config.FailedTimeout
|
||||
}
|
||||
|
||||
if config.KeepaliveInterval == nil {
|
||||
a.keepaliveInterval = defaultKeepaliveInterval
|
||||
agent.keepaliveInterval = defaultKeepaliveInterval
|
||||
} else {
|
||||
a.keepaliveInterval = *config.KeepaliveInterval
|
||||
agent.keepaliveInterval = *config.KeepaliveInterval
|
||||
}
|
||||
|
||||
if config.CheckInterval == nil {
|
||||
a.checkInterval = defaultCheckInterval
|
||||
agent.checkInterval = defaultCheckInterval
|
||||
} else {
|
||||
a.checkInterval = *config.CheckInterval
|
||||
agent.checkInterval = *config.CheckInterval
|
||||
}
|
||||
|
||||
if config.CandidateTypes == nil || len(config.CandidateTypes) == 0 {
|
||||
a.candidateTypes = defaultCandidateTypes()
|
||||
if len(config.CandidateTypes) == 0 {
|
||||
agent.candidateTypes = defaultCandidateTypes()
|
||||
} else {
|
||||
a.candidateTypes = config.CandidateTypes
|
||||
agent.candidateTypes = config.CandidateTypes
|
||||
}
|
||||
}
|
||||
|
||||
func (config *AgentConfig) initExtIPMapping(a *Agent) error {
|
||||
func (config *AgentConfig) initExtIPMapping(agent *Agent) error { //nolint:cyclop
|
||||
var err error
|
||||
a.extIPMapper, err = newExternalIPMapper(config.NAT1To1IPCandidateType, config.NAT1To1IPs)
|
||||
agent.extIPMapper, err = newExternalIPMapper(config.NAT1To1IPCandidateType, config.NAT1To1IPs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if a.extIPMapper == nil {
|
||||
if agent.extIPMapper == nil {
|
||||
return nil // This may happen when config.NAT1To1IPs is an empty array
|
||||
}
|
||||
if a.extIPMapper.candidateType == CandidateTypeHost {
|
||||
if a.mDNSMode == MulticastDNSModeQueryAndGather {
|
||||
if agent.extIPMapper.candidateType == CandidateTypeHost { //nolint:nestif
|
||||
if agent.mDNSMode == MulticastDNSModeQueryAndGather {
|
||||
return ErrMulticastDNSWithNAT1To1IPMapping
|
||||
}
|
||||
candiHostEnabled := false
|
||||
for _, candiType := range a.candidateTypes {
|
||||
for _, candiType := range agent.candidateTypes {
|
||||
if candiType == CandidateTypeHost {
|
||||
candiHostEnabled = true
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
if !candiHostEnabled {
|
||||
return ErrIneffectiveNAT1To1IPMappingHost
|
||||
}
|
||||
} else if a.extIPMapper.candidateType == CandidateTypeServerReflexive {
|
||||
} else if agent.extIPMapper.candidateType == CandidateTypeServerReflexive {
|
||||
candiSrflxEnabled := false
|
||||
for _, candiType := range a.candidateTypes {
|
||||
for _, candiType := range agent.candidateTypes {
|
||||
if candiType == CandidateTypeServerReflexive {
|
||||
candiSrflxEnabled = true
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -294,5 +321,6 @@ func (config *AgentConfig) initExtIPMapping(a *Agent) error {
|
||||
return ErrIneffectiveNAT1To1IPMappingSrflx
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -13,19 +13,12 @@ import (
|
||||
)
|
||||
|
||||
func TestNoBestAvailableCandidatePairAfterAgentConstruction(t *testing.T) {
|
||||
agent := setupTest(t)
|
||||
|
||||
require.Nil(t, agent.getBestAvailableCandidatePair())
|
||||
|
||||
tearDownTest(t, agent)
|
||||
}
|
||||
|
||||
func setupTest(t *testing.T) *Agent {
|
||||
agent, err := NewAgent(&AgentConfig{})
|
||||
require.NoError(t, err)
|
||||
return agent
|
||||
}
|
||||
|
||||
func tearDownTest(t *testing.T, agent *Agent) {
|
||||
require.NoError(t, agent.Close())
|
||||
defer func() {
|
||||
require.NoError(t, agent.Close())
|
||||
}()
|
||||
|
||||
require.Nil(t, agent.getBestAvailableCandidatePair())
|
||||
}
|
||||
|
||||
@@ -9,12 +9,14 @@ package ice
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestAgentGetBestValidCandidatePair(t *testing.T) {
|
||||
f := setupTestAgentGetBestValidCandidatePair(t)
|
||||
defer func() {
|
||||
require.NoError(t, f.sut.Close())
|
||||
}()
|
||||
|
||||
remoteCandidatesFromLowestPriorityToHighest := []Candidate{f.relayRemote, f.srflxRemote, f.prflxRemote, f.hostRemote}
|
||||
|
||||
@@ -27,11 +29,11 @@ func TestAgentGetBestValidCandidatePair(t *testing.T) {
|
||||
|
||||
require.Equal(t, actualBestPair.String(), expectedBestPair.String())
|
||||
}
|
||||
|
||||
assert.NoError(t, f.sut.Close())
|
||||
}
|
||||
|
||||
func setupTestAgentGetBestValidCandidatePair(t *testing.T) *TestAgentGetBestValidCandidatePairFixture {
|
||||
t.Helper()
|
||||
|
||||
fixture := new(TestAgentGetBestValidCandidatePairFixture)
|
||||
fixture.hostLocal = newHostLocal(t)
|
||||
fixture.relayRemote = newRelayRemote(t)
|
||||
|
||||
+143
-25
@@ -3,16 +3,20 @@
|
||||
|
||||
package ice
|
||||
|
||||
// OnConnectionStateChange sets a handler that is fired when the connection state changes
|
||||
import "sync"
|
||||
|
||||
// OnConnectionStateChange sets a handler that is fired when the connection state changes.
|
||||
func (a *Agent) OnConnectionStateChange(f func(ConnectionState)) error {
|
||||
a.onConnectionStateChangeHdlr.Store(f)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// OnSelectedCandidatePairChange sets a handler that is fired when the final candidate
|
||||
// pair is selected
|
||||
// OnSelectedCandidatePairChange sets a handler that is fired when the final candidate.
|
||||
// pair is selected.
|
||||
func (a *Agent) OnSelectedCandidatePairChange(f func(Candidate, Candidate)) error {
|
||||
a.onSelectedCandidatePairChangeHdlr.Store(f)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -20,53 +24,167 @@ func (a *Agent) OnSelectedCandidatePairChange(f func(Candidate, Candidate)) erro
|
||||
// the gathering process complete the last candidate is nil.
|
||||
func (a *Agent) OnCandidate(f func(Candidate)) error {
|
||||
a.onCandidateHdlr.Store(f)
|
||||
return nil
|
||||
}
|
||||
|
||||
// OnSuccessfulSelectedPairBindingResponse sets a handler that is fired when a successful binding response is received for the selected candidate pair
|
||||
func (a *Agent) OnSuccessfulSelectedPairBindingResponse(f func(*CandidatePair)) error {
|
||||
a.onSuccessfulSelectedPairBindingResponseHdlr.Store(f)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *Agent) onSelectedCandidatePairChange(p *CandidatePair) {
|
||||
if h, ok := a.onSelectedCandidatePairChangeHdlr.Load().(func(Candidate, Candidate)); ok {
|
||||
if h, ok := a.onSelectedCandidatePairChangeHdlr.Load().(func(Candidate, Candidate)); ok && h != nil {
|
||||
h(p.Local, p.Remote)
|
||||
}
|
||||
}
|
||||
|
||||
func (a *Agent) onCandidate(c Candidate) {
|
||||
if onCandidateHdlr, ok := a.onCandidateHdlr.Load().(func(Candidate)); ok {
|
||||
if onCandidateHdlr, ok := a.onCandidateHdlr.Load().(func(Candidate)); ok && onCandidateHdlr != nil {
|
||||
onCandidateHdlr(c)
|
||||
}
|
||||
}
|
||||
|
||||
func (a *Agent) onConnectionStateChange(s ConnectionState) {
|
||||
if hdlr, ok := a.onConnectionStateChangeHdlr.Load().(func(ConnectionState)); ok {
|
||||
if hdlr, ok := a.onConnectionStateChangeHdlr.Load().(func(ConnectionState)); ok && hdlr != nil {
|
||||
hdlr(s)
|
||||
}
|
||||
}
|
||||
|
||||
func (a *Agent) onSuccessfulSelectedPairBindingResponse(p *CandidatePair) {
|
||||
if h, ok := a.onSuccessfulSelectedPairBindingResponseHdlr.Load().(func(*CandidatePair)); ok {
|
||||
h(p)
|
||||
type handlerNotifier struct {
|
||||
sync.Mutex
|
||||
running bool
|
||||
notifiers sync.WaitGroup
|
||||
|
||||
connectionStates []ConnectionState
|
||||
connectionStateFunc func(ConnectionState)
|
||||
|
||||
candidates []Candidate
|
||||
candidateFunc func(Candidate)
|
||||
|
||||
selectedCandidatePairs []*CandidatePair
|
||||
candidatePairFunc func(*CandidatePair)
|
||||
|
||||
// State for closing
|
||||
done chan struct{}
|
||||
}
|
||||
|
||||
func (h *handlerNotifier) Close(graceful bool) {
|
||||
if graceful {
|
||||
// if we were closed ungracefully before, we now
|
||||
// want ot wait.
|
||||
defer h.notifiers.Wait()
|
||||
}
|
||||
|
||||
h.Lock()
|
||||
|
||||
select {
|
||||
case <-h.done:
|
||||
h.Unlock()
|
||||
|
||||
return
|
||||
default:
|
||||
}
|
||||
close(h.done)
|
||||
h.Unlock()
|
||||
}
|
||||
|
||||
func (h *handlerNotifier) EnqueueConnectionState(state ConnectionState) {
|
||||
h.Lock()
|
||||
defer h.Unlock()
|
||||
|
||||
select {
|
||||
case <-h.done:
|
||||
return
|
||||
default:
|
||||
}
|
||||
|
||||
notify := func() {
|
||||
defer h.notifiers.Done()
|
||||
for {
|
||||
h.Lock()
|
||||
if len(h.connectionStates) == 0 {
|
||||
h.running = false
|
||||
h.Unlock()
|
||||
|
||||
return
|
||||
}
|
||||
notification := h.connectionStates[0]
|
||||
h.connectionStates = h.connectionStates[1:]
|
||||
h.Unlock()
|
||||
h.connectionStateFunc(notification)
|
||||
}
|
||||
}
|
||||
|
||||
h.connectionStates = append(h.connectionStates, state)
|
||||
if !h.running {
|
||||
h.running = true
|
||||
h.notifiers.Add(1)
|
||||
go notify()
|
||||
}
|
||||
}
|
||||
|
||||
func (a *Agent) candidatePairRoutine() {
|
||||
for p := range a.chanCandidatePair {
|
||||
a.onSelectedCandidatePairChange(p)
|
||||
func (h *handlerNotifier) EnqueueCandidate(cand Candidate) {
|
||||
h.Lock()
|
||||
defer h.Unlock()
|
||||
|
||||
select {
|
||||
case <-h.done:
|
||||
return
|
||||
default:
|
||||
}
|
||||
|
||||
notify := func() {
|
||||
defer h.notifiers.Done()
|
||||
for {
|
||||
h.Lock()
|
||||
if len(h.candidates) == 0 {
|
||||
h.running = false
|
||||
h.Unlock()
|
||||
|
||||
return
|
||||
}
|
||||
notification := h.candidates[0]
|
||||
h.candidates = h.candidates[1:]
|
||||
h.Unlock()
|
||||
h.candidateFunc(notification)
|
||||
}
|
||||
}
|
||||
|
||||
h.candidates = append(h.candidates, cand)
|
||||
if !h.running {
|
||||
h.running = true
|
||||
h.notifiers.Add(1)
|
||||
go notify()
|
||||
}
|
||||
}
|
||||
|
||||
func (a *Agent) connectionStateRoutine() {
|
||||
for s := range a.chanState {
|
||||
go a.onConnectionStateChange(s)
|
||||
}
|
||||
}
|
||||
func (h *handlerNotifier) EnqueueSelectedCandidatePair(pair *CandidatePair) {
|
||||
h.Lock()
|
||||
defer h.Unlock()
|
||||
|
||||
func (a *Agent) candidateRoutine() {
|
||||
for c := range a.chanCandidate {
|
||||
a.onCandidate(c)
|
||||
select {
|
||||
case <-h.done:
|
||||
return
|
||||
default:
|
||||
}
|
||||
|
||||
notify := func() {
|
||||
defer h.notifiers.Done()
|
||||
for {
|
||||
h.Lock()
|
||||
if len(h.selectedCandidatePairs) == 0 {
|
||||
h.running = false
|
||||
h.Unlock()
|
||||
|
||||
return
|
||||
}
|
||||
notification := h.selectedCandidatePairs[0]
|
||||
h.selectedCandidatePairs = h.selectedCandidatePairs[1:]
|
||||
h.Unlock()
|
||||
h.candidatePairFunc(notification)
|
||||
}
|
||||
}
|
||||
|
||||
h.selectedCandidatePairs = append(h.selectedCandidatePairs, pair)
|
||||
if !h.running {
|
||||
h.running = true
|
||||
h.notifiers.Add(1)
|
||||
go notify()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package ice
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/pion/transport/v3/test"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestConnectionStateNotifier(t *testing.T) {
|
||||
t.Run("TestManyUpdates", func(t *testing.T) {
|
||||
defer test.CheckRoutines(t)()
|
||||
|
||||
updates := make(chan struct{}, 1)
|
||||
notifier := &handlerNotifier{
|
||||
connectionStateFunc: func(_ ConnectionState) {
|
||||
updates <- struct{}{}
|
||||
},
|
||||
done: make(chan struct{}),
|
||||
}
|
||||
// Enqueue all updates upfront to ensure that it
|
||||
// doesn't block
|
||||
for i := 0; i < 10000; i++ {
|
||||
notifier.EnqueueConnectionState(ConnectionStateNew)
|
||||
}
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
for i := 0; i < 10000; i++ {
|
||||
<-updates
|
||||
}
|
||||
select {
|
||||
case <-updates:
|
||||
t.Errorf("received more updates than expected") // nolint
|
||||
case <-time.After(1 * time.Second):
|
||||
}
|
||||
close(done)
|
||||
}()
|
||||
<-done
|
||||
notifier.Close(true)
|
||||
})
|
||||
t.Run("TestUpdateOrdering", func(t *testing.T) {
|
||||
defer test.CheckRoutines(t)()
|
||||
updates := make(chan ConnectionState)
|
||||
notifer := &handlerNotifier{
|
||||
connectionStateFunc: func(cs ConnectionState) {
|
||||
updates <- cs
|
||||
},
|
||||
done: make(chan struct{}),
|
||||
}
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
for i := 0; i < 10000; i++ {
|
||||
assert.Equal(t, ConnectionState(i), <-updates)
|
||||
}
|
||||
select {
|
||||
case <-updates:
|
||||
t.Errorf("received more updates than expected") // nolint
|
||||
case <-time.After(1 * time.Second):
|
||||
}
|
||||
close(done)
|
||||
}()
|
||||
for i := 0; i < 10000; i++ {
|
||||
notifer.EnqueueConnectionState(ConnectionState(i))
|
||||
}
|
||||
<-done
|
||||
notifer.Close(true)
|
||||
})
|
||||
}
|
||||
@@ -15,34 +15,42 @@ import (
|
||||
|
||||
func TestOnSelectedCandidatePairChange(t *testing.T) {
|
||||
agent, candidatePair := fixtureTestOnSelectedCandidatePairChange(t)
|
||||
defer func() {
|
||||
require.NoError(t, agent.Close())
|
||||
}()
|
||||
|
||||
callbackCalled := make(chan struct{}, 1)
|
||||
err := agent.OnSelectedCandidatePairChange(func(local, remote Candidate) {
|
||||
err := agent.OnSelectedCandidatePairChange(func(_, _ Candidate) {
|
||||
close(callbackCalled)
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
err = agent.run(context.Background(), func(ctx context.Context, agent *Agent) {
|
||||
err = agent.loop.Run(context.Background(), func(_ context.Context) {
|
||||
agent.setSelectedPair(candidatePair)
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
<-callbackCalled
|
||||
require.NoError(t, agent.Close())
|
||||
}
|
||||
|
||||
func fixtureTestOnSelectedCandidatePairChange(t *testing.T) (*Agent, *CandidatePair) {
|
||||
t.Helper()
|
||||
|
||||
agent, err := NewAgent(&AgentConfig{})
|
||||
require.NoError(t, err)
|
||||
|
||||
candidatePair := makeCandidatePair(t)
|
||||
|
||||
return agent, candidatePair
|
||||
}
|
||||
|
||||
func makeCandidatePair(t *testing.T) *CandidatePair {
|
||||
t.Helper()
|
||||
|
||||
hostLocal := newHostLocal(t)
|
||||
relayRemote := newRelayRemote(t)
|
||||
|
||||
candidatePair := newCandidatePair(hostLocal, relayRemote, false)
|
||||
|
||||
return candidatePair
|
||||
}
|
||||
|
||||
+90
-29
@@ -8,12 +8,12 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// GetCandidatePairsStats returns a list of candidate pair stats
|
||||
// GetCandidatePairsStats returns a list of candidate pair stats.
|
||||
func (a *Agent) GetCandidatePairsStats() []CandidatePairStats {
|
||||
var res []CandidatePairStats
|
||||
err := a.run(a.context(), func(ctx context.Context, agent *Agent) {
|
||||
result := make([]CandidatePairStats, 0, len(agent.checklist))
|
||||
for _, cp := range agent.checklist {
|
||||
err := a.loop.Run(a.loop, func(_ context.Context) {
|
||||
result := make([]CandidatePairStats, 0, len(a.checklist))
|
||||
for _, cp := range a.checklist {
|
||||
stat := CandidatePairStats{
|
||||
Timestamp: time.Now(),
|
||||
LocalCandidateID: cp.Local.ID(),
|
||||
@@ -26,18 +26,22 @@ func (a *Agent) GetCandidatePairsStats() []CandidatePairStats {
|
||||
// BytesReceived uint64
|
||||
// LastPacketSentTimestamp time.Time
|
||||
// LastPacketReceivedTimestamp time.Time
|
||||
// FirstRequestTimestamp time.Time
|
||||
// LastRequestTimestamp time.Time
|
||||
// LastResponseTimestamp time.Time
|
||||
// TotalRoundTripTime float64
|
||||
// CurrentRoundTripTime float64
|
||||
FirstRequestTimestamp: cp.FirstRequestSentAt(),
|
||||
LastRequestTimestamp: cp.LastRequestSentAt(),
|
||||
FirstResponseTimestamp: cp.FirstReponseReceivedAt(),
|
||||
LastResponseTimestamp: cp.LastResponseReceivedAt(),
|
||||
FirstRequestReceivedTimestamp: cp.FirstRequestReceivedAt(),
|
||||
LastRequestReceivedTimestamp: cp.LastRequestReceivedAt(),
|
||||
|
||||
TotalRoundTripTime: cp.TotalRoundTripTime(),
|
||||
CurrentRoundTripTime: cp.CurrentRoundTripTime(),
|
||||
// AvailableOutgoingBitrate float64
|
||||
// AvailableIncomingBitrate float64
|
||||
// CircuitBreakerTriggerCount uint32
|
||||
// RequestsReceived uint64
|
||||
// RequestsSent uint64
|
||||
// ResponsesReceived uint64
|
||||
// ResponsesSent uint64
|
||||
RequestsReceived: cp.RequestsReceived(),
|
||||
RequestsSent: cp.RequestsSent(),
|
||||
ResponsesReceived: cp.ResponsesReceived(),
|
||||
ResponsesSent: cp.ResponsesSent(),
|
||||
// RetransmissionsReceived uint64
|
||||
// RetransmissionsSent uint64
|
||||
// ConsentRequestsSent uint64
|
||||
@@ -49,32 +53,85 @@ func (a *Agent) GetCandidatePairsStats() []CandidatePairStats {
|
||||
})
|
||||
if err != nil {
|
||||
a.log.Errorf("Failed to get candidate pairs stats: %v", err)
|
||||
|
||||
return []CandidatePairStats{}
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
// GetLocalCandidatesStats returns a list of local candidates stats
|
||||
// GetSelectedCandidatePairStats returns a candidate pair stats for selected candidate pair.
|
||||
// Returns false if there is no selected pair.
|
||||
func (a *Agent) GetSelectedCandidatePairStats() (CandidatePairStats, bool) {
|
||||
isAvailable := false
|
||||
var res CandidatePairStats
|
||||
err := a.loop.Run(a.loop, func(_ context.Context) {
|
||||
sp := a.getSelectedPair()
|
||||
if sp == nil {
|
||||
return
|
||||
}
|
||||
|
||||
isAvailable = true
|
||||
res = CandidatePairStats{
|
||||
Timestamp: time.Now(),
|
||||
LocalCandidateID: sp.Local.ID(),
|
||||
RemoteCandidateID: sp.Remote.ID(),
|
||||
State: sp.state,
|
||||
Nominated: sp.nominated,
|
||||
// PacketsSent uint32
|
||||
// PacketsReceived uint32
|
||||
// BytesSent uint64
|
||||
// BytesReceived uint64
|
||||
// LastPacketSentTimestamp time.Time
|
||||
// LastPacketReceivedTimestamp time.Time
|
||||
// FirstRequestTimestamp time.Time
|
||||
// LastRequestTimestamp time.Time
|
||||
// LastResponseTimestamp time.Time
|
||||
TotalRoundTripTime: sp.TotalRoundTripTime(),
|
||||
CurrentRoundTripTime: sp.CurrentRoundTripTime(),
|
||||
// AvailableOutgoingBitrate float64
|
||||
// AvailableIncomingBitrate float64
|
||||
// CircuitBreakerTriggerCount uint32
|
||||
// RequestsReceived uint64
|
||||
// RequestsSent uint64
|
||||
ResponsesReceived: sp.ResponsesReceived(),
|
||||
// ResponsesSent uint64
|
||||
// RetransmissionsReceived uint64
|
||||
// RetransmissionsSent uint64
|
||||
// ConsentRequestsSent uint64
|
||||
// ConsentExpiredTimestamp time.Time
|
||||
}
|
||||
})
|
||||
if err != nil {
|
||||
a.log.Errorf("Failed to get selected candidate pair stats: %v", err)
|
||||
|
||||
return CandidatePairStats{}, false
|
||||
}
|
||||
|
||||
return res, isAvailable
|
||||
}
|
||||
|
||||
// GetLocalCandidatesStats returns a list of local candidates stats.
|
||||
func (a *Agent) GetLocalCandidatesStats() []CandidateStats {
|
||||
var res []CandidateStats
|
||||
err := a.run(a.context(), func(ctx context.Context, agent *Agent) {
|
||||
result := make([]CandidateStats, 0, len(agent.localCandidates))
|
||||
for networkType, localCandidates := range agent.localCandidates {
|
||||
for _, c := range localCandidates {
|
||||
err := a.loop.Run(a.loop, func(_ context.Context) {
|
||||
result := make([]CandidateStats, 0, len(a.localCandidates))
|
||||
for networkType, localCandidates := range a.localCandidates {
|
||||
for _, cand := range localCandidates {
|
||||
relayProtocol := ""
|
||||
if c.Type() == CandidateTypeRelay {
|
||||
if cRelay, ok := c.(*CandidateRelay); ok {
|
||||
if cand.Type() == CandidateTypeRelay {
|
||||
if cRelay, ok := cand.(*CandidateRelay); ok {
|
||||
relayProtocol = cRelay.RelayProtocol()
|
||||
}
|
||||
}
|
||||
stat := CandidateStats{
|
||||
Timestamp: time.Now(),
|
||||
ID: c.ID(),
|
||||
ID: cand.ID(),
|
||||
NetworkType: networkType,
|
||||
IP: c.Address(),
|
||||
Port: c.Port(),
|
||||
CandidateType: c.Type(),
|
||||
Priority: c.Priority(),
|
||||
IP: cand.Address(),
|
||||
Port: cand.Port(),
|
||||
CandidateType: cand.Type(),
|
||||
Priority: cand.Priority(),
|
||||
// URL string
|
||||
RelayProtocol: relayProtocol,
|
||||
// Deleted bool
|
||||
@@ -86,17 +143,19 @@ func (a *Agent) GetLocalCandidatesStats() []CandidateStats {
|
||||
})
|
||||
if err != nil {
|
||||
a.log.Errorf("Failed to get candidate pair stats: %v", err)
|
||||
|
||||
return []CandidateStats{}
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
// GetRemoteCandidatesStats returns a list of remote candidates stats
|
||||
// GetRemoteCandidatesStats returns a list of remote candidates stats.
|
||||
func (a *Agent) GetRemoteCandidatesStats() []CandidateStats {
|
||||
var res []CandidateStats
|
||||
err := a.run(a.context(), func(ctx context.Context, agent *Agent) {
|
||||
result := make([]CandidateStats, 0, len(agent.remoteCandidates))
|
||||
for networkType, remoteCandidates := range agent.remoteCandidates {
|
||||
err := a.loop.Run(a.loop, func(_ context.Context) {
|
||||
result := make([]CandidateStats, 0, len(a.remoteCandidates))
|
||||
for networkType, remoteCandidates := range a.remoteCandidates {
|
||||
for _, c := range remoteCandidates {
|
||||
stat := CandidateStats{
|
||||
Timestamp: time.Now(),
|
||||
@@ -116,7 +175,9 @@ func (a *Agent) GetRemoteCandidatesStats() []CandidateStats {
|
||||
})
|
||||
if err != nil {
|
||||
a.log.Errorf("Failed to get candidate pair stats: %v", err)
|
||||
|
||||
return []CandidateStats{}
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
+926
-714
File diff suppressed because it is too large
Load Diff
+22
-7
@@ -16,13 +16,11 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// TestMuxAgent is an end to end test over UDP mux, ensuring two agents could connect over mux
|
||||
// TestMuxAgent is an end to end test over UDP mux, ensuring two agents could connect over mux.
|
||||
func TestMuxAgent(t *testing.T) {
|
||||
report := test.CheckRoutines(t)
|
||||
defer report()
|
||||
defer test.CheckRoutines(t)()
|
||||
|
||||
lim := test.TimeOut(time.Second * 30)
|
||||
defer lim.Stop()
|
||||
defer test.TimeOut(time.Second * 30).Stop()
|
||||
|
||||
const muxPort = 7686
|
||||
|
||||
@@ -49,16 +47,31 @@ func TestMuxAgent(t *testing.T) {
|
||||
NetworkTypes: []NetworkType{
|
||||
NetworkTypeUDP4,
|
||||
},
|
||||
IncludeLoopback: addr.IP.IsLoopback(),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
var muxedAClosed bool
|
||||
defer func() {
|
||||
if muxedAClosed {
|
||||
return
|
||||
}
|
||||
require.NoError(t, muxedA.Close())
|
||||
}()
|
||||
|
||||
a, err := NewAgent(&AgentConfig{
|
||||
agent, err := NewAgent(&AgentConfig{
|
||||
CandidateTypes: []CandidateType{CandidateTypeHost},
|
||||
NetworkTypes: supportedNetworkTypes(),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
var aClosed bool
|
||||
defer func() {
|
||||
if aClosed {
|
||||
return
|
||||
}
|
||||
require.NoError(t, agent.Close())
|
||||
}()
|
||||
|
||||
conn, muxedConn := connect(a, muxedA)
|
||||
conn, muxedConn := connect(t, agent, muxedA)
|
||||
|
||||
pair := muxedA.getSelectedPair()
|
||||
require.NotNil(t, pair)
|
||||
@@ -84,7 +97,9 @@ func TestMuxAgent(t *testing.T) {
|
||||
|
||||
// Close it down
|
||||
require.NoError(t, conn.Close())
|
||||
aClosed = true
|
||||
require.NoError(t, muxedConn.Close())
|
||||
muxedAClosed = true
|
||||
require.NoError(t, udpMux.Close())
|
||||
|
||||
// Expect error when reading from closed mux
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user