1
vendor/github.com/pion/transport/vnet/.gitignore
generated
vendored
Normal file
1
vendor/github.com/pion/transport/vnet/.gitignore
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
*.sw[poe]
|
||||
239
vendor/github.com/pion/transport/vnet/README.md
generated
vendored
Normal file
239
vendor/github.com/pion/transport/vnet/README.md
generated
vendored
Normal file
@@ -0,0 +1,239 @@
|
||||
# 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 (Netork Interface Controller)
|
||||
[1]: Net implments 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>> (vnet.Interface) (net.Addr)
|
||||
|
|
||||
| * +-----------+ 1 * +-----------+ 1 * +------+
|
||||
+------>o--| :Router |<>------|:Interface |<>------|:Addr |
|
||||
NIC +-----------+ +-----------+ +------+
|
||||
<<interface>> (vnet.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 vnet.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 propages all other routers
|
||||
|
||||
#### Example: WAN with one endpoint (vnet)
|
||||
```go
|
||||
import (
|
||||
"net"
|
||||
|
||||
"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 goroutines 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: *vnet.Net,
|
||||
}
|
||||
|
||||
type Agent struct {
|
||||
:
|
||||
net: *vnet.Net,
|
||||
}
|
||||
|
||||
func NetAgent(config *AgentConfig) *Agent {
|
||||
if config.Net == nil {
|
||||
config.Net = vnet.NewNet(nil) // defaults to native operation
|
||||
}
|
||||
|
||||
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|vnet.Interface||
|
||||
|net.PacketConn|(use it as-is)||
|
||||
|net.UDPConn|vnet.UDPConn|Use vnet.UDPPacketConn in your code|
|
||||
|net.TCPConn|vnet.TCPConn|(TODO)|Use net.Conn in your code|
|
||||
|net.Dialer|vnet.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 lantecy / 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)
|
||||
|
||||
### Code experiments
|
||||
* [CIDR and IPMask](https://play.golang.org/p/B7OBhkZqjmj)
|
||||
* [Test with net.IP](https://play.golang.org/p/AgXd23wKY4W)
|
||||
* [ListenPacket](https://play.golang.org/p/d4vasbnRimQ)
|
||||
* [isDottedIP()](https://play.golang.org/p/t4aZ47TgJfO)
|
||||
* [SplitHostPort](https://play.golang.org/p/JtvurlcMbhn)
|
||||
283
vendor/github.com/pion/transport/vnet/chunk.go
generated
vendored
Normal file
283
vendor/github.com/pion/transport/vnet/chunk.go
generated
vendored
Normal file
@@ -0,0 +1,283 @@
|
||||
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 udpString
|
||||
}
|
||||
|
||||
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(udpString, 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(udpString, 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
|
||||
}
|
||||
65
vendor/github.com/pion/transport/vnet/chunk_queue.go
generated
vendored
Normal file
65
vendor/github.com/pion/transport/vnet/chunk_queue.go
generated
vendored
Normal file
@@ -0,0 +1,65 @@
|
||||
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]
|
||||
}
|
||||
246
vendor/github.com/pion/transport/vnet/conn.go
generated
vendored
Normal file
246
vendor/github.com/pion/transport/vnet/conn.go
generated
vendored
Normal file
@@ -0,0 +1,246 @@
|
||||
package vnet
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"math"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
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")
|
||||
)
|
||||
|
||||
// UDPPacketConn is packet-oriented connection for UDP.
|
||||
type UDPPacketConn interface {
|
||||
net.PacketConn
|
||||
Read(b []byte) (int, error)
|
||||
RemoteAddr() net.Addr
|
||||
Write(b []byte) (int, error)
|
||||
}
|
||||
|
||||
// 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.
|
||||
// comatible 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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// 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,
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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(t 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
|
||||
}
|
||||
|
||||
// RemoteAddr returns the remote network address.
|
||||
func (c *UDPConn) RemoteAddr() net.Addr {
|
||||
return c.remAddr
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
func (c *UDPConn) onInboundChunk(chunk Chunk) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
if c.closed {
|
||||
return
|
||||
}
|
||||
|
||||
select {
|
||||
case c.readCh <- chunk:
|
||||
default:
|
||||
}
|
||||
}
|
||||
136
vendor/github.com/pion/transport/vnet/conn_map.go
generated
vendored
Normal file
136
vendor/github.com/pion/transport/vnet/conn_map.go
generated
vendored
Normal file
@@ -0,0 +1,136 @@
|
||||
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)
|
||||
|
||||
// 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)
|
||||
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)
|
||||
|
||||
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)
|
||||
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)
|
||||
|
||||
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)
|
||||
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
|
||||
}
|
||||
75
vendor/github.com/pion/transport/vnet/delay_filter.go
generated
vendored
Normal file
75
vendor/github.com/pion/transport/vnet/delay_filter.go
generated
vendored
Normal file
@@ -0,0 +1,75 @@
|
||||
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)
|
||||
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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
19
vendor/github.com/pion/transport/vnet/errors.go
generated
vendored
Normal file
19
vendor/github.com/pion/transport/vnet/errors.go
generated
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
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
|
||||
}
|
||||
40
vendor/github.com/pion/transport/vnet/interface.go
generated
vendored
Normal file
40
vendor/github.com/pion/transport/vnet/interface.go
generated
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
package vnet
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
)
|
||||
|
||||
var errNoAddressAssigned = errors.New("no address assigned")
|
||||
|
||||
// See: https://play.golang.org/p/nBO9KGYEziv
|
||||
|
||||
// InterfaceBase ...
|
||||
type InterfaceBase net.Interface
|
||||
|
||||
// Interface ...
|
||||
type Interface struct {
|
||||
InterfaceBase
|
||||
addrs []net.Addr
|
||||
}
|
||||
|
||||
// NewInterface ...
|
||||
func NewInterface(ifc net.Interface) *Interface {
|
||||
return &Interface{
|
||||
InterfaceBase: InterfaceBase(ifc),
|
||||
addrs: nil,
|
||||
}
|
||||
}
|
||||
|
||||
// AddAddr ...
|
||||
func (ifc *Interface) AddAddr(addr net.Addr) {
|
||||
ifc.addrs = append(ifc.addrs, addr)
|
||||
}
|
||||
|
||||
// Addrs ...
|
||||
func (ifc *Interface) Addrs() ([]net.Addr, error) {
|
||||
if len(ifc.addrs) == 0 {
|
||||
return nil, errNoAddressAssigned
|
||||
}
|
||||
return ifc.addrs, nil
|
||||
}
|
||||
33
vendor/github.com/pion/transport/vnet/loss_filter.go
generated
vendored
Normal file
33
vendor/github.com/pion/transport/vnet/loss_filter.go
generated
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
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)
|
||||
}
|
||||
338
vendor/github.com/pion/transport/vnet/nat.go
generated
vendored
Normal file
338
vendor/github.com/pion/transport/vnet/nat.go
generated
vendored
Normal file
@@ -0,0 +1,338 @@
|
||||
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
|
||||
Hairpining 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() == udpString {
|
||||
if n.natType.Mode == NATModeNAT1To1 {
|
||||
// 1:1 NAT behavior
|
||||
srcAddr := from.SourceAddr().(*net.UDPAddr)
|
||||
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 // silently discard
|
||||
}
|
||||
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",
|
||||
n.name,
|
||||
oKey,
|
||||
iKey)
|
||||
|
||||
m.filters[filterKey] = struct{}{}
|
||||
n.log.Debugf("[%s] permit access from %s to %s\n", 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", 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() == udpString {
|
||||
if n.natType.Mode == NATModeNAT1To1 {
|
||||
// 1:1 NAT behavior
|
||||
dstAddr := from.DestinationAddr().(*net.UDPAddr)
|
||||
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
|
||||
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)
|
||||
}
|
||||
677
vendor/github.com/pion/transport/vnet/net.go
generated
vendored
Normal file
677
vendor/github.com/pion/transport/vnet/net.go
generated
vendored
Normal file
@@ -0,0 +1,677 @@
|
||||
package vnet
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
const (
|
||||
lo0String = "lo0String"
|
||||
udpString = "udp"
|
||||
)
|
||||
|
||||
var (
|
||||
macAddrCounter uint64 = 0xBEEFED910200 //nolint:gochecknoglobals
|
||||
errNoInterface = errors.New("no interface is available")
|
||||
errNotFound = errors.New("not found")
|
||||
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")
|
||||
errBindFailerFor = errors.New("bind failed for")
|
||||
errEndPortLessThanStart = errors.New("end port is less than the start")
|
||||
errPortSpaceExhausted = errors.New("port space exhausted")
|
||||
errVNetDisabled = errors.New("vnet is not enabled")
|
||||
)
|
||||
|
||||
func newMACAddress() net.HardwareAddr {
|
||||
b := make([]byte, 8)
|
||||
binary.BigEndian.PutUint64(b, macAddrCounter)
|
||||
macAddrCounter++
|
||||
return b[2:]
|
||||
}
|
||||
|
||||
type vNet struct {
|
||||
interfaces []*Interface // read-only
|
||||
staticIPs []net.IP // read-only
|
||||
router *Router // read-only
|
||||
udpConns *udpConnMap // read-only
|
||||
mutex sync.RWMutex
|
||||
}
|
||||
|
||||
func (v *vNet) _getInterfaces() ([]*Interface, error) {
|
||||
if len(v.interfaces) == 0 {
|
||||
return nil, errNoInterface
|
||||
}
|
||||
|
||||
return v.interfaces, nil
|
||||
}
|
||||
|
||||
func (v *vNet) getInterfaces() ([]*Interface, error) {
|
||||
v.mutex.RLock()
|
||||
defer v.mutex.RUnlock()
|
||||
|
||||
return v._getInterfaces()
|
||||
}
|
||||
|
||||
// caller must hold the mutex (read)
|
||||
func (v *vNet) _getInterface(ifName string) (*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("interface %s %w", ifName, errNotFound)
|
||||
}
|
||||
|
||||
func (v *vNet) getInterface(ifName string) (*Interface, error) {
|
||||
v.mutex.RLock()
|
||||
defer v.mutex.RUnlock()
|
||||
|
||||
return v._getInterface(ifName)
|
||||
}
|
||||
|
||||
// caller must hold the mutex
|
||||
func (v *vNet) 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 *vNet) setRouter(r *Router) error {
|
||||
v.mutex.Lock()
|
||||
defer v.mutex.Unlock()
|
||||
|
||||
v.router = r
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v *vNet) onInboundChunk(c Chunk) {
|
||||
v.mutex.Lock()
|
||||
defer v.mutex.Unlock()
|
||||
|
||||
if c.Network() == udpString {
|
||||
if conn, ok := v.udpConns.find(c.DestinationAddr()); ok {
|
||||
conn.onInboundChunk(c)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// caller must hold the mutex
|
||||
func (v *vNet) _dialUDP(network string, locAddr, remAddr *net.UDPAddr) (UDPPacketConn, error) {
|
||||
// validate network
|
||||
if network != udpString && 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
|
||||
}
|
||||
|
||||
func (v *vNet) listenPacket(network string, address string) (UDPPacketConn, 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)
|
||||
}
|
||||
|
||||
func (v *vNet) listenUDP(network string, locAddr *net.UDPAddr) (UDPPacketConn, error) {
|
||||
v.mutex.Lock()
|
||||
defer v.mutex.Unlock()
|
||||
|
||||
return v._dialUDP(network, locAddr, nil)
|
||||
}
|
||||
|
||||
func (v *vNet) dialUDP(network string, locAddr, remAddr *net.UDPAddr) (UDPPacketConn, error) {
|
||||
v.mutex.Lock()
|
||||
defer v.mutex.Unlock()
|
||||
|
||||
return v._dialUDP(network, locAddr, remAddr)
|
||||
}
|
||||
|
||||
func (v *vNet) dial(network string, address string) (UDPPacketConn, 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)
|
||||
}
|
||||
|
||||
func (v *vNet) resolveUDPAddr(network, address string) (*net.UDPAddr, error) {
|
||||
if network != udpString && network != "udp4" {
|
||||
return nil, fmt.Errorf("%w %s", errUnknownNetwork, network)
|
||||
}
|
||||
|
||||
host, sPort, err := net.SplitHostPort(address)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Check if host is a domain name
|
||||
ip := net.ParseIP(host)
|
||||
if ip == nil {
|
||||
host = strings.ToLower(host)
|
||||
if host == "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(host)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
port, err := strconv.Atoi(sPort)
|
||||
if err != nil {
|
||||
return nil, errInvalidPortNumber
|
||||
}
|
||||
|
||||
udpAddr := &net.UDPAddr{
|
||||
IP: ip,
|
||||
Port: port,
|
||||
}
|
||||
|
||||
return udpAddr, nil
|
||||
}
|
||||
|
||||
func (v *vNet) write(c Chunk) error {
|
||||
if c.Network() == udpString {
|
||||
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 *vNet) onClosed(addr net.Addr) {
|
||||
if addr.Network() == udpString {
|
||||
//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 *vNet) 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
|
||||
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 *vNet) 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 *vNet) 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", errBindFailerFor, 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: udpString,
|
||||
Addr: addr,
|
||||
Err: fmt.Errorf("bind: %w", errAddressAlreadyInUse),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// caller must hold the mutex
|
||||
func (v *vNet) 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
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// Net represents a local network stack euivalent to a set of layers from NIC
|
||||
// up to the transport (UDP / TCP) layer.
|
||||
type Net struct {
|
||||
v *vNet
|
||||
ifs []*Interface
|
||||
}
|
||||
|
||||
// NewNet creates an instance of Net.
|
||||
// If config is nil, the virtual network is disabled. (uses corresponding
|
||||
// net.Xxxx() operations.
|
||||
// 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 {
|
||||
if config == nil {
|
||||
ifs := []*Interface{}
|
||||
if orgIfs, err := net.Interfaces(); err == nil {
|
||||
for _, orgIfc := range orgIfs {
|
||||
ifc := NewInterface(orgIfc)
|
||||
if addrs, err := orgIfc.Addrs(); err == nil {
|
||||
for _, addr := range addrs {
|
||||
ifc.AddAddr(addr)
|
||||
}
|
||||
}
|
||||
|
||||
ifs = append(ifs, ifc)
|
||||
}
|
||||
}
|
||||
|
||||
return &Net{ifs: ifs}
|
||||
}
|
||||
|
||||
lo0 := NewInterface(net.Interface{
|
||||
Index: 1,
|
||||
MTU: 16384,
|
||||
Name: lo0String,
|
||||
HardwareAddr: nil,
|
||||
Flags: net.FlagUp | net.FlagLoopback | net.FlagMulticast,
|
||||
})
|
||||
lo0.AddAddr(&net.IPNet{
|
||||
IP: net.ParseIP("127.0.0.1"),
|
||||
Mask: net.CIDRMask(8, 32),
|
||||
})
|
||||
|
||||
eth0 := 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)
|
||||
}
|
||||
}
|
||||
|
||||
v := &vNet{
|
||||
interfaces: []*Interface{lo0, eth0},
|
||||
staticIPs: staticIPs,
|
||||
udpConns: newUDPConnMap(),
|
||||
}
|
||||
|
||||
return &Net{
|
||||
v: v,
|
||||
}
|
||||
}
|
||||
|
||||
// Interfaces returns a list of the system's network interfaces.
|
||||
func (n *Net) Interfaces() ([]*Interface, error) {
|
||||
if n.v == nil {
|
||||
return n.ifs, nil
|
||||
}
|
||||
|
||||
return n.v.getInterfaces()
|
||||
}
|
||||
|
||||
// InterfaceByName returns the interface specified by name.
|
||||
func (n *Net) InterfaceByName(name string) (*Interface, error) {
|
||||
if n.v == nil {
|
||||
for _, ifc := range n.ifs {
|
||||
if ifc.Name == name {
|
||||
return ifc, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("interface %s %w", name, errNotFound)
|
||||
}
|
||||
|
||||
return n.v.getInterface(name)
|
||||
}
|
||||
|
||||
// ListenPacket announces on the local network address.
|
||||
func (n *Net) ListenPacket(network string, address string) (net.PacketConn, error) {
|
||||
if n.v == nil {
|
||||
return net.ListenPacket(network, address)
|
||||
}
|
||||
|
||||
return n.v.listenPacket(network, address)
|
||||
}
|
||||
|
||||
// ListenUDP acts like ListenPacket for UDP networks.
|
||||
func (n *Net) ListenUDP(network string, locAddr *net.UDPAddr) (UDPPacketConn, error) {
|
||||
if n.v == nil {
|
||||
return net.ListenUDP(network, locAddr)
|
||||
}
|
||||
|
||||
return n.v.listenUDP(network, locAddr)
|
||||
}
|
||||
|
||||
// Dial connects to the address on the named network.
|
||||
func (n *Net) Dial(network, address string) (net.Conn, error) {
|
||||
if n.v == nil {
|
||||
return net.Dial(network, address)
|
||||
}
|
||||
|
||||
return n.v.dial(network, address)
|
||||
}
|
||||
|
||||
// CreateDialer creates an instance of vnet.Dialer
|
||||
func (n *Net) CreateDialer(dialer *net.Dialer) Dialer {
|
||||
if n.v == nil {
|
||||
return &vDialer{
|
||||
dialer: dialer,
|
||||
}
|
||||
}
|
||||
|
||||
return &vDialer{
|
||||
dialer: dialer,
|
||||
v: n.v,
|
||||
}
|
||||
}
|
||||
|
||||
// DialUDP acts like Dial for UDP networks.
|
||||
func (n *Net) DialUDP(network string, laddr, raddr *net.UDPAddr) (UDPPacketConn, error) {
|
||||
if n.v == nil {
|
||||
return net.DialUDP(network, laddr, raddr)
|
||||
}
|
||||
|
||||
return n.v.dialUDP(network, laddr, raddr)
|
||||
}
|
||||
|
||||
// ResolveUDPAddr returns an address of UDP end point.
|
||||
func (n *Net) ResolveUDPAddr(network, address string) (*net.UDPAddr, error) {
|
||||
if n.v == nil {
|
||||
return net.ResolveUDPAddr(network, address)
|
||||
}
|
||||
|
||||
return n.v.resolveUDPAddr(network, address)
|
||||
}
|
||||
|
||||
func (n *Net) getInterface(ifName string) (*Interface, error) {
|
||||
if n.v == nil {
|
||||
return nil, errVNetDisabled
|
||||
}
|
||||
|
||||
return n.v.getInterface(ifName)
|
||||
}
|
||||
|
||||
func (n *Net) setRouter(r *Router) error {
|
||||
if n.v == nil {
|
||||
return errVNetDisabled
|
||||
}
|
||||
|
||||
return n.v.setRouter(r)
|
||||
}
|
||||
|
||||
func (n *Net) onInboundChunk(c Chunk) {
|
||||
if n.v == nil {
|
||||
return
|
||||
}
|
||||
|
||||
n.v.onInboundChunk(c)
|
||||
}
|
||||
|
||||
func (n *Net) getStaticIPs() []net.IP {
|
||||
if n.v == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return n.v.staticIPs
|
||||
}
|
||||
|
||||
// IsVirtual tests if the virtual network is enabled.
|
||||
func (n *Net) IsVirtual() bool {
|
||||
return n.v != nil
|
||||
}
|
||||
|
||||
// Dialer is identical to net.Dialer excepts that its methods
|
||||
// (Dial, DialContext) are overridden to use virtual network.
|
||||
// Use vnet.CreateDialer() to create an instance of this Dialer.
|
||||
type Dialer interface {
|
||||
Dial(network, address string) (net.Conn, error)
|
||||
}
|
||||
|
||||
type vDialer struct {
|
||||
dialer *net.Dialer
|
||||
v *vNet
|
||||
}
|
||||
|
||||
func (d *vDialer) Dial(network, address string) (net.Conn, error) {
|
||||
if d.v == nil {
|
||||
return d.dialer.Dial(network, address)
|
||||
}
|
||||
|
||||
return d.v.dial(network, address)
|
||||
}
|
||||
89
vendor/github.com/pion/transport/vnet/resolver.go
generated
vendored
Normal file
89
vendor/github.com/pion/transport/vnet/resolver.go
generated
vendored
Normal file
@@ -0,0 +1,89 @@
|
||||
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,
|
||||
}
|
||||
}
|
||||
620
vendor/github.com/pion/transport/vnet/router.go
generated
vendored
Normal file
620
vendor/github.com/pion/transport/vnet/router.go
generated
vendored
Normal file
@@ -0,0 +1,620 @@
|
||||
package vnet
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"net"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/pion/logging"
|
||||
)
|
||||
|
||||
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 nework inerface controller that interfaces Router
|
||||
type NIC interface {
|
||||
getInterface(ifName string) (*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 []*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 := NewInterface(net.Interface{
|
||||
Index: 1,
|
||||
MTU: 16384,
|
||||
Name: lo0String,
|
||||
HardwareAddr: nil,
|
||||
Flags: net.FlagUp | net.FlagLoopback | net.FlagMulticast,
|
||||
})
|
||||
lo0.AddAddr(&net.IPAddr{IP: net.ParseIP("127.0.0.1"), Zone: ""})
|
||||
|
||||
// set up network interface, eth0
|
||||
eth0 := 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: []*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() ([]*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) (*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("interface %s %w", ifName, errNotFound)
|
||||
}
|
||||
|
||||
// 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.AddAddr(&net.IPNet{
|
||||
IP: ip,
|
||||
Mask: r.ipv4Net.Mask,
|
||||
})
|
||||
|
||||
r.nics[ip.String()] = nic
|
||||
}
|
||||
|
||||
if err = nic.setRouter(r); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddRouter adds a chile 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 desination 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.Hairpining {
|
||||
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
|
||||
}
|
||||
|
||||
if len(ifc.addrs) == 0 {
|
||||
return errNoIPAddrEth0
|
||||
}
|
||||
|
||||
mappedIPs := []net.IP{}
|
||||
localIPs := []net.IP{}
|
||||
|
||||
for _, ifcAddr := range ifc.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,
|
||||
Hairpining: 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
|
||||
}
|
||||
149
vendor/github.com/pion/transport/vnet/tbf.go
generated
vendored
Normal file
149
vendor/github.com/pion/transport/vnet/tbf.go
generated
vendored
Normal file
@@ -0,0 +1,149 @@
|
||||
package vnet
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
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 int
|
||||
c chan Chunk
|
||||
queue *chunkQueue
|
||||
queueSize int // in bytes
|
||||
|
||||
mutex sync.Mutex
|
||||
rate int
|
||||
maxBurst int
|
||||
|
||||
wg sync.WaitGroup
|
||||
done chan struct{}
|
||||
}
|
||||
|
||||
// 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 bitrate 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: 2 * KBit,
|
||||
wg: sync.WaitGroup{},
|
||||
done: make(chan struct{}),
|
||||
}
|
||||
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()
|
||||
ticker := time.NewTicker(1 * time.Millisecond)
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-t.done:
|
||||
ticker.Stop()
|
||||
t.drainQueue()
|
||||
return
|
||||
case <-ticker.C:
|
||||
t.mutex.Lock()
|
||||
if t.currentTokensInBucket < t.maxBurst {
|
||||
// add (bitrate * S) / 1000 converted to bytes (divide by 8) S
|
||||
// is the update interval in milliseconds
|
||||
t.currentTokensInBucket += (t.rate / 1000) / 8
|
||||
}
|
||||
t.mutex.Unlock()
|
||||
t.drainQueue()
|
||||
case chunk := <-t.c:
|
||||
t.queue.push(chunk)
|
||||
t.drainQueue()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (t *TokenBucketFilter) drainQueue() {
|
||||
for {
|
||||
next := t.queue.peek()
|
||||
if next == nil {
|
||||
break
|
||||
}
|
||||
tokens := len(next.UserData())
|
||||
if t.currentTokensInBucket < tokens {
|
||||
break
|
||||
}
|
||||
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
|
||||
}
|
||||
215
vendor/github.com/pion/transport/vnet/udpproxy.go
generated
vendored
Normal file
215
vendor/github.com/pion/transport/vnet/udpproxy.go
generated
vendored
Normal file
@@ -0,0 +1,215 @@
|
||||
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()
|
||||
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, client *Net, serverAddr *net.UDPAddr) error { // nolint:gocognit
|
||||
// Create vnet for real server by serverAddr.
|
||||
nw := NewNet(&NetConfig{
|
||||
StaticIP: serverAddr.IP.String(),
|
||||
})
|
||||
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
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
44
vendor/github.com/pion/transport/vnet/udpproxy_direct.go
generated
vendored
Normal file
44
vendor/github.com/pion/transport/vnet/udpproxy_direct.go
generated
vendored
Normal file
@@ -0,0 +1,44 @@
|
||||
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, destAddr 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
|
||||
if value, ok := v.endpoints.Load(addr.String()); !ok {
|
||||
return 0, nil
|
||||
} else { // nolint:golint
|
||||
realSocket = value.(*net.UDPConn)
|
||||
}
|
||||
|
||||
// Send to real server.
|
||||
if _, err := realSocket.Write(b); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return len(b), nil
|
||||
}
|
||||
2
vendor/github.com/pion/transport/vnet/vnet.go
generated
vendored
Normal file
2
vendor/github.com/pion/transport/vnet/vnet.go
generated
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
// Package vnet provides a virtual network layer for pion
|
||||
package vnet
|
||||
Reference in New Issue
Block a user