pax_global_header00006660000000000000000000000064143741256150014522gustar00rootroot0000000000000052 comment=f328cf087ea60d383b1ac7932fc2a63ff7b04105 udp-2.0.1/000077500000000000000000000000001437412561500123125ustar00rootroot00000000000000udp-2.0.1/.github/000077500000000000000000000000001437412561500136525ustar00rootroot00000000000000udp-2.0.1/.github/.gitignore000066400000000000000000000000121437412561500156330ustar00rootroot00000000000000.goassets udp-2.0.1/.github/fetch-scripts.sh000077500000000000000000000014351437412561500167720ustar00rootroot00000000000000#!/bin/sh # # DO NOT EDIT THIS FILE # # It is automatically copied from https://github.com/pion/.goassets repository. # # If you want to update the shared CI config, send a PR to # https://github.com/pion/.goassets instead of this repository. # set -eu SCRIPT_PATH="$(realpath "$(dirname "$0")")" GOASSETS_PATH="${SCRIPT_PATH}/.goassets" GOASSETS_REF=${GOASSETS_REF:-master} if [ -d "${GOASSETS_PATH}" ]; then if ! git -C "${GOASSETS_PATH}" diff --exit-code; then echo "${GOASSETS_PATH} has uncommitted changes" >&2 exit 1 fi git -C "${GOASSETS_PATH}" fetch origin git -C "${GOASSETS_PATH}" checkout ${GOASSETS_REF} git -C "${GOASSETS_PATH}" reset --hard origin/${GOASSETS_REF} else git clone -b ${GOASSETS_REF} https://github.com/pion/.goassets.git "${GOASSETS_PATH}" fi udp-2.0.1/.github/install-hooks.sh000077500000000000000000000010771437412561500170050ustar00rootroot00000000000000#!/bin/sh # # DO NOT EDIT THIS FILE # # It is automatically copied from https://github.com/pion/.goassets repository. # # If you want to update the shared CI config, send a PR to # https://github.com/pion/.goassets instead of this repository. # SCRIPT_PATH="$(realpath "$(dirname "$0")")" . ${SCRIPT_PATH}/fetch-scripts.sh cp "${GOASSETS_PATH}/hooks/commit-msg.sh" "${SCRIPT_PATH}/../.git/hooks/commit-msg" cp "${GOASSETS_PATH}/hooks/pre-commit.sh" "${SCRIPT_PATH}/../.git/hooks/pre-commit" cp "${GOASSETS_PATH}/hooks/pre-push.sh" "${SCRIPT_PATH}/../.git/hooks/pre-push" udp-2.0.1/.github/workflows/000077500000000000000000000000001437412561500157075ustar00rootroot00000000000000udp-2.0.1/.github/workflows/codeql-analysis.yml000066400000000000000000000011551437412561500215240ustar00rootroot00000000000000# # 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. # name: CodeQL on: workflow_dispatch: schedule: - cron: '23 5 * * 0' pull_request: branches: - master paths: - '**.go' jobs: analyze: uses: pion/.goassets/.github/workflows/codeql-analysis.reusable.yml@master udp-2.0.1/.github/workflows/generate-authors.yml000066400000000000000000000011041437412561500217030ustar00rootroot00000000000000# # 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. # name: Generate Authors on: pull_request: jobs: generate: uses: pion/.goassets/.github/workflows/generate-authors.reusable.yml@master secrets: token: ${{ secrets.PIONBOT_PRIVATE_KEY }} udp-2.0.1/.github/workflows/lint.yaml000066400000000000000000000007521437412561500175450ustar00rootroot00000000000000# # 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. # name: Lint on: pull_request: jobs: lint: uses: pion/.goassets/.github/workflows/lint.reusable.yml@master udp-2.0.1/.github/workflows/release.yml000066400000000000000000000011051437412561500200470ustar00rootroot00000000000000# # 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. # name: Release on: push: tags: - 'v*' jobs: release: uses: pion/.goassets/.github/workflows/release.reusable.yml@master with: go-version: '1.19' # auto-update/latest-go-version udp-2.0.1/.github/workflows/renovate-go-sum-fix.yaml000066400000000000000000000011241437412561500224050ustar00rootroot00000000000000# # 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. # name: Fix go.sum on: push: branches: - renovate/* jobs: fix: uses: pion/.goassets/.github/workflows/renovate-go-sum-fix.reusable.yml@master secrets: token: ${{ secrets.PIONBOT_PRIVATE_KEY }} udp-2.0.1/.github/workflows/test.yaml000066400000000000000000000021121437412561500175460ustar00rootroot00000000000000# # 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. # name: Test on: push: branches: - master pull_request: jobs: test: uses: pion/.goassets/.github/workflows/test.reusable.yml@master strategy: matrix: go: ['1.19', '1.18'] # auto-update/supported-go-version-list fail-fast: false with: go-version: ${{ matrix.go }} test-i386: uses: pion/.goassets/.github/workflows/test-i386.reusable.yml@master strategy: matrix: go: ['1.19', '1.18'] # auto-update/supported-go-version-list fail-fast: false with: go-version: ${{ matrix.go }} test-wasm: uses: pion/.goassets/.github/workflows/test-wasm.reusable.yml@master with: go-version: '1.19' # auto-update/latest-go-version udp-2.0.1/.github/workflows/tidy-check.yaml000066400000000000000000000011371437412561500206210ustar00rootroot00000000000000# # 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. # name: Go mod tidy on: pull_request: push: branches: - master jobs: tidy: uses: pion/.goassets/.github/workflows/tidy-check.reusable.yml@master with: go-version: '1.19' # auto-update/latest-go-version udp-2.0.1/.gitignore000066400000000000000000000004661437412561500143100ustar00rootroot00000000000000### JetBrains IDE ### ##################### .idea/ ### Emacs Temporary Files ### ############################# *~ ### Folders ### ############### bin/ vendor/ node_modules/ ### Files ### ############# *.ivf *.ogg tags cover.out *.sw[poe] *.wasm examples/sfu-ws/cert.pem examples/sfu-ws/key.pem wasm_exec.js udp-2.0.1/.golangci.yml000066400000000000000000000172751437412561500147120ustar00rootroot00000000000000linters-settings: govet: check-shadowing: true misspell: locale: US exhaustive: default-signifies-exhaustive: true gomodguard: blocked: modules: - github.com/pkg/errors: recommendations: - errors 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 - contextcheck # check the function whether use a non-inherited context - decorder # check declaration order and count of types, constants, variables and functions - depguard # Go linter that checks if package imports are in a list of acceptable packages - 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 - 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 - 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 - 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 - misspell # Finds commonly misspelled English words in comments - nakedret # Finds naked returns in functions greater than a specified function length - 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. - 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 - unconvert # Remove unnecessary type conversions - unparam # Reports unused function parameters - unused # Checks Go code for unused constants, variables, functions and types - wastedassign # wastedassign finds wasted assignment statements - whitespace # Tool for detection of leading and trailing whitespace disable: - 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 - forbidigo # Forbids identifiers - 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 - 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 - nestif # Reports deeply nested if statements - nlreturn # nlreturn checks for a new line before return and branch statements to increase code clarity - 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 - promlinter # Check Prometheus metrics naming via promlint - 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 - 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, better to be self contained - path: _test\.go linters: - gocognit # Allow complex main function in examples - path: examples text: "of func `main` is high" linters: - gocognit run: skip-dirs-use-default: false udp-2.0.1/.goreleaser.yml000066400000000000000000000000251437412561500152400ustar00rootroot00000000000000builds: - skip: true udp-2.0.1/AUTHORS.txt000066400000000000000000000013621437412561500142020ustar00rootroot00000000000000# Thank you to everyone that made Pion possible. If you are interested in contributing # we would love to have you https://github.com/pion/webrtc/wiki/Contributing # # This file is auto generated, using git to list all individuals contributors. # see https://github.com/pion/.goassets/blob/master/scripts/generate-authors.sh for the scripting Atsushi Watanabe Daniel Beseda Daniele Sluijters Jozef Kralik Mathias Fredriksson Michiel De Backker Sean DuBois sterling.deng ZHENK # List of contributors not appearing in Git history udp-2.0.1/LICENSE000066400000000000000000000020401437412561500133130ustar00rootroot00000000000000MIT License Copyright (c) 2020 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. udp-2.0.1/README.md000066400000000000000000000043741437412561500136010ustar00rootroot00000000000000


Pion UDP

A connection-oriented listener over a UDP PacketConn

Pion UDP Slack Widget
Build Status GoDoc Coverage Status Go Report Card License: MIT


### Roadmap This package is used in the [DTLS](https://github.com/pion/dtls) and [SCTP](https://github.com/pion/sctp) transport to provide a connection-oriented listener over a UDP. ### Community Pion has an active community on the [Golang Slack](https://pion.ly/slack/). You can also use [Pion mailing list](https://groups.google.com/forum/#!forum/pion). 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) ### Contributing Check out the **[contributing wiki](https://github.com/pion/webrtc/wiki/Contributing)** to join the group of amazing people making this project possible: ### License MIT License - see [LICENSE](LICENSE) for full text udp-2.0.1/codecov.yml000066400000000000000000000005521437412561500144610ustar00rootroot00000000000000# # DO NOT EDIT THIS FILE # # It is automatically copied from https://github.com/pion/.goassets repository. # coverage: status: project: default: # Allow decreasing 2% of total coverage to avoid noise. threshold: 2% patch: default: target: 70% only_pulls: true ignore: - "examples/*" - "examples/**/*" udp-2.0.1/conn.go000066400000000000000000000157531437412561500136110ustar00rootroot00000000000000// Package udp provides a connection-oriented listener over a UDP PacketConn package udp import ( "context" "errors" "net" "sync" "sync/atomic" "time" "github.com/pion/transport/v2/deadline" "github.com/pion/transport/v2/packetio" pkgSync "github.com/pion/udp/v2/pkg/sync" ) const ( receiveMTU = 8192 defaultListenBacklog = 128 // same as Linux default ) // Typed errors var ( ErrClosedListener = errors.New("udp: listener closed") ErrListenQueueExceeded = errors.New("udp: listen queue exceeded") ) // listener augments a connection-oriented Listener over a UDP PacketConn type listener struct { pConn *net.UDPConn accepting atomic.Value // bool acceptCh chan *Conn doneCh chan struct{} doneOnce sync.Once acceptFilter func([]byte) bool readBufferPool *sync.Pool connLock sync.Mutex conns map[string]*Conn connWG *pkgSync.WaitGroup readWG sync.WaitGroup errClose atomic.Value // error } // Accept waits for and returns the next connection to the listener. func (l *listener) Accept() (net.Conn, error) { select { case c := <-l.acceptCh: l.connWG.Add(1) return c, nil case <-l.doneCh: return nil, ErrClosedListener } } // Close closes the listener. // Any blocked Accept operations will be unblocked and return errors. func (l *listener) Close() error { var err error l.doneOnce.Do(func() { l.accepting.Store(false) close(l.doneCh) l.connLock.Lock() // Close unaccepted connections L_CLOSE: for { select { case c := <-l.acceptCh: close(c.doneCh) delete(l.conns, c.rAddr.String()) default: break L_CLOSE } } nConns := len(l.conns) l.connLock.Unlock() l.connWG.Done() if nConns == 0 { // Wait if this is the final connection l.readWG.Wait() if errClose, ok := l.errClose.Load().(error); ok { err = errClose } } else { err = nil } }) return err } // Addr returns the listener's network address. func (l *listener) Addr() net.Addr { return l.pConn.LocalAddr() } // ListenConfig stores options for listening to an address. type ListenConfig struct { // Backlog defines the maximum length of the queue of pending // connections. It is equivalent of the backlog argument of // POSIX listen function. // If a connection request arrives when the queue is full, // the request will be silently discarded, unlike TCP. // Set zero to use default value 128 which is same as Linux default. Backlog int // AcceptFilter determines whether the new conn should be made for // the incoming packet. If not set, any packet creates new conn. AcceptFilter func([]byte) bool } // Listen creates a new listener based on the ListenConfig. func (lc *ListenConfig) Listen(network string, laddr *net.UDPAddr) (net.Listener, error) { if lc.Backlog == 0 { lc.Backlog = defaultListenBacklog } conn, err := net.ListenUDP(network, laddr) if err != nil { return nil, err } l := &listener{ pConn: conn, acceptCh: make(chan *Conn, lc.Backlog), conns: make(map[string]*Conn), doneCh: make(chan struct{}), acceptFilter: lc.AcceptFilter, readBufferPool: &sync.Pool{ New: func() interface{} { buf := make([]byte, receiveMTU) return &buf }, }, connWG: pkgSync.NewWaitGroup(), } l.accepting.Store(true) l.connWG.Add(1) l.readWG.Add(2) // wait readLoop and Close execution routine go l.readLoop() go func() { l.connWG.Wait() if err := l.pConn.Close(); err != nil { l.errClose.Store(err) } l.readWG.Done() }() return l, nil } // Listen creates a new listener using default ListenConfig. func Listen(network string, laddr *net.UDPAddr) (net.Listener, error) { return (&ListenConfig{}).Listen(network, laddr) } // readLoop has to tasks: // 1. Dispatching incoming packets to the correct Conn. // It can therefore not be ended until all Conns are closed. // 2. Creating a new Conn when receiving from a new remote. func (l *listener) readLoop() { defer l.readWG.Done() for { buf, ok := l.readBufferPool.Get().(*[]byte) if !ok { return } n, raddr, err := l.pConn.ReadFrom(*buf) if err != nil { return } conn, ok, err := l.getConn(raddr, (*buf)[:n]) if err != nil { continue } if ok { _, _ = conn.buffer.Write((*buf)[:n]) } } } func (l *listener) getConn(raddr net.Addr, buf []byte) (*Conn, bool, error) { l.connLock.Lock() defer l.connLock.Unlock() conn, ok := l.conns[raddr.String()] if !ok { if isAccepting, ok := l.accepting.Load().(bool); !isAccepting || !ok { return nil, false, ErrClosedListener } if l.acceptFilter != nil { if !l.acceptFilter(buf) { return nil, false, nil } } conn = l.newConn(raddr) select { case l.acceptCh <- conn: l.conns[raddr.String()] = conn default: return nil, false, ErrListenQueueExceeded } } return conn, true, nil } // Conn augments a connection-oriented connection over a UDP PacketConn type Conn struct { listener *listener rAddr net.Addr buffer *packetio.Buffer doneCh chan struct{} doneOnce sync.Once writeDeadline *deadline.Deadline } func (l *listener) newConn(rAddr net.Addr) *Conn { return &Conn{ listener: l, rAddr: rAddr, buffer: packetio.NewBuffer(), doneCh: make(chan struct{}), writeDeadline: deadline.New(), } } // Read reads from c into p func (c *Conn) Read(p []byte) (int, error) { return c.buffer.Read(p) } // Write writes len(p) bytes from p to the DTLS connection func (c *Conn) Write(p []byte) (n int, err error) { select { case <-c.writeDeadline.Done(): return 0, context.DeadlineExceeded default: } return c.listener.pConn.WriteTo(p, c.rAddr) } // Close closes the conn and releases any Read calls func (c *Conn) Close() error { var err error c.doneOnce.Do(func() { c.listener.connWG.Done() close(c.doneCh) c.listener.connLock.Lock() delete(c.listener.conns, c.rAddr.String()) nConns := len(c.listener.conns) c.listener.connLock.Unlock() if isAccepting, ok := c.listener.accepting.Load().(bool); nConns == 0 && !isAccepting && ok { // Wait if this is the final connection c.listener.readWG.Wait() if errClose, ok := c.listener.errClose.Load().(error); ok { err = errClose } } else { err = nil } if errBuf := c.buffer.Close(); errBuf != nil && err == nil { err = errBuf } }) return err } // LocalAddr implements net.Conn.LocalAddr func (c *Conn) LocalAddr() net.Addr { return c.listener.pConn.LocalAddr() } // RemoteAddr implements net.Conn.RemoteAddr func (c *Conn) RemoteAddr() net.Addr { return c.rAddr } // SetDeadline implements net.Conn.SetDeadline func (c *Conn) SetDeadline(t time.Time) error { c.writeDeadline.Set(t) return c.SetReadDeadline(t) } // SetReadDeadline implements net.Conn.SetDeadline func (c *Conn) SetReadDeadline(t time.Time) error { return c.buffer.SetReadDeadline(t) } // SetWriteDeadline implements net.Conn.SetDeadline func (c *Conn) SetWriteDeadline(t time.Time) error { c.writeDeadline.Set(t) // Write deadline of underlying connection should not be changed // since the connection can be shared. return nil } udp-2.0.1/conn_test.go000066400000000000000000000241651437412561500146450ustar00rootroot00000000000000//go:build !js // +build !js package udp import ( "bytes" "errors" "fmt" "io" "net" "sync" "testing" "time" "github.com/pion/transport/v2/test" ) var errHandshakeFailed = errors.New("handshake failed") // Note: doesn't work since closing isn't propagated to the other side // func TestNetTest(t *testing.T) { // lim := test.TimeOut(time.Minute*1 + time.Second*10) // defer lim.Stop() // // nettest.TestConn(t, func() (c1, c2 net.Conn, stop func(), err error) { // listener, c1, c2, err = pipe() // if err != nil { // return nil, nil, nil, err // } // stop = func() { // c1.Close() // c2.Close() // listener.Close(1 * time.Second) // } // return // }) //} func TestStressDuplex(t *testing.T) { // Limit runtime in case of deadlocks lim := test.TimeOut(time.Second * 20) defer lim.Stop() // Check for leaking routines report := test.CheckRoutines(t) defer report() // Run the test stressDuplex(t) } func stressDuplex(t *testing.T) { listener, ca, cb, err := pipe() if err != nil { t.Fatal(err) } defer func() { err = ca.Close() if err != nil { t.Fatal(err) } err = cb.Close() if err != nil { t.Fatal(err) } err = listener.Close() if err != nil { t.Fatal(err) } }() opt := test.Options{ MsgSize: 2048, MsgCount: 1, // Can't rely on UDP message order in CI } err = test.StressDuplex(ca, cb, opt) if err != nil { t.Fatal(err) } } func TestListenerCloseTimeout(t *testing.T) { // Limit runtime in case of deadlocks lim := test.TimeOut(time.Second * 20) defer lim.Stop() // Check for leaking routines report := test.CheckRoutines(t) defer report() listener, ca, _, err := pipe() if err != nil { t.Fatal(err) } err = listener.Close() if err != nil { t.Fatal(err) } // Close client after server closes to cleanup err = ca.Close() if err != nil { t.Fatal(err) } } func TestListenerCloseUnaccepted(t *testing.T) { // Limit runtime in case of deadlocks lim := test.TimeOut(time.Second * 20) defer lim.Stop() // Check for leaking routines report := test.CheckRoutines(t) defer report() const backlog = 2 network, addr := getConfig() listener, err := (&ListenConfig{ Backlog: backlog, }).Listen(network, addr) if err != nil { t.Fatal(err) } for i := 0; i < backlog; i++ { conn, derr := net.DialUDP(network, nil, listener.Addr().(*net.UDPAddr)) if derr != nil { t.Error(derr) continue } if _, werr := conn.Write([]byte{byte(i)}); werr != nil { t.Error(werr) } if cerr := conn.Close(); cerr != nil { t.Error(cerr) } } time.Sleep(100 * time.Millisecond) // Wait all packets being processed by readLoop // Unaccepted connections must be closed by listener.Close() err = listener.Close() if err != nil { t.Fatal(err) } } func TestListenerAcceptFilter(t *testing.T) { // Limit runtime in case of deadlocks lim := test.TimeOut(time.Second * 20) defer lim.Stop() // Check for leaking routines report := test.CheckRoutines(t) defer report() testCases := map[string]struct { packet []byte accept bool }{ "CreateConn": { packet: []byte{0xAA}, accept: true, }, "Discarded": { packet: []byte{0x00}, accept: false, }, } for name, testCase := range testCases { testCase := testCase t.Run(name, func(t *testing.T) { network, addr := getConfig() listener, err := (&ListenConfig{ AcceptFilter: func(pkt []byte) bool { return pkt[0] == 0xAA }, }).Listen(network, addr) if err != nil { t.Fatal(err) } var wgAcceptLoop sync.WaitGroup wgAcceptLoop.Add(1) defer func() { cerr := listener.Close() if cerr != nil { t.Fatal(cerr) } wgAcceptLoop.Wait() }() conn, derr := net.DialUDP(network, nil, listener.Addr().(*net.UDPAddr)) if derr != nil { t.Fatal(derr) } if _, werr := conn.Write(testCase.packet); werr != nil { t.Fatal(werr) } defer func() { if cerr := conn.Close(); cerr != nil { t.Error(cerr) } }() chAccepted := make(chan struct{}) go func() { defer wgAcceptLoop.Done() conn, aerr := listener.Accept() if aerr != nil { if !errors.Is(aerr, ErrClosedListener) { t.Error(aerr) } return } close(chAccepted) if cerr := conn.Close(); cerr != nil { t.Error(cerr) } }() var accepted bool select { case <-chAccepted: accepted = true case <-time.After(10 * time.Millisecond): } if accepted != testCase.accept { if testCase.accept { t.Error("Packet should create new conn") } else { t.Error("Packet should not create new conn") } } }) } } func TestListenerConcurrent(t *testing.T) { // Limit runtime in case of deadlocks lim := test.TimeOut(time.Second * 20) defer lim.Stop() // Check for leaking routines report := test.CheckRoutines(t) defer report() const backlog = 2 network, addr := getConfig() listener, err := (&ListenConfig{ Backlog: backlog, }).Listen(network, addr) if err != nil { t.Fatal(err) } for i := 0; i < backlog+1; i++ { conn, derr := net.DialUDP(network, nil, listener.Addr().(*net.UDPAddr)) if derr != nil { t.Error(derr) continue } if _, werr := conn.Write([]byte{byte(i)}); werr != nil { t.Error(werr) } if cerr := conn.Close(); cerr != nil { t.Error(cerr) } } time.Sleep(100 * time.Millisecond) // Wait all packets being processed by readLoop for i := 0; i < backlog; i++ { conn, aerr := listener.Accept() if aerr != nil { t.Error(aerr) continue } b := make([]byte, 1) n, rerr := conn.Read(b) if rerr != nil { t.Error(rerr) } else if !bytes.Equal([]byte{byte(i)}, b[:n]) { t.Errorf("Packet from connection %d is wrong, expected: [%d], got: %v", i, i, b[:n]) } if err = conn.Close(); err != nil { t.Error(err) } } var wg sync.WaitGroup wg.Add(1) go func() { defer wg.Done() if conn, aerr := listener.Accept(); !errors.Is(aerr, ErrClosedListener) { t.Errorf("Connection exceeding backlog limit must be discarded: %v", aerr) if aerr == nil { _ = conn.Close() } } }() time.Sleep(100 * time.Millisecond) // Last Accept should be discarded err = listener.Close() if err != nil { t.Fatal(err) } wg.Wait() } func pipe() (net.Listener, net.Conn, *net.UDPConn, error) { // Start listening network, addr := getConfig() listener, err := Listen(network, addr) if err != nil { return nil, nil, nil, fmt.Errorf("failed to listen: %w", err) } // Open a connection var dConn *net.UDPConn dConn, err = net.DialUDP(network, nil, listener.Addr().(*net.UDPAddr)) if err != nil { return nil, nil, nil, fmt.Errorf("failed to dial: %w", err) } // Write to the connection to initiate it handshake := "hello" _, err = dConn.Write([]byte(handshake)) if err != nil { return nil, nil, nil, fmt.Errorf("failed to write to dialed Conn: %w", err) } // Accept the connection var lConn net.Conn lConn, err = listener.Accept() if err != nil { return nil, nil, nil, fmt.Errorf("failed to accept Conn: %w", err) } var n int buf := make([]byte, len(handshake)) if n, err = lConn.Read(buf); err != nil { return nil, nil, nil, fmt.Errorf("failed to read handshake: %w", err) } result := string(buf[:n]) if handshake != result { return nil, nil, nil, fmt.Errorf("%w: %s != %s", errHandshakeFailed, handshake, result) } return listener, lConn, dConn, nil } func getConfig() (string, *net.UDPAddr) { return "udp", &net.UDPAddr{IP: net.ParseIP("127.0.0.1"), Port: 0} } func TestConnClose(t *testing.T) { lim := test.TimeOut(time.Second * 5) defer lim.Stop() t.Run("Close", func(t *testing.T) { // Check for leaking routines report := test.CheckRoutines(t) defer report() l, ca, cb, errPipe := pipe() if errPipe != nil { t.Fatal(errPipe) } if err := ca.Close(); err != nil { t.Errorf("Failed to close A side: %v", err) } if err := cb.Close(); err != nil { t.Errorf("Failed to close B side: %v", err) } if err := l.Close(); err != nil { t.Errorf("Failed to close listener: %v", err) } }) t.Run("CloseError1", func(t *testing.T) { // Check for leaking routines report := test.CheckRoutines(t) defer report() l, ca, cb, errPipe := pipe() if errPipe != nil { t.Fatal(errPipe) } // Close l.pConn to inject error. if err := l.(*listener).pConn.Close(); err != nil { //nolint:forcetypeassert t.Error(err) } if err := cb.Close(); err != nil { t.Errorf("Failed to close A side: %v", err) } if err := ca.Close(); err != nil { t.Errorf("Failed to close B side: %v", err) } if err := l.Close(); err == nil { t.Errorf("Error is not propagated to Listener.Close") } }) t.Run("CloseError2", func(t *testing.T) { // Check for leaking routines report := test.CheckRoutines(t) defer report() l, ca, cb, errPipe := pipe() if errPipe != nil { t.Fatal(errPipe) } // Close l.pConn to inject error. if err := l.(*listener).pConn.Close(); err != nil { //nolint:forcetypeassert t.Error(err) } if err := cb.Close(); err != nil { t.Errorf("Failed to close A side: %v", err) } if err := l.Close(); err != nil { t.Errorf("Failed to close listener: %v", err) } if err := ca.Close(); err == nil { t.Errorf("Error is not propagated to Conn.Close") } }) t.Run("CancelRead", func(t *testing.T) { // Limit runtime in case of deadlocks lim := test.TimeOut(time.Second * 5) defer lim.Stop() // Check for leaking routines report := test.CheckRoutines(t) defer report() l, ca, cb, errPipe := pipe() if errPipe != nil { t.Fatal(errPipe) } errC := make(chan error, 1) go func() { buf := make([]byte, 1024) // This read will block because we don't write on the other side. // Calling Close must unblock the call. _, readErr := ca.Read(buf) errC <- readErr }() if err := ca.Close(); err != nil { // Trigger Read cancellation. t.Errorf("Failed to close B side: %v", err) } // Main test condition, Read should return // after ca.Close() by closing the buffer. if err := <-errC; !errors.Is(err, io.EOF) { t.Errorf("expected err to be io.EOF but got %v", err) } if err := cb.Close(); err != nil { t.Errorf("Failed to close A side: %v", err) } if err := l.Close(); err != nil { t.Errorf("Failed to close listener: %v", err) } }) } udp-2.0.1/go.mod000066400000000000000000000001241437412561500134150ustar00rootroot00000000000000module github.com/pion/udp/v2 go 1.14 require github.com/pion/transport/v2 v2.0.2 udp-2.0.1/go.sum000066400000000000000000000106611437412561500134510ustar00rootroot00000000000000github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms= github.com/pion/transport/v2 v2.0.2 h1:St+8o+1PEzPT51O9bv+tH/KYYLMNR5Vwm5Z3Qkjsywg= github.com/pion/transport/v2 v2.0.2/go.mod h1:vrz6bUbFr/cjdwbnxq8OdDDzHf7JJfGsIRkxfpZoTA0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= udp-2.0.1/pkg/000077500000000000000000000000001437412561500130735ustar00rootroot00000000000000udp-2.0.1/pkg/sync/000077500000000000000000000000001437412561500140475ustar00rootroot00000000000000udp-2.0.1/pkg/sync/waitgroup.go000066400000000000000000000032311437412561500164160ustar00rootroot00000000000000// Package sync extends basic synchronization primitives. package sync import ( "sync" ) // A WaitGroup waits for a collection of goroutines to finish. // The main goroutine calls Add to set the number of // goroutines to wait for. Then each of the goroutines // runs and calls Done when finished. At the same time, // Wait can be used to block until all goroutines have finished. // // WaitGroups in the sync package do not allow adding or // subtracting from the counter while another goroutine is // waiting, while this one does. // // A WaitGroup must not be copied after first use. // // In the terminology of the Go memory model, a call to Done type WaitGroup struct { c int64 mutex sync.Mutex cond *sync.Cond } // NewWaitGroup creates a new WaitGroup. func NewWaitGroup() *WaitGroup { wg := &WaitGroup{} wg.cond = sync.NewCond(&wg.mutex) return wg } // Add adds delta, which may be negative, to the WaitGroup counter. // If the counter becomes zero, all goroutines blocked on Wait are released. // If the counter goes negative, Add panics. func (wg *WaitGroup) Add(delta int) { wg.mutex.Lock() defer wg.mutex.Unlock() wg.c += int64(delta) if wg.c < 0 { panic("udp: negative WaitGroup counter") // nolint } wg.cond.Signal() } // Done decrements the WaitGroup counter by one. func (wg *WaitGroup) Done() { wg.Add(-1) } // Wait blocks until the WaitGroup counter is zero. func (wg *WaitGroup) Wait() { wg.mutex.Lock() defer wg.mutex.Unlock() for { c := wg.c switch { case c == 0: // wake another goroutine if there is one wg.cond.Signal() return case c < 0: panic("udp: negative WaitGroup counter") // nolint } wg.cond.Wait() } } udp-2.0.1/pkg/sync/waitgroup_test.go000066400000000000000000000014531437412561500174610ustar00rootroot00000000000000//go:build !js // +build !js package sync_test import ( "testing" "github.com/pion/udp/v2/pkg/sync" ) func testWaitGroup(t *testing.T, wg1 *sync.WaitGroup, wg2 *sync.WaitGroup) { n := 16 wg1.Add(n) wg2.Add(n) exited := make(chan bool, n) for i := 0; i != n; i++ { go func() { wg1.Done() wg2.Wait() exited <- true }() } wg1.Wait() for i := 0; i != n; i++ { select { case <-exited: t.Fatal("WaitGroup released group too soon") default: } wg2.Done() } for i := 0; i != n; i++ { <-exited // Will block if barrier fails to unlock someone. } } func TestWaitGroup(t *testing.T) { wg1 := sync.NewWaitGroup() wg2 := sync.NewWaitGroup() // Run the same test a few times to ensure barrier is in a proper state. for i := 0; i != 8; i++ { testWaitGroup(t, wg1, wg2) } } udp-2.0.1/renovate.json000066400000000000000000000001731437412561500150310ustar00rootroot00000000000000{ "$schema": "https://docs.renovatebot.com/renovate-schema.json", "extends": [ "github>pion/renovate-config" ] }