diff --git a/core/config.go b/core/config.go index 78c4b4c..c82756c 100644 --- a/core/config.go +++ b/core/config.go @@ -15,14 +15,15 @@ var gConf Config type AppConfig struct { // required - AppName string - Protocol string - SrcPort int - PeerNode string - DstPort int - DstHost string - PeerUser string - Enabled int // default:1 + AppName string + Protocol string + Whitelist string + SrcPort int + PeerNode string + DstPort int + DstHost string + PeerUser string + Enabled int // default:1 // runtime info peerVersion string peerToken uint64 @@ -207,6 +208,7 @@ func parseParams(subCommand string) { node := fset.String("node", "", "node name. 8-31 characters. if not set, it will be hostname") peerNode := fset.String("peernode", "", "peer node name that you want to connect") dstIP := fset.String("dstip", "127.0.0.1", "destination ip ") + whiteList := fset.String("whitelist", "", "whitelist for p2pApp ") dstPort := fset.Int("dstport", 0, "destination port ") srcPort := fset.Int("srcport", 0, "source port ") tcpPort := fset.Int("tcpport", 0, "tcp port for upnp or publicip") @@ -226,6 +228,7 @@ func parseParams(subCommand string) { config := AppConfig{Enabled: 1} config.PeerNode = *peerNode config.DstHost = *dstIP + config.Whitelist = *whiteList config.DstPort = *dstPort config.SrcPort = *srcPort config.Protocol = *protocol @@ -264,15 +267,11 @@ func parseParams(subCommand string) { gConf.Network.ServerHost = *serverHost } if *node != "" { - if len(*node) < MinNodeNameLen { - gLog.Println(LvERROR, ErrNodeTooShort) - os.Exit(9) - } gConf.Network.Node = *node } else { envNode := os.Getenv("OPENP2P_NODE") if envNode != "" { - gConf.setNode(envNode) + gConf.Network.Node = envNode } if gConf.Network.Node == "" { // if node name not set. use os.Hostname gConf.Network.Node = defaultNodeName() diff --git a/core/handlepush.go b/core/handlepush.go index d005500..edf2a2c 100644 --- a/core/handlepush.go +++ b/core/handlepush.go @@ -139,6 +139,7 @@ func handleEditApp(pn *P2PNetwork, subType uint16, msg []byte) (err error) { // protocol0+srcPort0 exist, delApp oldConf.AppName = newApp.AppName oldConf.Protocol = newApp.Protocol0 + oldConf.Whitelist = newApp.Whitelist oldConf.SrcPort = newApp.SrcPort0 oldConf.PeerNode = newApp.PeerNode oldConf.DstHost = newApp.DstHost @@ -235,6 +236,7 @@ func handleReportApps(pn *P2PNetwork, subType uint16, msg []byte) (err error) { AppName: config.AppName, Error: config.errMsg, Protocol: config.Protocol, + Whitelist: config.Whitelist, SrcPort: config.SrcPort, RelayNode: relayNode, RelayMode: relayMode, diff --git a/core/iptree.go b/core/iptree.go new file mode 100644 index 0000000..72b1f46 --- /dev/null +++ b/core/iptree.go @@ -0,0 +1,155 @@ +package openp2p + +import ( + "bytes" + "encoding/binary" + "fmt" + "log" + "net" + "strings" + "sync" + + "github.com/emirpasic/gods/trees/avltree" + "github.com/emirpasic/gods/utils" +) + +type IPTree struct { + tree *avltree.Tree + treeMtx sync.RWMutex +} + +// add 120k cost 0.5s +func (iptree *IPTree) AddIntIP(minIP uint32, maxIP uint32) bool { + if minIP > maxIP { + return false + } + iptree.treeMtx.Lock() + defer iptree.treeMtx.Unlock() + newMinIP := minIP + newMaxIP := maxIP + cur := iptree.tree.Root + for { + if cur == nil { + break + } + curMaxIP := cur.Value.(uint32) + curMinIP := cur.Key.(uint32) + + // newNode all in existNode, treat as inserted. + if newMinIP >= curMinIP && newMaxIP <= curMaxIP { + return true + } + // has no interset + if newMinIP > curMaxIP { + cur = cur.Children[1] + continue + } + if newMaxIP < curMinIP { + cur = cur.Children[0] + continue + } + // has interset, rm it and Add the new merged ip segment + iptree.tree.Remove(curMinIP) + if curMinIP < newMinIP { + newMinIP = curMinIP + } + if curMaxIP > newMaxIP { + newMaxIP = curMaxIP + } + cur = iptree.tree.Root + } + // put in the tree + iptree.tree.Put(newMinIP, newMaxIP) + return true +} + +func (iptree *IPTree) Add(minIPStr string, maxIPStr string) bool { + var minIP, maxIP uint32 + binary.Read(bytes.NewBuffer(net.ParseIP(minIPStr).To4()), binary.BigEndian, &minIP) + binary.Read(bytes.NewBuffer(net.ParseIP(maxIPStr).To4()), binary.BigEndian, &maxIP) + return iptree.AddIntIP(minIP, maxIP) +} + +func (iptree *IPTree) Contains(ipStr string) bool { + var ip uint32 + binary.Read(bytes.NewBuffer(net.ParseIP(ipStr).To4()), binary.BigEndian, &ip) + return iptree.ContainsInt(ip) +} + +func (iptree *IPTree) ContainsInt(ip uint32) bool { + iptree.treeMtx.RLock() + defer iptree.treeMtx.RUnlock() + if iptree.tree == nil { + return false + } + n := iptree.tree.Root + for n != nil { + curMaxIP := n.Value.(uint32) + curMinIP := n.Key.(uint32) + switch { + case ip >= curMinIP && ip <= curMaxIP: // hit + return true + case ip < curMinIP: + n = n.Children[0] + default: + n = n.Children[1] + } + } + return false +} + +func (iptree *IPTree) Size() int { + iptree.treeMtx.RLock() + defer iptree.treeMtx.RUnlock() + return iptree.tree.Size() +} + +func (iptree *IPTree) Print() { + iptree.treeMtx.RLock() + defer iptree.treeMtx.RUnlock() + log.Println("size:", iptree.Size()) + log.Println(iptree.tree.String()) +} + +func (iptree *IPTree) Clear() { + iptree.treeMtx.Lock() + defer iptree.treeMtx.Unlock() + iptree.tree.Clear() +} + +// input format 127.0.0.1,192.168.1.0/24,10.1.1.30-10.1.1.50 +// 127.0.0.1 +// 192.168.1.0/24 +// 192.168.1.1-192.168.1.10 +func NewIPTree(ips string) *IPTree { + iptree := &IPTree{ + tree: avltree.NewWith(utils.UInt32Comparator), + } + ipArr := strings.Split(ips, ",") + for _, ip := range ipArr { + if strings.Contains(ip, "/") { // x.x.x.x/24 + _, ipNet, err := net.ParseCIDR(ip) + if err != nil { + fmt.Println("Error parsing CIDR:", err) + continue + } + minIP := ipNet.IP.Mask(ipNet.Mask).String() + maxIP := calculateMaxIP(ipNet).String() + iptree.Add(minIP, maxIP) + } else if strings.Contains(ip, "-") { // x.x.x.x-y.y.y.y + minAndMax := strings.Split(ip, "-") + iptree.Add(minAndMax[0], minAndMax[1]) + } else { // single ip + iptree.Add(ip, ip) + } + } + return iptree +} +func calculateMaxIP(ipNet *net.IPNet) net.IP { + maxIP := make(net.IP, len(ipNet.IP)) + copy(maxIP, ipNet.IP) + for i := range maxIP { + maxIP[i] |= ^ipNet.Mask[i] + } + return maxIP +} diff --git a/core/iptree_test.go b/core/iptree_test.go new file mode 100644 index 0000000..f477e44 --- /dev/null +++ b/core/iptree_test.go @@ -0,0 +1,171 @@ +package openp2p + +import ( + "bytes" + "encoding/binary" + "net" + "testing" +) + +func wrapTestContains(t *testing.T, iptree *IPTree, ip string, result bool) { + if iptree.Contains(ip) == result { + // t.Logf("compare version %s %s ok\n", v1, v2) + } else { + t.Errorf("test %s fail\n", ip) + } +} +func wrapBenchmarkContains(t *testing.B, iptree *IPTree, ip string, result bool) { + if iptree.Contains(ip) == result { + // t.Logf("compare version %s %s ok\n", v1, v2) + } else { + t.Errorf("test %s fail\n", ip) + } +} + +func TestAllInputFormat(t *testing.T) { + iptree := NewIPTree("219.137.185.70,127.0.0.1,127.0.0.0/8,192.168.1.0/24,192.168.3.100-192.168.3.255,192.168.100.0-192.168.200.255") + wrapTestContains(t, iptree, "127.0.0.1", true) + wrapTestContains(t, iptree, "127.0.0.2", true) + wrapTestContains(t, iptree, "127.1.1.1", true) + wrapTestContains(t, iptree, "219.137.185.70", true) + wrapTestContains(t, iptree, "219.137.185.71", false) + wrapTestContains(t, iptree, "192.168.1.2", true) + wrapTestContains(t, iptree, "192.168.2.2", false) + wrapTestContains(t, iptree, "192.168.3.1", false) + wrapTestContains(t, iptree, "192.168.3.100", true) + wrapTestContains(t, iptree, "192.168.3.255", true) + wrapTestContains(t, iptree, "192.168.150.1", true) + wrapTestContains(t, iptree, "192.168.250.1", false) +} + +func TestSingleIP(t *testing.T) { + iptree := NewIPTree("") + iptree.Add("219.137.185.70", "219.137.185.70") + wrapTestContains(t, iptree, "219.137.185.70", true) + wrapTestContains(t, iptree, "219.137.185.71", false) +} + +func TestWrongSegment(t *testing.T) { + iptree := NewIPTree("") + inserted := iptree.Add("87.251.75.0", "82.251.75.255") + if inserted { + t.Errorf("TestWrongSegment failed\n") + } +} + +func TestSegment2(t *testing.T) { + iptree := NewIPTree("") + iptree.Clear() + iptree.Add("10.1.5.50", "10.1.5.100") + iptree.Add("10.1.1.50", "10.1.1.100") + iptree.Add("10.1.2.50", "10.1.2.100") + iptree.Add("10.1.6.50", "10.1.6.100") + iptree.Add("10.1.7.50", "10.1.7.100") + iptree.Add("10.1.3.50", "10.1.3.100") + iptree.Add("10.1.1.1", "10.1.1.10") // no interset + iptree.Add("10.1.1.200", "10.1.1.250") // no interset + iptree.Print() + + iptree.Add("10.1.1.80", "10.1.1.90") // all in + iptree.Add("10.1.1.40", "10.1.1.60") // interset + iptree.Print() + iptree.Add("10.1.1.90", "10.1.1.110") // interset + iptree.Print() + t.Logf("blocklist size:%d\n", iptree.Size()) + wrapTestContains(t, iptree, "10.1.1.40", true) + wrapTestContains(t, iptree, "10.1.5.50", true) + wrapTestContains(t, iptree, "10.1.6.50", true) + wrapTestContains(t, iptree, "10.1.7.50", true) + wrapTestContains(t, iptree, "10.1.2.50", true) + wrapTestContains(t, iptree, "10.1.3.50", true) + wrapTestContains(t, iptree, "10.1.1.60", true) + wrapTestContains(t, iptree, "10.1.1.90", true) + wrapTestContains(t, iptree, "10.1.1.110", true) + wrapTestContains(t, iptree, "10.1.1.250", true) + wrapTestContains(t, iptree, "10.1.2.60", true) + wrapTestContains(t, iptree, "10.1.100.30", false) + wrapTestContains(t, iptree, "10.1.200.30", false) + + iptree.Add("10.0.0.0", "10.255.255.255") // will merge all segment + iptree.Print() + if iptree.Size() != 1 { + t.Errorf("merge ip segment error\n") + } + +} + +func BenchmarkBuildBlockList20k(t *testing.B) { + iptree := NewIPTree("") + iptree.Clear() + iptree.Add("10.1.5.50", "10.1.5.100") + iptree.Add("10.1.1.50", "10.1.1.100") + iptree.Add("10.1.2.50", "10.1.2.100") + iptree.Add("10.1.6.50", "10.1.6.100") + iptree.Add("10.1.7.50", "10.1.7.100") + iptree.Add("10.1.3.50", "10.1.3.100") + iptree.Add("10.1.1.1", "10.1.1.10") // no interset + iptree.Add("10.1.1.200", "10.1.1.250") // no interset + iptree.Add("10.1.1.80", "10.1.1.90") // all in + iptree.Add("10.1.1.40", "10.1.1.60") // interset + iptree.Add("10.1.1.90", "10.1.1.110") // interset + var minIP uint32 + binary.Read(bytes.NewBuffer(net.ParseIP("10.1.1.1").To4()), binary.BigEndian, &minIP) + + // insert 10k block ip single + nodeNum := uint32(10000 * 1) + gap := uint32(10) + for i := minIP; i < minIP+nodeNum*gap; i += gap { + iptree.AddIntIP(i, i) + // t.Logf("blocklist size:%d\n", iptree.Size()) + } + binary.Read(bytes.NewBuffer(net.ParseIP("100.1.1.1").To4()), binary.BigEndian, &minIP) + // insert 100k block ip segment + for i := minIP; i < minIP+nodeNum*gap; i += gap { + iptree.AddIntIP(i, i+5) + } + t.Logf("blocklist size:%d\n", iptree.Size()) + iptree.Clear() + t.Logf("clear. blocklist size:%d\n", iptree.Size()) +} +func BenchmarkQuery(t *testing.B) { + iptree := NewIPTree("") + iptree.Clear() + iptree.Add("10.1.5.50", "10.1.5.100") + iptree.Add("10.1.1.50", "10.1.1.100") + iptree.Add("10.1.2.50", "10.1.2.100") + iptree.Add("10.1.6.50", "10.1.6.100") + iptree.Add("10.1.7.50", "10.1.7.100") + iptree.Add("10.1.3.50", "10.1.3.100") + iptree.Add("10.1.1.1", "10.1.1.10") // no interset + iptree.Add("10.1.1.200", "10.1.1.250") // no interset + iptree.Add("10.1.1.80", "10.1.1.90") // all in + iptree.Add("10.1.1.40", "10.1.1.60") // interset + iptree.Add("10.1.1.90", "10.1.1.110") // interset + var minIP uint32 + binary.Read(bytes.NewBuffer(net.ParseIP("10.1.1.1").To4()), binary.BigEndian, &minIP) + + // insert 10k block ip single + nodeNum := uint32(10000 * 100) + gap := uint32(10) + for i := minIP; i < minIP+nodeNum*gap; i += gap { + iptree.AddIntIP(i, i) + // t.Logf("blocklist size:%d\n", iptree.Size()) + } + binary.Read(bytes.NewBuffer(net.ParseIP("100.1.1.1").To4()), binary.BigEndian, &minIP) + // insert 100k block ip segment + for i := minIP; i < minIP+nodeNum*gap; i += gap { + iptree.AddIntIP(i, i+5) + } + t.Logf("blocklist size:%d\n", iptree.Size()) + t.ResetTimer() + queryNum := 100 * 10000 + for i := 0; i < queryNum; i++ { + iptree.ContainsInt(minIP + uint32(i)) + wrapBenchmarkContains(t, iptree, "10.1.5.55", true) + wrapBenchmarkContains(t, iptree, "10.1.1.1", true) + wrapBenchmarkContains(t, iptree, "10.1.5.200", false) + wrapBenchmarkContains(t, iptree, "200.1.1.1", false) + } + t.Logf("query list:%d\n", queryNum*4) + +} diff --git a/core/p2papp.go b/core/p2papp.go index 2574df8..4a6b7ee 100644 --- a/core/p2papp.go +++ b/core/p2papp.go @@ -17,6 +17,7 @@ type p2pApp struct { listener net.Listener listenerUDP *net.UDPConn tunnel *P2PTunnel + iptree *IPTree rtid uint64 // relay tunnelID relayNode string relayMode string @@ -64,6 +65,15 @@ func (app *p2pApp) listenTCP() error { } break } + // check white list + if app.config.Whitelist != "" { + remoteIP := strings.Split(conn.RemoteAddr().String(), ":")[0] + if !app.iptree.Contains(remoteIP) { + conn.Close() + gLog.Printf(LvERROR, "%s not in whitelist, access denied", remoteIP) + continue + } + } oConn := overlayConn{ tunnel: app.tunnel, connTCP: conn, diff --git a/core/p2pnetwork.go b/core/p2pnetwork.go index 6342171..7ae0633 100644 --- a/core/p2pnetwork.go +++ b/core/p2pnetwork.go @@ -286,6 +286,7 @@ func (pn *P2PNetwork) AddApp(config AppConfig) error { key: appKey, tunnel: t, config: config, + iptree: NewIPTree(config.Whitelist), rtid: rtid, relayNode: relayNode, relayMode: relayMode, diff --git a/core/protocol.go b/core/protocol.go index a5b72d2..55fde16 100644 --- a/core/protocol.go +++ b/core/protocol.go @@ -10,7 +10,7 @@ import ( "time" ) -const OpenP2PVersion = "3.10.2" +const OpenP2PVersion = "3.10.3" const ProductName string = "openp2p" const LeastSupportVersion = "3.0.0" const SyncServerTimeVersion = "3.9.0" @@ -351,9 +351,10 @@ type AppInfo struct { AppName string `json:"appName,omitempty"` Error string `json:"error,omitempty"` Protocol string `json:"protocol,omitempty"` + Whitelist string `json:"whitelist,omitempty"` SrcPort int `json:"srcPort,omitempty"` Protocol0 string `json:"protocol0,omitempty"` - SrcPort0 int `json:"srcPort0,omitempty"` + SrcPort0 int `json:"srcPort0,omitempty"` // srcport+protocol is uneque, use as old app id NatType int `json:"natType,omitempty"` PeerNode string `json:"peerNode,omitempty"` DstPort int `json:"dstPort,omitempty"` diff --git a/go.mod b/go.mod index e2cff94..85590a8 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module openp2p go 1.18 require ( + github.com/emirpasic/gods v1.18.1 github.com/gorilla/websocket v1.4.2 github.com/openp2p-cn/go-reuseport v0.3.2 github.com/openp2p-cn/service v1.0.0