From d665698013b2abc2ec894ecf20d40541459d4e32 Mon Sep 17 00:00:00 2001 From: Yutaka Takeda Date: Sun, 18 Aug 2019 22:58:47 -0700 Subject: [PATCH 1/3] Allow more than one static IP addresses Resolves #32 --- vnet/net.go | 28 ++++++++++--- vnet/net_native_test.go | 6 +-- vnet/net_test.go | 92 +++++++++++++++++++++++++++++++++++++++++ vnet/router.go | 57 +++++++++++++++++-------- vnet/stress_test.go | 2 +- 5 files changed, 157 insertions(+), 28 deletions(-) diff --git a/vnet/net.go b/vnet/net.go index 191bc60..8994e74 100644 --- a/vnet/net.go +++ b/vnet/net.go @@ -21,7 +21,7 @@ func newMACAddress() net.HardwareAddr { type vNet struct { interfaces []*Interface // read-only - staticIP net.IP // read-only + staticIPs []net.IP // read-only router *Router // read-only udpConns *udpConnMap // read-only mutex sync.RWMutex @@ -432,8 +432,12 @@ func (v *vNet) assignPort(ip net.IP, start, end int) (int, error) { // NetConfig is a bag of configuration parameters passed to NewNet(). type NetConfig struct { - // StaticIP is a static IP address to be assigned for this Net. If nil, - // the router will automatically assign an IP address. + // StaticIPs is an array of static IP addresses to be assigned for this Net. + // If no static IP address is given, the router will automatically assign + // an IP address. + StaticIPs []string + + // StaticIP is deprecated. Use StaticIPs. StaticIP string } @@ -489,9 +493,21 @@ func NewNet(config *NetConfig) *Net { Flags: net.FlagUp | net.FlagMulticast, }) + var staticIPs []net.IP + for _, ipStr := range config.StaticIPs { + if ip := net.ParseIP(ipStr); ip != nil { + staticIPs = append(staticIPs, ip) + } + } + if len(config.StaticIP) > 0 { + if ip := net.ParseIP(config.StaticIP); ip != nil { + staticIPs = append(staticIPs, ip) + } + } + v := &vNet{ interfaces: []*Interface{lo0, eth0}, - staticIP: net.ParseIP(config.StaticIP), + staticIPs: staticIPs, udpConns: newUDPConnMap(), } @@ -607,12 +623,12 @@ func (n *Net) onInboundChunk(c Chunk) { n.v.onInboundChunk(c) } -func (n *Net) getStaticIP() net.IP { +func (n *Net) getStaticIPs() []net.IP { if n.v == nil { return nil } - return n.v.staticIP + return n.v.staticIPs } // IsVirtual tests if the virtual network is enabled. diff --git a/vnet/net_native_test.go b/vnet/net_native_test.go index 710d56b..0030a06 100644 --- a/vnet/net_native_test.go +++ b/vnet/net_native_test.go @@ -202,8 +202,8 @@ func TestNetNative(t *testing.T) { // onInboundChunk (shouldn't crash) nw.onInboundChunk(nil) - // getStaticIP - ip := nw.getStaticIP() - assert.Nil(t, ip, "should be nil") + // getStaticIPs + ips := nw.getStaticIPs() + assert.Nil(t, ips, "should be nil") }) } diff --git a/vnet/net_test.go b/vnet/net_test.go index f245f23..4e86ba0 100644 --- a/vnet/net_test.go +++ b/vnet/net_test.go @@ -644,4 +644,96 @@ func TestNetVirtual(t *testing.T) { assert.NoError(t, conn.Close(), "should succeed") assert.Equal(t, 0, nw.v.udpConns.size(), "should match") }) + + t.Run("Two IPs on a NIC", func(t *testing.T) { + doneCh := make(chan struct{}) + + // WAN + wan, err := NewRouter(&RouterConfig{ + CIDR: "1.2.3.0/24", + LoggerFactory: loggerFactory, + }) + assert.NoError(t, err, "should succeed") + assert.NotNil(t, wan, "should succeed") + + net1 := NewNet(&NetConfig{ + StaticIPs: []string{ + "1.2.3.4", + "1.2.3.5", + }, + }) + + err = wan.AddNet(net1) + assert.NoError(t, err, "should succeed") + + // start the router + err = wan.Start() + assert.NoError(t, err, "should succeed") + + conn1, err := net1.ListenPacket("udp", "1.2.3.4:1234") + assert.NoError(t, err, "should succeed") + + conn2, err := net1.ListenPacket("udp", "1.2.3.5:1234") + assert.NoError(t, err, "should succeed") + + conn1RcvdCh := make(chan bool) + + // conn1 + go func() { + buf := make([]byte, 1500) + for { + log.Debug("conn1: wait for a message..") + n, _, err2 := conn1.ReadFrom(buf) + if err2 != nil { + log.Debugf("ReadFrom returned: %v", err2) + break + } + + log.Debugf("conn1 received %s", string(buf[:n])) + conn1RcvdCh <- true + } + close(doneCh) + }() + + // conn2 + go func() { + buf := make([]byte, 1500) + for { + log.Debug("conn2: wait for a message..") + n, addr, err2 := conn2.ReadFrom(buf) + if err2 != nil { + log.Debugf("ReadFrom returned: %v", err2) + break + } + + log.Debugf("conn2 received %s", string(buf[:n])) + + // echo back to conn1 + nSent, err2 := conn2.WriteTo([]byte("Good-bye!"), addr) + assert.NoError(t, err2, "should succeed") + assert.Equal(t, 9, nSent, "should match") + } + }() + + log.Debug("conn1: sending") + nSent, err := conn1.WriteTo( + []byte("Hello!"), + conn2.LocalAddr(), + ) + assert.NoError(t, err, "should succeed") + assert.Equal(t, 6, nSent, "should match") + + loop: + for { + select { + case <-conn1RcvdCh: + assert.NoError(t, conn1.Close(), "should succeed") + assert.NoError(t, conn2.Close(), "should succeed") + case <-doneCh: + break loop + } + } + + assert.NoError(t, wan.Stop(), "should succeed") + }) } diff --git a/vnet/router.go b/vnet/router.go index 6189c8b..567d0ab 100644 --- a/vnet/router.go +++ b/vnet/router.go @@ -31,8 +31,12 @@ type RouterConfig struct { Name string // CIDR notation, like "192.0.2.0/24" CIDR string - // StaticIP is a static IP address to be assigned for this external network. + // StaticIPs is an array of static IP addresses to be assigned for this router. + // If no static IP address is given, the router will automatically assign + // an IP address. // This will be ignored if this router is the root. + StaticIPs []string + // StaticIP is deprecated. Use StaticIPs. StaticIP string // Internal queue size QueueSize int @@ -50,7 +54,7 @@ type RouterConfig struct { type NIC interface { getInterface(ifName string) (*Interface, error) onInboundChunk(c Chunk) - getStaticIP() net.IP + getStaticIPs() []net.IP setRouter(r *Router) error } @@ -63,7 +67,7 @@ type Router struct { name string // read-only interfaces []*Interface // read-only ipv4Net *net.IPNet // read-only - staticIP net.IP // read-only + staticIPs []net.IP // read-only lastID byte // requires mutex [x], used to assign the last digit of IPv4 address queue *chunkQueue // read-only parent *Router // read-only @@ -123,11 +127,23 @@ func NewRouter(config *RouterConfig) (*Router, error) { name = assignRouterName() } + var staticIPs []net.IP + for _, ipStr := range config.StaticIPs { + if ip := net.ParseIP(ipStr); ip != nil { + staticIPs = append(staticIPs, ip) + } + } + if len(config.StaticIP) > 0 { + if ip := net.ParseIP(config.StaticIP); ip != nil { + staticIPs = append(staticIPs, ip) + } + } + return &Router{ name: name, interfaces: []*Interface{lo0, eth0}, ipv4Net: ipNet, - staticIP: net.ParseIP(config.StaticIP), + staticIPs: staticIPs, queue: newChunkQueue(queueSize), natType: config.NATType, nics: map[string]NIC{}, @@ -247,29 +263,34 @@ func (r *Router) addNIC(nic NIC) error { return err } - var ip net.IP - if ip = nic.getStaticIP(); ip != nil { - if !r.ipv4Net.Contains(ip) { - return fmt.Errorf("static IP is beyond subnet: %s", r.ipv4Net.String()) - } - } else { + var ips []net.IP + + if ips = nic.getStaticIPs(); len(ips) == 0 { // assign an IP address - ip, err = r.assignIPAddress() + ip, err := r.assignIPAddress() if err != nil { return err } + ips = append(ips, ip) } - ifc.AddAddr(&net.IPNet{ - IP: ip, - Mask: r.ipv4Net.Mask, - }) + for _, ip := range ips { + if !r.ipv4Net.Contains(ip) { + return fmt.Errorf("static IP is beyond subnet: %s", r.ipv4Net.String()) + } + + ifc.AddAddr(&net.IPNet{ + IP: ip, + Mask: r.ipv4Net.Mask, + }) + + r.nics[ip.String()] = nic + } if err = nic.setRouter(r); err != nil { return err } - r.nics[ip.String()] = nic return nil } @@ -521,6 +542,6 @@ func (r *Router) onInboundChunk(c Chunk) { r.push(fromParent) } -func (r *Router) getStaticIP() net.IP { - return r.staticIP +func (r *Router) getStaticIPs() []net.IP { + return r.staticIPs } diff --git a/vnet/stress_test.go b/vnet/stress_test.go index 419b2d1..b270d5f 100644 --- a/vnet/stress_test.go +++ b/vnet/stress_test.go @@ -30,7 +30,7 @@ func TestStressTestUDP(t *testing.T) { assert.NotNil(t, wan, "should succeed") net0 := NewNet(&NetConfig{ - StaticIP: "1.2.3.4", + StaticIPs: []string{"1.2.3.4"}, }) err = wan.AddNet(net0) From c46b229ff102cca86b2c3c93deea4b0239e95392 Mon Sep 17 00:00:00 2001 From: Yutaka Takeda Date: Sun, 18 Aug 2019 23:48:04 -0700 Subject: [PATCH 2/3] Fix a lint error Resolves #32 --- vnet/router.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/vnet/router.go b/vnet/router.go index 567d0ab..1cf040e 100644 --- a/vnet/router.go +++ b/vnet/router.go @@ -267,9 +267,9 @@ func (r *Router) addNIC(nic NIC) error { if ips = nic.getStaticIPs(); len(ips) == 0 { // assign an IP address - ip, err := r.assignIPAddress() - if err != nil { - return err + ip, err2 := r.assignIPAddress() + if err2 != nil { + return err2 } ips = append(ips, ip) } From b3a4a679f2a8a9f6cb6f4750e9583d4c77394049 Mon Sep 17 00:00:00 2001 From: Yutaka Takeda Date: Tue, 20 Aug 2019 15:53:59 -0700 Subject: [PATCH 3/3] Added more tests Resolves #32 --- vnet/router_test.go | 102 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 102 insertions(+) diff --git a/vnet/router_test.go b/vnet/router_test.go index 6e341ba..7ce9dde 100644 --- a/vnet/router_test.go +++ b/vnet/router_test.go @@ -424,3 +424,105 @@ func TestRouterOneChild(t *testing.T) { }) } + +func TestRouterStaticIPs(t *testing.T) { + loggerFactory := logging.NewDefaultLoggerFactory() + //log := loggerFactory.NewLogger("test") + + t.Run("more than one static IP", func(t *testing.T) { + // WAN + wan, err := NewRouter(&RouterConfig{ + CIDR: "1.2.3.0/24", + StaticIPs: []string{ + "1.2.3.1", + "1.2.3.2", + "1.2.3.3", + }, + LoggerFactory: loggerFactory, + }) + assert.Nil(t, err, "should succeed") + assert.NotNil(t, wan, "should succeed") + + assert.Equal(t, 3, len(wan.staticIPs), "should be 3") + assert.Equal(t, "1.2.3.1", wan.staticIPs[0].String(), "should match") + assert.Equal(t, "1.2.3.2", wan.staticIPs[1].String(), "should match") + assert.Equal(t, "1.2.3.3", wan.staticIPs[2].String(), "should match") + }) + + t.Run("StaticIPs and StaticIP in the mix", func(t *testing.T) { + // WAN + wan, err := NewRouter(&RouterConfig{ + CIDR: "1.2.3.0/24", + StaticIPs: []string{ + "1.2.3.1", + "1.2.3.2", + "1.2.3.3", + }, + StaticIP: "1.2.3.4", + LoggerFactory: loggerFactory, + }) + assert.Nil(t, err, "should succeed") + assert.NotNil(t, wan, "should succeed") + + assert.Equal(t, 4, len(wan.staticIPs), "should be 4") + assert.Equal(t, "1.2.3.1", wan.staticIPs[0].String(), "should match") + assert.Equal(t, "1.2.3.2", wan.staticIPs[1].String(), "should match") + assert.Equal(t, "1.2.3.3", wan.staticIPs[2].String(), "should match") + assert.Equal(t, "1.2.3.4", wan.staticIPs[3].String(), "should match") + }) +} + +func TestRouterFailures(t *testing.T) { + loggerFactory := logging.NewDefaultLoggerFactory() + //log := loggerFactory.NewLogger("test") + + t.Run("Stop when router is stopped", func(t *testing.T) { + r, err := NewRouter(&RouterConfig{ + CIDR: "1.2.3.0/24", + LoggerFactory: loggerFactory, + }) + assert.Nil(t, err, "should succeed") + + err = r.Stop() + assert.Error(t, err, "should fail") + }) + + t.Run("AddNet", func(t *testing.T) { + r, err := NewRouter(&RouterConfig{ + CIDR: "1.2.3.0/24", + LoggerFactory: loggerFactory, + }) + assert.Nil(t, err, "should succeed") + + nic := NewNet(&NetConfig{ + StaticIPs: []string{ + "5.6.7.8", // out of parent router'c CIDR + }, + }) + assert.NotNil(t, nic, "should succeed") + + err = r.AddNet(nic) + assert.Error(t, err, "should fail") + }) + + t.Run("AddRouter", func(t *testing.T) { + r1, err := NewRouter(&RouterConfig{ + CIDR: "1.2.3.0/24", + LoggerFactory: loggerFactory, + }) + assert.Nil(t, err, "should succeed") + + r2, err := NewRouter(&RouterConfig{ + CIDR: "192.168.0.0/24", + StaticIPs: []string{ + "5.6.7.8", // out of parent router'c CIDR + }, + + LoggerFactory: loggerFactory, + }) + assert.Nil(t, err, "should succeed") + + err = r1.AddRouter(r2) + assert.Error(t, err, "should fail") + }) +}