直播:后台 JWT 推流、前台画中画;WebRTC 服务与 Nginx WebSocket 代理

Made-with: Cursor
This commit is contained in:
whm
2026-03-25 15:00:14 +08:00
parent b83ec91b1a
commit 7811adca66
1050 changed files with 146524 additions and 37 deletions

28
server/vendor/github.com/pion/transport/v2/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,28 @@
# SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
# SPDX-License-Identifier: MIT
### 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

View File

@@ -0,0 +1,137 @@
# 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)?$
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
- 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
- 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
- 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
- forbidigo
# Allow complex main function in examples
- path: examples
text: "of func `main` is high"
linters:
- gocognit
# Allow forbidden identifiers in examples
- path: examples
linters:
- forbidigo
# Allow forbidden identifiers in CLI commands
- path: cmd
linters:
- forbidigo
run:
skip-dirs-use-default: false

View File

@@ -0,0 +1,5 @@
# SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
# SPDX-License-Identifier: MIT
builds:
- skip: true

29
server/vendor/github.com/pion/transport/v2/AUTHORS.txt generated vendored Normal file
View File

@@ -0,0 +1,29 @@
# 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
Adrian Cable <adrian.cable@gmail.com>
Atsushi Watanabe <atsushi.w@ieee.org>
backkem <mail@backkem.me>
cnderrauber <zengjie9004@gmail.com>
Daniel Mangum <georgedanielmangum@gmail.com>
Hugo Arregui <hugo.arregui@gmail.com>
Jeremiah Millay <jmillay@fastly.com>
Jozef Kralik <jojo.lwin@gmail.com>
Juliusz Chroboczek <jch@irif.fr>
Luke Curley <kixelated@gmail.com>
Mathis Engelbart <mathis.engelbart@gmail.com>
OrlandoCo <luisorlando.co@gmail.com>
Sean DuBois <duboisea@justin.tv>
Sean DuBois <duboisea@twitch.tv>
Sean DuBois <seaduboi@amazon.com>
Sean DuBois <sean@siobud.com>
Steffen Vogel <post@steffenvogel.de>
Winlin <winlin@vip.126.com>
Woodrow Douglass <wdouglass@carnegierobotics.com>
Yutaka Takeda <yt0916@gmail.com>
ZHENK <chengzhenyang@gmail.com>
# List of contributors not appearing in Git history

9
server/vendor/github.com/pion/transport/v2/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,9 @@
MIT License
Copyright (c) 2023 The Pion community <https://pion.ly>
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.

34
server/vendor/github.com/pion/transport/v2/README.md generated vendored Normal file
View File

@@ -0,0 +1,34 @@
<h1 align="center">
<br>
Pion Transport
<br>
</h1>
<h4 align="center">Transport testing for Pion</h4>
<p align="center">
<a href="https://pion.ly"><img src="https://img.shields.io/badge/pion-transport-gray.svg?longCache=true&colorB=brightgreen" alt="Pion transport"></a>
<a href="https://pion.ly/slack"><img src="https://img.shields.io/badge/join-us%20on%20slack-gray.svg?longCache=true&logo=slack&colorB=brightgreen" alt="Slack Widget"></a>
<br>
<img alt="GitHub Workflow Status" src="https://img.shields.io/github/actions/workflow/status/pion/transport/test.yaml">
<a href="https://pkg.go.dev/github.com/pion/transport"><img src="https://pkg.go.dev/badge/github.com/pion/transport.svg" alt="Go Reference"></a>
<a href="https://codecov.io/gh/pion/transport"><img src="https://codecov.io/gh/pion/transport/branch/master/graph/badge.svg" alt="Coverage Status"></a>
<a href="https://goreportcard.com/report/github.com/pion/transport"><img src="https://goreportcard.com/badge/github.com/pion/transport" alt="Go Report Card"></a>
<a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="License: MIT"></a>
</p>
<br>
### Roadmap
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).
Follow the [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)
### Contributing
Check out the [contributing wiki](https://github.com/pion/webrtc/wiki/Contributing) to join the group of amazing people making this project possible: [AUTHORS.txt](./AUTHORS.txt)
### License
MIT License - see [LICENSE](LICENSE) for full text

22
server/vendor/github.com/pion/transport/v2/codecov.yml generated vendored Normal file
View File

@@ -0,0 +1,22 @@
#
# DO NOT EDIT THIS FILE
#
# It is automatically copied from https://github.com/pion/.goassets repository.
#
# SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
# SPDX-License-Identifier: MIT
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/**/*"

View File

@@ -0,0 +1,177 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
// Package connctx wraps net.Conn using context.Context.
//
// Deprecated: use netctx instead.
package connctx
import (
"context"
"errors"
"io"
"net"
"sync"
"sync/atomic"
"time"
)
// ErrClosing is returned on Write to closed connection.
var ErrClosing = errors.New("use of closed network connection")
// Reader is an interface for context controlled reader.
type Reader interface {
ReadContext(context.Context, []byte) (int, error)
}
// Writer is an interface for context controlled writer.
type Writer interface {
WriteContext(context.Context, []byte) (int, error)
}
// ReadWriter is a composite of ReadWriter.
type ReadWriter interface {
Reader
Writer
}
// ConnCtx is a wrapper of net.Conn using context.Context.
type ConnCtx interface {
Reader
Writer
io.Closer
LocalAddr() net.Addr
RemoteAddr() net.Addr
Conn() net.Conn
}
type connCtx struct {
nextConn net.Conn
closed chan struct{}
closeOnce sync.Once
readMu sync.Mutex
writeMu sync.Mutex
}
var veryOld = time.Unix(0, 1) //nolint:gochecknoglobals
// New creates a new ConnCtx wrapping given net.Conn.
func New(conn net.Conn) ConnCtx {
c := &connCtx{
nextConn: conn,
closed: make(chan struct{}),
}
return c
}
func (c *connCtx) ReadContext(ctx context.Context, b []byte) (int, error) {
c.readMu.Lock()
defer c.readMu.Unlock()
select {
case <-c.closed:
return 0, io.EOF
default:
}
done := make(chan struct{})
var wg sync.WaitGroup
var errSetDeadline atomic.Value
wg.Add(1)
go func() {
defer wg.Done()
select {
case <-ctx.Done():
// context canceled
if err := c.nextConn.SetReadDeadline(veryOld); err != nil {
errSetDeadline.Store(err)
return
}
<-done
if err := c.nextConn.SetReadDeadline(time.Time{}); err != nil {
errSetDeadline.Store(err)
}
case <-done:
}
}()
n, err := c.nextConn.Read(b)
close(done)
wg.Wait()
if e := ctx.Err(); e != nil && n == 0 {
err = e
}
if err2, ok := errSetDeadline.Load().(error); ok && err == nil && err2 != nil {
err = err2
}
return n, err
}
func (c *connCtx) WriteContext(ctx context.Context, b []byte) (int, error) {
c.writeMu.Lock()
defer c.writeMu.Unlock()
select {
case <-c.closed:
return 0, ErrClosing
default:
}
done := make(chan struct{})
var wg sync.WaitGroup
var errSetDeadline atomic.Value
wg.Add(1)
go func() {
defer wg.Done()
select {
case <-ctx.Done():
// context canceled
if err := c.nextConn.SetWriteDeadline(veryOld); err != nil {
errSetDeadline.Store(err)
return
}
<-done
if err := c.nextConn.SetWriteDeadline(time.Time{}); err != nil {
errSetDeadline.Store(err)
}
case <-done:
}
}()
n, err := c.nextConn.Write(b)
close(done)
wg.Wait()
if e := ctx.Err(); e != nil && n == 0 {
err = e
}
if err2, ok := errSetDeadline.Load().(error); ok && err == nil && err2 != nil {
err = err2
}
return n, err
}
func (c *connCtx) Close() error {
err := c.nextConn.Close()
c.closeOnce.Do(func() {
c.writeMu.Lock()
c.readMu.Lock()
close(c.closed)
c.readMu.Unlock()
c.writeMu.Unlock()
})
return err
}
func (c *connCtx) LocalAddr() net.Addr {
return c.nextConn.LocalAddr()
}
func (c *connCtx) RemoteAddr() net.Addr {
return c.nextConn.RemoteAddr()
}
func (c *connCtx) Conn() net.Conn {
return c.nextConn
}

View File

@@ -0,0 +1,14 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package connctx
import (
"net"
)
// Pipe creates piped pair of ConnCtx.
func Pipe() (ConnCtx, ConnCtx) {
ca, cb := net.Pipe()
return New(ca), New(cb)
}

View File

@@ -0,0 +1,124 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
// Package deadline provides deadline timer used to implement
// net.Conn compatible connection
package deadline
import (
"context"
"sync"
"time"
)
type deadlineState uint8
const (
deadlineStopped deadlineState = iota
deadlineStarted
deadlineExceeded
)
var _ context.Context = (*Deadline)(nil)
// Deadline signals updatable deadline timer.
// Also, it implements context.Context.
type Deadline struct {
mu sync.RWMutex
timer timer
done chan struct{}
deadline time.Time
state deadlineState
pending uint8
}
// New creates new deadline timer.
func New() *Deadline {
return &Deadline{
done: make(chan struct{}),
}
}
func (d *Deadline) timeout() {
d.mu.Lock()
if d.pending--; d.pending != 0 || d.state != deadlineStarted {
d.mu.Unlock()
return
}
d.state = deadlineExceeded
done := d.done
d.mu.Unlock()
close(done)
}
// Set new deadline. Zero value means no deadline.
func (d *Deadline) Set(t time.Time) {
d.mu.Lock()
defer d.mu.Unlock()
if d.state == deadlineStarted && d.timer.Stop() {
d.pending--
}
d.deadline = t
d.pending++
if d.state == deadlineExceeded {
d.done = make(chan struct{})
}
if t.IsZero() {
d.pending--
d.state = deadlineStopped
return
}
if dur := time.Until(t); dur > 0 {
d.state = deadlineStarted
if d.timer == nil {
d.timer = afterFunc(dur, d.timeout)
} else {
d.timer.Reset(dur)
}
return
}
d.pending--
d.state = deadlineExceeded
close(d.done)
}
// Done receives deadline signal.
func (d *Deadline) Done() <-chan struct{} {
d.mu.RLock()
defer d.mu.RUnlock()
return d.done
}
// Err returns context.DeadlineExceeded if the deadline is exceeded.
// Otherwise, it returns nil.
func (d *Deadline) Err() error {
d.mu.RLock()
defer d.mu.RUnlock()
if d.state == deadlineExceeded {
return context.DeadlineExceeded
}
return nil
}
// Deadline returns current deadline.
func (d *Deadline) Deadline() (time.Time, bool) {
d.mu.RLock()
defer d.mu.RUnlock()
if d.deadline.IsZero() {
return d.deadline, false
}
return d.deadline, true
}
// Value returns nil.
func (d *Deadline) Value(interface{}) interface{} {
return nil
}

View File

@@ -0,0 +1,13 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package deadline
import (
"time"
)
type timer interface {
Stop() bool
Reset(time.Duration) bool
}

View File

@@ -0,0 +1,15 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
//go:build !js
// +build !js
package deadline
import (
"time"
)
func afterFunc(d time.Duration, f func()) timer {
return time.AfterFunc(d, f)
}

View File

@@ -0,0 +1,67 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
//go:build js
// +build js
package deadline
import (
"sync"
"time"
)
// jsTimer is a timer utility for wasm with a working Reset function.
type jsTimer struct {
f func()
mu sync.Mutex
timer *time.Timer
version uint64
started bool
}
func afterFunc(d time.Duration, f func()) timer {
t := &jsTimer{f: f}
t.Reset(d)
return t
}
func (t *jsTimer) Stop() bool {
t.mu.Lock()
defer t.mu.Unlock()
t.version++
t.timer.Stop()
started := t.started
t.started = false
return started
}
func (t *jsTimer) Reset(d time.Duration) bool {
t.mu.Lock()
defer t.mu.Unlock()
if t.timer != nil {
t.timer.Stop()
}
t.version++
version := t.version
t.timer = time.AfterFunc(d, func() {
t.mu.Lock()
if version != t.version {
t.mu.Unlock()
return
}
t.started = false
t.mu.Unlock()
t.f()
})
started := t.started
t.started = true
return started
}

418
server/vendor/github.com/pion/transport/v2/net.go generated vendored Normal file
View File

@@ -0,0 +1,418 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
// Package transport implements various networking related
// functions used throughout the Pion modules.
package transport
import (
"errors"
"io"
"net"
"time"
)
var (
// ErrNoAddressAssigned ...
ErrNoAddressAssigned = errors.New("no address assigned")
// ErrNotSupported ...
ErrNotSupported = errors.New("not supported yey")
// ErrInterfaceNotFound ...
ErrInterfaceNotFound = errors.New("interface not found")
// ErrNotUDPAddress ...
ErrNotUDPAddress = errors.New("not a UDP address")
)
// Net is an interface providing common networking functions which are
// similar to the functions provided by standard net package.
type Net interface {
// ListenPacket announces on the local network address.
//
// The network must be "udp", "udp4", "udp6", "unixgram", or an IP
// transport. The IP transports are "ip", "ip4", or "ip6" followed by
// a colon and a literal protocol number or a protocol name, as in
// "ip:1" or "ip:icmp".
//
// For UDP and IP networks, if the host in the address parameter is
// empty or a literal unspecified IP address, ListenPacket listens on
// all available IP addresses of the local system except multicast IP
// addresses.
// To only use IPv4, use network "udp4" or "ip4:proto".
// The address can use a host name, but this is not recommended,
// because it will create a listener for at most one of the host's IP
// addresses.
// If the port in the address parameter is empty or "0", as in
// "127.0.0.1:" or "[::1]:0", a port number is automatically chosen.
// The LocalAddr method of PacketConn can be used to discover the
// chosen port.
//
// See func Dial for a description of the network and address
// parameters.
//
// ListenPacket uses context.Background internally; to specify the context, use
// ListenConfig.ListenPacket.
ListenPacket(network string, address string) (net.PacketConn, error)
// ListenUDP acts like ListenPacket for UDP networks.
//
// The network must be a UDP network name; see func Dial for details.
//
// If the IP field of laddr is nil or an unspecified IP address,
// ListenUDP listens on all available IP addresses of the local system
// except multicast IP addresses.
// If the Port field of laddr is 0, a port number is automatically
// chosen.
ListenUDP(network string, locAddr *net.UDPAddr) (UDPConn, error)
// ListenTCP acts like Listen for TCP networks.
//
// The network must be a TCP network name; see func Dial for details.
//
// If the IP field of laddr is nil or an unspecified IP address,
// ListenTCP listens on all available unicast and anycast IP addresses
// of the local system.
// If the Port field of laddr is 0, a port number is automatically
// chosen.
ListenTCP(network string, laddr *net.TCPAddr) (TCPListener, error)
// Dial connects to the address on the named network.
//
// Known networks are "tcp", "tcp4" (IPv4-only), "tcp6" (IPv6-only),
// "udp", "udp4" (IPv4-only), "udp6" (IPv6-only), "ip", "ip4"
// (IPv4-only), "ip6" (IPv6-only), "unix", "unixgram" and
// "unixpacket".
//
// For TCP and UDP networks, the address has the form "host:port".
// The host must be a literal IP address, or a host name that can be
// resolved to IP addresses.
// The port must be a literal port number or a service name.
// If the host is a literal IPv6 address it must be enclosed in square
// brackets, as in "[2001:db8::1]:80" or "[fe80::1%zone]:80".
// The zone specifies the scope of the literal IPv6 address as defined
// in RFC 4007.
// The functions JoinHostPort and SplitHostPort manipulate a pair of
// host and port in this form.
// When using TCP, and the host resolves to multiple IP addresses,
// Dial will try each IP address in order until one succeeds.
//
// Examples:
//
// Dial("tcp", "golang.org:http")
// Dial("tcp", "192.0.2.1:http")
// Dial("tcp", "198.51.100.1:80")
// Dial("udp", "[2001:db8::1]:domain")
// Dial("udp", "[fe80::1%lo0]:53")
// Dial("tcp", ":80")
//
// For IP networks, the network must be "ip", "ip4" or "ip6" followed
// by a colon and a literal protocol number or a protocol name, and
// the address has the form "host". The host must be a literal IP
// address or a literal IPv6 address with zone.
// It depends on each operating system how the operating system
// behaves with a non-well known protocol number such as "0" or "255".
//
// Examples:
//
// Dial("ip4:1", "192.0.2.1")
// Dial("ip6:ipv6-icmp", "2001:db8::1")
// Dial("ip6:58", "fe80::1%lo0")
//
// For TCP, UDP and IP networks, if the host is empty or a literal
// unspecified IP address, as in ":80", "0.0.0.0:80" or "[::]:80" for
// TCP and UDP, "", "0.0.0.0" or "::" for IP, the local system is
// assumed.
//
// For Unix networks, the address must be a file system path.
Dial(network, address string) (net.Conn, error)
// DialUDP acts like Dial for UDP networks.
//
// The network must be a UDP network name; see func Dial for details.
//
// If laddr is nil, a local address is automatically chosen.
// If the IP field of raddr is nil or an unspecified IP address, the
// local system is assumed.
DialUDP(network string, laddr, raddr *net.UDPAddr) (UDPConn, error)
// DialTCP acts like Dial for TCP networks.
//
// The network must be a TCP network name; see func Dial for details.
//
// If laddr is nil, a local address is automatically chosen.
// If the IP field of raddr is nil or an unspecified IP address, the
// local system is assumed.
DialTCP(network string, laddr, raddr *net.TCPAddr) (TCPConn, error)
// ResolveIPAddr returns an address of IP end point.
//
// The network must be an IP network name.
//
// If the host in the address parameter is not a literal IP address,
// ResolveIPAddr resolves the address to an address of IP end point.
// Otherwise, it parses the address as a literal IP address.
// The address parameter can use a host name, but this is not
// recommended, because it will return at most one of the host name's
// IP addresses.
//
// See func Dial for a description of the network and address
// parameters.
ResolveIPAddr(network, address string) (*net.IPAddr, error)
// ResolveUDPAddr returns an address of UDP end point.
//
// The network must be a UDP network name.
//
// If the host in the address parameter is not a literal IP address or
// the port is not a literal port number, ResolveUDPAddr resolves the
// address to an address of UDP end point.
// Otherwise, it parses the address as a pair of literal IP address
// and port number.
// The address parameter can use a host name, but this is not
// recommended, because it will return at most one of the host name's
// IP addresses.
//
// See func Dial for a description of the network and address
// parameters.
ResolveUDPAddr(network, address string) (*net.UDPAddr, error)
// ResolveTCPAddr returns an address of TCP end point.
//
// The network must be a TCP network name.
//
// If the host in the address parameter is not a literal IP address or
// the port is not a literal port number, ResolveTCPAddr resolves the
// address to an address of TCP end point.
// Otherwise, it parses the address as a pair of literal IP address
// and port number.
// The address parameter can use a host name, but this is not
// recommended, because it will return at most one of the host name's
// IP addresses.
//
// See func Dial for a description of the network and address
// parameters.
ResolveTCPAddr(network, address string) (*net.TCPAddr, error)
// Interfaces returns a list of the system's network interfaces.
Interfaces() ([]*Interface, error)
// InterfaceByIndex returns the interface specified by index.
//
// On Solaris, it returns one of the logical network interfaces
// sharing the logical data link; for more precision use
// InterfaceByName.
InterfaceByIndex(index int) (*Interface, error)
// InterfaceByName returns the interface specified by name.
InterfaceByName(name string) (*Interface, error)
// The following functions are extensions to Go's standard net package
CreateDialer(dialer *net.Dialer) Dialer
}
// Dialer is identical to net.Dialer excepts that its methods
// (Dial, DialContext) are overridden to use the Net interface.
// Use vnet.CreateDialer() to create an instance of this Dialer.
type Dialer interface {
Dial(network, address string) (net.Conn, error)
}
// UDPConn is packet-oriented connection for UDP.
type UDPConn interface {
// Close closes the connection.
// Any blocked Read or Write operations will be unblocked and return errors.
Close() error
// LocalAddr returns the local network address, if known.
LocalAddr() net.Addr
// RemoteAddr returns the remote network address, if known.
RemoteAddr() net.Addr
// SetDeadline sets the read and write deadlines associated
// with the connection. It is equivalent to calling both
// SetReadDeadline and SetWriteDeadline.
//
// A deadline is an absolute time after which I/O operations
// fail instead of blocking. The deadline applies to all future
// and pending I/O, not just the immediately following call to
// Read or Write. After a deadline has been exceeded, the
// connection can be refreshed by setting a deadline in the future.
//
// If the deadline is exceeded a call to Read or Write or to other
// I/O methods will return an error that wraps os.ErrDeadlineExceeded.
// This can be tested using errors.Is(err, os.ErrDeadlineExceeded).
// The error's Timeout method will return true, but note that there
// are other possible errors for which the Timeout method will
// return true even if the deadline has not been exceeded.
//
// An idle timeout can be implemented by repeatedly extending
// the deadline after successful Read or Write calls.
//
// A zero value for t means I/O operations will not time out.
SetDeadline(t time.Time) error
// SetReadDeadline sets the deadline for future Read calls
// and any currently-blocked Read call.
// A zero value for t means Read will not time out.
SetReadDeadline(t time.Time) error
// SetWriteDeadline sets the deadline for future Write calls
// and any currently-blocked Write call.
// Even if write times out, it may return n > 0, indicating that
// some of the data was successfully written.
// A zero value for t means Write will not time out.
SetWriteDeadline(t time.Time) error
// SetReadBuffer sets the size of the operating system's
// receive buffer associated with the connection.
SetReadBuffer(bytes int) error
// SetWriteBuffer sets the size of the operating system's
// transmit buffer associated with the connection.
SetWriteBuffer(bytes int) error
// Read reads data from the connection.
// Read can be made to time out and return an error after a fixed
// time limit; see SetDeadline and SetReadDeadline.
Read(b []byte) (n int, err error)
// ReadFrom reads a packet from the connection,
// copying the payload into p. It returns the number of
// bytes copied into p and the return address that
// was on the packet.
// It returns the number of bytes read (0 <= n <= len(p))
// and any error encountered. Callers should always process
// the n > 0 bytes returned before considering the error err.
// ReadFrom can be made to time out and return an error after a
// fixed time limit; see SetDeadline and SetReadDeadline.
ReadFrom(p []byte) (n int, addr net.Addr, err error)
// ReadFromUDP acts like ReadFrom but returns a UDPAddr.
ReadFromUDP(b []byte) (n int, addr *net.UDPAddr, err error)
// ReadMsgUDP reads a message from c, copying the payload into b and
// the associated out-of-band data into oob. It returns the number of
// bytes copied into b, the number of bytes copied into oob, the flags
// that were set on the message and the source address of the message.
//
// The packages golang.org/x/net/ipv4 and golang.org/x/net/ipv6 can be
// used to manipulate IP-level socket options in oob.
ReadMsgUDP(b, oob []byte) (n, oobn, flags int, addr *net.UDPAddr, err error)
// Write writes data to the connection.
// Write can be made to time out and return an error after a fixed
// time limit; see SetDeadline and SetWriteDeadline.
Write(b []byte) (n int, err error)
// WriteTo writes a packet with payload p to addr.
// WriteTo can be made to time out and return an Error after a
// fixed time limit; see SetDeadline and SetWriteDeadline.
// On packet-oriented connections, write timeouts are rare.
WriteTo(p []byte, addr net.Addr) (n int, err error)
// WriteToUDP acts like WriteTo but takes a UDPAddr.
WriteToUDP(b []byte, addr *net.UDPAddr) (int, error)
// WriteMsgUDP writes a message to addr via c if c isn't connected, or
// to c's remote address if c is connected (in which case addr must be
// nil). The payload is copied from b and the associated out-of-band
// data is copied from oob. It returns the number of payload and
// out-of-band bytes written.
//
// The packages golang.org/x/net/ipv4 and golang.org/x/net/ipv6 can be
// used to manipulate IP-level socket options in oob.
WriteMsgUDP(b, oob []byte, addr *net.UDPAddr) (n, oobn int, err error)
}
// TCPConn is an interface for TCP network connections.
type TCPConn interface {
net.Conn
// CloseRead shuts down the reading side of the TCP connection.
// Most callers should just use Close.
CloseRead() error
// CloseWrite shuts down the writing side of the TCP connection.
// Most callers should just use Close.
CloseWrite() error
// ReadFrom implements the io.ReaderFrom ReadFrom method.
ReadFrom(r io.Reader) (int64, error)
// SetLinger sets the behavior of Close on a connection which still
// has data waiting to be sent or to be acknowledged.
//
// If sec < 0 (the default), the operating system finishes sending the
// data in the background.
//
// If sec == 0, the operating system discards any unsent or
// unacknowledged data.
//
// If sec > 0, the data is sent in the background as with sec < 0. On
// some operating systems after sec seconds have elapsed any remaining
// unsent data may be discarded.
SetLinger(sec int) error
// SetKeepAlive sets whether the operating system should send
// keep-alive messages on the connection.
SetKeepAlive(keepalive bool) error
// SetKeepAlivePeriod sets period between keep-alives.
SetKeepAlivePeriod(d time.Duration) error
// SetNoDelay controls whether the operating system should delay
// packet transmission in hopes of sending fewer packets (Nagle's
// algorithm). The default is true (no delay), meaning that data is
// sent as soon as possible after a Write.
SetNoDelay(noDelay bool) error
// SetWriteBuffer sets the size of the operating system's
// transmit buffer associated with the connection.
SetWriteBuffer(bytes int) error
// SetReadBuffer sets the size of the operating system's
// receive buffer associated with the connection.
SetReadBuffer(bytes int) error
}
// TCPListener is a TCP network listener. Clients should typically
// use variables of type Listener instead of assuming TCP.
type TCPListener interface {
net.Listener
// AcceptTCP accepts the next incoming call and returns the new
// connection.
AcceptTCP() (TCPConn, error)
// SetDeadline sets the deadline associated with the listener.
// A zero time value disables the deadline.
SetDeadline(t time.Time) error
}
// Interface wraps a standard net.Interfaces and its assigned addresses
type Interface struct {
net.Interface
addrs []net.Addr
}
// NewInterface creates a new interface based of a standard net.Interface
func NewInterface(ifc net.Interface) *Interface {
return &Interface{
Interface: ifc,
addrs: nil,
}
}
// AddAddress adds a new address to the interface
func (ifc *Interface) AddAddress(addr net.Addr) {
ifc.addrs = append(ifc.addrs, addr)
}
// Addrs returns a slice of configured addresses on the interface
func (ifc *Interface) Addrs() ([]net.Addr, error) {
if len(ifc.addrs) == 0 {
return nil, ErrNoAddressAssigned
}
return ifc.addrs, nil
}

View File

@@ -0,0 +1,335 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
// Package packetio provides packet buffer
package packetio
import (
"errors"
"io"
"sync"
"time"
"github.com/pion/transport/v2/deadline"
)
var errPacketTooBig = errors.New("packet too big")
// BufferPacketType allow the Buffer to know which packet protocol is writing.
type BufferPacketType int
const (
// RTPBufferPacket indicates the Buffer that is handling RTP packets
RTPBufferPacket BufferPacketType = 1
// RTCPBufferPacket indicates the Buffer that is handling RTCP packets
RTCPBufferPacket BufferPacketType = 2
)
// Buffer allows writing packets to an intermediate buffer, which can then be read form.
// This is verify similar to bytes.Buffer but avoids combining multiple writes into a single read.
type Buffer struct {
mutex sync.Mutex
// this is a circular buffer. If head <= tail, then the useful
// data is in the interval [head, tail[. If tail < head, then
// the useful data is the union of [head, len[ and [0, tail[.
// In order to avoid ambiguity when head = tail, we always leave
// an unused byte in the buffer.
data []byte
head, tail int
notify chan struct{}
waiting bool
closed bool
count int
limitCount, limitSize int
readDeadline *deadline.Deadline
}
const (
minSize = 2048
cutoffSize = 128 * 1024
maxSize = 4 * 1024 * 1024
)
// NewBuffer creates a new Buffer.
func NewBuffer() *Buffer {
return &Buffer{
notify: make(chan struct{}, 1),
readDeadline: deadline.New(),
}
}
// available returns true if the buffer is large enough to fit a packet
// of the given size, taking overhead into account.
func (b *Buffer) available(size int) bool {
available := b.head - b.tail
if available <= 0 {
available += len(b.data)
}
// we interpret head=tail as empty, so always keep a byte free
if size+2+1 > available {
return false
}
return true
}
// grow increases the size of the buffer. If it returns nil, then the
// buffer has been grown. It returns ErrFull if hits a limit.
func (b *Buffer) grow() error {
var newSize int
if len(b.data) < cutoffSize {
newSize = 2 * len(b.data)
} else {
newSize = 5 * len(b.data) / 4
}
if newSize < minSize {
newSize = minSize
}
if (b.limitSize <= 0 || sizeHardLimit) && newSize > maxSize {
newSize = maxSize
}
// one byte slack
if b.limitSize > 0 && newSize > b.limitSize+1 {
newSize = b.limitSize + 1
}
if newSize <= len(b.data) {
return ErrFull
}
newData := make([]byte, newSize)
var n int
if b.head <= b.tail {
// data was contiguous
n = copy(newData, b.data[b.head:b.tail])
} else {
// data was discontinuous
n = copy(newData, b.data[b.head:])
n += copy(newData[n:], b.data[:b.tail])
}
b.head = 0
b.tail = n
b.data = newData
return nil
}
// Write appends a copy of the packet data to the buffer.
// Returns ErrFull if the packet doesn't fit.
//
// Note that the packet size is limited to 65536 bytes since v0.11.0 due to the internal data structure.
func (b *Buffer) Write(packet []byte) (int, error) {
if len(packet) >= 0x10000 {
return 0, errPacketTooBig
}
b.mutex.Lock()
if b.closed {
b.mutex.Unlock()
return 0, io.ErrClosedPipe
}
if (b.limitCount > 0 && b.count >= b.limitCount) ||
(b.limitSize > 0 && b.size()+2+len(packet) > b.limitSize) {
b.mutex.Unlock()
return 0, ErrFull
}
// grow the buffer until the packet fits
for !b.available(len(packet)) {
err := b.grow()
if err != nil {
b.mutex.Unlock()
return 0, err
}
}
// store the length of the packet
b.data[b.tail] = uint8(len(packet) >> 8)
b.tail++
if b.tail >= len(b.data) {
b.tail = 0
}
b.data[b.tail] = uint8(len(packet))
b.tail++
if b.tail >= len(b.data) {
b.tail = 0
}
// store the packet
n := copy(b.data[b.tail:], packet)
b.tail += n
if b.tail >= len(b.data) {
// we reached the end, wrap around
m := copy(b.data, packet[n:])
b.tail = m
}
b.count++
select {
case b.notify <- struct{}{}:
default:
}
b.mutex.Unlock()
return len(packet), nil
}
// Read populates the given byte slice, returning the number of bytes read.
// Blocks until data is available or the buffer is closed.
// Returns io.ErrShortBuffer is the packet is too small to copy the Write.
// Returns io.EOF if the buffer is closed.
func (b *Buffer) Read(packet []byte) (n int, err error) { //nolint:gocognit
// Return immediately if the deadline is already exceeded.
select {
case <-b.readDeadline.Done():
return 0, &netError{ErrTimeout, true, true}
default:
}
for {
b.mutex.Lock()
if b.head != b.tail {
// decode the packet size
n1 := b.data[b.head]
b.head++
if b.head >= len(b.data) {
b.head = 0
}
n2 := b.data[b.head]
b.head++
if b.head >= len(b.data) {
b.head = 0
}
count := int((uint16(n1) << 8) | uint16(n2))
// determine the number of bytes we'll actually copy
copied := count
if copied > len(packet) {
copied = len(packet)
}
// copy the data
if b.head+copied < len(b.data) {
copy(packet, b.data[b.head:b.head+copied])
} else {
k := copy(packet, b.data[b.head:])
copy(packet[k:], b.data[:copied-k])
}
// advance head, discarding any data that wasn't copied
b.head += count
if b.head >= len(b.data) {
b.head -= len(b.data)
}
if b.head == b.tail {
// the buffer is empty, reset to beginning
// in order to improve cache locality.
b.head = 0
b.tail = 0
}
b.count--
b.mutex.Unlock()
if copied < count {
return copied, io.ErrShortBuffer
}
return copied, nil
}
if b.closed {
b.mutex.Unlock()
return 0, io.EOF
}
b.mutex.Unlock()
select {
case <-b.readDeadline.Done():
return 0, &netError{ErrTimeout, true, true}
case <-b.notify:
}
}
}
// Close the buffer, unblocking any pending reads.
// Data in the buffer can still be read, Read will return io.EOF only when empty.
func (b *Buffer) Close() (err error) {
b.mutex.Lock()
if b.closed {
b.mutex.Unlock()
return nil
}
b.waiting = false
b.closed = true
close(b.notify)
b.mutex.Unlock()
return nil
}
// Count returns the number of packets in the buffer.
func (b *Buffer) Count() int {
b.mutex.Lock()
defer b.mutex.Unlock()
return b.count
}
// SetLimitCount controls the maximum number of packets that can be buffered.
// Causes Write to return ErrFull when this limit is reached.
// A zero value will disable this limit.
func (b *Buffer) SetLimitCount(limit int) {
b.mutex.Lock()
defer b.mutex.Unlock()
b.limitCount = limit
}
// Size returns the total byte size of packets in the buffer, including
// a small amount of administrative overhead.
func (b *Buffer) Size() int {
b.mutex.Lock()
defer b.mutex.Unlock()
return b.size()
}
func (b *Buffer) size() int {
size := b.tail - b.head
if size < 0 {
size += len(b.data)
}
return size
}
// SetLimitSize controls the maximum number of bytes that can be buffered.
// Causes Write to return ErrFull when this limit is reached.
// A zero value means 4MB since v0.11.0.
//
// User can set packetioSizeHardLimit build tag to enable 4MB hard limit.
// When packetioSizeHardLimit build tag is set, SetLimitSize exceeding
// the hard limit will be silently discarded.
func (b *Buffer) SetLimitSize(limit int) {
b.mutex.Lock()
defer b.mutex.Unlock()
b.limitSize = limit
}
// SetReadDeadline sets the deadline for the Read operation.
// Setting to zero means no deadline.
func (b *Buffer) SetReadDeadline(t time.Time) error {
b.readDeadline.Set(t)
return nil
}

View File

@@ -0,0 +1,30 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package packetio
import (
"errors"
)
// netError implements net.Error
type netError struct {
error
timeout, temporary bool
}
func (e *netError) Timeout() bool {
return e.timeout
}
func (e *netError) Temporary() bool {
return e.temporary
}
var (
// ErrFull is returned when the buffer has hit the configured limits.
ErrFull = errors.New("packetio.Buffer is full, discarding write")
// ErrTimeout is returned when a deadline has expired
ErrTimeout = errors.New("i/o timeout")
)

View File

@@ -0,0 +1,9 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
//go:build packetioSizeHardlimit
// +build packetioSizeHardlimit
package packetio
const sizeHardLimit = true

View File

@@ -0,0 +1,9 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
//go:build !packetioSizeHardlimit
// +build !packetioSizeHardlimit
package packetio
const sizeHardLimit = false

View File

@@ -0,0 +1,6 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"github>pion/renovate-config"
]
}

View File

@@ -0,0 +1,81 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package replaydetector
import (
"fmt"
)
// fixedBigInt is the fix-sized multi-word integer.
type fixedBigInt struct {
bits []uint64
n uint
msbMask uint64
}
// newFixedBigInt creates a new fix-sized multi-word int.
func newFixedBigInt(n uint) *fixedBigInt {
chunkSize := (n + 63) / 64
if chunkSize == 0 {
chunkSize = 1
}
return &fixedBigInt{
bits: make([]uint64, chunkSize),
n: n,
msbMask: (1 << (64 - n%64)) - 1,
}
}
// Lsh is the left shift operation.
func (s *fixedBigInt) Lsh(n uint) {
if n == 0 {
return
}
nChunk := int(n / 64)
nN := n % 64
for i := len(s.bits) - 1; i >= 0; i-- {
var carry uint64
if i-nChunk >= 0 {
carry = s.bits[i-nChunk] << nN
if i-nChunk-1 >= 0 {
carry |= s.bits[i-nChunk-1] >> (64 - nN)
}
}
s.bits[i] = (s.bits[i] << n) | carry
}
s.bits[len(s.bits)-1] &= s.msbMask
}
// Bit returns i-th bit of the fixedBigInt.
func (s *fixedBigInt) Bit(i uint) uint {
if i >= s.n {
return 0
}
chunk := i / 64
pos := i % 64
if s.bits[chunk]&(1<<pos) != 0 {
return 1
}
return 0
}
// SetBit sets i-th bit to 1.
func (s *fixedBigInt) SetBit(i uint) {
if i >= s.n {
return
}
chunk := i / 64
pos := i % 64
s.bits[chunk] |= 1 << pos
}
// String returns string representation of fixedBigInt.
func (s *fixedBigInt) String() string {
var out string
for i := len(s.bits) - 1; i >= 0; i-- {
out += fmt.Sprintf("%016X", s.bits[i])
}
return out
}

View File

@@ -0,0 +1,119 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
// Package replaydetector provides packet replay detection algorithm.
package replaydetector
// ReplayDetector is the interface of sequence replay detector.
type ReplayDetector interface {
// Check returns true if given sequence number is not replayed.
// Call accept() to mark the packet is received properly.
Check(seq uint64) (accept func(), ok bool)
}
type slidingWindowDetector struct {
latestSeq uint64
maxSeq uint64
windowSize uint
mask *fixedBigInt
}
// New creates ReplayDetector.
// Created ReplayDetector doesn't allow wrapping.
// It can handle monotonically increasing sequence number up to
// full 64bit number. It is suitable for DTLS replay protection.
func New(windowSize uint, maxSeq uint64) ReplayDetector {
return &slidingWindowDetector{
maxSeq: maxSeq,
windowSize: windowSize,
mask: newFixedBigInt(windowSize),
}
}
func (d *slidingWindowDetector) Check(seq uint64) (accept func(), ok bool) {
if seq > d.maxSeq {
// Exceeded upper limit.
return func() {}, false
}
if seq <= d.latestSeq {
if d.latestSeq >= uint64(d.windowSize)+seq {
return func() {}, false
}
if d.mask.Bit(uint(d.latestSeq-seq)) != 0 {
// The sequence number is duplicated.
return func() {}, false
}
}
return func() {
if seq > d.latestSeq {
// Update the head of the window.
d.mask.Lsh(uint(seq - d.latestSeq))
d.latestSeq = seq
}
diff := (d.latestSeq - seq) % d.maxSeq
d.mask.SetBit(uint(diff))
}, true
}
// WithWrap creates ReplayDetector allowing sequence wrapping.
// This is suitable for short bit width counter like SRTP and SRTCP.
func WithWrap(windowSize uint, maxSeq uint64) ReplayDetector {
return &wrappedSlidingWindowDetector{
maxSeq: maxSeq,
windowSize: windowSize,
mask: newFixedBigInt(windowSize),
}
}
type wrappedSlidingWindowDetector struct {
latestSeq uint64
maxSeq uint64
windowSize uint
mask *fixedBigInt
init bool
}
func (d *wrappedSlidingWindowDetector) Check(seq uint64) (accept func(), ok bool) {
if seq > d.maxSeq {
// Exceeded upper limit.
return func() {}, false
}
if !d.init {
if seq != 0 {
d.latestSeq = seq - 1
} else {
d.latestSeq = d.maxSeq
}
d.init = true
}
diff := int64(d.latestSeq) - int64(seq)
// Wrap the number.
if diff > int64(d.maxSeq)/2 {
diff -= int64(d.maxSeq + 1)
} else if diff <= -int64(d.maxSeq)/2 {
diff += int64(d.maxSeq + 1)
}
if diff >= int64(d.windowSize) {
// Too old.
return func() {}, false
}
if diff >= 0 {
if d.mask.Bit(uint(diff)) != 0 {
// The sequence number is duplicated.
return func() {}, false
}
}
return func() {
if diff < 0 {
// Update the head of the window.
d.mask.Lsh(uint(-diff))
d.latestSeq = seq
}
d.mask.SetBit(uint(d.latestSeq - seq))
}, true
}

View File

@@ -0,0 +1,168 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
// Package stdnet implements the transport.Net interface
// using methods from Go's standard net package.
package stdnet
import (
"fmt"
"net"
"github.com/pion/transport/v2"
"github.com/wlynxg/anet"
)
const (
lo0String = "lo0String"
udpString = "udp"
)
// Net is an implementation of the net.Net interface
// based on functions of the standard net package.
type Net struct {
interfaces []*transport.Interface
}
// NewNet creates a new StdNet instance.
func NewNet() (*Net, error) {
n := &Net{}
return n, n.UpdateInterfaces()
}
// Compile-time assertion
var _ transport.Net = &Net{}
// UpdateInterfaces updates the internal list of network interfaces
// and associated addresses.
func (n *Net) UpdateInterfaces() error {
ifs := []*transport.Interface{}
oifs, err := anet.Interfaces()
if err != nil {
return err
}
for i := range oifs {
ifc := transport.NewInterface(oifs[i])
addrs, err := anet.InterfaceAddrsByInterface(&oifs[i])
if err != nil {
return err
}
for _, addr := range addrs {
ifc.AddAddress(addr)
}
ifs = append(ifs, ifc)
}
n.interfaces = ifs
return nil
}
// Interfaces returns a slice of interfaces which are available on the
// system
func (n *Net) Interfaces() ([]*transport.Interface, error) {
return n.interfaces, nil
}
// InterfaceByIndex returns the interface specified by index.
//
// On Solaris, it returns one of the logical network interfaces
// sharing the logical data link; for more precision use
// InterfaceByName.
func (n *Net) InterfaceByIndex(index int) (*transport.Interface, error) {
for _, ifc := range n.interfaces {
if ifc.Index == index {
return ifc, nil
}
}
return nil, fmt.Errorf("%w: index=%d", transport.ErrInterfaceNotFound, index)
}
// InterfaceByName returns the interface specified by name.
func (n *Net) InterfaceByName(name string) (*transport.Interface, error) {
for _, ifc := range n.interfaces {
if ifc.Name == name {
return ifc, nil
}
}
return nil, fmt.Errorf("%w: %s", transport.ErrInterfaceNotFound, name)
}
// ListenPacket announces on the local network address.
func (n *Net) ListenPacket(network string, address string) (net.PacketConn, error) {
return net.ListenPacket(network, address)
}
// ListenUDP acts like ListenPacket for UDP networks.
func (n *Net) ListenUDP(network string, locAddr *net.UDPAddr) (transport.UDPConn, error) {
return net.ListenUDP(network, locAddr)
}
// Dial connects to the address on the named network.
func (n *Net) Dial(network, address string) (net.Conn, error) {
return net.Dial(network, address)
}
// DialUDP acts like Dial for UDP networks.
func (n *Net) DialUDP(network string, laddr, raddr *net.UDPAddr) (transport.UDPConn, error) {
return net.DialUDP(network, laddr, raddr)
}
// ResolveIPAddr returns an address of IP end point.
func (n *Net) ResolveIPAddr(network, address string) (*net.IPAddr, error) {
return net.ResolveIPAddr(network, address)
}
// ResolveUDPAddr returns an address of UDP end point.
func (n *Net) ResolveUDPAddr(network, address string) (*net.UDPAddr, error) {
return net.ResolveUDPAddr(network, address)
}
// ResolveTCPAddr returns an address of TCP end point.
func (n *Net) ResolveTCPAddr(network, address string) (*net.TCPAddr, error) {
return net.ResolveTCPAddr(network, address)
}
// DialTCP acts like Dial for TCP networks.
func (n *Net) DialTCP(network string, laddr, raddr *net.TCPAddr) (transport.TCPConn, error) {
return net.DialTCP(network, laddr, raddr)
}
// ListenTCP acts like Listen for TCP networks.
func (n *Net) ListenTCP(network string, laddr *net.TCPAddr) (transport.TCPListener, error) {
l, err := net.ListenTCP(network, laddr)
if err != nil {
return nil, err
}
return tcpListener{l}, nil
}
type tcpListener struct {
*net.TCPListener
}
func (l tcpListener) AcceptTCP() (transport.TCPConn, error) {
return l.TCPListener.AcceptTCP()
}
type stdDialer struct {
*net.Dialer
}
func (d stdDialer) Dial(network, address string) (net.Conn, error) {
return d.Dialer.Dial(network, address)
}
// CreateDialer creates an instance of vnet.Dialer
func (n *Net) CreateDialer(d *net.Dialer) transport.Dialer {
return stdDialer{d}
}

View File

@@ -0,0 +1,170 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package udp
import (
"io"
"net"
"runtime"
"sync"
"sync/atomic"
"time"
"golang.org/x/net/ipv4"
"golang.org/x/net/ipv6"
)
// BatchWriter represents conn can write messages in batch
type BatchWriter interface {
WriteBatch(ms []ipv4.Message, flags int) (int, error)
}
// BatchReader represents conn can read messages in batch
type BatchReader interface {
ReadBatch(msg []ipv4.Message, flags int) (int, error)
}
// BatchPacketConn represents conn can read/write messages in batch
type BatchPacketConn interface {
BatchWriter
BatchReader
io.Closer
}
// BatchConn uses ipv4/v6.NewPacketConn to wrap a net.PacketConn to write/read messages in batch,
// only available in linux. In other platform, it will use single Write/Read as same as net.Conn.
type BatchConn struct {
net.PacketConn
batchConn BatchPacketConn
batchWriteMutex sync.Mutex
batchWriteMessages []ipv4.Message
batchWritePos int
batchWriteLast time.Time
batchWriteSize int
batchWriteInterval time.Duration
closed int32
}
// NewBatchConn creates a *BatchConn from net.PacketConn with batch configs.
func NewBatchConn(conn net.PacketConn, batchWriteSize int, batchWriteInterval time.Duration) *BatchConn {
bc := &BatchConn{
PacketConn: conn,
batchWriteLast: time.Now(),
batchWriteInterval: batchWriteInterval,
batchWriteSize: batchWriteSize,
batchWriteMessages: make([]ipv4.Message, batchWriteSize),
}
for i := range bc.batchWriteMessages {
bc.batchWriteMessages[i].Buffers = [][]byte{make([]byte, sendMTU)}
}
// batch write only supports linux
if runtime.GOOS == "linux" {
if pc4 := ipv4.NewPacketConn(conn); pc4 != nil {
bc.batchConn = pc4
} else if pc6 := ipv6.NewPacketConn(conn); pc6 != nil {
bc.batchConn = pc6
}
}
if bc.batchConn != nil {
go func() {
writeTicker := time.NewTicker(batchWriteInterval / 2)
defer writeTicker.Stop()
for atomic.LoadInt32(&bc.closed) != 1 {
<-writeTicker.C
bc.batchWriteMutex.Lock()
if bc.batchWritePos > 0 && time.Since(bc.batchWriteLast) >= bc.batchWriteInterval {
_ = bc.flush()
}
bc.batchWriteMutex.Unlock()
}
}()
}
return bc
}
// Close batchConn and the underlying PacketConn
func (c *BatchConn) Close() error {
atomic.StoreInt32(&c.closed, 1)
c.batchWriteMutex.Lock()
if c.batchWritePos > 0 {
_ = c.flush()
}
c.batchWriteMutex.Unlock()
if c.batchConn != nil {
return c.batchConn.Close()
}
return c.PacketConn.Close()
}
// WriteTo write message to an UDPAddr, addr should be nil if it is a connected socket.
func (c *BatchConn) WriteTo(b []byte, addr net.Addr) (int, error) {
if c.batchConn == nil {
return c.PacketConn.WriteTo(b, addr)
}
return c.enqueueMessage(b, addr)
}
func (c *BatchConn) enqueueMessage(buf []byte, raddr net.Addr) (int, error) {
var err error
c.batchWriteMutex.Lock()
defer c.batchWriteMutex.Unlock()
msg := &c.batchWriteMessages[c.batchWritePos]
// reset buffers
msg.Buffers = msg.Buffers[:1]
msg.Buffers[0] = msg.Buffers[0][:cap(msg.Buffers[0])]
c.batchWritePos++
if raddr != nil {
msg.Addr = raddr
}
if n := copy(msg.Buffers[0], buf); n < len(buf) {
extraBuffer := make([]byte, len(buf)-n)
copy(extraBuffer, buf[n:])
msg.Buffers = append(msg.Buffers, extraBuffer)
} else {
msg.Buffers[0] = msg.Buffers[0][:n]
}
if c.batchWritePos == c.batchWriteSize {
err = c.flush()
}
return len(buf), err
}
// ReadBatch reads messages in batch, return length of message readed and error.
func (c *BatchConn) ReadBatch(msgs []ipv4.Message, flags int) (int, error) {
if c.batchConn == nil {
n, addr, err := c.PacketConn.ReadFrom(msgs[0].Buffers[0])
if err == nil {
msgs[0].N = n
msgs[0].Addr = addr
return 1, nil
}
return 0, err
}
return c.batchConn.ReadBatch(msgs, flags)
}
func (c *BatchConn) flush() error {
var writeErr error
var txN int
for txN < c.batchWritePos {
n, err := c.batchConn.WriteBatch(c.batchWriteMessages[txN:c.batchWritePos], 0)
if err != nil {
writeErr = err
break
}
txN += n
}
c.batchWritePos = 0
c.batchWriteLast = time.Now()
return writeErr
}

406
server/vendor/github.com/pion/transport/v2/udp/conn.go generated vendored Normal file
View File

@@ -0,0 +1,406 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
// Package udp provides a connection-oriented listener over a UDP PacketConn
package udp
import (
"context"
"errors"
"net"
"sync"
"sync/atomic"
"time"
"github.com/pion/logging"
"github.com/pion/transport/v2/deadline"
"github.com/pion/transport/v2/packetio"
"golang.org/x/net/ipv4"
)
const (
receiveMTU = 8192
sendMTU = 1500
defaultListenBacklog = 128 // same as Linux default
)
// Typed errors
var (
ErrClosedListener = errors.New("udp: listener closed")
ErrListenQueueExceeded = errors.New("udp: listen queue exceeded")
ErrInvalidBatchConfig = errors.New("udp: invalid batch config")
)
// listener augments a connection-oriented Listener over a UDP PacketConn
type listener struct {
pConn net.PacketConn
readBatchSize int
accepting atomic.Value // bool
acceptCh chan *Conn
doneCh chan struct{}
doneOnce sync.Once
acceptFilter func([]byte) bool
connLock sync.Mutex
conns map[string]*Conn
connWG *sync.WaitGroup
readWG sync.WaitGroup
errClose atomic.Value // error
readDoneCh chan struct{}
errRead atomic.Value // error
logger logging.LeveledLogger
}
// 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.readDoneCh:
err, _ := l.errRead.Load().(error)
return nil, err
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
lclose:
for {
select {
case c := <-l.acceptCh:
close(c.doneCh)
delete(l.conns, c.rAddr.String())
default:
break lclose
}
}
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()
}
// BatchIOConfig indicates config to batch read/write packets,
// it will use ReadBatch/WriteBatch to improve throughput for UDP.
type BatchIOConfig struct {
Enable bool
// ReadBatchSize indicates the maximum number of packets to be read in one batch, a batch size less than 2 means
// disable read batch.
ReadBatchSize int
// WriteBatchSize indicates the maximum number of packets to be written in one batch
WriteBatchSize int
// WriteBatchInterval indicates the maximum interval to wait before writing packets in one batch
// small interval will reduce latency/jitter, but increase the io count.
WriteBatchInterval time.Duration
}
// 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
// ReadBufferSize sets the size of the operating system's
// receive buffer associated with the listener.
ReadBufferSize int
// WriteBufferSize sets the size of the operating system's
// send buffer associated with the connection.
WriteBufferSize int
Batch BatchIOConfig
LoggerFactory logging.LoggerFactory
}
// 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
}
if lc.Batch.Enable && (lc.Batch.WriteBatchSize <= 0 || lc.Batch.WriteBatchInterval <= 0) {
return nil, ErrInvalidBatchConfig
}
conn, err := net.ListenUDP(network, laddr)
if err != nil {
return nil, err
}
if lc.ReadBufferSize > 0 {
_ = conn.SetReadBuffer(lc.ReadBufferSize)
}
if lc.WriteBufferSize > 0 {
_ = conn.SetWriteBuffer(lc.WriteBufferSize)
}
loggerFactory := lc.LoggerFactory
if loggerFactory == nil {
loggerFactory = logging.NewDefaultLoggerFactory()
}
logger := loggerFactory.NewLogger("transport")
l := &listener{
pConn: conn,
acceptCh: make(chan *Conn, lc.Backlog),
conns: make(map[string]*Conn),
doneCh: make(chan struct{}),
acceptFilter: lc.AcceptFilter,
connWG: &sync.WaitGroup{},
readDoneCh: make(chan struct{}),
logger: logger,
}
if lc.Batch.Enable {
l.pConn = NewBatchConn(conn, lc.Batch.WriteBatchSize, lc.Batch.WriteBatchInterval)
l.readBatchSize = lc.Batch.ReadBatchSize
}
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()
defer close(l.readDoneCh)
if br, ok := l.pConn.(BatchReader); ok && l.readBatchSize > 1 {
l.readBatch(br)
} else {
l.read()
}
}
func (l *listener) readBatch(br BatchReader) {
msgs := make([]ipv4.Message, l.readBatchSize)
for i := range msgs {
msg := &msgs[i]
msg.Buffers = [][]byte{make([]byte, receiveMTU)}
msg.OOB = make([]byte, 40)
}
for {
n, err := br.ReadBatch(msgs, 0)
if err != nil {
l.errRead.Store(err)
return
}
for i := 0; i < n; i++ {
l.dispatchMsg(msgs[i].Addr, msgs[i].Buffers[0][:msgs[i].N])
}
}
}
func (l *listener) read() {
buf := make([]byte, receiveMTU)
for {
n, raddr, err := l.pConn.ReadFrom(buf)
if err != nil {
l.errRead.Store(err)
l.logger.Tracef("error reading from connection err=%v", err)
return
}
l.dispatchMsg(raddr, buf[:n])
}
}
func (l *listener) dispatchMsg(addr net.Addr, buf []byte) {
conn, ok, err := l.getConn(addr, buf)
if err != nil {
return
}
if ok {
_, err := conn.buffer.Write(buf)
if err != nil {
l.logger.Tracef("error dispatching message addr=%v err=%v", addr, err)
}
}
}
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
}

View File

@@ -0,0 +1,29 @@
// SPDX-FileCopyrightText: 2018 The Go Authors. All rights reserved.
// SPDX-License-Identifier: BSD-3-Clause
//go:build !gccgo
// +build !gccgo
// Package xor provides utility functions used by other Pion
// packages. AMD64 arch.
package xor
// XorBytes xors the bytes in a and b. The destination should have enough
// space, otherwise xorBytes will panic. Returns the number of bytes xor'd.
//
//revive:disable-next-line
func XorBytes(dst, a, b []byte) int {
n := len(a)
if len(b) < n {
n = len(b)
}
if n == 0 {
return 0
}
_ = dst[n-1]
xorBytesSSE2(&dst[0], &a[0], &b[0], n) // amd64 must have SSE2
return n
}
//go:noescape
func xorBytesSSE2(dst, a, b *byte, n int)

View File

@@ -0,0 +1,56 @@
// SPDX-FileCopyrightText: 2018 The Go Authors. All rights reserved.
// SPDX-License-Identifier: BSD-3-Clause
// go:build !gccgo
// +build !gccgo
#include "textflag.h"
// func xorBytesSSE2(dst, a, b *byte, n int)
TEXT ·xorBytesSSE2(SB), NOSPLIT, $0
MOVQ dst+0(FP), BX
MOVQ a+8(FP), SI
MOVQ b+16(FP), CX
MOVQ n+24(FP), DX
TESTQ $15, DX // AND 15 & len, if not zero jump to not_aligned.
JNZ not_aligned
aligned:
MOVQ $0, AX // position in slices
loop16b:
MOVOU (SI)(AX*1), X0 // XOR 16byte forwards.
MOVOU (CX)(AX*1), X1
PXOR X1, X0
MOVOU X0, (BX)(AX*1)
ADDQ $16, AX
CMPQ DX, AX
JNE loop16b
RET
loop_1b:
SUBQ $1, DX // XOR 1byte backwards.
MOVB (SI)(DX*1), DI
MOVB (CX)(DX*1), AX
XORB AX, DI
MOVB DI, (BX)(DX*1)
TESTQ $7, DX // AND 7 & len, if not zero jump to loop_1b.
JNZ loop_1b
CMPQ DX, $0 // if len is 0, ret.
JE ret
TESTQ $15, DX // AND 15 & len, if zero jump to aligned.
JZ aligned
not_aligned:
TESTQ $7, DX // AND $7 & len, if not zero jump to loop_1b.
JNE loop_1b
SUBQ $8, DX // XOR 8bytes backwards.
MOVQ (SI)(DX*1), DI
MOVQ (CX)(DX*1), AX
XORQ AX, DI
MOVQ DI, (BX)(DX*1)
CMPQ DX, $16 // if len is greater or equal 16 here, it must be aligned.
JGE aligned
ret:
RET

View File

@@ -0,0 +1,60 @@
// SPDX-FileCopyrightText: 2022 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
//go:build !gccgo
// +build !gccgo
// Package xor provides utility functions used by other Pion
// packages. ARM arch.
package xor
import (
"unsafe"
"golang.org/x/sys/cpu"
)
const wordSize = int(unsafe.Sizeof(uintptr(0))) // nolint:gosec
var hasNEON = cpu.ARM.HasNEON // nolint:gochecknoglobals
func isAligned(a *byte) bool {
return uintptr(unsafe.Pointer(a))%uintptr(wordSize) == 0
}
// XorBytes xors the bytes in a and b. The destination should have enough
// space, otherwise xorBytes will panic. Returns the number of bytes xor'd.
//
//revive:disable-next-line
func XorBytes(dst, a, b []byte) int {
n := len(a)
if len(b) < n {
n = len(b)
}
if n == 0 {
return 0
}
// make sure dst has enough space
_ = dst[n-1]
if hasNEON {
xorBytesNEON32(&dst[0], &a[0], &b[0], n)
} else if isAligned(&dst[0]) && isAligned(&a[0]) && isAligned(&b[0]) {
xorBytesARM32(&dst[0], &a[0], &b[0], n)
} else {
safeXORBytes(dst, a, b, n)
}
return n
}
// n needs to be smaller or equal than the length of a and b.
func safeXORBytes(dst, a, b []byte, n int) {
for i := 0; i < n; i++ {
dst[i] = a[i] ^ b[i]
}
}
//go:noescape
func xorBytesARM32(dst, a, b *byte, n int)
//go:noescape
func xorBytesNEON32(dst, a, b *byte, n int)

View File

@@ -0,0 +1,116 @@
// SPDX-FileCopyrightText: 2022 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
// go:build !gccgo
// +build !gccgo
#include "textflag.h"
// func xorBytesARM32(dst, a, b *byte, n int)
TEXT ·xorBytesARM32(SB), NOSPLIT|NOFRAME, $0
MOVW dst+0(FP), R0
MOVW a+4(FP), R1
MOVW b+8(FP), R2
MOVW n+12(FP), R3
CMP $4, R3
BLT less_than4
loop_4:
MOVW.P 4(R1), R4
MOVW.P 4(R2), R5
EOR R4, R5, R5
MOVW.P R5, 4(R0)
SUB $4, R3
CMP $4, R3
BGE loop_4
less_than4:
CMP $2, R3
BLT less_than2
MOVH.P 2(R1), R4
MOVH.P 2(R2), R5
EOR R4, R5, R5
MOVH.P R5, 2(R0)
SUB $2, R3
less_than2:
CMP $0, R3
BEQ end
MOVB (R1), R4
MOVB (R2), R5
EOR R4, R5, R5
MOVB R5, (R0)
end:
RET
// func xorBytesNEON32(dst, a, b *byte, n int)
TEXT ·xorBytesNEON32(SB), NOSPLIT|NOFRAME, $0
MOVW dst+0(FP), R0
MOVW a+4(FP), R1
MOVW b+8(FP), R2
MOVW n+12(FP), R3
CMP $32, R3
BLT less_than32
loop_32:
WORD $0xF421020D // vld1.u8 {q0, q1}, [r1]!
WORD $0xF422420D // vld1.u8 {q2, q3}, [r2]!
WORD $0xF3004154 // veor q2, q0, q2
WORD $0xF3026156 // veor q3, q1, q3
WORD $0xF400420D // vst1.u8 {q2, q3}, [r0]!
SUB $32, R3
CMP $32, R3
BGE loop_32
less_than32:
CMP $16, R3
BLT less_than16
WORD $0xF4210A0D // vld1.u8 q0, [r1]!
WORD $0xF4222A0D // vld1.u8 q1, [r2]!
WORD $0xF3002152 // veor q1, q0, q1
WORD $0xF4002A0D // vst1.u8 {q1}, [r0]!
SUB $16, R3
less_than16:
CMP $8, R3
BLT less_than8
WORD $0xF421070D // vld1.u8 d0, [r1]!
WORD $0xF422170D // vld1.u8 d1, [r2]!
WORD $0xF3001111 // veor d1, d0, d1
WORD $0xF400170D // vst1.u8 {d1}, [r0]!
SUB $8, R3
less_than8:
CMP $4, R3
BLT less_than4
MOVW.P 4(R1), R4
MOVW.P 4(R2), R5
EOR R4, R5, R5
MOVW.P R5, 4(R0)
SUB $4, R3
less_than4:
CMP $2, R3
BLT less_than2
MOVH.P 2(R1), R4
MOVH.P 2(R2), R5
EOR R4, R5, R5
MOVH.P R5, 2(R0)
SUB $2, R3
less_than2:
CMP $0, R3
BEQ end
MOVB (R1), R4
MOVB (R2), R5
EOR R4, R5, R5
MOVB R5, (R0)
end:
RET

View File

@@ -0,0 +1,31 @@
// SPDX-FileCopyrightText: 2020 The Go Authors. All rights reserved.
// SPDX-License-Identifier: BSD-3-Clause
//go:build !gccgo
// +build !gccgo
// Package xor provides utility functions used by other Pion
// packages. ARM64 arch.
package xor
// XorBytes xors the bytes in a and b. The destination should have enough
// space, otherwise xorBytes will panic. Returns the number of bytes xor'd.
//
//revive:disable-next-line
func XorBytes(dst, a, b []byte) int {
n := len(a)
if len(b) < n {
n = len(b)
}
if n == 0 {
return 0
}
// make sure dst has enough space
_ = dst[n-1]
xorBytesARM64(&dst[0], &a[0], &b[0], n)
return n
}
//go:noescape
func xorBytesARM64(dst, a, b *byte, n int)

View File

@@ -0,0 +1,69 @@
// SPDX-FileCopyrightText: 2020 The Go Authors. All rights reserved.
// SPDX-License-Identifier: BSD-3-Clause
//go:build !gccgo
// +build !gccgo
#include "textflag.h"
// func xorBytesARM64(dst, a, b *byte, n int)
TEXT ·xorBytesARM64(SB), NOSPLIT|NOFRAME, $0
MOVD dst+0(FP), R0
MOVD a+8(FP), R1
MOVD b+16(FP), R2
MOVD n+24(FP), R3
CMP $64, R3
BLT tail
loop_64:
VLD1.P 64(R1), [V0.B16, V1.B16, V2.B16, V3.B16]
VLD1.P 64(R2), [V4.B16, V5.B16, V6.B16, V7.B16]
VEOR V0.B16, V4.B16, V4.B16
VEOR V1.B16, V5.B16, V5.B16
VEOR V2.B16, V6.B16, V6.B16
VEOR V3.B16, V7.B16, V7.B16
VST1.P [V4.B16, V5.B16, V6.B16, V7.B16], 64(R0)
SUBS $64, R3
CMP $64, R3
BGE loop_64
tail:
// quick end
CBZ R3, end
TBZ $5, R3, less_than32
VLD1.P 32(R1), [V0.B16, V1.B16]
VLD1.P 32(R2), [V2.B16, V3.B16]
VEOR V0.B16, V2.B16, V2.B16
VEOR V1.B16, V3.B16, V3.B16
VST1.P [V2.B16, V3.B16], 32(R0)
less_than32:
TBZ $4, R3, less_than16
LDP.P 16(R1), (R11, R12)
LDP.P 16(R2), (R13, R14)
EOR R11, R13, R13
EOR R12, R14, R14
STP.P (R13, R14), 16(R0)
less_than16:
TBZ $3, R3, less_than8
MOVD.P 8(R1), R11
MOVD.P 8(R2), R12
EOR R11, R12, R12
MOVD.P R12, 8(R0)
less_than8:
TBZ $2, R3, less_than4
MOVWU.P 4(R1), R13
MOVWU.P 4(R2), R14
EORW R13, R14, R14
MOVWU.P R14, 4(R0)
less_than4:
TBZ $1, R3, less_than2
MOVHU.P 2(R1), R15
MOVHU.P 2(R2), R16
EORW R15, R16, R16
MOVHU.P R16, 2(R0)
less_than2:
TBZ $0, R3, end
MOVBU (R1), R17
MOVBU (R2), R19
EORW R17, R19, R19
MOVBU R19, (R0)
end:
RET

View File

@@ -0,0 +1,78 @@
// SPDX-FileCopyrightText: 2013 The Go Authors. All rights reserved.
// SPDX-License-Identifier: BSD-3-Clause
// SPDX-FileCopyrightText: 2022 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
//go:build (!amd64 && !ppc64 && !ppc64le && !arm64 && !arm) || gccgo
// +build !amd64,!ppc64,!ppc64le,!arm64,!arm gccgo
// Package xor provides utility functions used by other Pion
// packages. Generic arch.
package xor
import (
"runtime"
"unsafe"
)
const (
wordSize = int(unsafe.Sizeof(uintptr(0))) // nolint:gosec
supportsUnaligned = runtime.GOARCH == "386" || runtime.GOARCH == "ppc64" || runtime.GOARCH == "ppc64le" || runtime.GOARCH == "s390x" // nolint:gochecknoglobals
)
func isAligned(a *byte) bool {
return uintptr(unsafe.Pointer(a))%uintptr(wordSize) == 0
}
// XorBytes xors the bytes in a and b. The destination should have enough
// space, otherwise xorBytes will panic. Returns the number of bytes xor'd.
//
//revive:disable-next-line
func XorBytes(dst, a, b []byte) int {
n := len(a)
if len(b) < n {
n = len(b)
}
if n == 0 {
return 0
}
switch {
case supportsUnaligned:
fastXORBytes(dst, a, b, n)
case isAligned(&dst[0]) && isAligned(&a[0]) && isAligned(&b[0]):
fastXORBytes(dst, a, b, n)
default:
safeXORBytes(dst, a, b, n)
}
return n
}
// fastXORBytes xors in bulk. It only works on architectures that
// support unaligned read/writes.
// n needs to be smaller or equal than the length of a and b.
func fastXORBytes(dst, a, b []byte, n int) {
// Assert dst has enough space
_ = dst[n-1]
w := n / wordSize
if w > 0 {
dw := *(*[]uintptr)(unsafe.Pointer(&dst)) // nolint:gosec
aw := *(*[]uintptr)(unsafe.Pointer(&a)) // nolint:gosec
bw := *(*[]uintptr)(unsafe.Pointer(&b)) // nolint:gosec
for i := 0; i < w; i++ {
dw[i] = aw[i] ^ bw[i]
}
}
for i := (n - n%wordSize); i < n; i++ {
dst[i] = a[i] ^ b[i]
}
}
// n needs to be smaller or equal than the length of a and b.
func safeXORBytes(dst, a, b []byte, n int) {
for i := 0; i < n; i++ {
dst[i] = a[i] ^ b[i]
}
}

View File

@@ -0,0 +1,29 @@
// SPDX-FileCopyrightText: 2018 The Go Authors. All rights reserved.
// SPDX-License-Identifier: BSD-3-Clause
//go:build (ppc64 && !gccgo) || (ppc64le && !gccgo)
// +build ppc64,!gccgo ppc64le,!gccgo
// Package xor provides utility functions used by other Pion
// packages. PPC64 arch.
package xor
// XorBytes xors the bytes in a and b. The destination should have enough
// space, otherwise xorBytes will panic. Returns the number of bytes xor'd.
//
//revive:disable-next-line
func XorBytes(dst, a, b []byte) int {
n := len(a)
if len(b) < n {
n = len(b)
}
if n == 0 {
return 0
}
_ = dst[n-1]
xorBytesVSX(&dst[0], &a[0], &b[0], n)
return n
}
//go:noescape
func xorBytesVSX(dst, a, b *byte, n int)

View File

@@ -0,0 +1,87 @@
// SPDX-FileCopyrightText: 2018 The Go Authors. All rights reserved.
// SPDX-License-Identifier: BSD-3-Clause
//go:build (ppc64 && !gccgo) || (ppc64le && !gccgo)
//+build ppc64,!gccgo ppc64le,!gccgo
#include "textflag.h"
// func xorBytesVSX(dst, a, b *byte, n int)
TEXT ·xorBytesVSX(SB), NOSPLIT, $0
MOVD dst+0(FP), R3 // R3 = dst
MOVD a+8(FP), R4 // R4 = a
MOVD b+16(FP), R5 // R5 = b
MOVD n+24(FP), R6 // R6 = n
CMPU R6, $32, CR7 // Check if n 32 bytes
MOVD R0, R8 // R8 = index
CMPU R6, $8, CR6 // Check if 8 n < 32 bytes
BLT CR6, small // Smaller than 8
BLT CR7, xor16 // Case for 16 n < 32 bytes
// Case for n 32 bytes
preloop32:
SRD $5, R6, R7 // Setup loop counter
MOVD R7, CTR
MOVD $16, R10
ANDCC $31, R6, R9 // Check for tailing bytes for later
loop32:
LXVD2X (R4)(R8), VS32 // VS32 = a[i,...,i+15]
LXVD2X (R4)(R10), VS34
LXVD2X (R5)(R8), VS33 // VS33 = b[i,...,i+15]
LXVD2X (R5)(R10), VS35
XXLXOR VS32, VS33, VS32 // VS34 = a[] ^ b[]
XXLXOR VS34, VS35, VS34
STXVD2X VS32, (R3)(R8) // Store to dst
STXVD2X VS34, (R3)(R10)
ADD $32, R8 // Update index
ADD $32, R10
BC 16, 0, loop32 // bdnz loop16
BEQ CR0, done
MOVD R9, R6
CMP R6, $8
BLT small
xor16:
CMP R6, $16
BLT xor8
LXVD2X (R4)(R8), VS32
LXVD2X (R5)(R8), VS33
XXLXOR VS32, VS33, VS32
STXVD2X VS32, (R3)(R8)
ADD $16, R8
ADD $-16, R6
CMP R6, $8
BLT small
xor8:
// Case for 8 n < 16 bytes
MOVD (R4)(R8), R14 // R14 = a[i,...,i+7]
MOVD (R5)(R8), R15 // R15 = b[i,...,i+7]
XOR R14, R15, R16 // R16 = a[] ^ b[]
SUB $8, R6 // n = n - 8
MOVD R16, (R3)(R8) // Store to dst
ADD $8, R8
// Check if we're finished
CMP R6, R0
BGT small
RET
// Case for n < 8 bytes and tailing bytes from the
// previous cases.
small:
CMP R6, R0
BEQ done
MOVD R6, CTR // Setup loop counter
loop:
MOVBZ (R4)(R8), R14 // R14 = a[i]
MOVBZ (R5)(R8), R15 // R15 = b[i]
XOR R14, R15, R16 // R16 = a[i] ^ b[i]
MOVB R16, (R3)(R8) // Store to dst
ADD $1, R8
BC 16, 0, loop // bdnz loop
done:
RET

View File

@@ -0,0 +1,4 @@
# SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
# SPDX-License-Identifier: MIT
*.sw[poe]

View File

@@ -0,0 +1,231 @@
# vnet
A virtual network layer for pion.
## Overview
### Goals
* To make NAT traversal tests easy.
* To emulate packet impairment at application level for testing.
* To monitor packets at specified arbitrary interfaces.
### Features
* Configurable virtual LAN and WAN
* Virtually hosted ICE servers
### Virtual network components
#### Top View
```
......................................
: Virtual Network (vnet) :
: :
+-------+ * 1 +----+ +--------+ :
| :App |------------>|:Net|--o<-----|:Router | :
+-------+ +----+ | | :
+-----------+ * 1 +----+ | | :
|:STUNServer|-------->|:Net|--o<-----| | :
+-----------+ +----+ | | :
+-----------+ * 1 +----+ | | :
|:TURNServer|-------->|:Net|--o<-----| | :
+-----------+ +----+ [1] | | :
: 1 | | 1 <<has>> :
: +---<>| |<>----+ [2] :
: | +--------+ | :
To form | *| v 0..1 :
a subnet tree | o [3] +-----+ :
: | ^ |:NAT | :
: | | +-----+ :
: +-------+ :
......................................
Note:
o: NIC (Network Interface Controller)
[1]: Net implements NIC interface.
[2]: Root router has no NAT. All child routers have a NAT always.
[3]: Router implements NIC interface for accesses from the
parent router.
```
#### Net
Net provides 3 interfaces:
* Configuration API (direct)
* Network API via Net (equivalent to net.Xxx())
* Router access via NIC interface
```
(Pion module/app, ICE servers, etc.)
+-----------+
| :App |
+-----------+
* |
| <<uses>>
1 v
+---------+ 1 * +-----------+ 1 * +-----------+ 1 * +------+
..| :Router |----+------>o--| :Net |<>------|:Interface |<>------|:Addr |
+---------+ | NIC +-----------+ +-----------+ +------+
| <<interface>> (transport.Interface) (net.Addr)
|
| * +-----------+ 1 * +-----------+ 1 * +------+
+------>o--| :Router |<>------|:Interface |<>------|:Addr |
NIC +-----------+ +-----------+ +------+
<<interface>> (transport.Interface) (net.Addr)
```
> The instance of `Net` will be the one passed around the project.
> Net class has public methods for configuration and for application use.
## Implementation
### Design Policy
* Each pion package should have config object which has `Net` (of type `transport.Net`) property.
- Just like how we distribute `LoggerFactory` throughout the pion project.
* DNS => a simple dictionary (global)?
* Each Net has routing capability (a goroutine)
* Use interface provided net package as much as possible
* Routers are connected in a tree structure (no loop is allowed)
- To simplify routing
- Easy to control / monitor (stats, etc)
* Root router has no NAT (== Internet / WAN)
* Non-root router has a NAT always
* When a Net is instantiated, it will automatically add `lo0` and `eth0` interface, and `lo0` will have one IP address, 127.0.0.1. (this is not used in pion/ice, however)
* When a Net is added to a router, the router automatically assign an IP address for `eth0` interface.
- For simplicity
* User data won't fragment, but optionally drop chunk larger than MTU
* IPv6 is not supported
### Basic steps for setting up virtual network
1. Create a root router (WAN)
1. Create child routers and add to its parent (forms a tree, don't create a loop!)
1. Add instances of Net to each routers
1. Call Stop(), or Stop(), on the top router, which propagates all other routers
#### Example: WAN with one endpoint (vnet)
```go
import (
"net"
"github.com/pion/transport"
"github.com/pion/transport/vnet"
"github.com/pion/logging"
)
// Create WAN (a root router).
wan, err := vnet.NewRouter(&RouterConfig{
CIDR: "0.0.0.0/0",
LoggerFactory: logging.NewDefaultLoggerFactory(),
})
// Create a network.
// You can specify a static IP for the instance of Net to use. If not specified,
// router will assign an IP address that is contained in the router's CIDR.
nw := vnet.NewNet(&vnet.NetConfig{
StaticIP: "27.1.2.3",
})
// Add the network to the router.
// The router will assign an IP address to `nw`.
if err = wan.AddNet(nw); err != nil {
// handle error
}
// Start router.
// This will start internal goroutine to route packets.
// If you set child routers (using AddRouter), the call on the root router
// will start the rest of routers for you.
if err = wan.Start(); err != nil {
// handle error
}
//
// Your application runs here using `nw`.
//
// Stop the router.
// This will stop all internal Go routines in the router tree.
// (No need to call Stop() on child routers)
if err = wan.Stop(); err != nil {
// handle error
}
```
#### Example of how to pass around the instance of vnet.Net
The instance of vnet.Net wraps a subset of net package to enable operations
on the virtual network. Your project must be able to pass the instance to
all your routines that do network operation with net package. A typical way
is to use a config param to create your instances with the virtual network
instance (`nw` in the above example) like this:
```go
type AgentConfig struct {
:
Net: transport.Net,
}
type Agent struct {
:
net: transport.Net,
}
func NetAgent(config *AgentConfig) *Agent {
if config.Net == nil {
config.Net = vnet.NewNet()
}
return &Agent {
:
net: config.Net,
}
}
```
```go
// a.net is the instance of vnet.Net class
func (a *Agent) listenUDP(...) error {
conn, err := a.net.ListenPacket(udpString, ...)
if err != nil {
return nil, err
}
:
}
```
### Compatibility and Support Status
|`net`<br>(built-in) |`vnet` |Note |
|:--- |:--- |:--- |
| net.Interfaces() | a.net.Interfaces() | |
| net.InterfaceByName() | a.net.InterfaceByName() | |
| net.ResolveUDPAddr() | a.net.ResolveUDPAddr() | |
| net.ListenPacket() | a.net.ListenPacket() | |
| net.ListenUDP() | a.net.ListenUDP() | ListenPacket() is recommended |
| net.Listen() | a.net.Listen() | TODO) |
| net.ListenTCP() | (not supported) | Listen() would be recommended |
| net.Dial() | a.net.Dial() | |
| net.DialUDP() | a.net.DialUDP() | |
| net.DialTCP() | (not supported) | |
| net.Interface | transport.Interface | |
| net.PacketConn | (use it as-is) | |
| net.UDPConn | transport.UDPConn | |
| net.TCPConn | transport.TCPConn | TODO: Use net.Conn in your code |
| net.Dialer | transport.Dialer | Use a.net.CreateDialer() to create it.<br>The use of vnet.Dialer is currently experimental. |
> `a.net` is an instance of Net class, and types are defined under the package name `vnet`
> Most of other `interface` types in net package can be used as is.
> Please post a github issue when other types/methods need to be added to vnet/vnet.Net.
## TODO / Next Step
* Implement TCP (TCPConn, Listen)
* Support of IPv6
* Write a bunch of examples for building virtual networks.
* Add network impairment features (on Router)
- Introduce latency / jitter
- Packet filtering handler (allow selectively drop packets, etc.)
* Add statistics data retrieval
- Total number of packets forward by each router
- Total number of packet loss
- Total number of connection failure (TCP)
## References
* [Comparing Simulated Packet Loss and RealWorld Network Congestion](https://www.riverbed.com/document/fpo/WhitePaper-Riverbed-SimulatedPacketLoss.pdf)
* [wireguard-go using GVisor's netstack](https://github.com/WireGuard/wireguard-go/tree/master/tun/netstack)

View File

@@ -0,0 +1,286 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package vnet
import (
"fmt"
"net"
"strconv"
"strings"
"sync/atomic"
"time"
)
type tcpFlag uint8
const (
tcpFIN tcpFlag = 0x01
tcpSYN tcpFlag = 0x02
tcpRST tcpFlag = 0x04
tcpPSH tcpFlag = 0x08
tcpACK tcpFlag = 0x10
)
func (f tcpFlag) String() string {
var sa []string
if f&tcpFIN != 0 {
sa = append(sa, "FIN")
}
if f&tcpSYN != 0 {
sa = append(sa, "SYN")
}
if f&tcpRST != 0 {
sa = append(sa, "RST")
}
if f&tcpPSH != 0 {
sa = append(sa, "PSH")
}
if f&tcpACK != 0 {
sa = append(sa, "ACK")
}
return strings.Join(sa, "-")
}
// Generate a base36-encoded unique tag
// See: https://play.golang.org/p/0ZaAID1q-HN
var assignChunkTag = func() func() string { //nolint:gochecknoglobals
var tagCtr uint64
return func() string {
n := atomic.AddUint64(&tagCtr, 1)
return strconv.FormatUint(n, 36)
}
}()
// Chunk represents a packet passed around in the vnet
type Chunk interface {
setTimestamp() time.Time // used by router
getTimestamp() time.Time // used by router
getSourceIP() net.IP // used by router
getDestinationIP() net.IP // used by router
setSourceAddr(address string) error // used by nat
setDestinationAddr(address string) error // used by nat
SourceAddr() net.Addr
DestinationAddr() net.Addr
UserData() []byte
Tag() string
Clone() Chunk
Network() string // returns "udp" or "tcp"
String() string
}
type chunkIP struct {
timestamp time.Time
sourceIP net.IP
destinationIP net.IP
tag string
}
func (c *chunkIP) setTimestamp() time.Time {
c.timestamp = time.Now()
return c.timestamp
}
func (c *chunkIP) getTimestamp() time.Time {
return c.timestamp
}
func (c *chunkIP) getDestinationIP() net.IP {
return c.destinationIP
}
func (c *chunkIP) getSourceIP() net.IP {
return c.sourceIP
}
func (c *chunkIP) Tag() string {
return c.tag
}
type chunkUDP struct {
chunkIP
sourcePort int
destinationPort int
userData []byte
}
func newChunkUDP(srcAddr, dstAddr *net.UDPAddr) *chunkUDP {
return &chunkUDP{
chunkIP: chunkIP{
sourceIP: srcAddr.IP,
destinationIP: dstAddr.IP,
tag: assignChunkTag(),
},
sourcePort: srcAddr.Port,
destinationPort: dstAddr.Port,
}
}
func (c *chunkUDP) SourceAddr() net.Addr {
return &net.UDPAddr{
IP: c.sourceIP,
Port: c.sourcePort,
}
}
func (c *chunkUDP) DestinationAddr() net.Addr {
return &net.UDPAddr{
IP: c.destinationIP,
Port: c.destinationPort,
}
}
func (c *chunkUDP) UserData() []byte {
return c.userData
}
func (c *chunkUDP) Clone() Chunk {
var userData []byte
if c.userData != nil {
userData = make([]byte, len(c.userData))
copy(userData, c.userData)
}
return &chunkUDP{
chunkIP: chunkIP{
timestamp: c.timestamp,
sourceIP: c.sourceIP,
destinationIP: c.destinationIP,
tag: c.tag,
},
sourcePort: c.sourcePort,
destinationPort: c.destinationPort,
userData: userData,
}
}
func (c *chunkUDP) Network() string {
return udp
}
func (c *chunkUDP) String() string {
src := c.SourceAddr()
dst := c.DestinationAddr()
return fmt.Sprintf("%s chunk %s %s => %s",
src.Network(),
c.tag,
src.String(),
dst.String(),
)
}
func (c *chunkUDP) setSourceAddr(address string) error {
addr, err := net.ResolveUDPAddr(udp, address)
if err != nil {
return err
}
c.sourceIP = addr.IP
c.sourcePort = addr.Port
return nil
}
func (c *chunkUDP) setDestinationAddr(address string) error {
addr, err := net.ResolveUDPAddr(udp, address)
if err != nil {
return err
}
c.destinationIP = addr.IP
c.destinationPort = addr.Port
return nil
}
type chunkTCP struct {
chunkIP
sourcePort int
destinationPort int
flags tcpFlag // control bits
userData []byte // only with PSH flag
// seq uint32 // always starts with 0
// ack uint32 // always starts with 0
}
func newChunkTCP(srcAddr, dstAddr *net.TCPAddr, flags tcpFlag) *chunkTCP {
return &chunkTCP{
chunkIP: chunkIP{
sourceIP: srcAddr.IP,
destinationIP: dstAddr.IP,
tag: assignChunkTag(),
},
sourcePort: srcAddr.Port,
destinationPort: dstAddr.Port,
flags: flags,
}
}
func (c *chunkTCP) SourceAddr() net.Addr {
return &net.TCPAddr{
IP: c.sourceIP,
Port: c.sourcePort,
}
}
func (c *chunkTCP) DestinationAddr() net.Addr {
return &net.TCPAddr{
IP: c.destinationIP,
Port: c.destinationPort,
}
}
func (c *chunkTCP) UserData() []byte {
return c.userData
}
func (c *chunkTCP) Clone() Chunk {
userData := make([]byte, len(c.userData))
copy(userData, c.userData)
return &chunkTCP{
chunkIP: chunkIP{
timestamp: c.timestamp,
sourceIP: c.sourceIP,
destinationIP: c.destinationIP,
},
sourcePort: c.sourcePort,
destinationPort: c.destinationPort,
userData: userData,
}
}
func (c *chunkTCP) Network() string {
return "tcp"
}
func (c *chunkTCP) String() string {
src := c.SourceAddr()
dst := c.DestinationAddr()
return fmt.Sprintf("%s %s chunk %s %s => %s",
src.Network(),
c.flags.String(),
c.tag,
src.String(),
dst.String(),
)
}
func (c *chunkTCP) setSourceAddr(address string) error {
addr, err := net.ResolveTCPAddr("tcp", address)
if err != nil {
return err
}
c.sourceIP = addr.IP
c.sourcePort = addr.Port
return nil
}
func (c *chunkTCP) setDestinationAddr(address string) error {
addr, err := net.ResolveTCPAddr("tcp", address)
if err != nil {
return err
}
c.destinationIP = addr.IP
c.destinationPort = addr.Port
return nil
}

View File

@@ -0,0 +1,68 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package vnet
import (
"sync"
)
type chunkQueue struct {
chunks []Chunk
maxSize int // 0 or negative value: unlimited
maxBytes int // 0 or negative value: unlimited
currentBytes int
mutex sync.RWMutex
}
func newChunkQueue(maxSize int, maxBytes int) *chunkQueue {
return &chunkQueue{
chunks: []Chunk{},
maxSize: maxSize,
maxBytes: maxBytes,
currentBytes: 0,
mutex: sync.RWMutex{},
}
}
func (q *chunkQueue) push(c Chunk) bool {
q.mutex.Lock()
defer q.mutex.Unlock()
if q.maxSize > 0 && len(q.chunks) >= q.maxSize {
return false // dropped
}
if q.maxBytes > 0 && q.currentBytes+len(c.UserData()) >= q.maxBytes {
return false
}
q.currentBytes += len(c.UserData())
q.chunks = append(q.chunks, c)
return true
}
func (q *chunkQueue) pop() (Chunk, bool) {
q.mutex.Lock()
defer q.mutex.Unlock()
if len(q.chunks) == 0 {
return nil, false
}
c := q.chunks[0]
q.chunks = q.chunks[1:]
q.currentBytes -= len(c.UserData())
return c, true
}
func (q *chunkQueue) peek() Chunk {
q.mutex.RLock()
defer q.mutex.RUnlock()
if len(q.chunks) == 0 {
return nil
}
return q.chunks[0]
}

298
server/vendor/github.com/pion/transport/v2/vnet/conn.go generated vendored Normal file
View File

@@ -0,0 +1,298 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package vnet
import (
"errors"
"fmt"
"io"
"math"
"net"
"sync"
"time"
"github.com/pion/transport/v2"
)
const (
maxReadQueueSize = 1024
)
var (
errObsCannotBeNil = errors.New("obs cannot be nil")
errUseClosedNetworkConn = errors.New("use of closed network connection")
errAddrNotUDPAddr = errors.New("addr is not a net.UDPAddr")
errLocAddr = errors.New("something went wrong with locAddr")
errAlreadyClosed = errors.New("already closed")
errNoRemAddr = errors.New("no remAddr defined")
)
// vNet implements this
type connObserver interface {
write(c Chunk) error
onClosed(addr net.Addr)
determineSourceIP(locIP, dstIP net.IP) net.IP
}
// UDPConn is the implementation of the Conn and PacketConn interfaces for UDP network connections.
// compatible with net.PacketConn and net.Conn
type UDPConn struct {
locAddr *net.UDPAddr // read-only
remAddr *net.UDPAddr // read-only
obs connObserver // read-only
readCh chan Chunk // thread-safe
closed bool // requires mutex
mu sync.Mutex // to mutex closed flag
readTimer *time.Timer // thread-safe
}
var _ transport.UDPConn = &UDPConn{}
func newUDPConn(locAddr, remAddr *net.UDPAddr, obs connObserver) (*UDPConn, error) {
if obs == nil {
return nil, errObsCannotBeNil
}
return &UDPConn{
locAddr: locAddr,
remAddr: remAddr,
obs: obs,
readCh: make(chan Chunk, maxReadQueueSize),
readTimer: time.NewTimer(time.Duration(math.MaxInt64)),
}, nil
}
// Close closes the connection.
// Any blocked ReadFrom or WriteTo operations will be unblocked and return errors.
func (c *UDPConn) Close() error {
c.mu.Lock()
defer c.mu.Unlock()
if c.closed {
return errAlreadyClosed
}
c.closed = true
close(c.readCh)
c.obs.onClosed(c.locAddr)
return nil
}
// LocalAddr returns the local network address.
func (c *UDPConn) LocalAddr() net.Addr {
return c.locAddr
}
// RemoteAddr returns the remote network address.
func (c *UDPConn) RemoteAddr() net.Addr {
return c.remAddr
}
// SetDeadline sets the read and write deadlines associated
// with the connection. It is equivalent to calling both
// SetReadDeadline and SetWriteDeadline.
//
// A deadline is an absolute time after which I/O operations
// fail with a timeout (see type Error) instead of
// blocking. The deadline applies to all future and pending
// I/O, not just the immediately following call to ReadFrom or
// WriteTo. After a deadline has been exceeded, the connection
// can be refreshed by setting a deadline in the future.
//
// An idle timeout can be implemented by repeatedly extending
// the deadline after successful ReadFrom or WriteTo calls.
//
// A zero value for t means I/O operations will not time out.
func (c *UDPConn) SetDeadline(t time.Time) error {
return c.SetReadDeadline(t)
}
// SetReadDeadline sets the deadline for future ReadFrom calls
// and any currently-blocked ReadFrom call.
// A zero value for t means ReadFrom will not time out.
func (c *UDPConn) SetReadDeadline(t time.Time) error {
var d time.Duration
var noDeadline time.Time
if t == noDeadline {
d = time.Duration(math.MaxInt64)
} else {
d = time.Until(t)
}
c.readTimer.Reset(d)
return nil
}
// SetWriteDeadline sets the deadline for future WriteTo calls
// and any currently-blocked WriteTo call.
// Even if write times out, it may return n > 0, indicating that
// some of the data was successfully written.
// A zero value for t means WriteTo will not time out.
func (c *UDPConn) SetWriteDeadline(time.Time) error {
// Write never blocks.
return nil
}
// Read reads data from the connection.
// Read can be made to time out and return an Error with Timeout() == true
// after a fixed time limit; see SetDeadline and SetReadDeadline.
func (c *UDPConn) Read(b []byte) (int, error) {
n, _, err := c.ReadFrom(b)
return n, err
}
// ReadFrom reads a packet from the connection,
// copying the payload into p. It returns the number of
// bytes copied into p and the return address that
// was on the packet.
// It returns the number of bytes read (0 <= n <= len(p))
// and any error encountered. Callers should always process
// the n > 0 bytes returned before considering the error err.
// ReadFrom can be made to time out and return
// an Error with Timeout() == true after a fixed time limit;
// see SetDeadline and SetReadDeadline.
func (c *UDPConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
loop:
for {
select {
case chunk, ok := <-c.readCh:
if !ok {
break loop
}
var err error
n := copy(p, chunk.UserData())
addr := chunk.SourceAddr()
if n < len(chunk.UserData()) {
err = io.ErrShortBuffer
}
if c.remAddr != nil {
if addr.String() != c.remAddr.String() {
break // discard (shouldn't happen)
}
}
return n, addr, err
case <-c.readTimer.C:
return 0, nil, &net.OpError{
Op: "read",
Net: c.locAddr.Network(),
Addr: c.locAddr,
Err: newTimeoutError("i/o timeout"),
}
}
}
return 0, nil, &net.OpError{
Op: "read",
Net: c.locAddr.Network(),
Addr: c.locAddr,
Err: errUseClosedNetworkConn,
}
}
// ReadFromUDP acts like ReadFrom but returns a UDPAddr.
func (c *UDPConn) ReadFromUDP(b []byte) (int, *net.UDPAddr, error) {
n, addr, err := c.ReadFrom(b)
udpAddr, ok := addr.(*net.UDPAddr)
if !ok {
return -1, nil, fmt.Errorf("%w: %s", transport.ErrNotUDPAddress, addr)
}
return n, udpAddr, err
}
// ReadMsgUDP reads a message from c, copying the payload into b and
// the associated out-of-band data into oob. It returns the number of
// bytes copied into b, the number of bytes copied into oob, the flags
// that were set on the message and the source address of the message.
//
// The packages golang.org/x/net/ipv4 and golang.org/x/net/ipv6 can be
// used to manipulate IP-level socket options in oob.
func (c *UDPConn) ReadMsgUDP([]byte, []byte) (n, oobn, flags int, addr *net.UDPAddr, err error) {
return -1, -1, -1, nil, transport.ErrNotSupported
}
// Write writes data to the connection.
// Write can be made to time out and return an Error with Timeout() == true
// after a fixed time limit; see SetDeadline and SetWriteDeadline.
func (c *UDPConn) Write(b []byte) (int, error) {
if c.remAddr == nil {
return 0, errNoRemAddr
}
return c.WriteTo(b, c.remAddr)
}
// WriteTo writes a packet with payload p to addr.
// WriteTo can be made to time out and return
// an Error with Timeout() == true after a fixed time limit;
// see SetDeadline and SetWriteDeadline.
// On packet-oriented connections, write timeouts are rare.
func (c *UDPConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
dstAddr, ok := addr.(*net.UDPAddr)
if !ok {
return 0, errAddrNotUDPAddr
}
srcIP := c.obs.determineSourceIP(c.locAddr.IP, dstAddr.IP)
if srcIP == nil {
return 0, errLocAddr
}
srcAddr := &net.UDPAddr{
IP: srcIP,
Port: c.locAddr.Port,
}
chunk := newChunkUDP(srcAddr, dstAddr)
chunk.userData = make([]byte, len(p))
copy(chunk.userData, p)
if err := c.obs.write(chunk); err != nil {
return 0, err
}
return len(p), nil
}
// WriteToUDP acts like WriteTo but takes a UDPAddr.
func (c *UDPConn) WriteToUDP(b []byte, addr *net.UDPAddr) (int, error) {
return c.WriteTo(b, addr)
}
// WriteMsgUDP writes a message to addr via c if c isn't connected, or
// to c's remote address if c is connected (in which case addr must be
// nil). The payload is copied from b and the associated out-of-band
// data is copied from oob. It returns the number of payload and
// out-of-band bytes written.
//
// The packages golang.org/x/net/ipv4 and golang.org/x/net/ipv6 can be
// used to manipulate IP-level socket options in oob.
func (c *UDPConn) WriteMsgUDP([]byte, []byte, *net.UDPAddr) (n, oobn int, err error) {
return -1, -1, transport.ErrNotSupported
}
// SetReadBuffer sets the size of the operating system's
// receive buffer associated with the connection.
func (c *UDPConn) SetReadBuffer(int) error {
return transport.ErrNotSupported
}
// SetWriteBuffer sets the size of the operating system's
// transmit buffer associated with the connection.
func (c *UDPConn) SetWriteBuffer(int) error {
return transport.ErrNotSupported
}
func (c *UDPConn) onInboundChunk(chunk Chunk) {
c.mu.Lock()
defer c.mu.Unlock()
if c.closed {
return
}
select {
case c.readCh <- chunk:
default:
}
}

View File

@@ -0,0 +1,139 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package vnet
import (
"errors"
"net"
"sync"
)
var (
errAddressAlreadyInUse = errors.New("address already in use")
errNoSuchUDPConn = errors.New("no such UDPConn")
errCannotRemoveUnspecifiedIP = errors.New("cannot remove unspecified IP by the specified IP")
)
type udpConnMap struct {
portMap map[int][]*UDPConn
mutex sync.RWMutex
}
func newUDPConnMap() *udpConnMap {
return &udpConnMap{
portMap: map[int][]*UDPConn{},
}
}
func (m *udpConnMap) insert(conn *UDPConn) error {
m.mutex.Lock()
defer m.mutex.Unlock()
udpAddr := conn.LocalAddr().(*net.UDPAddr) //nolint:forcetypeassert
// check if the port has a listener
conns, ok := m.portMap[udpAddr.Port]
if ok {
if udpAddr.IP.IsUnspecified() {
return errAddressAlreadyInUse
}
for _, conn := range conns {
laddr := conn.LocalAddr().(*net.UDPAddr) //nolint:forcetypeassert
if laddr.IP.IsUnspecified() || laddr.IP.Equal(udpAddr.IP) {
return errAddressAlreadyInUse
}
}
conns = append(conns, conn)
} else {
conns = []*UDPConn{conn}
}
m.portMap[udpAddr.Port] = conns
return nil
}
func (m *udpConnMap) find(addr net.Addr) (*UDPConn, bool) {
m.mutex.Lock() // could be RLock, but we have delete() op
defer m.mutex.Unlock()
udpAddr := addr.(*net.UDPAddr) //nolint:forcetypeassert
if conns, ok := m.portMap[udpAddr.Port]; ok {
if udpAddr.IP.IsUnspecified() {
// pick the first one appears in the iteration
if len(conns) == 0 {
// This can't happen!
delete(m.portMap, udpAddr.Port)
return nil, false
}
return conns[0], true
}
for _, conn := range conns {
laddr := conn.LocalAddr().(*net.UDPAddr) //nolint:forcetypeassert
if laddr.IP.IsUnspecified() || laddr.IP.Equal(udpAddr.IP) {
return conn, ok
}
}
}
return nil, false
}
func (m *udpConnMap) delete(addr net.Addr) error {
m.mutex.Lock()
defer m.mutex.Unlock()
udpAddr := addr.(*net.UDPAddr) //nolint:forcetypeassert
conns, ok := m.portMap[udpAddr.Port]
if !ok {
return errNoSuchUDPConn
}
if udpAddr.IP.IsUnspecified() {
// remove all from this port
delete(m.portMap, udpAddr.Port)
return nil
}
newConns := []*UDPConn{}
for _, conn := range conns {
laddr := conn.LocalAddr().(*net.UDPAddr) //nolint:forcetypeassert
if laddr.IP.IsUnspecified() {
// This can't happen!
return errCannotRemoveUnspecifiedIP
}
if laddr.IP.Equal(udpAddr.IP) {
continue
}
newConns = append(newConns, conn)
}
if len(newConns) == 0 {
delete(m.portMap, udpAddr.Port)
} else {
m.portMap[udpAddr.Port] = newConns
}
return nil
}
// size returns the number of UDPConns (UDP listeners)
func (m *udpConnMap) size() int {
m.mutex.RLock()
defer m.mutex.RUnlock()
n := 0
for _, conns := range m.portMap {
n += len(conns)
}
return n
}

View File

@@ -0,0 +1,78 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package vnet
import (
"context"
"time"
)
// DelayFilter delays outgoing packets by the given delay. Run must be called
// before any packets will be forwarded.
type DelayFilter struct {
NIC
delay time.Duration
push chan struct{}
queue *chunkQueue
}
type timedChunk struct {
Chunk
deadline time.Time
}
// NewDelayFilter creates a new DelayFilter with the given nic and delay.
func NewDelayFilter(nic NIC, delay time.Duration) (*DelayFilter, error) {
return &DelayFilter{
NIC: nic,
delay: delay,
push: make(chan struct{}),
queue: newChunkQueue(0, 0),
}, nil
}
func (f *DelayFilter) onInboundChunk(c Chunk) {
f.queue.push(timedChunk{
Chunk: c,
deadline: time.Now().Add(f.delay),
})
f.push <- struct{}{}
}
// Run starts forwarding of packets. Packets will be forwarded if they spent
// >delay time in the internal queue. Must be called before any packet will be
// forwarded.
func (f *DelayFilter) Run(ctx context.Context) {
timer := time.NewTimer(0)
for {
select {
case <-ctx.Done():
return
case <-f.push:
next := f.queue.peek().(timedChunk) //nolint:forcetypeassert
if !timer.Stop() {
<-timer.C
}
timer.Reset(time.Until(next.deadline))
case now := <-timer.C:
next := f.queue.peek()
if next == nil {
timer.Reset(time.Minute)
continue
}
if n, ok := next.(timedChunk); ok && n.deadline.Before(now) {
f.queue.pop() // ignore result because we already got and casted it from peek
f.NIC.onInboundChunk(n.Chunk)
}
next = f.queue.peek()
if next == nil {
timer.Reset(time.Minute)
continue
}
if n, ok := next.(timedChunk); ok {
timer.Reset(time.Until(n.deadline))
}
}
}
}

View File

@@ -0,0 +1,22 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package vnet
type timeoutError struct {
msg string
}
func newTimeoutError(msg string) error {
return &timeoutError{
msg: msg,
}
}
func (e *timeoutError) Error() string {
return e.msg
}
func (e *timeoutError) Timeout() bool {
return true
}

View File

@@ -0,0 +1,36 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package vnet
import (
"math/rand"
"time"
)
// LossFilter is a wrapper around NICs, that drops some of the packets passed to
// onInboundChunk
type LossFilter struct {
NIC
chance int
}
// NewLossFilter creates a new LossFilter that drops every packet with a
// probability of chance/100. Every packet that is not dropped is passed on to
// the given NIC.
func NewLossFilter(nic NIC, chance int) (*LossFilter, error) {
f := &LossFilter{
NIC: nic,
chance: chance,
}
rand.Seed(time.Now().UTC().UnixNano())
return f, nil
}
func (f *LossFilter) onInboundChunk(c Chunk) {
if rand.Intn(100) < f.chance { //nolint:gosec
return
}
f.NIC.onInboundChunk(c)
}

342
server/vendor/github.com/pion/transport/v2/vnet/nat.go generated vendored Normal file
View File

@@ -0,0 +1,342 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package vnet
import (
"errors"
"fmt"
"net"
"sync"
"time"
"github.com/pion/logging"
)
var (
errNATRequriesMapping = errors.New("1:1 NAT requires more than one mapping")
errMismatchLengthIP = errors.New("length mismtach between mappedIPs and localIPs")
errNonUDPTranslationNotSupported = errors.New("non-udp translation is not supported yet")
errNoAssociatedLocalAddress = errors.New("no associated local address")
errNoNATBindingFound = errors.New("no NAT binding found")
errHasNoPermission = errors.New("has no permission")
)
// EndpointDependencyType defines a type of behavioral dependendency on the
// remote endpoint's IP address or port number. This is used for the two
// kinds of behaviors:
// - Port mapping behavior
// - Filtering behavior
//
// See: https://tools.ietf.org/html/rfc4787
type EndpointDependencyType uint8
const (
// EndpointIndependent means the behavior is independent of the endpoint's address or port
EndpointIndependent EndpointDependencyType = iota
// EndpointAddrDependent means the behavior is dependent on the endpoint's address
EndpointAddrDependent
// EndpointAddrPortDependent means the behavior is dependent on the endpoint's address and port
EndpointAddrPortDependent
)
// NATMode defines basic behavior of the NAT
type NATMode uint8
const (
// NATModeNormal means the NAT behaves as a standard NAPT (RFC 2663).
NATModeNormal NATMode = iota
// NATModeNAT1To1 exhibits 1:1 DNAT where the external IP address is statically mapped to
// a specific local IP address with port number is preserved always between them.
// When this mode is selected, MappingBehavior, FilteringBehavior, PortPreservation and
// MappingLifeTime of NATType are ignored.
NATModeNAT1To1
)
const (
defaultNATMappingLifeTime = 30 * time.Second
)
// NATType has a set of parameters that define the behavior of NAT.
type NATType struct {
Mode NATMode
MappingBehavior EndpointDependencyType
FilteringBehavior EndpointDependencyType
Hairpinning bool // Not implemented yet
PortPreservation bool // Not implemented yet
MappingLifeTime time.Duration
}
type natConfig struct {
name string
natType NATType
mappedIPs []net.IP // mapped IPv4
localIPs []net.IP // local IPv4, required only when the mode is NATModeNAT1To1
loggerFactory logging.LoggerFactory
}
type mapping struct {
proto string // "udp" or "tcp"
local string // "<local-ip>:<local-port>"
mapped string // "<mapped-ip>:<mapped-port>"
bound string // key: "[<remote-ip>[:<remote-port>]]"
filters map[string]struct{} // key: "[<remote-ip>[:<remote-port>]]"
expires time.Time // time to expire
}
type networkAddressTranslator struct {
name string
natType NATType
mappedIPs []net.IP // mapped IPv4
localIPs []net.IP // local IPv4, required only when the mode is NATModeNAT1To1
outboundMap map[string]*mapping // key: "<proto>:<local-ip>:<local-port>[:remote-ip[:remote-port]]
inboundMap map[string]*mapping // key: "<proto>:<mapped-ip>:<mapped-port>"
udpPortCounter int
mutex sync.RWMutex
log logging.LeveledLogger
}
func newNAT(config *natConfig) (*networkAddressTranslator, error) {
natType := config.natType
if natType.Mode == NATModeNAT1To1 {
// 1:1 NAT behavior
natType.MappingBehavior = EndpointIndependent
natType.FilteringBehavior = EndpointIndependent
natType.PortPreservation = true
natType.MappingLifeTime = 0
if len(config.mappedIPs) == 0 {
return nil, errNATRequriesMapping
}
if len(config.mappedIPs) != len(config.localIPs) {
return nil, errMismatchLengthIP
}
} else {
// Normal (NAPT) behavior
natType.Mode = NATModeNormal
if natType.MappingLifeTime == 0 {
natType.MappingLifeTime = defaultNATMappingLifeTime
}
}
return &networkAddressTranslator{
name: config.name,
natType: natType,
mappedIPs: config.mappedIPs,
localIPs: config.localIPs,
outboundMap: map[string]*mapping{},
inboundMap: map[string]*mapping{},
log: config.loggerFactory.NewLogger("vnet"),
}, nil
}
func (n *networkAddressTranslator) getPairedMappedIP(locIP net.IP) net.IP {
for i, ip := range n.localIPs {
if ip.Equal(locIP) {
return n.mappedIPs[i]
}
}
return nil
}
func (n *networkAddressTranslator) getPairedLocalIP(mappedIP net.IP) net.IP {
for i, ip := range n.mappedIPs {
if ip.Equal(mappedIP) {
return n.localIPs[i]
}
}
return nil
}
func (n *networkAddressTranslator) translateOutbound(from Chunk) (Chunk, error) {
n.mutex.Lock()
defer n.mutex.Unlock()
to := from.Clone()
if from.Network() == udp {
if n.natType.Mode == NATModeNAT1To1 {
// 1:1 NAT behavior
srcAddr := from.SourceAddr().(*net.UDPAddr) //nolint:forcetypeassert
srcIP := n.getPairedMappedIP(srcAddr.IP)
if srcIP == nil {
n.log.Debugf("[%s] drop outbound chunk %s with not route", n.name, from.String())
return nil, nil // nolint:nilnil
}
srcPort := srcAddr.Port
if err := to.setSourceAddr(fmt.Sprintf("%s:%d", srcIP.String(), srcPort)); err != nil {
return nil, err
}
} else {
// Normal (NAPT) behavior
var bound, filterKey string
switch n.natType.MappingBehavior {
case EndpointIndependent:
bound = ""
case EndpointAddrDependent:
bound = from.getDestinationIP().String()
case EndpointAddrPortDependent:
bound = from.DestinationAddr().String()
}
switch n.natType.FilteringBehavior {
case EndpointIndependent:
filterKey = ""
case EndpointAddrDependent:
filterKey = from.getDestinationIP().String()
case EndpointAddrPortDependent:
filterKey = from.DestinationAddr().String()
}
oKey := fmt.Sprintf("udp:%s:%s", from.SourceAddr().String(), bound)
m := n.findOutboundMapping(oKey)
if m == nil {
// Create a new mapping
mappedPort := 0xC000 + n.udpPortCounter
n.udpPortCounter++
m = &mapping{
proto: from.SourceAddr().Network(),
local: from.SourceAddr().String(),
bound: bound,
mapped: fmt.Sprintf("%s:%d", n.mappedIPs[0].String(), mappedPort),
filters: map[string]struct{}{},
expires: time.Now().Add(n.natType.MappingLifeTime),
}
n.outboundMap[oKey] = m
iKey := fmt.Sprintf("udp:%s", m.mapped)
n.log.Debugf("[%s] created a new NAT binding oKey=%s iKey=%s",
n.name,
oKey,
iKey)
m.filters[filterKey] = struct{}{}
n.log.Debugf("[%s] permit access from %s to %s", n.name, filterKey, m.mapped)
n.inboundMap[iKey] = m
} else if _, ok := m.filters[filterKey]; !ok {
n.log.Debugf("[%s] permit access from %s to %s", n.name, filterKey, m.mapped)
m.filters[filterKey] = struct{}{}
}
if err := to.setSourceAddr(m.mapped); err != nil {
return nil, err
}
}
n.log.Debugf("[%s] translate outbound chunk from %s to %s", n.name, from.String(), to.String())
return to, nil
}
return nil, errNonUDPTranslationNotSupported
}
func (n *networkAddressTranslator) translateInbound(from Chunk) (Chunk, error) {
n.mutex.Lock()
defer n.mutex.Unlock()
to := from.Clone()
if from.Network() == udp {
if n.natType.Mode == NATModeNAT1To1 {
// 1:1 NAT behavior
dstAddr := from.DestinationAddr().(*net.UDPAddr) //nolint:forcetypeassert
dstIP := n.getPairedLocalIP(dstAddr.IP)
if dstIP == nil {
return nil, fmt.Errorf("drop %s as %w", from.String(), errNoAssociatedLocalAddress)
}
dstPort := from.DestinationAddr().(*net.UDPAddr).Port //nolint:forcetypeassert
if err := to.setDestinationAddr(fmt.Sprintf("%s:%d", dstIP, dstPort)); err != nil {
return nil, err
}
} else {
// Normal (NAPT) behavior
iKey := fmt.Sprintf("udp:%s", from.DestinationAddr().String())
m := n.findInboundMapping(iKey)
if m == nil {
return nil, fmt.Errorf("drop %s as %w", from.String(), errNoNATBindingFound)
}
var filterKey string
switch n.natType.FilteringBehavior {
case EndpointIndependent:
filterKey = ""
case EndpointAddrDependent:
filterKey = from.getSourceIP().String()
case EndpointAddrPortDependent:
filterKey = from.SourceAddr().String()
}
if _, ok := m.filters[filterKey]; !ok {
return nil, fmt.Errorf("drop %s as the remote %s %w", from.String(), filterKey, errHasNoPermission)
}
// See RFC 4847 Section 4.3. Mapping Refresh
// a) Inbound refresh may be useful for applications with no outgoing
// UDP traffic. However, allowing inbound refresh may allow an
// external attacker or misbehaving application to keep a mapping
// alive indefinitely. This may be a security risk. Also, if the
// process is repeated with different ports, over time, it could
// use up all the ports on the NAT.
if err := to.setDestinationAddr(m.local); err != nil {
return nil, err
}
}
n.log.Debugf("[%s] translate inbound chunk from %s to %s", n.name, from.String(), to.String())
return to, nil
}
return nil, errNonUDPTranslationNotSupported
}
// caller must hold the mutex
func (n *networkAddressTranslator) findOutboundMapping(oKey string) *mapping {
now := time.Now()
m, ok := n.outboundMap[oKey]
if ok {
// check if this mapping is expired
if now.After(m.expires) {
n.removeMapping(m)
m = nil // expired
} else {
m.expires = time.Now().Add(n.natType.MappingLifeTime)
}
}
return m
}
// caller must hold the mutex
func (n *networkAddressTranslator) findInboundMapping(iKey string) *mapping {
now := time.Now()
m, ok := n.inboundMap[iKey]
if !ok {
return nil
}
// check if this mapping is expired
if now.After(m.expires) {
n.removeMapping(m)
return nil
}
return m
}
// caller must hold the mutex
func (n *networkAddressTranslator) removeMapping(m *mapping) {
oKey := fmt.Sprintf("%s:%s:%s", m.proto, m.local, m.bound)
iKey := fmt.Sprintf("%s:%s", m.proto, m.mapped)
delete(n.outboundMap, oKey)
delete(n.inboundMap, iKey)
}

618
server/vendor/github.com/pion/transport/v2/vnet/net.go generated vendored Normal file
View File

@@ -0,0 +1,618 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package vnet
import (
"encoding/binary"
"errors"
"fmt"
"math/rand"
"net"
"strconv"
"strings"
"sync"
"github.com/pion/transport/v2"
)
const (
lo0String = "lo0String"
udp = "udp"
udp4 = "udp4"
)
var (
macAddrCounter uint64 = 0xBEEFED910200 //nolint:gochecknoglobals
errNoInterface = errors.New("no interface is available")
errUnexpectedNetwork = errors.New("unexpected network")
errCantAssignRequestedAddr = errors.New("can't assign requested address")
errUnknownNetwork = errors.New("unknown network")
errNoRouterLinked = errors.New("no router linked")
errInvalidPortNumber = errors.New("invalid port number")
errUnexpectedTypeSwitchFailure = errors.New("unexpected type-switch failure")
errBindFailedFor = errors.New("bind failed for")
errEndPortLessThanStart = errors.New("end port is less than the start")
errPortSpaceExhausted = errors.New("port space exhausted")
)
func newMACAddress() net.HardwareAddr {
b := make([]byte, 8)
binary.BigEndian.PutUint64(b, macAddrCounter)
macAddrCounter++
return b[2:]
}
// Net represents a local network stack equivalent to a set of layers from NIC
// up to the transport (UDP / TCP) layer.
type Net struct {
interfaces []*transport.Interface // read-only
staticIPs []net.IP // read-only
router *Router // read-only
udpConns *udpConnMap // read-only
mutex sync.RWMutex
}
// Compile-time assertion
var _ transport.Net = &Net{}
func (v *Net) _getInterfaces() ([]*transport.Interface, error) {
if len(v.interfaces) == 0 {
return nil, errNoInterface
}
return v.interfaces, nil
}
// Interfaces returns a list of the system's network interfaces.
func (v *Net) Interfaces() ([]*transport.Interface, error) {
v.mutex.RLock()
defer v.mutex.RUnlock()
return v._getInterfaces()
}
// caller must hold the mutex (read)
func (v *Net) _getInterface(ifName string) (*transport.Interface, error) {
ifs, err := v._getInterfaces()
if err != nil {
return nil, err
}
for _, ifc := range ifs {
if ifc.Name == ifName {
return ifc, nil
}
}
return nil, fmt.Errorf("%w: %s", transport.ErrInterfaceNotFound, ifName)
}
func (v *Net) getInterface(ifName string) (*transport.Interface, error) {
v.mutex.RLock()
defer v.mutex.RUnlock()
return v._getInterface(ifName)
}
// InterfaceByIndex returns the interface specified by index.
//
// On Solaris, it returns one of the logical network interfaces
// sharing the logical data link; for more precision use
// InterfaceByName.
func (v *Net) InterfaceByIndex(index int) (*transport.Interface, error) {
for _, ifc := range v.interfaces {
if ifc.Index == index {
return ifc, nil
}
}
return nil, fmt.Errorf("%w: index=%d", transport.ErrInterfaceNotFound, index)
}
// InterfaceByName returns the interface specified by name.
func (v *Net) InterfaceByName(ifName string) (*transport.Interface, error) {
return v.getInterface(ifName)
}
// caller must hold the mutex
func (v *Net) getAllIPAddrs(ipv6 bool) []net.IP {
ips := []net.IP{}
for _, ifc := range v.interfaces {
addrs, err := ifc.Addrs()
if err != nil {
continue
}
for _, addr := range addrs {
var ip net.IP
if ipNet, ok := addr.(*net.IPNet); ok {
ip = ipNet.IP
} else if ipAddr, ok := addr.(*net.IPAddr); ok {
ip = ipAddr.IP
} else {
continue
}
if !ipv6 {
if ip.To4() != nil {
ips = append(ips, ip)
}
}
}
}
return ips
}
func (v *Net) setRouter(r *Router) error {
v.mutex.Lock()
defer v.mutex.Unlock()
v.router = r
return nil
}
func (v *Net) onInboundChunk(c Chunk) {
v.mutex.Lock()
defer v.mutex.Unlock()
if c.Network() == udp {
if conn, ok := v.udpConns.find(c.DestinationAddr()); ok {
conn.onInboundChunk(c)
}
}
}
// caller must hold the mutex
func (v *Net) _dialUDP(network string, locAddr, remAddr *net.UDPAddr) (transport.UDPConn, error) {
// validate network
if network != udp && network != udp4 {
return nil, fmt.Errorf("%w: %s", errUnexpectedNetwork, network)
}
if locAddr == nil {
locAddr = &net.UDPAddr{
IP: net.IPv4zero,
}
} else if locAddr.IP == nil {
locAddr.IP = net.IPv4zero
}
// validate address. do we have that address?
if !v.hasIPAddr(locAddr.IP) {
return nil, &net.OpError{
Op: "listen",
Net: network,
Addr: locAddr,
Err: fmt.Errorf("bind: %w", errCantAssignRequestedAddr),
}
}
if locAddr.Port == 0 {
// choose randomly from the range between 5000 and 5999
port, err := v.assignPort(locAddr.IP, 5000, 5999)
if err != nil {
return nil, &net.OpError{
Op: "listen",
Net: network,
Addr: locAddr,
Err: err,
}
}
locAddr.Port = port
} else if _, ok := v.udpConns.find(locAddr); ok {
return nil, &net.OpError{
Op: "listen",
Net: network,
Addr: locAddr,
Err: fmt.Errorf("bind: %w", errAddressAlreadyInUse),
}
}
conn, err := newUDPConn(locAddr, remAddr, v)
if err != nil {
return nil, err
}
err = v.udpConns.insert(conn)
if err != nil {
return nil, err
}
return conn, nil
}
// ListenPacket announces on the local network address.
func (v *Net) ListenPacket(network string, address string) (net.PacketConn, error) {
v.mutex.Lock()
defer v.mutex.Unlock()
locAddr, err := v.ResolveUDPAddr(network, address)
if err != nil {
return nil, err
}
return v._dialUDP(network, locAddr, nil)
}
// ListenUDP acts like ListenPacket for UDP networks.
func (v *Net) ListenUDP(network string, locAddr *net.UDPAddr) (transport.UDPConn, error) {
v.mutex.Lock()
defer v.mutex.Unlock()
return v._dialUDP(network, locAddr, nil)
}
// DialUDP acts like Dial for UDP networks.
func (v *Net) DialUDP(network string, locAddr, remAddr *net.UDPAddr) (transport.UDPConn, error) {
v.mutex.Lock()
defer v.mutex.Unlock()
return v._dialUDP(network, locAddr, remAddr)
}
// Dial connects to the address on the named network.
func (v *Net) Dial(network string, address string) (net.Conn, error) {
v.mutex.Lock()
defer v.mutex.Unlock()
remAddr, err := v.ResolveUDPAddr(network, address)
if err != nil {
return nil, err
}
// Determine source address
srcIP := v.determineSourceIP(nil, remAddr.IP)
locAddr := &net.UDPAddr{IP: srcIP, Port: 0}
return v._dialUDP(network, locAddr, remAddr)
}
// ResolveIPAddr returns an address of IP end point.
func (v *Net) ResolveIPAddr(_, address string) (*net.IPAddr, error) {
var err error
// Check if host is a domain name
ip := net.ParseIP(address)
if ip == nil {
address = strings.ToLower(address)
if address == "localhost" {
ip = net.IPv4(127, 0, 0, 1)
} else {
// host is a domain name. resolve IP address by the name
if v.router == nil {
return nil, errNoRouterLinked
}
ip, err = v.router.resolver.lookUp(address)
if err != nil {
return nil, err
}
}
}
return &net.IPAddr{
IP: ip,
}, nil
}
// ResolveUDPAddr returns an address of UDP end point.
func (v *Net) ResolveUDPAddr(network, address string) (*net.UDPAddr, error) {
if network != udp && network != udp4 {
return nil, fmt.Errorf("%w %s", errUnknownNetwork, network)
}
host, sPort, err := net.SplitHostPort(address)
if err != nil {
return nil, err
}
ipAddress, err := v.ResolveIPAddr("ip", host)
if err != nil {
return nil, err
}
port, err := strconv.Atoi(sPort)
if err != nil {
return nil, errInvalidPortNumber
}
udpAddr := &net.UDPAddr{
IP: ipAddress.IP,
Zone: ipAddress.Zone,
Port: port,
}
return udpAddr, nil
}
// ResolveTCPAddr returns an address of TCP end point.
func (v *Net) ResolveTCPAddr(network, address string) (*net.TCPAddr, error) {
if network != udp && network != "udp4" {
return nil, fmt.Errorf("%w %s", errUnknownNetwork, network)
}
host, sPort, err := net.SplitHostPort(address)
if err != nil {
return nil, err
}
ipAddr, err := v.ResolveIPAddr("ip", host)
if err != nil {
return nil, err
}
port, err := strconv.Atoi(sPort)
if err != nil {
return nil, errInvalidPortNumber
}
udpAddr := &net.TCPAddr{
IP: ipAddr.IP,
Zone: ipAddr.Zone,
Port: port,
}
return udpAddr, nil
}
func (v *Net) write(c Chunk) error {
if c.Network() == udp {
if udp, ok := c.(*chunkUDP); ok {
if c.getDestinationIP().IsLoopback() {
if conn, ok := v.udpConns.find(udp.DestinationAddr()); ok {
conn.onInboundChunk(udp)
}
return nil
}
} else {
return errUnexpectedTypeSwitchFailure
}
}
if v.router == nil {
return errNoRouterLinked
}
v.router.push(c)
return nil
}
func (v *Net) onClosed(addr net.Addr) {
if addr.Network() == udp {
//nolint:errcheck
v.udpConns.delete(addr) // #nosec
}
}
// This method determines the srcIP based on the dstIP when locIP
// is any IP address ("0.0.0.0" or "::"). If locIP is a non-any addr,
// this method simply returns locIP.
// caller must hold the mutex
func (v *Net) determineSourceIP(locIP, dstIP net.IP) net.IP {
if locIP != nil && !locIP.IsUnspecified() {
return locIP
}
var srcIP net.IP
if dstIP.IsLoopback() {
srcIP = net.ParseIP("127.0.0.1")
} else {
ifc, err2 := v._getInterface("eth0")
if err2 != nil {
return nil
}
addrs, err2 := ifc.Addrs()
if err2 != nil {
return nil
}
if len(addrs) == 0 {
return nil
}
var findIPv4 bool
if locIP != nil {
findIPv4 = (locIP.To4() != nil)
} else {
findIPv4 = (dstIP.To4() != nil)
}
for _, addr := range addrs {
ip := addr.(*net.IPNet).IP //nolint:forcetypeassert
if findIPv4 {
if ip.To4() != nil {
srcIP = ip
break
}
} else {
if ip.To4() == nil {
srcIP = ip
break
}
}
}
}
return srcIP
}
// caller must hold the mutex
func (v *Net) hasIPAddr(ip net.IP) bool { //nolint:gocognit
for _, ifc := range v.interfaces {
if addrs, err := ifc.Addrs(); err == nil {
for _, addr := range addrs {
var locIP net.IP
if ipNet, ok := addr.(*net.IPNet); ok {
locIP = ipNet.IP
} else if ipAddr, ok := addr.(*net.IPAddr); ok {
locIP = ipAddr.IP
} else {
continue
}
switch ip.String() {
case "0.0.0.0":
if locIP.To4() != nil {
return true
}
case "::":
if locIP.To4() == nil {
return true
}
default:
if locIP.Equal(ip) {
return true
}
}
}
}
}
return false
}
// caller must hold the mutex
func (v *Net) allocateLocalAddr(ip net.IP, port int) error {
// gather local IP addresses to bind
var ips []net.IP
if ip.IsUnspecified() {
ips = v.getAllIPAddrs(ip.To4() == nil)
} else if v.hasIPAddr(ip) {
ips = []net.IP{ip}
}
if len(ips) == 0 {
return fmt.Errorf("%w %s", errBindFailedFor, ip.String())
}
// check if all these transport addresses are not in use
for _, ip2 := range ips {
addr := &net.UDPAddr{
IP: ip2,
Port: port,
}
if _, ok := v.udpConns.find(addr); ok {
return &net.OpError{
Op: "bind",
Net: udp,
Addr: addr,
Err: fmt.Errorf("bind: %w", errAddressAlreadyInUse),
}
}
}
return nil
}
// caller must hold the mutex
func (v *Net) assignPort(ip net.IP, start, end int) (int, error) {
// choose randomly from the range between start and end (inclusive)
if end < start {
return -1, errEndPortLessThanStart
}
space := end + 1 - start
offset := rand.Intn(space) //nolint:gosec
for i := 0; i < space; i++ {
port := ((offset + i) % space) + start
err := v.allocateLocalAddr(ip, port)
if err == nil {
return port, nil
}
}
return -1, errPortSpaceExhausted
}
func (v *Net) getStaticIPs() []net.IP {
return v.staticIPs
}
// NetConfig is a bag of configuration parameters passed to NewNet().
type NetConfig struct {
// StaticIPs is an array of static IP addresses to be assigned for this Net.
// If no static IP address is given, the router will automatically assign
// an IP address.
StaticIPs []string
// StaticIP is deprecated. Use StaticIPs.
StaticIP string
}
// NewNet creates an instance of a virtual network.
//
// By design, it always have lo0 and eth0 interfaces.
// The lo0 has the address 127.0.0.1 assigned by default.
// IP address for eth0 will be assigned when this Net is added to a router.
func NewNet(config *NetConfig) (*Net, error) {
lo0 := transport.NewInterface(net.Interface{
Index: 1,
MTU: 16384,
Name: lo0String,
HardwareAddr: nil,
Flags: net.FlagUp | net.FlagLoopback | net.FlagMulticast,
})
lo0.AddAddress(&net.IPNet{
IP: net.ParseIP("127.0.0.1"),
Mask: net.CIDRMask(8, 32),
})
eth0 := transport.NewInterface(net.Interface{
Index: 2,
MTU: 1500,
Name: "eth0",
HardwareAddr: newMACAddress(),
Flags: net.FlagUp | net.FlagMulticast,
})
var staticIPs []net.IP
for _, ipStr := range config.StaticIPs {
if ip := net.ParseIP(ipStr); ip != nil {
staticIPs = append(staticIPs, ip)
}
}
if len(config.StaticIP) > 0 {
if ip := net.ParseIP(config.StaticIP); ip != nil {
staticIPs = append(staticIPs, ip)
}
}
return &Net{
interfaces: []*transport.Interface{lo0, eth0},
staticIPs: staticIPs,
udpConns: newUDPConnMap(),
}, nil
}
// DialTCP acts like Dial for TCP networks.
func (v *Net) DialTCP(string, *net.TCPAddr, *net.TCPAddr) (transport.TCPConn, error) {
return nil, transport.ErrNotSupported
}
// ListenTCP acts like Listen for TCP networks.
func (v *Net) ListenTCP(string, *net.TCPAddr) (transport.TCPListener, error) {
return nil, transport.ErrNotSupported
}
// CreateDialer creates an instance of vnet.Dialer
func (v *Net) CreateDialer(d *net.Dialer) transport.Dialer {
return &dialer{
dialer: d,
net: v,
}
}
type dialer struct {
dialer *net.Dialer
net *Net
}
func (d *dialer) Dial(network, address string) (net.Conn, error) {
return d.net.Dial(network, address)
}

View File

@@ -0,0 +1,92 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package vnet
import (
"errors"
"fmt"
"net"
"sync"
"github.com/pion/logging"
)
var (
errHostnameEmpty = errors.New("host name must not be empty")
errFailedToParseIPAddr = errors.New("failed to parse IP address")
)
type resolverConfig struct {
LoggerFactory logging.LoggerFactory
}
type resolver struct {
parent *resolver // read-only
hosts map[string]net.IP // requires mutex
mutex sync.RWMutex // thread-safe
log logging.LeveledLogger // read-only
}
func newResolver(config *resolverConfig) *resolver {
r := &resolver{
hosts: map[string]net.IP{},
log: config.LoggerFactory.NewLogger("vnet"),
}
if err := r.addHost("localhost", "127.0.0.1"); err != nil {
r.log.Warn("failed to add localhost to resolver")
}
return r
}
func (r *resolver) setParent(parent *resolver) {
r.mutex.Lock()
defer r.mutex.Unlock()
r.parent = parent
}
func (r *resolver) addHost(name string, ipAddr string) error {
r.mutex.Lock()
defer r.mutex.Unlock()
if len(name) == 0 {
return errHostnameEmpty
}
ip := net.ParseIP(ipAddr)
if ip == nil {
return fmt.Errorf("%w \"%s\"", errFailedToParseIPAddr, ipAddr)
}
r.hosts[name] = ip
return nil
}
func (r *resolver) lookUp(hostName string) (net.IP, error) {
ip := func() net.IP {
r.mutex.RLock()
defer r.mutex.RUnlock()
if ip2, ok := r.hosts[hostName]; ok {
return ip2
}
return nil
}()
if ip != nil {
return ip, nil
}
// mutex must be unlocked before calling into parent resolver
if r.parent != nil {
return r.parent.lookUp(hostName)
}
return nil, &net.DNSError{
Err: "host not found",
Name: hostName,
Server: "vnet resolver",
IsTimeout: false,
IsTemporary: false,
}
}

View File

@@ -0,0 +1,621 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package vnet
import (
"errors"
"fmt"
"math/rand"
"net"
"strings"
"sync"
"sync/atomic"
"time"
"github.com/pion/logging"
"github.com/pion/transport/v2"
)
const (
defaultRouterQueueSize = 0 // unlimited
)
var (
errInvalidLocalIPinStaticIPs = errors.New("invalid local IP in StaticIPs")
errLocalIPBeyondStaticIPsSubset = errors.New("mapped in StaticIPs is beyond subnet")
errLocalIPNoStaticsIPsAssociated = errors.New("all StaticIPs must have associated local IPs")
errRouterAlreadyStarted = errors.New("router already started")
errRouterAlreadyStopped = errors.New("router already stopped")
errStaticIPisBeyondSubnet = errors.New("static IP is beyond subnet")
errAddressSpaceExhausted = errors.New("address space exhausted")
errNoIPAddrEth0 = errors.New("no IP address is assigned for eth0")
)
// Generate a unique router name
var assignRouterName = func() func() string { //nolint:gochecknoglobals
var routerIDCtr uint64
return func() string {
n := atomic.AddUint64(&routerIDCtr, 1)
return fmt.Sprintf("router%d", n)
}
}()
// RouterConfig ...
type RouterConfig struct {
// Name of router. If not specified, a unique name will be assigned.
Name string
// CIDR notation, like "192.0.2.0/24"
CIDR string
// StaticIPs is an array of static IP addresses to be assigned for this router.
// If no static IP address is given, the router will automatically assign
// an IP address.
// This will be ignored if this router is the root.
StaticIPs []string
// StaticIP is deprecated. Use StaticIPs.
StaticIP string
// Internal queue size
QueueSize int
// Effective only when this router has a parent router
NATType *NATType
// Minimum Delay
MinDelay time.Duration
// Max Jitter
MaxJitter time.Duration
// Logger factory
LoggerFactory logging.LoggerFactory
}
// NIC is a network interface controller that interfaces Router
type NIC interface {
getInterface(ifName string) (*transport.Interface, error)
onInboundChunk(c Chunk)
getStaticIPs() []net.IP
setRouter(r *Router) error
}
// ChunkFilter is a handler users can add to filter chunks.
// If the filter returns false, the packet will be dropped.
type ChunkFilter func(c Chunk) bool
// Router ...
type Router struct {
name string // read-only
interfaces []*transport.Interface // read-only
ipv4Net *net.IPNet // read-only
staticIPs []net.IP // read-only
staticLocalIPs map[string]net.IP // read-only,
lastID byte // requires mutex [x], used to assign the last digit of IPv4 address
queue *chunkQueue // read-only
parent *Router // read-only
children []*Router // read-only
natType *NATType // read-only
nat *networkAddressTranslator // read-only
nics map[string]NIC // read-only
stopFunc func() // requires mutex [x]
resolver *resolver // read-only
chunkFilters []ChunkFilter // requires mutex [x]
minDelay time.Duration // requires mutex [x]
maxJitter time.Duration // requires mutex [x]
mutex sync.RWMutex // thread-safe
pushCh chan struct{} // writer requires mutex
loggerFactory logging.LoggerFactory // read-only
log logging.LeveledLogger // read-only
}
// NewRouter ...
func NewRouter(config *RouterConfig) (*Router, error) {
loggerFactory := config.LoggerFactory
log := loggerFactory.NewLogger("vnet")
_, ipv4Net, err := net.ParseCIDR(config.CIDR)
if err != nil {
return nil, err
}
queueSize := defaultRouterQueueSize
if config.QueueSize > 0 {
queueSize = config.QueueSize
}
// set up network interface, lo0
lo0 := transport.NewInterface(net.Interface{
Index: 1,
MTU: 16384,
Name: lo0String,
HardwareAddr: nil,
Flags: net.FlagUp | net.FlagLoopback | net.FlagMulticast,
})
lo0.AddAddress(&net.IPAddr{IP: net.ParseIP("127.0.0.1"), Zone: ""})
// set up network interface, eth0
eth0 := transport.NewInterface(net.Interface{
Index: 2,
MTU: 1500,
Name: "eth0",
HardwareAddr: newMACAddress(),
Flags: net.FlagUp | net.FlagMulticast,
})
// local host name resolver
resolver := newResolver(&resolverConfig{
LoggerFactory: config.LoggerFactory,
})
name := config.Name
if len(name) == 0 {
name = assignRouterName()
}
var staticIPs []net.IP
staticLocalIPs := map[string]net.IP{}
for _, ipStr := range config.StaticIPs {
ipPair := strings.Split(ipStr, "/")
if ip := net.ParseIP(ipPair[0]); ip != nil {
if len(ipPair) > 1 {
locIP := net.ParseIP(ipPair[1])
if locIP == nil {
return nil, errInvalidLocalIPinStaticIPs
}
if !ipv4Net.Contains(locIP) {
return nil, fmt.Errorf("local IP %s %w", locIP.String(), errLocalIPBeyondStaticIPsSubset)
}
staticLocalIPs[ip.String()] = locIP
}
staticIPs = append(staticIPs, ip)
}
}
if len(config.StaticIP) > 0 {
log.Warn("StaticIP is deprecated. Use StaticIPs instead")
if ip := net.ParseIP(config.StaticIP); ip != nil {
staticIPs = append(staticIPs, ip)
}
}
if nStaticLocal := len(staticLocalIPs); nStaticLocal > 0 {
if nStaticLocal != len(staticIPs) {
return nil, errLocalIPNoStaticsIPsAssociated
}
}
return &Router{
name: name,
interfaces: []*transport.Interface{lo0, eth0},
ipv4Net: ipv4Net,
staticIPs: staticIPs,
staticLocalIPs: staticLocalIPs,
queue: newChunkQueue(queueSize, 0),
natType: config.NATType,
nics: map[string]NIC{},
resolver: resolver,
minDelay: config.MinDelay,
maxJitter: config.MaxJitter,
pushCh: make(chan struct{}, 1),
loggerFactory: loggerFactory,
log: log,
}, nil
}
// caller must hold the mutex
func (r *Router) getInterfaces() ([]*transport.Interface, error) {
if len(r.interfaces) == 0 {
return nil, fmt.Errorf("%w is available", errNoInterface)
}
return r.interfaces, nil
}
func (r *Router) getInterface(ifName string) (*transport.Interface, error) {
r.mutex.RLock()
defer r.mutex.RUnlock()
ifs, err := r.getInterfaces()
if err != nil {
return nil, err
}
for _, ifc := range ifs {
if ifc.Name == ifName {
return ifc, nil
}
}
return nil, fmt.Errorf("%w: %s", transport.ErrInterfaceNotFound, ifName)
}
// Start ...
func (r *Router) Start() error {
r.mutex.Lock()
defer r.mutex.Unlock()
if r.stopFunc != nil {
return errRouterAlreadyStarted
}
cancelCh := make(chan struct{})
go func() {
loop:
for {
d, err := r.processChunks()
if err != nil {
r.log.Errorf("[%s] %s", r.name, err.Error())
break
}
if d <= 0 {
select {
case <-r.pushCh:
case <-cancelCh:
break loop
}
} else {
t := time.NewTimer(d)
select {
case <-t.C:
case <-cancelCh:
break loop
}
}
}
}()
r.stopFunc = func() {
close(cancelCh)
}
for _, child := range r.children {
if err := child.Start(); err != nil {
return err
}
}
return nil
}
// Stop ...
func (r *Router) Stop() error {
r.mutex.Lock()
defer r.mutex.Unlock()
if r.stopFunc == nil {
return errRouterAlreadyStopped
}
for _, router := range r.children {
r.mutex.Unlock()
err := router.Stop()
r.mutex.Lock()
if err != nil {
return err
}
}
r.stopFunc()
r.stopFunc = nil
return nil
}
// caller must hold the mutex
func (r *Router) addNIC(nic NIC) error {
ifc, err := nic.getInterface("eth0")
if err != nil {
return err
}
var ips []net.IP
if ips = nic.getStaticIPs(); len(ips) == 0 {
// assign an IP address
ip, err2 := r.assignIPAddress()
if err2 != nil {
return err2
}
ips = append(ips, ip)
}
for _, ip := range ips {
if !r.ipv4Net.Contains(ip) {
return fmt.Errorf("%w: %s", errStaticIPisBeyondSubnet, r.ipv4Net.String())
}
ifc.AddAddress(&net.IPNet{
IP: ip,
Mask: r.ipv4Net.Mask,
})
r.nics[ip.String()] = nic
}
return nic.setRouter(r)
}
// AddRouter adds a child Router.
func (r *Router) AddRouter(router *Router) error {
r.mutex.Lock()
defer r.mutex.Unlock()
// Router is a NIC. Add it as a NIC so that packets are routed to this child
// router.
err := r.addNIC(router)
if err != nil {
return err
}
if err = router.setRouter(r); err != nil {
return err
}
r.children = append(r.children, router)
return nil
}
// AddChildRouter is like AddRouter, but does not add the child routers NIC to
// the parent. This has to be done manually by calling AddNet, which allows to
// use a wrapper around the subrouters NIC.
// AddNet MUST be called before AddChildRouter.
func (r *Router) AddChildRouter(router *Router) error {
r.mutex.Lock()
defer r.mutex.Unlock()
if err := router.setRouter(r); err != nil {
return err
}
r.children = append(r.children, router)
return nil
}
// AddNet ...
func (r *Router) AddNet(nic NIC) error {
r.mutex.Lock()
defer r.mutex.Unlock()
return r.addNIC(nic)
}
// AddHost adds a mapping of hostname and an IP address to the local resolver.
func (r *Router) AddHost(hostName string, ipAddr string) error {
return r.resolver.addHost(hostName, ipAddr)
}
// AddChunkFilter adds a filter for chunks traversing this router.
// You may add more than one filter. The filters are called in the order of this method call.
// If a chunk is dropped by a filter, subsequent filter will not receive the chunk.
func (r *Router) AddChunkFilter(filter ChunkFilter) {
r.mutex.Lock()
defer r.mutex.Unlock()
r.chunkFilters = append(r.chunkFilters, filter)
}
// caller should hold the mutex
func (r *Router) assignIPAddress() (net.IP, error) {
// See: https://stackoverflow.com/questions/14915188/ip-address-ending-with-zero
if r.lastID == 0xfe {
return nil, errAddressSpaceExhausted
}
ip := make(net.IP, 4)
copy(ip, r.ipv4Net.IP[:3])
r.lastID++
ip[3] = r.lastID
return ip, nil
}
func (r *Router) push(c Chunk) {
r.mutex.Lock()
defer r.mutex.Unlock()
r.log.Debugf("[%s] route %s", r.name, c.String())
if r.stopFunc != nil {
c.setTimestamp()
if r.queue.push(c) {
select {
case r.pushCh <- struct{}{}:
default:
}
} else {
r.log.Warnf("[%s] queue was full. dropped a chunk", r.name)
}
}
}
func (r *Router) processChunks() (time.Duration, error) {
r.mutex.Lock()
defer r.mutex.Unlock()
// Introduce jitter by delaying the processing of chunks.
if r.maxJitter > 0 {
jitter := time.Duration(rand.Int63n(int64(r.maxJitter))) //nolint:gosec
time.Sleep(jitter)
}
// cutOff
// v min delay
// |<--->|
// +------------:--
// |OOOOOOXXXXX : --> time
// +------------:--
// |<--->| now
// due
enteredAt := time.Now()
cutOff := enteredAt.Add(-r.minDelay)
var d time.Duration // the next sleep duration
for {
d = 0
c := r.queue.peek()
if c == nil {
break // no more chunk in the queue
}
// check timestamp to find if the chunk is due
if c.getTimestamp().After(cutOff) {
// There is one or more chunk in the queue but none of them are due.
// Calculate the next sleep duration here.
nextExpire := c.getTimestamp().Add(r.minDelay)
d = nextExpire.Sub(enteredAt)
break
}
var ok bool
if c, ok = r.queue.pop(); !ok {
break // no more chunk in the queue
}
blocked := false
for i := 0; i < len(r.chunkFilters); i++ {
filter := r.chunkFilters[i]
if !filter(c) {
blocked = true
break
}
}
if blocked {
continue // discard
}
dstIP := c.getDestinationIP()
// check if the destination is in our subnet
if r.ipv4Net.Contains(dstIP) {
// search for the destination NIC
var nic NIC
if nic, ok = r.nics[dstIP.String()]; !ok {
// NIC not found. drop it.
r.log.Debugf("[%s] %s unreachable", r.name, c.String())
continue
}
// found the NIC, forward the chunk to the NIC.
// call to NIC must unlock mutex
r.mutex.Unlock()
nic.onInboundChunk(c)
r.mutex.Lock()
continue
}
// the destination is outside of this subnet
// is this WAN?
if r.parent == nil {
// this WAN. No route for this chunk
r.log.Debugf("[%s] no route found for %s", r.name, c.String())
continue
}
// Pass it to the parent via NAT
toParent, err := r.nat.translateOutbound(c)
if err != nil {
return 0, err
}
if toParent == nil {
continue
}
//nolint:godox
/* FIXME: this implementation would introduce a duplicate packet!
if r.nat.natType.Hairpinning {
hairpinned, err := r.nat.translateInbound(toParent)
if err != nil {
r.log.Warnf("[%s] %s", r.name, err.Error())
} else {
go func() {
r.push(hairpinned)
}()
}
}
*/
// call to parent router mutex unlock mutex
r.mutex.Unlock()
r.parent.push(toParent)
r.mutex.Lock()
}
return d, nil
}
// caller must hold the mutex
func (r *Router) setRouter(parent *Router) error {
r.parent = parent
r.resolver.setParent(parent.resolver)
// when this method is called, one or more IP address has already been assigned by
// the parent router.
ifc, err := r.getInterface("eth0")
if err != nil {
return err
}
addrs, _ := ifc.Addrs()
if len(addrs) == 0 {
return errNoIPAddrEth0
}
mappedIPs := []net.IP{}
localIPs := []net.IP{}
for _, ifcAddr := range addrs {
var ip net.IP
switch addr := ifcAddr.(type) {
case *net.IPNet:
ip = addr.IP
case *net.IPAddr: // Do we really need this case?
ip = addr.IP
default:
}
if ip == nil {
continue
}
mappedIPs = append(mappedIPs, ip)
if locIP := r.staticLocalIPs[ip.String()]; locIP != nil {
localIPs = append(localIPs, locIP)
}
}
// Set up NAT here
if r.natType == nil {
r.natType = &NATType{
MappingBehavior: EndpointIndependent,
FilteringBehavior: EndpointAddrPortDependent,
Hairpinning: false,
PortPreservation: false,
MappingLifeTime: 30 * time.Second,
}
}
r.nat, err = newNAT(&natConfig{
name: r.name,
natType: *r.natType,
mappedIPs: mappedIPs,
localIPs: localIPs,
loggerFactory: r.loggerFactory,
})
if err != nil {
return err
}
return nil
}
func (r *Router) onInboundChunk(c Chunk) {
fromParent, err := r.nat.translateInbound(c)
if err != nil {
r.log.Warnf("[%s] %s", r.name, err.Error())
return
}
r.push(fromParent)
}
func (r *Router) getStaticIPs() []net.IP {
return r.staticIPs
}

167
server/vendor/github.com/pion/transport/v2/vnet/tbf.go generated vendored Normal file
View File

@@ -0,0 +1,167 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package vnet
import (
"math"
"sync"
"time"
"github.com/pion/logging"
)
const (
// Bit is a single bit
Bit = 1
// KBit is a kilobit
KBit = 1000 * Bit
// MBit is a Megabit
MBit = 1000 * KBit
)
// TokenBucketFilter implements a token bucket rate limit algorithm.
type TokenBucketFilter struct {
NIC
currentTokensInBucket float64
c chan Chunk
queue *chunkQueue
queueSize int // in bytes
mutex sync.Mutex
rate int
maxBurst int
minRefillDuration time.Duration
wg sync.WaitGroup
done chan struct{}
log logging.LeveledLogger
}
// TBFOption is the option type to configure a TokenBucketFilter
type TBFOption func(*TokenBucketFilter) TBFOption
// TBFQueueSizeInBytes sets the max number of bytes waiting in the queue. Can
// only be set in constructor before using the TBF.
func TBFQueueSizeInBytes(bytes int) TBFOption {
return func(t *TokenBucketFilter) TBFOption {
prev := t.queueSize
t.queueSize = bytes
return TBFQueueSizeInBytes(prev)
}
}
// TBFRate sets the bit rate of a TokenBucketFilter
func TBFRate(rate int) TBFOption {
return func(t *TokenBucketFilter) TBFOption {
t.mutex.Lock()
defer t.mutex.Unlock()
previous := t.rate
t.rate = rate
return TBFRate(previous)
}
}
// TBFMaxBurst sets the bucket size of the token bucket filter. This is the
// maximum size that can instantly leave the filter, if the bucket is full.
func TBFMaxBurst(size int) TBFOption {
return func(t *TokenBucketFilter) TBFOption {
t.mutex.Lock()
defer t.mutex.Unlock()
previous := t.maxBurst
t.maxBurst = size
return TBFMaxBurst(previous)
}
}
// Set updates a setting on the token bucket filter
func (t *TokenBucketFilter) Set(opts ...TBFOption) (previous TBFOption) {
for _, opt := range opts {
previous = opt(t)
}
return previous
}
// NewTokenBucketFilter creates and starts a new TokenBucketFilter
func NewTokenBucketFilter(n NIC, opts ...TBFOption) (*TokenBucketFilter, error) {
tbf := &TokenBucketFilter{
NIC: n,
currentTokensInBucket: 0,
c: make(chan Chunk),
queue: nil,
queueSize: 50000,
mutex: sync.Mutex{},
rate: 1 * MBit,
maxBurst: 8 * KBit,
minRefillDuration: 100 * time.Millisecond,
wg: sync.WaitGroup{},
done: make(chan struct{}),
log: logging.NewDefaultLoggerFactory().NewLogger("tbf"),
}
tbf.Set(opts...)
tbf.queue = newChunkQueue(0, tbf.queueSize)
tbf.wg.Add(1)
go tbf.run()
return tbf, nil
}
func (t *TokenBucketFilter) onInboundChunk(c Chunk) {
t.c <- c
}
func (t *TokenBucketFilter) run() {
defer t.wg.Done()
t.refillTokens(t.minRefillDuration)
lastRefill := time.Now()
for {
select {
case <-t.done:
t.drainQueue()
return
case chunk := <-t.c:
if time.Since(lastRefill) > t.minRefillDuration {
t.refillTokens(time.Since(lastRefill))
lastRefill = time.Now()
}
t.queue.push(chunk)
t.drainQueue()
}
}
}
func (t *TokenBucketFilter) refillTokens(dt time.Duration) {
m := 1000.0 / float64(dt.Milliseconds())
add := (float64(t.rate) / m) / 8.0
t.mutex.Lock()
defer t.mutex.Unlock()
t.currentTokensInBucket = math.Min(float64(t.maxBurst), t.currentTokensInBucket+add)
t.log.Tracef("add=(%v / %v) / 8 = %v, currentTokensInBucket=%v, maxBurst=%v", t.rate, m, add, t.currentTokensInBucket, t.maxBurst)
}
func (t *TokenBucketFilter) drainQueue() {
for {
next := t.queue.peek()
if next == nil {
break
}
tokens := float64(len(next.UserData()))
if t.currentTokensInBucket < tokens {
t.log.Tracef("currentTokensInBucket=%v, tokens=%v, stop drain", t.currentTokensInBucket, tokens)
break
}
t.log.Tracef("currentTokensInBucket=%v, tokens=%v, pop chunk", t.currentTokensInBucket, tokens)
t.queue.pop()
t.NIC.onInboundChunk(next)
t.currentTokensInBucket -= tokens
}
}
// Close closes and stops the token bucket filter queue
func (t *TokenBucketFilter) Close() error {
close(t.done)
t.wg.Wait()
return nil
}

View File

@@ -0,0 +1,223 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package vnet
import (
"context"
"net"
"sync"
"time"
)
// UDPProxy is a proxy between real server(net.UDPConn) and vnet.UDPConn.
//
// High level design:
//
// ..............................................
// : Virtual Network (vnet) :
// : :
// +-------+ * 1 +----+ +--------+ :
// | :App |------------>|:Net|--o<-----|:Router | .............................
// +-------+ +----+ | | : UDPProxy :
// : | | +----+ +---------+ +---------+ +--------+
// : | |--->o--|:Net|-->o-| vnet. |-->o-| net. |--->-| :Real |
// : | | +----+ | UDPConn | | UDPConn | | Server |
// : | | : +---------+ +---------+ +--------+
// : | | ............................:
// : +--------+ :
// ...............................................
type UDPProxy struct {
// The router bind to.
router *Router
// Each vnet source, bind to a real socket to server.
// key is real server addr, which is net.Addr
// value is *aUDPProxyWorker
workers sync.Map
// For each endpoint, we never know when to start and stop proxy,
// so we stop the endpoint when timeout.
timeout time.Duration
// For utest, to mock the target real server.
// Optional, use the address of received client packet.
mockRealServerAddr *net.UDPAddr
}
// NewProxy create a proxy, the router for this proxy belongs/bind to. If need to proxy for
// please create a new proxy for each router. For all addresses we proxy, we will create a
// vnet.Net in this router and proxy all packets.
func NewProxy(router *Router) (*UDPProxy, error) {
v := &UDPProxy{router: router, timeout: 2 * time.Minute}
return v, nil
}
// Close the proxy, stop all workers.
func (v *UDPProxy) Close() error {
v.workers.Range(func(key, value interface{}) bool {
_ = value.(*aUDPProxyWorker).Close() //nolint:forcetypeassert
return true
})
return nil
}
// Proxy starts a worker for server, ignore if already started.
func (v *UDPProxy) Proxy(client *Net, server *net.UDPAddr) error {
// Note that even if the worker exists, it's also ok to create a same worker,
// because the router will use the last one, and the real server will see a address
// change event after we switch to the next worker.
if _, ok := v.workers.Load(server.String()); ok {
// nolint:godox // TODO: Need to restart the stopped worker?
return nil
}
// Not exists, create a new one.
worker := &aUDPProxyWorker{
router: v.router, mockRealServerAddr: v.mockRealServerAddr,
}
// Create context for cleanup.
var ctx context.Context
ctx, worker.ctxDisposeCancel = context.WithCancel(context.Background())
v.workers.Store(server.String(), worker)
return worker.Proxy(ctx, client, server)
}
// A proxy worker for a specified proxy server.
type aUDPProxyWorker struct {
router *Router
mockRealServerAddr *net.UDPAddr
// Each vnet source, bind to a real socket to server.
// key is vnet client addr, which is net.Addr
// value is *net.UDPConn
endpoints sync.Map
// For cleanup.
ctxDisposeCancel context.CancelFunc
wg sync.WaitGroup
}
func (v *aUDPProxyWorker) Close() error {
// Notify all goroutines to dispose.
v.ctxDisposeCancel()
// Wait for all goroutines quit.
v.wg.Wait()
return nil
}
func (v *aUDPProxyWorker) Proxy(ctx context.Context, _ *Net, serverAddr *net.UDPAddr) error { // nolint:gocognit
// Create vnet for real server by serverAddr.
nw, err := NewNet(&NetConfig{
StaticIP: serverAddr.IP.String(),
})
if err != nil {
return err
}
if err = v.router.AddNet(nw); err != nil {
return err
}
// We must create a "same" vnet.UDPConn as the net.UDPConn,
// which has the same ip:port, to copy packets between them.
vnetSocket, err := nw.ListenUDP("udp4", serverAddr)
if err != nil {
return err
}
// User stop proxy, we should close the socket.
go func() {
<-ctx.Done()
_ = vnetSocket.Close()
}()
// Got new vnet client, start a new endpoint.
findEndpointBy := func(addr net.Addr) (*net.UDPConn, error) {
// Exists binding.
if value, ok := v.endpoints.Load(addr.String()); ok {
// Exists endpoint, reuse it.
return value.(*net.UDPConn), nil //nolint:forcetypeassert
}
// The real server we proxy to, for utest to mock it.
realAddr := serverAddr
if v.mockRealServerAddr != nil {
realAddr = v.mockRealServerAddr
}
// Got new vnet client, create new endpoint.
realSocket, err := net.DialUDP("udp4", nil, realAddr)
if err != nil {
return nil, err
}
// User stop proxy, we should close the socket.
go func() {
<-ctx.Done()
_ = realSocket.Close()
}()
// Bind address.
v.endpoints.Store(addr.String(), realSocket)
// Got packet from real serverAddr, we should proxy it to vnet.
v.wg.Add(1)
go func(vnetClientAddr net.Addr) {
defer v.wg.Done()
buf := make([]byte, 1500)
for {
n, _, err := realSocket.ReadFrom(buf)
if err != nil {
return
}
if n <= 0 {
continue // Drop packet
}
if _, err := vnetSocket.WriteTo(buf[:n], vnetClientAddr); err != nil {
return
}
}
}(addr)
return realSocket, nil
}
// Start a proxy goroutine.
v.wg.Add(1)
go func() {
defer v.wg.Done()
buf := make([]byte, 1500)
for {
n, addr, err := vnetSocket.ReadFrom(buf)
if err != nil {
return
}
if n <= 0 || addr == nil {
continue // Drop packet
}
realSocket, err := findEndpointBy(addr)
if err != nil {
continue // Drop packet.
}
if _, err := realSocket.Write(buf[:n]); err != nil {
return
}
}
}()
return nil
}

View File

@@ -0,0 +1,48 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package vnet
import (
"fmt"
"net"
)
// Deliver directly send packet to vnet or real-server.
// For example, we can use this API to simulate the REPLAY ATTACK.
func (v *UDPProxy) Deliver(sourceAddr, destAddr net.Addr, b []byte) (nn int, err error) {
v.workers.Range(func(key, value interface{}) bool {
if nn, err = value.(*aUDPProxyWorker).Deliver(sourceAddr, destAddr, b); err != nil {
return false // Fail, abort.
} else if nn == len(b) {
return false // Done.
}
return true // Deliver by next worker.
})
return
}
func (v *aUDPProxyWorker) Deliver(sourceAddr, _ net.Addr, b []byte) (nn int, err error) {
addr, ok := sourceAddr.(*net.UDPAddr)
if !ok {
return 0, fmt.Errorf("invalid addr %v", sourceAddr) // nolint:goerr113
}
// nolint:godox // TODO: Support deliver packet from real server to vnet.
// If packet is from vnet, proxy to real server.
var realSocket *net.UDPConn
value, ok := v.endpoints.Load(addr.String())
if !ok {
return 0, nil
}
realSocket = value.(*net.UDPConn) // nolint:forcetypeassert
// Send to real server.
if _, err := realSocket.Write(b); err != nil {
return 0, err
}
return len(b), nil
}

View File

@@ -0,0 +1,5 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
// Package vnet provides a virtual network layer for pion
package vnet