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())