From 35de69e6cd1dbb323318c4615412bd61e99db45d Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Thu, 1 Sep 2022 19:46:19 +0200 Subject: [PATCH] Add a new interface 'transport.Net' With initial implementations by 'stdnet.Net' and 'vnet.Net'. This PR closes #34 and clearly separates the virtual network 'vnet.Net' from the code which is using in production. Furthermore this change allows users to provide their own 'net.Net' implementation for reasons like the one described in #34. --- net.go | 302 ++++++++++++++++ stdnet/net.go | 121 +++++++ vnet/net_native_test.go => stdnet/net_test.go | 53 ++- vnet/README.md | 74 ++-- vnet/chunk.go | 6 +- vnet/chunk_test.go | 2 +- vnet/conn.go | 225 +++++++----- vnet/interface.go | 40 --- vnet/loss_filter_test.go | 15 +- vnet/nat.go | 11 +- vnet/nat_test.go | 16 +- vnet/net.go | 325 ++++++------------ vnet/net_test.go | 165 +++++---- vnet/router.go | 34 +- vnet/router_test.go | 24 +- vnet/stress_test.go | 2 +- vnet/udpproxy.go | 27 +- vnet/udpproxy_direct_test.go | 8 +- vnet/udpproxy_test.go | 30 +- 19 files changed, 900 insertions(+), 580 deletions(-) create mode 100644 net.go create mode 100644 stdnet/net.go rename vnet/net_native_test.go => stdnet/net_test.go (86%) delete mode 100644 vnet/interface.go diff --git a/net.go b/net.go new file mode 100644 index 0000000..e428cc2 --- /dev/null +++ b/net.go @@ -0,0 +1,302 @@ +// Package transport implements various networking related +// functions used throughout the Pion modules. +package transport + +import ( + "errors" + "net" + "time" +) + +var ( + // ErrNoAddressAssigned ... + ErrNoAddressAssigned = errors.New("no address assigned") + // ErrNotSupported ... + ErrNotSupported = errors.New("not supported yey") + // ErrInterfaceNotFound ... + ErrInterfaceNotFound = errors.New("interface not found") + // ErrNotUDPAddress ... + ErrNotUDPAddress = errors.New("not a UDP address") +) + +// Net is an interface providing common networking functions which are +// similar to the functions provided by standard net package. +type Net interface { + // ListenPacket announces on the local network address. + // + // The network must be "udp", "udp4", "udp6", "unixgram", or an IP + // transport. The IP transports are "ip", "ip4", or "ip6" followed by + // a colon and a literal protocol number or a protocol name, as in + // "ip:1" or "ip:icmp". + // + // For UDP and IP networks, if the host in the address parameter is + // empty or a literal unspecified IP address, ListenPacket listens on + // all available IP addresses of the local system except multicast IP + // addresses. + // To only use IPv4, use network "udp4" or "ip4:proto". + // The address can use a host name, but this is not recommended, + // because it will create a listener for at most one of the host's IP + // addresses. + // If the port in the address parameter is empty or "0", as in + // "127.0.0.1:" or "[::1]:0", a port number is automatically chosen. + // The LocalAddr method of PacketConn can be used to discover the + // chosen port. + // + // See func Dial for a description of the network and address + // parameters. + // + // ListenPacket uses context.Background internally; to specify the context, use + // ListenConfig.ListenPacket. + ListenPacket(network string, address string) (net.PacketConn, error) + + // ListenUDP acts like ListenPacket for UDP networks. + // + // The network must be a UDP network name; see func Dial for details. + // + // If the IP field of laddr is nil or an unspecified IP address, + // ListenUDP listens on all available IP addresses of the local system + // except multicast IP addresses. + // If the Port field of laddr is 0, a port number is automatically + // chosen. + ListenUDP(network string, locAddr *net.UDPAddr) (UDPConn, error) + + // Dial connects to the address on the named network. + // + // Known networks are "tcp", "tcp4" (IPv4-only), "tcp6" (IPv6-only), + // "udp", "udp4" (IPv4-only), "udp6" (IPv6-only), "ip", "ip4" + // (IPv4-only), "ip6" (IPv6-only), "unix", "unixgram" and + // "unixpacket". + // + // For TCP and UDP networks, the address has the form "host:port". + // The host must be a literal IP address, or a host name that can be + // resolved to IP addresses. + // The port must be a literal port number or a service name. + // If the host is a literal IPv6 address it must be enclosed in square + // brackets, as in "[2001:db8::1]:80" or "[fe80::1%zone]:80". + // The zone specifies the scope of the literal IPv6 address as defined + // in RFC 4007. + // The functions JoinHostPort and SplitHostPort manipulate a pair of + // host and port in this form. + // When using TCP, and the host resolves to multiple IP addresses, + // Dial will try each IP address in order until one succeeds. + // + // Examples: + // + // Dial("tcp", "golang.org:http") + // Dial("tcp", "192.0.2.1:http") + // Dial("tcp", "198.51.100.1:80") + // Dial("udp", "[2001:db8::1]:domain") + // Dial("udp", "[fe80::1%lo0]:53") + // Dial("tcp", ":80") + // + // For IP networks, the network must be "ip", "ip4" or "ip6" followed + // by a colon and a literal protocol number or a protocol name, and + // the address has the form "host". The host must be a literal IP + // address or a literal IPv6 address with zone. + // It depends on each operating system how the operating system + // behaves with a non-well known protocol number such as "0" or "255". + // + // Examples: + // + // Dial("ip4:1", "192.0.2.1") + // Dial("ip6:ipv6-icmp", "2001:db8::1") + // Dial("ip6:58", "fe80::1%lo0") + // + // For TCP, UDP and IP networks, if the host is empty or a literal + // unspecified IP address, as in ":80", "0.0.0.0:80" or "[::]:80" for + // TCP and UDP, "", "0.0.0.0" or "::" for IP, the local system is + // assumed. + // + // For Unix networks, the address must be a file system path. + Dial(network, address string) (net.Conn, error) + + // DialUDP acts like Dial for UDP networks. + // + // The network must be a UDP network name; see func Dial for details. + // + // If laddr is nil, a local address is automatically chosen. + // If the IP field of raddr is nil or an unspecified IP address, the + // local system is assumed. + DialUDP(network string, laddr, raddr *net.UDPAddr) (UDPConn, error) + + // ResolveIPAddr returns an address of IP end point. + // + // The network must be an IP network name. + // + // If the host in the address parameter is not a literal IP address, + // ResolveIPAddr resolves the address to an address of IP end point. + // Otherwise, it parses the address as a literal IP address. + // The address parameter can use a host name, but this is not + // recommended, because it will return at most one of the host name's + // IP addresses. + // + // See func Dial for a description of the network and address + // parameters. + ResolveIPAddr(network, address string) (*net.IPAddr, error) + + // ResolveUDPAddr returns an address of UDP end point. + // + // The network must be a UDP network name. + // + // If the host in the address parameter is not a literal IP address or + // the port is not a literal port number, ResolveUDPAddr resolves the + // address to an address of UDP end point. + // Otherwise, it parses the address as a pair of literal IP address + // and port number. + // The address parameter can use a host name, but this is not + // recommended, because it will return at most one of the host name's + // IP addresses. + // + // See func Dial for a description of the network and address + // parameters. + ResolveUDPAddr(network, address string) (*net.UDPAddr, error) + + // Interfaces returns a list of the system's network interfaces. + Interfaces() ([]*Interface, error) + + // The following functions are extensions to Go's standard net package + + CreateDialer(dialer *net.Dialer) Dialer +} + +// Dialer is identical to net.Dialer excepts that its methods +// (Dial, DialContext) are overridden to use the Net interface. +// Use vnet.CreateDialer() to create an instance of this Dialer. +type Dialer interface { + Dial(network, address string) (net.Conn, error) +} + +// UDPConn is packet-oriented connection for UDP. +type UDPConn interface { + // Close closes the connection. + // Any blocked Read or Write operations will be unblocked and return errors. + Close() error + + // LocalAddr returns the local network address, if known. + LocalAddr() net.Addr + + // RemoteAddr returns the remote network address, if known. + RemoteAddr() net.Addr + + // SetDeadline sets the read and write deadlines associated + // with the connection. It is equivalent to calling both + // SetReadDeadline and SetWriteDeadline. + // + // A deadline is an absolute time after which I/O operations + // fail instead of blocking. The deadline applies to all future + // and pending I/O, not just the immediately following call to + // Read or Write. After a deadline has been exceeded, the + // connection can be refreshed by setting a deadline in the future. + // + // If the deadline is exceeded a call to Read or Write or to other + // I/O methods will return an error that wraps os.ErrDeadlineExceeded. + // This can be tested using errors.Is(err, os.ErrDeadlineExceeded). + // The error's Timeout method will return true, but note that there + // are other possible errors for which the Timeout method will + // return true even if the deadline has not been exceeded. + // + // An idle timeout can be implemented by repeatedly extending + // the deadline after successful Read or Write calls. + // + // A zero value for t means I/O operations will not time out. + SetDeadline(t time.Time) error + + // SetReadDeadline sets the deadline for future Read calls + // and any currently-blocked Read call. + // A zero value for t means Read will not time out. + SetReadDeadline(t time.Time) error + + // SetWriteDeadline sets the deadline for future Write calls + // and any currently-blocked Write call. + // Even if write times out, it may return n > 0, indicating that + // some of the data was successfully written. + // A zero value for t means Write will not time out. + SetWriteDeadline(t time.Time) error + + // SetReadBuffer sets the size of the operating system's + // receive buffer associated with the connection. + SetReadBuffer(bytes int) error + + // SetWriteBuffer sets the size of the operating system's + // transmit buffer associated with the connection. + SetWriteBuffer(bytes int) error + + // Read reads data from the connection. + // Read can be made to time out and return an error after a fixed + // time limit; see SetDeadline and SetReadDeadline. + Read(b []byte) (n int, err error) + + // ReadFrom reads a packet from the connection, + // copying the payload into p. It returns the number of + // bytes copied into p and the return address that + // was on the packet. + // It returns the number of bytes read (0 <= n <= len(p)) + // and any error encountered. Callers should always process + // the n > 0 bytes returned before considering the error err. + // ReadFrom can be made to time out and return an error after a + // fixed time limit; see SetDeadline and SetReadDeadline. + ReadFrom(p []byte) (n int, addr net.Addr, err error) + + // ReadFromUDP acts like ReadFrom but returns a UDPAddr. + ReadFromUDP(b []byte) (n int, addr *net.UDPAddr, err error) + + // ReadMsgUDP reads a message from c, copying the payload into b and + // the associated out-of-band data into oob. It returns the number of + // bytes copied into b, the number of bytes copied into oob, the flags + // that were set on the message and the source address of the message. + // + // The packages golang.org/x/net/ipv4 and golang.org/x/net/ipv6 can be + // used to manipulate IP-level socket options in oob. + ReadMsgUDP(b, oob []byte) (n, oobn, flags int, addr *net.UDPAddr, err error) + + // Write writes data to the connection. + // Write can be made to time out and return an error after a fixed + // time limit; see SetDeadline and SetWriteDeadline. + Write(b []byte) (n int, err error) + + // WriteTo writes a packet with payload p to addr. + // WriteTo can be made to time out and return an Error after a + // fixed time limit; see SetDeadline and SetWriteDeadline. + // On packet-oriented connections, write timeouts are rare. + WriteTo(p []byte, addr net.Addr) (n int, err error) + + // WriteToUDP acts like WriteTo but takes a UDPAddr. + WriteToUDP(b []byte, addr *net.UDPAddr) (int, error) + + // WriteMsgUDP writes a message to addr via c if c isn't connected, or + // to c's remote address if c is connected (in which case addr must be + // nil). The payload is copied from b and the associated out-of-band + // data is copied from oob. It returns the number of payload and + // out-of-band bytes written. + // + // The packages golang.org/x/net/ipv4 and golang.org/x/net/ipv6 can be + // used to manipulate IP-level socket options in oob. + WriteMsgUDP(b, oob []byte, addr *net.UDPAddr) (n, oobn int, err error) +} + +// Interface wraps a standard net.Interfaces and its assigned addresses +type Interface struct { + net.Interface + addrs []net.Addr +} + +// NewInterface creates a new interface based of a standard net.Interface +func NewInterface(ifc net.Interface) *Interface { + return &Interface{ + Interface: ifc, + addrs: nil, + } +} + +// AddAddress adds a new address to the interface +func (ifc *Interface) AddAddress(addr net.Addr) { + ifc.addrs = append(ifc.addrs, addr) +} + +// Addresses returns a slice of configured addresses on the interface +func (ifc *Interface) Addresses() ([]net.Addr, error) { + if len(ifc.addrs) == 0 { + return nil, ErrNoAddressAssigned + } + return ifc.addrs, nil +} diff --git a/stdnet/net.go b/stdnet/net.go new file mode 100644 index 0000000..91348d1 --- /dev/null +++ b/stdnet/net.go @@ -0,0 +1,121 @@ +// Package stdnet implements the transport.Net interface +// using methods from Go's standard net package. +package stdnet + +import ( + "fmt" + "net" + + "github.com/pion/transport" +) + +const ( + lo0String = "lo0String" + udpString = "udp" +) + +// Net is an implementation of the net.Net interface +// based on functions of the standard net package. +type Net struct { + interfaces []*transport.Interface +} + +// NewNet creates a new StdNet instance. +func NewNet() (*Net, error) { + n := &Net{} + + return n, n.UpdateInterfaces() +} + +// Compile-time assertion +var _ transport.Net = &Net{} + +// UpdateInterfaces updates the internal list of network interfaces +// and associated addresses. +func (n *Net) UpdateInterfaces() error { + ifs := []*transport.Interface{} + + oifs, err := net.Interfaces() + if err != nil { + return err + } + + for _, oif := range oifs { + ifc := transport.NewInterface(oif) + + addrs, err := oif.Addrs() + if err != nil { + return err + } + + for _, addr := range addrs { + ifc.AddAddress(addr) + } + + ifs = append(ifs, ifc) + } + + n.interfaces = ifs + + return nil +} + +// Interfaces returns a slice of interfaces which are available on the +// system +func (n *Net) Interfaces() ([]*transport.Interface, error) { + return n.interfaces, nil +} + +// InterfaceByName returns the interface specified by name. +func (n *Net) InterfaceByName(name string) (*transport.Interface, error) { + for _, ifc := range n.interfaces { + if ifc.Name == name { + return ifc, nil + } + } + + return nil, fmt.Errorf("%w: %s", transport.ErrInterfaceNotFound, name) +} + +// ListenPacket announces on the local network address. +func (n *Net) ListenPacket(network string, address string) (net.PacketConn, error) { + return net.ListenPacket(network, address) +} + +// ListenUDP acts like ListenPacket for UDP networks. +func (n *Net) ListenUDP(network string, locAddr *net.UDPAddr) (transport.UDPConn, error) { + return net.ListenUDP(network, locAddr) +} + +// Dial connects to the address on the named network. +func (n *Net) Dial(network, address string) (net.Conn, error) { + return net.Dial(network, address) +} + +// DialUDP acts like Dial for UDP networks. +func (n *Net) DialUDP(network string, laddr, raddr *net.UDPAddr) (transport.UDPConn, error) { + return net.DialUDP(network, laddr, raddr) +} + +// ResolveIPAddr returns an address of IP end point. +func (n *Net) ResolveIPAddr(network, address string) (*net.IPAddr, error) { + return net.ResolveIPAddr(network, address) +} + +// ResolveUDPAddr returns an address of UDP end point. +func (n *Net) ResolveUDPAddr(network, address string) (*net.UDPAddr, error) { + return net.ResolveUDPAddr(network, address) +} + +type stdDialer struct { + *net.Dialer +} + +func (d stdDialer) Dial(network, address string) (net.Conn, error) { + return d.Dialer.Dial(network, address) +} + +// CreateDialer creates an instance of vnet.Dialer +func (n *Net) CreateDialer(d *net.Dialer) transport.Dialer { + return stdDialer{d} +} diff --git a/vnet/net_native_test.go b/stdnet/net_test.go similarity index 86% rename from vnet/net_native_test.go rename to stdnet/net_test.go index 79b60b1..4d3c9c0 100644 --- a/vnet/net_native_test.go +++ b/stdnet/net_test.go @@ -1,7 +1,7 @@ //go:build !js // +build !js -package vnet +package stdnet import ( "net" @@ -11,22 +11,22 @@ import ( "github.com/stretchr/testify/assert" ) -func TestNetNative(t *testing.T) { +func TestStdNet(t *testing.T) { log := logging.NewDefaultLoggerFactory().NewLogger("test") t.Run("Interfaces", func(t *testing.T) { - nw := NewNet(nil) - assert.False(t, nw.IsVirtual(), "should be false") + nw, err := NewNet() + assert.Nil(t, err, "should succeed") interfaces, err := nw.Interfaces() assert.NoError(t, err, "should succeed") log.Debugf("interfaces: %+v", interfaces) for _, ifc := range interfaces { if ifc.Name == lo0String { - _, err := ifc.Addrs() + _, err := ifc.Addresses() assert.NoError(t, err, "should succeed") } - if addrs, err := ifc.Addrs(); err == nil { + if addrs, err := ifc.Addresses(); err == nil { for _, addr := range addrs { log.Debugf("[%d] %s:%s", ifc.Index, @@ -38,7 +38,8 @@ func TestNetNative(t *testing.T) { }) t.Run("ResolveUDPAddr", func(t *testing.T) { - nw := NewNet(nil) + nw, err := NewNet() + assert.Nil(t, err, "should succeed") udpAddr, err := nw.ResolveUDPAddr(udpString, "localhost:1234") if !assert.NoError(t, err, "should succeed") { @@ -49,7 +50,8 @@ func TestNetNative(t *testing.T) { }) t.Run("ListenPacket", func(t *testing.T) { - nw := NewNet(nil) + nw, err := NewNet() + assert.Nil(t, err, "should succeed") conn, err := nw.ListenPacket(udpString, "127.0.0.1:0") if !assert.NoError(t, err, "should succeed") { @@ -65,7 +67,8 @@ func TestNetNative(t *testing.T) { }) t.Run("ListenUDP random port", func(t *testing.T) { - nw := NewNet(nil) + nw, err := NewNet() + assert.Nil(t, err, "should succeed") srcAddr := &net.UDPAddr{ IP: net.ParseIP("127.0.0.1"), @@ -80,7 +83,8 @@ func TestNetNative(t *testing.T) { }) t.Run("Dial (UDP)", func(t *testing.T) { - nw := NewNet(nil) + nw, err := NewNet() + assert.Nil(t, err, "should succeed") conn, err := nw.Dial(udpString, "127.0.0.1:1234") assert.NoError(t, err, "should succeed") @@ -98,7 +102,8 @@ func TestNetNative(t *testing.T) { }) t.Run("DialUDP", func(t *testing.T) { - nw := NewNet(nil) + nw, err := NewNet() + assert.Nil(t, err, "should succeed") locAddr := &net.UDPAddr{ IP: net.IPv4(127, 0, 0, 1), @@ -126,7 +131,8 @@ func TestNetNative(t *testing.T) { }) t.Run("UDPLoopback", func(t *testing.T) { - nw := NewNet(nil) + nw, err := NewNet() + assert.Nil(t, err, "should succeed") conn, err := nw.ListenPacket(udpString, "127.0.0.1:0") assert.NoError(t, err, "should succeed") @@ -146,7 +152,8 @@ func TestNetNative(t *testing.T) { }) t.Run("Dialer", func(t *testing.T) { - nw := NewNet(nil) + nw, err := NewNet() + assert.Nil(t, err, "should succeed") dialer := nw.CreateDialer(&net.Dialer{ LocalAddr: &net.UDPAddr{ @@ -171,7 +178,7 @@ func TestNetNative(t *testing.T) { }) t.Run("Unexpected operations", func(t *testing.T) { - // For portability of test, find a name of loopack interface name first + // For portability of test, find a name of loopback interface name first var loName string ifs, err := net.Interfaces() assert.NoError(t, err, "should succeed") @@ -182,31 +189,17 @@ func TestNetNative(t *testing.T) { } } - nw := NewNet(nil) + nw, err := NewNet() + assert.Nil(t, err, "should succeed") if len(loName) > 0 { // InterfaceByName ifc, err2 := nw.InterfaceByName(loName) assert.NoError(t, err2, "should succeed") assert.Equal(t, loName, ifc.Name, "should match") - - // getInterface - _, err2 = nw.getInterface(loName) - assert.Error(t, err2, "should fail") } _, err = nw.InterfaceByName("foo0") assert.Error(t, err, "should fail") - - // setRouter - err = nw.setRouter(nil) - assert.Error(t, err, "should fail") - - // onInboundChunk (shouldn't crash) - nw.onInboundChunk(nil) - - // getStaticIPs - ips := nw.getStaticIPs() - assert.Nil(t, ips, "should be nil") }) } diff --git a/vnet/README.md b/vnet/README.md index b502f9f..bd0af25 100644 --- a/vnet/README.md +++ b/vnet/README.md @@ -38,8 +38,8 @@ A virtual network layer for pion. : +-------+ : ...................................... Note: - o: NIC (Netork Interface Controller) - [1]: Net implments NIC interface. + o: NIC (Network Interface Controller) + [1]: Net implements NIC interface. [2]: Root router has no NAT. All child routers have a NAT always. [3]: Router implements NIC interface for accesses from the parent router. @@ -61,12 +61,12 @@ Net provides 3 interfaces: +---------+ 1 * +-----------+ 1 * +-----------+ 1 * +------+ ..| :Router |----+------>o--| :Net |<>------|:Interface |<>------|:Addr | +---------+ | NIC +-----------+ +-----------+ +------+ - | <> (vnet.Interface) (net.Addr) + | <> (transport.Interface) (net.Addr) | | * +-----------+ 1 * +-----------+ 1 * +------+ +------>o--| :Router |<>------|:Interface |<>------|:Addr | NIC +-----------+ +-----------+ +------+ - <> (vnet.Interface) (net.Addr) + <> (transport.Interface) (net.Addr) ``` > The instance of `Net` will be the one passed around the project. @@ -76,8 +76,8 @@ Net provides 3 interfaces: ## 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. +* Each pion package should have config object which has `Net` (of type `transport.Net`) property. + - Just like how we distribute `LoggerFactory` throughout the pion project. * DNS => a simple dictionary (global)? * Each Net has routing capability (a goroutine) * Use interface provided net package as much as possible @@ -86,10 +86,8 @@ Net provides 3 interfaces: - 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. +* 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 @@ -98,13 +96,14 @@ interface. 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 +1. Call Stop(), or Stop(), on the top router, which propagates all other routers #### Example: WAN with one endpoint (vnet) ```go import ( "net" + "github.com/pion/transport" "github.com/pion/transport/vnet" "github.com/pion/logging" ) @@ -141,7 +140,7 @@ if err = wan.Start(); err != nil { // // Stop the router. -// This will stop all internal goroutines in the router tree. +// This will stop all internal Go routines in the router tree. // (No need to call Stop() on child routers) if err = wan.Stop(); err != nil { // handle error @@ -158,17 +157,17 @@ instance (`nw` in the above example) like this: ```go type AgentConfig struct { : - Net: *vnet.Net, + Net: transport.Net, } type Agent struct { : - net: *vnet.Net, + net: transport.Net, } func NetAgent(config *AgentConfig) *Agent { if config.Net == nil { - config.Net = vnet.NewNet(nil) // defaults to native operation + config.Net = vnet.NewNet() } return &Agent { @@ -189,26 +188,25 @@ func (a *Agent) listenUDP(...) error { } ``` - ### Compatibility and Support Status -|`net`
(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.
The use of vnet.Dialer is currently experimental.| +|`net`
(built-in) |`vnet` |Note | +|:--- |:--- |:--- | +| net.Interfaces() | a.net.Interfaces() | | +| net.InterfaceByName() | a.net.InterfaceByName() | | +| net.ResolveUDPAddr() | a.net.ResolveUDPAddr() | | +| net.ListenPacket() | a.net.ListenPacket() | | +| net.ListenUDP() | a.net.ListenUDP() | ListenPacket() is recommended | +| net.Listen() | a.net.Listen() | TODO) | +| net.ListenTCP() | (not supported) | Listen() would be recommended | +| net.Dial() | a.net.Dial() | | +| net.DialUDP() | a.net.DialUDP() | | +| net.DialTCP() | (not supported) | | +| net.Interface | transport.Interface | | +| net.PacketConn | (use it as-is) | | +| net.UDPConn | transport.UDPConn | | +| net.TCPConn | transport.TCPConn | TODO: Use net.Conn in your code | +| net.Dialer | transport.Dialer | Use a.net.CreateDialer() to create it.
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` @@ -221,7 +219,7 @@ func (a *Agent) listenUDP(...) error { * Support of IPv6 * Write a bunch of examples for building virtual networks. * Add network impairment features (on Router) - - Introduce lantecy / jitter + - Introduce latency / jitter - Packet filtering handler (allow selectively drop packets, etc.) * Add statistics data retrieval - Total number of packets forward by each router @@ -230,10 +228,4 @@ func (a *Agent) listenUDP(...) error { ## 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) +* [wireguard-go using GVisor's netstack](https://github.com/WireGuard/wireguard-go/tree/master/tun/netstack) \ No newline at end of file diff --git a/vnet/chunk.go b/vnet/chunk.go index 7a87a2f..56294a2 100644 --- a/vnet/chunk.go +++ b/vnet/chunk.go @@ -155,7 +155,7 @@ func (c *chunkUDP) Clone() Chunk { } func (c *chunkUDP) Network() string { - return udpString + return udp } func (c *chunkUDP) String() string { @@ -170,7 +170,7 @@ func (c *chunkUDP) String() string { } func (c *chunkUDP) setSourceAddr(address string) error { - addr, err := net.ResolveUDPAddr(udpString, address) + addr, err := net.ResolveUDPAddr(udp, address) if err != nil { return err } @@ -180,7 +180,7 @@ func (c *chunkUDP) setSourceAddr(address string) error { } func (c *chunkUDP) setDestinationAddr(address string) error { - addr, err := net.ResolveUDPAddr(udpString, address) + addr, err := net.ResolveUDPAddr(udp, address) if err != nil { return err } diff --git a/vnet/chunk_test.go b/vnet/chunk_test.go index e5ab1eb..5859c1f 100644 --- a/vnet/chunk_test.go +++ b/vnet/chunk_test.go @@ -41,7 +41,7 @@ func TestChunk(t *testing.T) { var c Chunk = newChunkUDP(src, dst) str := c.String() log.Debugf("chunk: %s", str) - assert.Equal(t, udpString, c.Network(), "should match") + assert.Equal(t, udp, c.Network(), "should match") assert.True(t, strings.Contains(str, src.Network()), "should include network type") assert.True(t, strings.Contains(str, src.String()), "should include address") assert.True(t, strings.Contains(str, dst.String()), "should include address") diff --git a/vnet/conn.go b/vnet/conn.go index 6aa788b..729dfaf 100644 --- a/vnet/conn.go +++ b/vnet/conn.go @@ -2,11 +2,14 @@ package vnet import ( "errors" + "fmt" "io" "math" "net" "sync" "time" + + "github.com/pion/transport" ) const ( @@ -22,14 +25,6 @@ var ( 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 @@ -49,6 +44,8 @@ type UDPConn struct { readTimer *time.Timer // thread-safe } +var _ transport.UDPConn = &UDPConn{} + func newUDPConn(locAddr, remAddr *net.UDPAddr, obs connObserver) (*UDPConn, error) { if obs == nil { return nil, errObsCannotBeNil @@ -63,6 +60,84 @@ func newUDPConn(locAddr, remAddr *net.UDPAddr, obs connObserver) (*UDPConn, erro }, nil } +// Close closes the connection. +// Any blocked ReadFrom or WriteTo operations will be unblocked and return errors. +func (c *UDPConn) Close() error { + c.mu.Lock() + defer c.mu.Unlock() + + if c.closed { + return errAlreadyClosed + } + c.closed = true + close(c.readCh) + + c.obs.onClosed(c.locAddr) + return nil +} + +// LocalAddr returns the local network address. +func (c *UDPConn) LocalAddr() net.Addr { + return c.locAddr +} + +// RemoteAddr returns the remote network address. +func (c *UDPConn) RemoteAddr() net.Addr { + return c.remAddr +} + +// SetDeadline sets the read and write deadlines associated +// with the connection. It is equivalent to calling both +// SetReadDeadline and SetWriteDeadline. +// +// A deadline is an absolute time after which I/O operations +// fail with a timeout (see type Error) instead of +// blocking. The deadline applies to all future and pending +// I/O, not just the immediately following call to ReadFrom or +// WriteTo. After a deadline has been exceeded, the connection +// can be refreshed by setting a deadline in the future. +// +// An idle timeout can be implemented by repeatedly extending +// the deadline after successful ReadFrom or WriteTo calls. +// +// A zero value for t means I/O operations will not time out. +func (c *UDPConn) SetDeadline(t time.Time) error { + return c.SetReadDeadline(t) +} + +// SetReadDeadline sets the deadline for future ReadFrom calls +// and any currently-blocked ReadFrom call. +// A zero value for t means ReadFrom will not time out. +func (c *UDPConn) SetReadDeadline(t time.Time) error { + var d time.Duration + var noDeadline time.Time + if t == noDeadline { + d = time.Duration(math.MaxInt64) + } else { + d = time.Until(t) + } + c.readTimer.Reset(d) + return nil +} + +// SetWriteDeadline sets the deadline for future WriteTo calls +// and any currently-blocked WriteTo call. +// Even if write times out, it may return n > 0, indicating that +// some of the data was successfully written. +// A zero value for t means WriteTo will not time out. +func (c *UDPConn) SetWriteDeadline(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 +} + // 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 @@ -113,6 +188,40 @@ loop: } } +// ReadFromUDP acts like ReadFrom but returns a UDPAddr. +func (c *UDPConn) ReadFromUDP(b []byte) (int, *net.UDPAddr, error) { + n, addr, err := c.ReadFrom(b) + + udpAddr, ok := addr.(*net.UDPAddr) + if !ok { + return -1, nil, fmt.Errorf("%w: %s", transport.ErrNotUDPAddress, addr) + } + + return n, udpAddr, err +} + +// ReadMsgUDP reads a message from c, copying the payload into b and +// the associated out-of-band data into oob. It returns the number of +// bytes copied into b, the number of bytes copied into oob, the flags +// that were set on the message and the source address of the message. +// +// The packages golang.org/x/net/ipv4 and golang.org/x/net/ipv6 can be +// used to manipulate IP-level socket options in oob. +func (c *UDPConn) ReadMsgUDP(b, oob []byte) (n, oobn, flags int, addr *net.UDPAddr, err error) { + return -1, -1, -1, nil, transport.ErrNotSupported +} + +// Write writes data to the connection. +// Write can be made to time out and return an Error with Timeout() == true +// after a fixed time limit; see SetDeadline and SetWriteDeadline. +func (c *UDPConn) Write(b []byte) (int, error) { + if c.remAddr == nil { + return 0, errNoRemAddr + } + + return c.WriteTo(b, c.remAddr) +} + // WriteTo writes a packet with payload p to addr. // WriteTo can be made to time out and return // an Error with Timeout() == true after a fixed time limit; @@ -142,93 +251,33 @@ func (c *UDPConn) WriteTo(p []byte, addr net.Addr) (n int, err error) { 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 +// WriteToUDP acts like WriteTo but takes a UDPAddr. +func (c *UDPConn) WriteToUDP(b []byte, addr *net.UDPAddr) (int, error) { + return c.WriteTo(b, addr) } -// SetDeadline sets the read and write deadlines associated -// with the connection. It is equivalent to calling both -// SetReadDeadline and SetWriteDeadline. +// WriteMsgUDP writes a message to addr via c if c isn't connected, or +// to c's remote address if c is connected (in which case addr must be +// nil). The payload is copied from b and the associated out-of-band +// data is copied from oob. It returns the number of payload and +// out-of-band bytes written. // -// 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) +// The packages golang.org/x/net/ipv4 and golang.org/x/net/ipv6 can be +// used to manipulate IP-level socket options in oob. +func (c *UDPConn) WriteMsgUDP(b, oob []byte, addr *net.UDPAddr) (n, oobn int, err error) { + return -1, -1, transport.ErrNotSupported } -// 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 +// SetReadBuffer sets the size of the operating system's +// receive buffer associated with the connection. +func (c *UDPConn) SetReadBuffer(bytes int) error { + return transport.ErrNotSupported } -// 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) +// SetWriteBuffer sets the size of the operating system's +// transmit buffer associated with the connection. +func (c *UDPConn) SetWriteBuffer(bytes int) error { + return transport.ErrNotSupported } func (c *UDPConn) onInboundChunk(chunk Chunk) { diff --git a/vnet/interface.go b/vnet/interface.go deleted file mode 100644 index ec80c0b..0000000 --- a/vnet/interface.go +++ /dev/null @@ -1,40 +0,0 @@ -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 -} diff --git a/vnet/loss_filter_test.go b/vnet/loss_filter_test.go index 45153d2..b6ac48e 100644 --- a/vnet/loss_filter_test.go +++ b/vnet/loss_filter_test.go @@ -4,17 +4,18 @@ import ( "net" "testing" + "github.com/pion/transport" "github.com/stretchr/testify/assert" ) type mockNIC struct { - mockGetInterface func(ifName string) (*Interface, error) + mockGetInterface func(ifName string) (*transport.Interface, error) mockOnInboundChunk func(c Chunk) mockGetStaticIPs func() []net.IP mockSetRouter func(r *Router) error } -func (n *mockNIC) getInterface(ifName string) (*Interface, error) { +func (n *mockNIC) getInterface(ifName string) (*transport.Interface, error) { return n.mockGetInterface(ifName) } @@ -32,19 +33,19 @@ func (n *mockNIC) setRouter(r *Router) error { func newMockNIC(t *testing.T) *mockNIC { return &mockNIC{ - mockGetInterface: func(string) (*Interface, error) { - assert.Fail(t, "unexpceted call to mockGetInterface") + mockGetInterface: func(string) (*transport.Interface, error) { + assert.Fail(t, "unexpected call to mockGetInterface") return nil, nil }, mockOnInboundChunk: func(Chunk) { - assert.Fail(t, "unexpceted call to mockOnInboundChunk") + assert.Fail(t, "unexpected call to mockOnInboundChunk") }, mockGetStaticIPs: func() []net.IP { - assert.Fail(t, "unexpceted call to mockGetStaticIPs") + assert.Fail(t, "unexpected call to mockGetStaticIPs") return nil }, mockSetRouter: func(*Router) error { - assert.Fail(t, "unexpceted call to mockSetRouter") + assert.Fail(t, "unexpected call to mockSetRouter") return nil }, } diff --git a/vnet/nat.go b/vnet/nat.go index 22e437f..7d88a07 100644 --- a/vnet/nat.go +++ b/vnet/nat.go @@ -22,8 +22,9 @@ var ( // 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 +// - Port mapping behavior +// - Filtering behavior +// // See: https://tools.ietf.org/html/rfc4787 type EndpointDependencyType uint8 @@ -58,7 +59,7 @@ type NATType struct { Mode NATMode MappingBehavior EndpointDependencyType FilteringBehavior EndpointDependencyType - Hairpining bool // Not implemented yet + Hairpinning bool // Not implemented yet PortPreservation bool // Not implemented yet MappingLifeTime time.Duration } @@ -151,7 +152,7 @@ func (n *networkAddressTranslator) translateOutbound(from Chunk) (Chunk, error) to := from.Clone() - if from.Network() == udpString { + if from.Network() == udp { if n.natType.Mode == NATModeNAT1To1 { // 1:1 NAT behavior srcAddr := from.SourceAddr().(*net.UDPAddr) //nolint:forcetypeassert @@ -238,7 +239,7 @@ func (n *networkAddressTranslator) translateInbound(from Chunk) (Chunk, error) { to := from.Clone() - if from.Network() == udpString { + if from.Network() == udp { if n.natType.Mode == NATModeNAT1To1 { // 1:1 NAT behavior dstAddr := from.DestinationAddr().(*net.UDPAddr) //nolint:forcetypeassert diff --git a/vnet/nat_test.go b/vnet/nat_test.go index b101b3e..47af353 100644 --- a/vnet/nat_test.go +++ b/vnet/nat_test.go @@ -27,7 +27,7 @@ func TestNATTypeDefaults(t *testing.T) { assert.Equal(t, EndpointIndependent, nat.natType.MappingBehavior, "should match") assert.Equal(t, EndpointIndependent, nat.natType.FilteringBehavior, "should match") - assert.False(t, nat.natType.Hairpining, "should be false") + assert.False(t, nat.natType.Hairpinning, "should be false") assert.False(t, nat.natType.PortPreservation, "should be false") assert.Equal(t, defaultNATMappingLifeTime, nat.natType.MappingLifeTime, "should be false") } @@ -41,7 +41,7 @@ func TestNATMappingBehavior(t *testing.T) { natType: NATType{ MappingBehavior: EndpointIndependent, FilteringBehavior: EndpointIndependent, - Hairpining: false, + Hairpinning: false, MappingLifeTime: 30 * time.Second, }, mappedIPs: []net.IP{net.ParseIP(demoIP)}, @@ -133,7 +133,7 @@ func TestNATMappingBehavior(t *testing.T) { natType: NATType{ MappingBehavior: EndpointIndependent, FilteringBehavior: EndpointAddrDependent, - Hairpining: false, + Hairpinning: false, MappingLifeTime: 30 * time.Second, }, mappedIPs: []net.IP{net.ParseIP(demoIP)}, @@ -257,7 +257,7 @@ func TestNATMappingBehavior(t *testing.T) { natType: NATType{ MappingBehavior: EndpointIndependent, FilteringBehavior: EndpointAddrPortDependent, - Hairpining: false, + Hairpinning: false, MappingLifeTime: 30 * time.Second, }, mappedIPs: []net.IP{net.ParseIP(demoIP)}, @@ -378,7 +378,7 @@ func TestNATMappingBehavior(t *testing.T) { natType: NATType{ MappingBehavior: EndpointAddrDependent, FilteringBehavior: EndpointAddrDependent, - Hairpining: false, + Hairpinning: false, MappingLifeTime: 30 * time.Second, }, mappedIPs: []net.IP{net.ParseIP(demoIP)}, @@ -448,7 +448,7 @@ func TestNATMappingBehavior(t *testing.T) { natType: NATType{ MappingBehavior: EndpointAddrPortDependent, FilteringBehavior: EndpointAddrPortDependent, - Hairpining: false, + Hairpinning: false, MappingLifeTime: 30 * time.Second, }, mappedIPs: []net.IP{net.ParseIP(demoIP)}, @@ -523,7 +523,7 @@ func TestNATMappingTimeout(t *testing.T) { natType: NATType{ MappingBehavior: EndpointIndependent, FilteringBehavior: EndpointIndependent, - Hairpining: false, + Hairpinning: false, MappingLifeTime: 100 * time.Millisecond, }, mappedIPs: []net.IP{net.ParseIP(demoIP)}, @@ -586,7 +586,7 @@ func TestNATMappingTimeout(t *testing.T) { natType: NATType{ MappingBehavior: EndpointIndependent, FilteringBehavior: EndpointIndependent, - Hairpining: false, + Hairpinning: false, MappingLifeTime: 100 * time.Millisecond, }, mappedIPs: []net.IP{net.ParseIP(demoIP)}, diff --git a/vnet/net.go b/vnet/net.go index 60d7028..4abb2f3 100644 --- a/vnet/net.go +++ b/vnet/net.go @@ -9,27 +9,28 @@ import ( "strconv" "strings" "sync" + + "github.com/pion/transport" ) const ( lo0String = "lo0String" - udpString = "udp" + udp = "udp" + udp4 = "udp4" ) 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") + errBindFailedFor = 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 { @@ -39,15 +40,20 @@ func newMACAddress() net.HardwareAddr { return b[2:] } -type vNet struct { - interfaces []*Interface // read-only - staticIPs []net.IP // read-only - router *Router // read-only - udpConns *udpConnMap // read-only +// Net represents a local network stack equivalent to a set of layers from NIC +// up to the transport (UDP / TCP) layer. +type Net struct { + interfaces []*transport.Interface // read-only + staticIPs []net.IP // read-only + router *Router // read-only + udpConns *udpConnMap // read-only mutex sync.RWMutex } -func (v *vNet) _getInterfaces() ([]*Interface, error) { +// Compile-time assertion +var _ transport.Net = &Net{} + +func (v *Net) _getInterfaces() ([]*transport.Interface, error) { if len(v.interfaces) == 0 { return nil, errNoInterface } @@ -55,7 +61,8 @@ func (v *vNet) _getInterfaces() ([]*Interface, error) { return v.interfaces, nil } -func (v *vNet) getInterfaces() ([]*Interface, error) { +// Interfaces returns a list of the system's network interfaces. +func (v *Net) Interfaces() ([]*transport.Interface, error) { v.mutex.RLock() defer v.mutex.RUnlock() @@ -63,7 +70,7 @@ func (v *vNet) getInterfaces() ([]*Interface, error) { } // caller must hold the mutex (read) -func (v *vNet) _getInterface(ifName string) (*Interface, error) { +func (v *Net) _getInterface(ifName string) (*transport.Interface, error) { ifs, err := v._getInterfaces() if err != nil { return nil, err @@ -74,22 +81,27 @@ func (v *vNet) _getInterface(ifName string) (*Interface, error) { } } - return nil, fmt.Errorf("interface %s %w", ifName, errNotFound) + return nil, fmt.Errorf("%w: %s", transport.ErrInterfaceNotFound, ifName) } -func (v *vNet) getInterface(ifName string) (*Interface, error) { +func (v *Net) getInterface(ifName string) (*transport.Interface, error) { v.mutex.RLock() defer v.mutex.RUnlock() return v._getInterface(ifName) } +// InterfaceByName returns the interface specified by name. +func (v *Net) InterfaceByName(ifName string) (*transport.Interface, error) { + return v.getInterface(ifName) +} + // caller must hold the mutex -func (v *vNet) getAllIPAddrs(ipv6 bool) []net.IP { +func (v *Net) getAllIPAddrs(ipv6 bool) []net.IP { ips := []net.IP{} for _, ifc := range v.interfaces { - addrs, err := ifc.Addrs() + addrs, err := ifc.Addresses() if err != nil { continue } @@ -115,7 +127,7 @@ func (v *vNet) getAllIPAddrs(ipv6 bool) []net.IP { return ips } -func (v *vNet) setRouter(r *Router) error { +func (v *Net) setRouter(r *Router) error { v.mutex.Lock() defer v.mutex.Unlock() @@ -123,11 +135,11 @@ func (v *vNet) setRouter(r *Router) error { return nil } -func (v *vNet) onInboundChunk(c Chunk) { +func (v *Net) onInboundChunk(c Chunk) { v.mutex.Lock() defer v.mutex.Unlock() - if c.Network() == udpString { + if c.Network() == udp { if conn, ok := v.udpConns.find(c.DestinationAddr()); ok { conn.onInboundChunk(c) } @@ -135,9 +147,9 @@ func (v *vNet) onInboundChunk(c Chunk) { } // caller must hold the mutex -func (v *vNet) _dialUDP(network string, locAddr, remAddr *net.UDPAddr) (UDPPacketConn, error) { +func (v *Net) _dialUDP(network string, locAddr, remAddr *net.UDPAddr) (transport.UDPConn, error) { // validate network - if network != udpString && network != "udp4" { + if network != udp && network != udp4 { return nil, fmt.Errorf("%w: %s", errUnexpectedNetwork, network) } @@ -193,11 +205,12 @@ func (v *vNet) _dialUDP(network string, locAddr, remAddr *net.UDPAddr) (UDPPacke return conn, nil } -func (v *vNet) listenPacket(network string, address string) (UDPPacketConn, error) { +// ListenPacket announces on the local network address. +func (v *Net) ListenPacket(network string, address string) (net.PacketConn, error) { v.mutex.Lock() defer v.mutex.Unlock() - locAddr, err := v.resolveUDPAddr(network, address) + locAddr, err := v.ResolveUDPAddr(network, address) if err != nil { return nil, err } @@ -205,25 +218,28 @@ func (v *vNet) listenPacket(network string, address string) (UDPPacketConn, erro return v._dialUDP(network, locAddr, nil) } -func (v *vNet) listenUDP(network string, locAddr *net.UDPAddr) (UDPPacketConn, error) { +// ListenUDP acts like ListenPacket for UDP networks. +func (v *Net) ListenUDP(network string, locAddr *net.UDPAddr) (transport.UDPConn, error) { v.mutex.Lock() defer v.mutex.Unlock() return v._dialUDP(network, locAddr, nil) } -func (v *vNet) dialUDP(network string, locAddr, remAddr *net.UDPAddr) (UDPPacketConn, error) { +// DialUDP acts like Dial for UDP networks. +func (v *Net) DialUDP(network string, locAddr, remAddr *net.UDPAddr) (transport.UDPConn, error) { v.mutex.Lock() defer v.mutex.Unlock() return v._dialUDP(network, locAddr, remAddr) } -func (v *vNet) dial(network string, address string) (UDPPacketConn, error) { +// Dial connects to the address on the named network. +func (v *Net) Dial(network string, address string) (net.Conn, error) { v.mutex.Lock() defer v.mutex.Unlock() - remAddr, err := v.resolveUDPAddr(network, address) + remAddr, err := v.ResolveUDPAddr(network, address) if err != nil { return nil, err } @@ -236,21 +252,15 @@ func (v *vNet) dial(network string, address string) (UDPPacketConn, error) { 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 - } +// ResolveIPAddr returns an address of IP end point. +func (v *Net) ResolveIPAddr(network, address string) (*net.IPAddr, error) { + var err error // Check if host is a domain name - ip := net.ParseIP(host) + ip := net.ParseIP(address) if ip == nil { - host = strings.ToLower(host) - if host == "localhost" { + address = strings.ToLower(address) + if address == "localhost" { ip = net.IPv4(127, 0, 0, 1) } else { // host is a domain name. resolve IP address by the name @@ -258,28 +268,50 @@ func (v *vNet) resolveUDPAddr(network, address string) (*net.UDPAddr, error) { return nil, errNoRouterLinked } - ip, err = v.router.resolver.lookUp(host) + ip, err = v.router.resolver.lookUp(address) if err != nil { return nil, err } } } + return &net.IPAddr{ + IP: ip, + }, nil +} + +// ResolveUDPAddr returns an address of UDP end point. +func (v *Net) ResolveUDPAddr(network, address string) (*net.UDPAddr, error) { + if network != udp && network != udp4 { + return nil, fmt.Errorf("%w %s", errUnknownNetwork, network) + } + + host, sPort, err := net.SplitHostPort(address) + if err != nil { + return nil, err + } + + ipAddress, err := v.ResolveIPAddr("ip", host) + if err != nil { + return nil, err + } + port, err := strconv.Atoi(sPort) if err != nil { return nil, errInvalidPortNumber } udpAddr := &net.UDPAddr{ - IP: ip, + IP: ipAddress.IP, + Zone: ipAddress.Zone, Port: port, } return udpAddr, nil } -func (v *vNet) write(c Chunk) error { - if c.Network() == udpString { +func (v *Net) write(c Chunk) error { + if c.Network() == udp { if udp, ok := c.(*chunkUDP); ok { if c.getDestinationIP().IsLoopback() { if conn, ok := v.udpConns.find(udp.DestinationAddr()); ok { @@ -300,8 +332,8 @@ func (v *vNet) write(c Chunk) error { return nil } -func (v *vNet) onClosed(addr net.Addr) { - if addr.Network() == udpString { +func (v *Net) onClosed(addr net.Addr) { + if addr.Network() == udp { //nolint:errcheck v.udpConns.delete(addr) // #nosec } @@ -311,7 +343,7 @@ func (v *vNet) onClosed(addr net.Addr) { // 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 { +func (v *Net) determineSourceIP(locIP, dstIP net.IP) net.IP { if locIP != nil && !locIP.IsUnspecified() { return locIP } @@ -326,7 +358,7 @@ func (v *vNet) determineSourceIP(locIP, dstIP net.IP) net.IP { return nil } - addrs, err2 := ifc.Addrs() + addrs, err2 := ifc.Addresses() if err2 != nil { return nil } @@ -362,9 +394,9 @@ func (v *vNet) determineSourceIP(locIP, dstIP net.IP) net.IP { } // caller must hold the mutex -func (v *vNet) hasIPAddr(ip net.IP) bool { //nolint:gocognit +func (v *Net) hasIPAddr(ip net.IP) bool { //nolint:gocognit for _, ifc := range v.interfaces { - if addrs, err := ifc.Addrs(); err == nil { + if addrs, err := ifc.Addresses(); err == nil { for _, addr := range addrs { var locIP net.IP if ipNet, ok := addr.(*net.IPNet); ok { @@ -397,7 +429,7 @@ func (v *vNet) hasIPAddr(ip net.IP) bool { //nolint:gocognit } // caller must hold the mutex -func (v *vNet) allocateLocalAddr(ip net.IP, port int) error { +func (v *Net) allocateLocalAddr(ip net.IP, port int) error { // gather local IP addresses to bind var ips []net.IP if ip.IsUnspecified() { @@ -407,7 +439,7 @@ func (v *vNet) allocateLocalAddr(ip net.IP, port int) error { } if len(ips) == 0 { - return fmt.Errorf("%w %s", errBindFailerFor, ip.String()) + return fmt.Errorf("%w %s", errBindFailedFor, ip.String()) } // check if all these transport addresses are not in use @@ -419,7 +451,7 @@ func (v *vNet) allocateLocalAddr(ip net.IP, port int) error { if _, ok := v.udpConns.find(addr); ok { return &net.OpError{ Op: "bind", - Net: udpString, + Net: udp, Addr: addr, Err: fmt.Errorf("bind: %w", errAddressAlreadyInUse), } @@ -430,7 +462,7 @@ func (v *vNet) allocateLocalAddr(ip net.IP, port int) error { } // caller must hold the mutex -func (v *vNet) assignPort(ip net.IP, start, end int) (int, error) { +func (v *Net) assignPort(ip net.IP, start, end int) (int, error) { // choose randomly from the range between start and end (inclusive) if end < start { return -1, errEndPortLessThanStart @@ -450,6 +482,10 @@ func (v *vNet) assignPort(ip net.IP, start, end int) (int, error) { return -1, errPortSpaceExhausted } +func (v *Net) getStaticIPs() []net.IP { + return v.staticIPs +} + // NetConfig is a bag of configuration parameters passed to NewNet(). type NetConfig struct { // StaticIPs is an array of static IP addresses to be assigned for this Net. @@ -461,51 +497,25 @@ type NetConfig struct { 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. +// NewNet creates an instance of a virtual network. +// // By design, it always have lo0 and eth0 interfaces. // The lo0 has the address 127.0.0.1 assigned by default. // IP address for eth0 will be assigned when this Net is added to a router. func NewNet(config *NetConfig) *Net { - 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{ + lo0 := transport.NewInterface(net.Interface{ Index: 1, MTU: 16384, Name: lo0String, HardwareAddr: nil, Flags: net.FlagUp | net.FlagLoopback | net.FlagMulticast, }) - lo0.AddAddr(&net.IPNet{ + lo0.AddAddress(&net.IPNet{ IP: net.ParseIP("127.0.0.1"), Mask: net.CIDRMask(8, 32), }) - eth0 := NewInterface(net.Interface{ + eth0 := transport.NewInterface(net.Interface{ Index: 2, MTU: 1500, Name: "eth0", @@ -525,153 +535,26 @@ func NewNet(config *NetConfig) *Net { } } - v := &vNet{ - interfaces: []*Interface{lo0, eth0}, + return &Net{ + interfaces: []*transport.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, +func (v *Net) CreateDialer(d *net.Dialer) transport.Dialer { + return &dialer{ + dialer: d, + net: 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 { +type dialer struct { dialer *net.Dialer - v *vNet + net *Net } -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) +func (d *dialer) Dial(network, address string) (net.Conn, error) { + return d.net.Dial(network, address) } diff --git a/vnet/net_test.go b/vnet/net_test.go index 3fad686..c982243 100644 --- a/vnet/net_test.go +++ b/vnet/net_test.go @@ -6,6 +6,7 @@ import ( "testing" "github.com/pion/logging" + "github.com/pion/transport" "github.com/stretchr/testify/assert" ) @@ -13,15 +14,13 @@ func TestNetVirtual(t *testing.T) { loggerFactory := logging.NewDefaultLoggerFactory() log := logging.NewDefaultLoggerFactory().NewLogger("test") - t.Run("Interfaces", func(t *testing.T) { + t.Run("tnet.Interfaces", func(t *testing.T) { nw := NewNet(&NetConfig{}) - assert.True(t, nw.IsVirtual(), "should be true") - - interfaces, err := nw.Interfaces() - assert.Equal(t, 2, len(interfaces), "should be one interface") + intfs, err := nw.Interfaces() + assert.Equal(t, 2, len(intfs), "should be one tnet.Interface") assert.NoError(t, err, "should succeed") - for _, ifc := range interfaces { + for _, ifc := range intfs { switch ifc.Name { case lo0String: assert.Equal(t, 1, ifc.Index, "Index mismatch") @@ -35,7 +34,7 @@ func TestNetVirtual(t *testing.T) { ifc.Flags, "Flags mismatch") - addrs, err := ifc.Addrs() + addrs, err := ifc.Addresses() assert.NoError(t, err, "should succeed") assert.Equal(t, 1, len(addrs), "should be one address") case "eth0": @@ -47,13 +46,13 @@ func TestNetVirtual(t *testing.T) { ifc.Flags, "Flags mismatch") - _, err := ifc.Addrs() + _, err := ifc.Addresses() assert.NotNil(t, err, "should fail") default: - assert.Fail(t, "unknown interface: %v", ifc.Name) + assert.Fail(t, "unknown tnet.Interface: %v", ifc.Name) } - if addrs, err := ifc.Addrs(); err == nil { + if addrs, err := ifc.Addresses(); err == nil { for _, addr := range addrs { log.Debugf("[%d] %s:%s", ifc.Index, @@ -64,14 +63,14 @@ func TestNetVirtual(t *testing.T) { } }) - t.Run("InterfaceByName", func(t *testing.T) { + t.Run("tnet.InterfaceByName", func(t *testing.T) { nw := NewNet(&NetConfig{}) - interfaces, err := nw.Interfaces() - assert.Equal(t, 2, len(interfaces), "should be one interface") + intfs, err := nw.Interfaces() + assert.Equal(t, 2, len(intfs), "should be one tnet.Interface") assert.NoError(t, err, "should succeed") - var ifc *Interface + var ifc *transport.Interface ifc, err = nw.InterfaceByName(lo0String) assert.NoError(t, err, "should succeed") @@ -87,7 +86,7 @@ func TestNetVirtual(t *testing.T) { ifc.Flags, "Flags mismatch") - addrs, err2 := ifc.Addrs() + addrs, err2 := ifc.Addresses() assert.NoError(t, err2, "should succeed") assert.Equal(t, 1, len(addrs), "should be one address") } @@ -102,7 +101,7 @@ func TestNetVirtual(t *testing.T) { ifc.Flags, "Flags mismatch") - _, err = ifc.Addrs() + _, err = ifc.Addresses() assert.NotNil(t, err, "should fail") _, err = nw.InterfaceByName("foo0") @@ -112,47 +111,47 @@ func TestNetVirtual(t *testing.T) { t.Run("hasIPAddr", func(t *testing.T) { nw := NewNet(&NetConfig{}) - interfaces, err := nw.Interfaces() - assert.Equal(t, 2, len(interfaces), "should be one interface") + intfs, err := nw.Interfaces() + assert.Equal(t, 2, len(intfs), "should be one tnet.Interface") assert.NoError(t, err, "should succeed") - var ifc *Interface + var ifc *transport.Interface ifc, err = nw.InterfaceByName("eth0") assert.NoError(t, err, "should succeed") - ifc.AddAddr(&net.IPNet{ + ifc.AddAddress(&net.IPNet{ IP: net.ParseIP("10.1.2.3"), Mask: net.CIDRMask(24, 32), }) - _, err = ifc.Addrs() + _, err = ifc.Addresses() assert.NoError(t, err, "should succeed") - assert.True(t, nw.v.hasIPAddr(net.ParseIP("127.0.0.1")), + assert.True(t, nw.hasIPAddr(net.ParseIP("127.0.0.1")), "the IP addr should exist") - assert.True(t, nw.v.hasIPAddr(net.ParseIP("10.1.2.3")), + assert.True(t, nw.hasIPAddr(net.ParseIP("10.1.2.3")), "the IP addr should exist") - assert.False(t, nw.v.hasIPAddr(net.ParseIP("192.168.1.1")), + assert.False(t, nw.hasIPAddr(net.ParseIP("192.168.1.1")), "the IP addr should NOT exist") }) t.Run("getAllIPAddrs", func(t *testing.T) { nw := NewNet(&NetConfig{}) - interfaces, err := nw.Interfaces() - assert.Equal(t, 2, len(interfaces), "should be one interface") + intfs, err := nw.Interfaces() + assert.Equal(t, 2, len(intfs), "should be one tnet.Interface") assert.NoError(t, err, "should succeed") - var ifc *Interface + var ifc *transport.Interface ifc, err = nw.InterfaceByName("eth0") assert.NoError(t, err, "should succeed") - ifc.AddAddr(&net.IPNet{ + ifc.AddAddress(&net.IPNet{ IP: net.ParseIP("10.1.2.3"), Mask: net.CIDRMask(24, 32), }) - ips := nw.v.getAllIPAddrs(false) + ips := nw.getAllIPAddrs(false) assert.Equal(t, 2, len(ips), "should match") for _, ip := range ips { @@ -168,25 +167,25 @@ func TestNetVirtual(t *testing.T) { end := 1002 space := end + 1 - start - interfaces, err := nw.Interfaces() - assert.Equal(t, 2, len(interfaces), "should be one interface") + intfs, err := nw.Interfaces() + assert.Equal(t, 2, len(intfs), "should be one tnet.Interface") assert.NoError(t, err, "should succeed") - var ifc *Interface + var ifc *transport.Interface ifc, err = nw.InterfaceByName("eth0") assert.NoError(t, err, "should succeed") - ifc.AddAddr(&net.IPNet{ + ifc.AddAddress(&net.IPNet{ IP: net.ParseIP(addr), Mask: net.CIDRMask(24, 32), }) // attempt to assign port with start > end should fail - _, err = nw.v.assignPort(net.ParseIP(addr), 3000, 2999) + _, err = nw.assignPort(net.ParseIP(addr), 3000, 2999) assert.NotNil(t, err, "should fail") for i := 0; i < space; i++ { - port, err2 := nw.v.assignPort(net.ParseIP(addr), start, end) + port, err2 := nw.assignPort(net.ParseIP(addr), start, end) assert.NoError(t, err2, "should succeed") log.Debugf("[%d] got port: %d", i, port) @@ -195,29 +194,29 @@ func TestNetVirtual(t *testing.T) { Port: port, }, nil, &myConnObserver{}) assert.NoError(t, err2, "should succeed") - err2 = nw.v.udpConns.insert(conn) + err2 = nw.udpConns.insert(conn) assert.NoError(t, err2, "should succeed") } - assert.Equal(t, space, nw.v.udpConns.size(), "should match") + assert.Equal(t, space, nw.udpConns.size(), "should match") // attempt to assign again should fail - _, err = nw.v.assignPort(net.ParseIP(addr), start, end) + _, err = nw.assignPort(net.ParseIP(addr), start, end) assert.NotNil(t, err, "should fail") }) t.Run("determineSourceIP()", func(t *testing.T) { nw := NewNet(&NetConfig{}) - interfaces, err := nw.Interfaces() - assert.Equal(t, 2, len(interfaces), "should be one interface") + intfs, err := nw.Interfaces() + assert.Equal(t, 2, len(intfs), "should be one tnet.Interface") assert.NoError(t, err, "should succeed") - var ifc *Interface + var ifc *transport.Interface ifc, err = nw.InterfaceByName("eth0") assert.NoError(t, err, "should succeed") - ifc.AddAddr(&net.IPNet{ + ifc.AddAddress(&net.IPNet{ IP: net.ParseIP(demoIP), Mask: net.CIDRMask(24, 32), }) @@ -225,7 +224,7 @@ func TestNetVirtual(t *testing.T) { // Any IP turned into non-loopback IP anyIP := net.ParseIP("0.0.0.0") dstIP := net.ParseIP("27.1.7.135") - srcIP := nw.v.determineSourceIP(anyIP, dstIP) + srcIP := nw.determineSourceIP(anyIP, dstIP) log.Debugf("anyIP: %s => %s", anyIP.String(), srcIP.String()) assert.NotNil(t, srcIP, "shouldn't be nil") assert.Equal(t, srcIP.String(), demoIP, "use non-loopback IP") @@ -233,7 +232,7 @@ func TestNetVirtual(t *testing.T) { // Any IP turned into loopback IP anyIP = net.ParseIP("0.0.0.0") dstIP = net.ParseIP("127.0.0.2") - srcIP = nw.v.determineSourceIP(anyIP, dstIP) + srcIP = nw.determineSourceIP(anyIP, dstIP) log.Debugf("anyIP: %s => %s", anyIP.String(), srcIP.String()) assert.NotNil(t, srcIP, "shouldn't be nil") assert.Equal(t, srcIP.String(), "127.0.0.1", "use loopback IP") @@ -241,7 +240,7 @@ func TestNetVirtual(t *testing.T) { // Non any IP won't change anyIP = net.ParseIP(demoIP) dstIP = net.ParseIP("127.0.0.2") - srcIP = nw.v.determineSourceIP(anyIP, dstIP) + srcIP = nw.determineSourceIP(anyIP, dstIP) log.Debugf("anyIP: %s => %s", anyIP.String(), srcIP.String()) assert.NotNil(t, srcIP, "shouldn't be nil") assert.True(t, srcIP.Equal(anyIP), "IP change") @@ -250,7 +249,7 @@ func TestNetVirtual(t *testing.T) { t.Run("ResolveUDPAddr", func(t *testing.T) { nw := NewNet(&NetConfig{}) - udpAddr, err := nw.ResolveUDPAddr(udpString, "localhost:1234") + udpAddr, err := nw.ResolveUDPAddr(udp, "localhost:1234") if !assert.NoError(t, err, "should succeed") { return } @@ -261,7 +260,7 @@ func TestNetVirtual(t *testing.T) { t.Run("UDPLoopback", func(t *testing.T) { nw := NewNet(&NetConfig{}) - conn, err := nw.ListenPacket(udpString, "127.0.0.1:0") + conn, err := nw.ListenPacket(udp, "127.0.0.1:0") assert.NoError(t, err, "should succeed") laddr := conn.LocalAddr() msg := "PING!" @@ -276,37 +275,37 @@ func TestNetVirtual(t *testing.T) { assert.Equal(t, msg, string(buf[:n]), "should match") assert.Equal(t, laddr.(*net.UDPAddr).String(), addr.(*net.UDPAddr).String(), "should match") //nolint:forcetypeassert - assert.Equal(t, 1, nw.v.udpConns.size(), "should match") + assert.Equal(t, 1, nw.udpConns.size(), "should match") assert.NoError(t, conn.Close(), "should succeed") - assert.Equal(t, 0, nw.v.udpConns.size(), "should match") + assert.Equal(t, 0, nw.udpConns.size(), "should match") }) t.Run("ListenPacket random port", func(t *testing.T) { nw := NewNet(&NetConfig{}) - conn, err := nw.ListenPacket(udpString, "127.0.0.1:0") + conn, err := nw.ListenPacket(udp, "127.0.0.1:0") assert.NoError(t, err, "should succeed") laddr := conn.LocalAddr().String() log.Debugf("laddr: %s", laddr) - assert.Equal(t, 1, nw.v.udpConns.size(), "should match") + assert.Equal(t, 1, nw.udpConns.size(), "should match") assert.NoError(t, conn.Close(), "should succeed") - assert.Equal(t, 0, nw.v.udpConns.size(), "should match") + assert.Equal(t, 0, nw.udpConns.size(), "should match") }) t.Run("ListenPacket specific port", func(t *testing.T) { nw := NewNet(&NetConfig{}) - conn, err := nw.ListenPacket(udpString, "127.0.0.1:50916") + conn, err := nw.ListenPacket(udp, "127.0.0.1:50916") assert.NoError(t, err, "should succeed") laddr := conn.LocalAddr().String() assert.Equal(t, "127.0.0.1:50916", laddr, "should match") - assert.Equal(t, 1, nw.v.udpConns.size(), "should match") + assert.Equal(t, 1, nw.udpConns.size(), "should match") assert.NoError(t, conn.Close(), "should succeed") - assert.Equal(t, 0, nw.v.udpConns.size(), "should match") + assert.Equal(t, 0, nw.udpConns.size(), "should match") }) t.Run("ListenUDP random port", func(t *testing.T) { @@ -315,15 +314,15 @@ func TestNetVirtual(t *testing.T) { srcAddr := &net.UDPAddr{ IP: net.ParseIP("127.0.0.1"), } - conn, err := nw.ListenUDP(udpString, srcAddr) + conn, err := nw.ListenUDP(udp, srcAddr) assert.NoError(t, err, "should succeed") laddr := conn.LocalAddr().String() log.Debugf("laddr: %s", laddr) - assert.Equal(t, 1, nw.v.udpConns.size(), "should match") + assert.Equal(t, 1, nw.udpConns.size(), "should match") assert.NoError(t, conn.Close(), "should succeed") - assert.Equal(t, 0, nw.v.udpConns.size(), "should match") + assert.Equal(t, 0, nw.udpConns.size(), "should match") }) t.Run("ListenUDP specific port", func(t *testing.T) { @@ -333,21 +332,21 @@ func TestNetVirtual(t *testing.T) { IP: net.ParseIP("127.0.0.1"), Port: 60916, } - conn, err := nw.ListenUDP(udpString, srcAddr) + conn, err := nw.ListenUDP(udp, srcAddr) assert.NoError(t, err, "should succeed") laddr := conn.LocalAddr().String() assert.Equal(t, "127.0.0.1:60916", laddr, "should match") - assert.Equal(t, 1, nw.v.udpConns.size(), "should match") + assert.Equal(t, 1, nw.udpConns.size(), "should match") assert.NoError(t, conn.Close(), "should succeed") - assert.Equal(t, 0, nw.v.udpConns.size(), "should match") + assert.Equal(t, 0, nw.udpConns.size(), "should match") }) t.Run("Dial (UDP) lo0", func(t *testing.T) { nw := NewNet(&NetConfig{}) - conn, err := nw.Dial(udpString, "127.0.0.1:1234") + conn, err := nw.Dial(udp, "127.0.0.1:1234") assert.NoError(t, err, "should succeed") laddr := conn.LocalAddr() @@ -359,9 +358,9 @@ func TestNetVirtual(t *testing.T) { assert.Equal(t, "127.0.0.1", laddr.(*net.UDPAddr).IP.String(), "should match") //nolint:forcetypeassert assert.True(t, laddr.(*net.UDPAddr).Port != 0, "should match") //nolint:forcetypeassert assert.Equal(t, "127.0.0.1:1234", raddr.String(), "should match") - assert.Equal(t, 1, nw.v.udpConns.size(), "should match") + assert.Equal(t, 1, nw.udpConns.size(), "should match") assert.NoError(t, conn.Close(), "should succeed") - assert.Equal(t, 0, nw.v.udpConns.size(), "should match") + assert.Equal(t, 0, nw.udpConns.size(), "should match") }) t.Run("Dial (UDP) eth0", func(t *testing.T) { @@ -376,7 +375,7 @@ func TestNetVirtual(t *testing.T) { assert.NoError(t, wan.AddNet(nw), "should succeed") - conn, err := nw.Dial(udpString, "27.3.4.5:1234") + conn, err := nw.Dial(udp, "27.3.4.5:1234") assert.NoError(t, err, "should succeed") laddr := conn.LocalAddr() @@ -388,9 +387,9 @@ func TestNetVirtual(t *testing.T) { assert.Equal(t, "1.2.3.1", laddr.(*net.UDPAddr).IP.String(), "should match") //nolint:forcetypeassert assert.True(t, laddr.(*net.UDPAddr).Port != 0, "should match") //nolint:forcetypeassert assert.Equal(t, "27.3.4.5:1234", raddr.String(), "should match") - assert.Equal(t, 1, nw.v.udpConns.size(), "should match") + assert.Equal(t, 1, nw.udpConns.size(), "should match") assert.NoError(t, conn.Close(), "should succeed") - assert.Equal(t, 0, nw.v.udpConns.size(), "should match") + assert.Equal(t, 0, nw.udpConns.size(), "should match") }) t.Run("DialUDP", func(t *testing.T) { @@ -406,7 +405,7 @@ func TestNetVirtual(t *testing.T) { Port: 1234, } - conn, err := nw.DialUDP(udpString, locAddr, remAddr) + conn, err := nw.DialUDP(udp, locAddr, remAddr) assert.NoError(t, err, "should succeed") laddr := conn.LocalAddr() @@ -418,9 +417,9 @@ func TestNetVirtual(t *testing.T) { assert.Equal(t, "127.0.0.1", laddr.(*net.UDPAddr).IP.String(), "should match") //nolint:forcetypeassert assert.True(t, laddr.(*net.UDPAddr).Port != 0, "should match") //nolint:forcetypeassert assert.Equal(t, "127.0.0.1:1234", raddr.String(), "should match") - assert.Equal(t, 1, nw.v.udpConns.size(), "should match") + assert.Equal(t, 1, nw.udpConns.size(), "should match") assert.NoError(t, conn.Close(), "should succeed") - assert.Equal(t, 0, nw.v.udpConns.size(), "should match") + assert.Equal(t, 0, nw.udpConns.size(), "should match") }) t.Run("Resolver", func(t *testing.T) { @@ -438,7 +437,7 @@ func TestNetVirtual(t *testing.T) { assert.NoError(t, wan.AddNet(nw), "should succeed") - conn, err := nw.Dial(udpString, "test.pion.ly:1234") + conn, err := nw.Dial(udp, "test.pion.ly:1234") assert.NoError(t, err, "should succeed") laddr := conn.LocalAddr() @@ -450,15 +449,15 @@ func TestNetVirtual(t *testing.T) { assert.Equal(t, "1.2.3.1", laddr.(*net.UDPAddr).IP.String(), "should match") //nolint:forcetypeassert assert.True(t, laddr.(*net.UDPAddr).Port != 0, "should match") //nolint:forcetypeassert assert.Equal(t, "30.31.32.33:1234", raddr.String(), "should match") - assert.Equal(t, 1, nw.v.udpConns.size(), "should match") + assert.Equal(t, 1, nw.udpConns.size(), "should match") assert.NoError(t, conn.Close(), "should succeed") - assert.Equal(t, 0, nw.v.udpConns.size(), "should match") + assert.Equal(t, 0, nw.udpConns.size(), "should match") }) t.Run("Loopback", func(t *testing.T) { nw := NewNet(&NetConfig{}) - conn, err := nw.ListenPacket(udpString, "127.0.0.1:50916") + conn, err := nw.ListenPacket(udp, "127.0.0.1:50916") assert.NoError(t, err, "should succeed") laddr := conn.LocalAddr() @@ -499,7 +498,7 @@ func TestNetVirtual(t *testing.T) { close(doneCh) }() - nw.v.onInboundChunk(c) + nw.onInboundChunk(c) loop: for { @@ -512,7 +511,7 @@ func TestNetVirtual(t *testing.T) { } } - assert.Equal(t, 0, nw.v.udpConns.size(), "should match") + assert.Equal(t, 0, nw.udpConns.size(), "should match") assert.True(t, hasReceived, "should have received data") }) @@ -542,13 +541,13 @@ func TestNetVirtual(t *testing.T) { assert.NoError(t, err, "should succeed") conn1, err := net1.ListenPacket( - udpString, + udp, fmt.Sprintf("%s:%d", ip1, 1234), ) assert.NoError(t, err, "should succeed") conn2, err := net2.ListenPacket( - udpString, + udp, fmt.Sprintf("%s:%d", ip2, 5678), ) assert.NoError(t, err, "should succeed") @@ -628,7 +627,7 @@ func TestNetVirtual(t *testing.T) { }, }) - conn, err := dialer.Dial(udpString, "127.0.0.1:1234") + conn, err := dialer.Dial(udp, "127.0.0.1:1234") assert.NoError(t, err, "should succeed") laddr := conn.LocalAddr() @@ -640,9 +639,9 @@ func TestNetVirtual(t *testing.T) { assert.Equal(t, "127.0.0.1", laddr.(*net.UDPAddr).IP.String(), "should match") //nolint:forcetypeassert assert.True(t, laddr.(*net.UDPAddr).Port != 0, "should match") //nolint:forcetypeassert assert.Equal(t, "127.0.0.1:1234", raddr.String(), "should match") - assert.Equal(t, 1, nw.v.udpConns.size(), "should match") + assert.Equal(t, 1, nw.udpConns.size(), "should match") assert.NoError(t, conn.Close(), "should succeed") - assert.Equal(t, 0, nw.v.udpConns.size(), "should match") + assert.Equal(t, 0, nw.udpConns.size(), "should match") }) t.Run("Two IPs on a NIC", func(t *testing.T) { @@ -670,10 +669,10 @@ func TestNetVirtual(t *testing.T) { err = wan.Start() assert.NoError(t, err, "should succeed") - conn1, err := net1.ListenPacket(udpString, "1.2.3.4:1234") + conn1, err := net1.ListenPacket(udp, "1.2.3.4:1234") assert.NoError(t, err, "should succeed") - conn2, err := net1.ListenPacket(udpString, "1.2.3.5:1234") + conn2, err := net1.ListenPacket(udp, "1.2.3.5:1234") assert.NoError(t, err, "should succeed") conn1RcvdCh := make(chan bool) diff --git a/vnet/router.go b/vnet/router.go index d75646f..2b09ed4 100644 --- a/vnet/router.go +++ b/vnet/router.go @@ -11,6 +11,7 @@ import ( "time" "github.com/pion/logging" + "github.com/pion/transport" ) const ( @@ -63,9 +64,9 @@ type RouterConfig struct { LoggerFactory logging.LoggerFactory } -// NIC is a nework inerface controller that interfaces Router +// NIC is a network interface controller that interfaces Router type NIC interface { - getInterface(ifName string) (*Interface, error) + getInterface(ifName string) (*transport.Interface, error) onInboundChunk(c Chunk) getStaticIPs() []net.IP setRouter(r *Router) error @@ -78,7 +79,7 @@ type ChunkFilter func(c Chunk) bool // Router ... type Router struct { name string // read-only - interfaces []*Interface // read-only + interfaces []*transport.Interface // read-only ipv4Net *net.IPNet // read-only staticIPs []net.IP // read-only staticLocalIPs map[string]net.IP // read-only, @@ -116,17 +117,17 @@ func NewRouter(config *RouterConfig) (*Router, error) { } // set up network interface, lo0 - lo0 := NewInterface(net.Interface{ + lo0 := transport.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: ""}) + lo0.AddAddress(&net.IPAddr{IP: net.ParseIP("127.0.0.1"), Zone: ""}) // set up network interface, eth0 - eth0 := NewInterface(net.Interface{ + eth0 := transport.NewInterface(net.Interface{ Index: 2, MTU: 1500, Name: "eth0", @@ -177,7 +178,7 @@ func NewRouter(config *RouterConfig) (*Router, error) { return &Router{ name: name, - interfaces: []*Interface{lo0, eth0}, + interfaces: []*transport.Interface{lo0, eth0}, ipv4Net: ipv4Net, staticIPs: staticIPs, staticLocalIPs: staticLocalIPs, @@ -194,7 +195,7 @@ func NewRouter(config *RouterConfig) (*Router, error) { } // caller must hold the mutex -func (r *Router) getInterfaces() ([]*Interface, error) { +func (r *Router) getInterfaces() ([]*transport.Interface, error) { if len(r.interfaces) == 0 { return nil, fmt.Errorf("%w is available", errNoInterface) } @@ -202,7 +203,7 @@ func (r *Router) getInterfaces() ([]*Interface, error) { return r.interfaces, nil } -func (r *Router) getInterface(ifName string) (*Interface, error) { +func (r *Router) getInterface(ifName string) (*transport.Interface, error) { r.mutex.RLock() defer r.mutex.RUnlock() @@ -216,7 +217,7 @@ func (r *Router) getInterface(ifName string) (*Interface, error) { } } - return nil, fmt.Errorf("interface %s %w", ifName, errNotFound) + return nil, fmt.Errorf("%w: %s", transport.ErrInterfaceNotFound, ifName) } // Start ... @@ -316,7 +317,7 @@ func (r *Router) addNIC(nic NIC) error { return fmt.Errorf("%w: %s", errStaticIPisBeyondSubnet, r.ipv4Net.String()) } - ifc.AddAddr(&net.IPNet{ + ifc.AddAddress(&net.IPNet{ IP: ip, Mask: r.ipv4Net.Mask, }) @@ -482,7 +483,7 @@ func (r *Router) processChunks() (time.Duration, error) { dstIP := c.getDestinationIP() - // check if the desination is in our subnet + // check if the destination is in our subnet if r.ipv4Net.Contains(dstIP) { // search for the destination NIC var nic NIC @@ -520,7 +521,7 @@ func (r *Router) processChunks() (time.Duration, error) { //nolint:godox /* FIXME: this implementation would introduce a duplicate packet! - if r.nat.natType.Hairpining { + if r.nat.natType.Hairpinning { hairpinned, err := r.nat.translateInbound(toParent) if err != nil { r.log.Warnf("[%s] %s", r.name, err.Error()) @@ -553,14 +554,15 @@ func (r *Router) setRouter(parent *Router) error { return err } - if len(ifc.addrs) == 0 { + addrs, _ := ifc.Addresses() + if len(addrs) == 0 { return errNoIPAddrEth0 } mappedIPs := []net.IP{} localIPs := []net.IP{} - for _, ifcAddr := range ifc.addrs { + for _, ifcAddr := range addrs { var ip net.IP switch addr := ifcAddr.(type) { case *net.IPNet: @@ -586,7 +588,7 @@ func (r *Router) setRouter(parent *Router) error { r.natType = &NATType{ MappingBehavior: EndpointIndependent, FilteringBehavior: EndpointAddrPortDependent, - Hairpining: false, + Hairpinning: false, PortPreservation: false, MappingLifeTime: 30 * time.Second, } diff --git a/vnet/router_test.go b/vnet/router_test.go index 91a9698..f8889da 100644 --- a/vnet/router_test.go +++ b/vnet/router_test.go @@ -14,7 +14,7 @@ import ( var errNoAddress = errors.New("there must be one address") type dummyNIC struct { - Net + *Net onInboundChunkHandler func(Chunk) } @@ -29,7 +29,7 @@ func getIPAddr(n NIC) (string, error) { return "", err } - addrs, err := eth0.Addrs() + addrs, err := eth0.Addresses() if err != nil { return "", err } @@ -90,9 +90,9 @@ func TestRouterStandalone(t *testing.T) { assert.Nil(t, err, "should succeed") // Now, eth0 must have one address assigned - eth0, err := nic.v.getInterface("eth0") + eth0, err := nic.getInterface("eth0") assert.Nil(t, err, "should succeed") - addrs, err := eth0.Addrs() + addrs, err := eth0.Addresses() assert.Nil(t, err, "should succeed") assert.Equal(t, 1, len(addrs), "should match") assert.Equal(t, "ip+net", addrs[0].Network(), "should match") @@ -141,7 +141,7 @@ func TestRouterStandalone(t *testing.T) { assert.NotNil(t, anic, "should succeed") nic[i] = &dummyNIC{ - Net: *anic, + Net: anic, } err2 := r.AddNet(nic[i]) @@ -150,7 +150,7 @@ func TestRouterStandalone(t *testing.T) { // Now, eth0 must have one address assigned eth0, err2 := nic[i].getInterface("eth0") assert.Nil(t, err2, "should succeed") - addrs, err2 := eth0.Addrs() + addrs, err2 := eth0.Addresses() assert.Nil(t, err2, "should succeed") assert.Equal(t, 1, len(addrs), "should match") //nolint:forcetypeassert @@ -199,7 +199,7 @@ func TestRouterStandalone(t *testing.T) { assert.NotNil(t, anic, "should succeed") nic[i] = &dummyNIC{ - Net: *anic, + Net: anic, } err2 := r.AddNet(nic[i]) @@ -208,7 +208,7 @@ func TestRouterStandalone(t *testing.T) { // Now, eth0 must have one address assigned eth0, err2 := nic[i].getInterface("eth0") assert.Nil(t, err2, "should succeed") - addrs, err2 := eth0.Addrs() + addrs, err2 := eth0.Addresses() assert.Nil(t, err2, "should succeed") assert.Equal(t, 1, len(addrs), "should match") //nolint:forcetypeassert @@ -297,7 +297,7 @@ func TestRouterDelay(t *testing.T) { assert.NotNil(t, anic, "should succeed") nic[i] = &dummyNIC{ - Net: *anic, + Net: anic, } err2 := r.AddNet(nic[i]) @@ -306,7 +306,7 @@ func TestRouterDelay(t *testing.T) { // Now, eth0 must have one address assigned eth0, err2 := nic[i].getInterface("eth0") assert.Nil(t, err2, "should succeed") - addrs, err2 := eth0.Addrs() + addrs, err2 := eth0.Addresses() assert.Nil(t, err2, "should succeed") assert.Equal(t, 1, len(addrs), "should match") //nolint:forcetypeassert @@ -378,7 +378,7 @@ func TestRouterOneChild(t *testing.T) { assert.NotNil(t, wan, "should succeed") wanNet := &dummyNIC{ - Net: *NewNet(&NetConfig{}), + Net: NewNet(&NetConfig{}), } err = wan.AddNet(wanNet) @@ -398,7 +398,7 @@ func TestRouterOneChild(t *testing.T) { assert.NotNil(t, lan, "should succeed") lanNet := &dummyNIC{ - Net: *NewNet(&NetConfig{}), + Net: NewNet(&NetConfig{}), } err = lan.AddNet(lanNet) assert.Nil(t, err, "should succeed") diff --git a/vnet/stress_test.go b/vnet/stress_test.go index a3c24bd..9e8483c 100644 --- a/vnet/stress_test.go +++ b/vnet/stress_test.go @@ -78,7 +78,7 @@ func TestStressTestUDP(t *testing.T) { continue } - addrs, err2 := ifc.Addrs() + addrs, err2 := ifc.Addresses() if !assert.NoError(t, err2, "should succeed") { return } diff --git a/vnet/udpproxy.go b/vnet/udpproxy.go index 2d37e2a..2e5ba11 100644 --- a/vnet/udpproxy.go +++ b/vnet/udpproxy.go @@ -10,19 +10,20 @@ import ( // 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 | -// : | | : +---------+ +---------+ +--------+ -// : | | ............................: -// : +--------+ : -// ............................................... +// +// .............................................. +// : 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 diff --git a/vnet/udpproxy_direct_test.go b/vnet/udpproxy_direct_test.go index e1af42e..99d7777 100644 --- a/vnet/udpproxy_direct_test.go +++ b/vnet/udpproxy_direct_test.go @@ -16,9 +16,13 @@ import ( ) // The vnet client: -// 10.0.0.11:5787 +// +// 10.0.0.11:5787 +// // which proxy to real server: -// 192.168.1.10:8000 +// +// 192.168.1.10:8000 +// // We should get a reply if directly deliver to proxy. func TestUDPProxyDirectDeliverTypical(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) diff --git a/vnet/udpproxy_test.go b/vnet/udpproxy_test.go index 09aea2d..98e1114 100644 --- a/vnet/udpproxy_test.go +++ b/vnet/udpproxy_test.go @@ -91,9 +91,12 @@ func TestMain(m *testing.M) { } // vnet client: -// 10.0.0.11:5787 +// +// 10.0.0.11:5787 +// // proxy to real server: -// 192.168.1.10:8000 +// +// 192.168.1.10:8000 func TestUDPProxyOne2One(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) @@ -236,10 +239,13 @@ func TestUDPProxyOne2One(t *testing.T) { } // vnet client: -// 10.0.0.11:5787 -// 10.0.0.11:5788 +// +// 10.0.0.11:5787 +// 10.0.0.11:5788 +// // proxy to real server: -// 192.168.1.10:8000 +// +// 192.168.1.10:8000 func TestUDPProxyTwo2One(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) @@ -410,14 +416,20 @@ func TestUDPProxyTwo2One(t *testing.T) { } // vnet client: -// 10.0.0.11:5787 +// +// 10.0.0.11:5787 +// // proxy to real server: -// 192.168.1.10:8000 +// +// 192.168.1.10:8000 // // vnet client: -// 10.0.0.11:5788 +// +// 10.0.0.11:5788 +// // proxy to real server: -// 192.168.1.10:8000 +// +// 192.168.1.10:8000 func TestUDPProxyProxyTwice(t *testing.T) { ctx, cancel := context.WithCancel(context.Background())