直播:后台 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

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