From f5adc3b7a237c1e9c13455790f3e52959f16fd3f Mon Sep 17 00:00:00 2001 From: Marco Munizaga Date: Sun, 19 Jun 2022 09:15:36 -0700 Subject: [PATCH] add ipcidr support (#177) * Add support for ipcidr protocol * Move test * Fix gocheck * PR comments * Check byte slice len --- multiaddr_test.go | 4 +++ net/convert.go | 28 ++++++++++++++++++++ net/convert_test.go | 62 +++++++++++++++++++++++++++++++++++++++++++++ protocols.go | 9 +++++++ transcoders.go | 16 ++++++++++++ 5 files changed, 119 insertions(+) diff --git a/multiaddr_test.go b/multiaddr_test.go index f5ae875..ee6ecf2 100644 --- a/multiaddr_test.go +++ b/multiaddr_test.go @@ -27,6 +27,8 @@ func TestConstructFails(t *testing.T) { "/ip4", "/ip4/::1", "/ip4/fdpsofodsajfdoisa", + "/ip4/::/ipcidr/256", + "/ip6/::/ipcidr/1026", "/ip6", "/ip6zone", "/ip6zone/", @@ -101,9 +103,11 @@ func TestConstructSucceeds(t *testing.T) { cases := []string{ "/ip4/1.2.3.4", "/ip4/0.0.0.0", + "/ip4/192.0.2.0/ipcidr/24", "/ip6/::1", "/ip6/2601:9:4f81:9700:803e:ca65:66e8:c21", "/ip6/2601:9:4f81:9700:803e:ca65:66e8:c21/udp/1234/quic", + "/ip6/2001:db8::/ipcidr/32", "/ip6zone/x/ip6/fe80::1", "/ip6zone/x%y/ip6/fe80::1", "/ip6zone/x%y/ip6/::", diff --git a/net/convert.go b/net/convert.go index 07057e8..4603fa2 100644 --- a/net/convert.go +++ b/net/convert.go @@ -1,6 +1,7 @@ package manet import ( + "errors" "fmt" "net" "path/filepath" @@ -51,6 +52,33 @@ func (cm *CodecMap) ToNetAddr(maddr ma.Multiaddr) (net.Addr, error) { return p(maddr) } +// MultiaddrToIPNet converts a multiaddr to an IPNet. Useful for seeing if another IP address is contained within this multiaddr network+mask +func MultiaddrToIPNet(m ma.Multiaddr) (*net.IPNet, error) { + var ipString string + var mask string + + ma.ForEach(m, func(c ma.Component) bool { + if c.Protocol().Code == ma.P_IP4 || c.Protocol().Code == ma.P_IP6 { + ipString = c.Value() + } + if c.Protocol().Code == ma.P_IPCIDR { + mask = c.Value() + } + return ipString == "" || mask == "" + }) + + if ipString == "" { + return nil, errors.New("no ip protocol found") + } + + if mask == "" { + return nil, errors.New("no mask found") + } + + _, ipnet, err := net.ParseCIDR(ipString + "/" + string(mask)) + return ipnet, err +} + func parseBasicNetMaddr(maddr ma.Multiaddr) (net.Addr, error) { network, host, err := DialArgs(maddr) if err != nil { diff --git a/net/convert_test.go b/net/convert_test.go index 1701824..a36f5e9 100644 --- a/net/convert_test.go +++ b/net/convert_test.go @@ -202,3 +202,65 @@ func TestDialArgs(t *testing.T) { test("/dns6/abc.com/udp/1234", "udp6", "abc.com:1234") // DNS6:port test("/dns6/abc.com", "ip6", "abc.com") // Just DNS6 } + +func TestMultiaddrToIPNet(t *testing.T) { + type testCase struct { + name string + ma string + ips []string + contained []bool + } + + testCases := []testCase{ + { + name: "basic", + ma: "/ip4/1.2.3.0/ipcidr/24", + ips: []string{"1.2.3.4", "1.2.3.9", "2.1.1.1"}, + contained: []bool{true, true, false}, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + ma := ma.StringCast(tc.ma) + + ipnet, err := MultiaddrToIPNet(ma) + if err != nil { + t.Fatalf("failed to parse multiaddr %v into ipnet", ma) + } + for i, ipString := range tc.ips { + ip := net.ParseIP(ipString) + if ip == nil { + t.Fatalf("failed to parse IP %s", ipString) + } + if ipnet.Contains(ip) != tc.contained[i] { + t.Fatalf("Contains check failed. Expected %v got %v", tc.contained[i], ipnet.Contains(ip)) + } + } + }) + } +} + +func TestFailMultiaddrToIPNet(t *testing.T) { + type testCase struct { + name string + ma string + } + + testCases := []testCase{ + {name: "missing ip addr", ma: "/ipcidr/24"}, + {name: "wrong mask", ma: "/ip4/1.2.3.0/ipcidr/128"}, + {name: "wrong mask", ma: "/ip6/::/ipcidr/255"}, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + ma := ma.StringCast(tc.ma) + + _, err := MultiaddrToIPNet(ma) + if err == nil { + t.Fatalf("Expected error when parsing: %s", tc.ma) + } + }) + } +} diff --git a/protocols.go b/protocols.go index 432ca06..5895894 100644 --- a/protocols.go +++ b/protocols.go @@ -13,6 +13,7 @@ const ( P_DCCP = 0x0021 P_IP6 = 0x0029 P_IP6ZONE = 0x002A + P_IPCIDR = 0x002B P_QUIC = 0x01CC P_SCTP = 0x0084 P_CIRCUIT = 0x0122 @@ -103,6 +104,13 @@ var ( Size: 128, Transcoder: TranscoderIP6, } + protoIPCIDR = Protocol{ + Name: "ipcidr", + Code: P_IPCIDR, + VCode: CodeToVarint(P_IPCIDR), + Size: 8, + Transcoder: TranscoderIPCIDR, + } // these require varint protoIP6ZONE = Protocol{ Name: "ip6zone", @@ -239,6 +247,7 @@ func init() { protoDCCP, protoIP6, protoIP6ZONE, + protoIPCIDR, protoSCTP, protoCIRCUIT, protoONION2, diff --git a/transcoders.go b/transcoders.go index 8f813cd..9238ffb 100644 --- a/transcoders.go +++ b/transcoders.go @@ -54,6 +54,22 @@ func (t twrp) ValidateBytes(b []byte) error { var TranscoderIP4 = NewTranscoderFromFunctions(ip4StB, ip4BtS, nil) var TranscoderIP6 = NewTranscoderFromFunctions(ip6StB, ip6BtS, nil) var TranscoderIP6Zone = NewTranscoderFromFunctions(ip6zoneStB, ip6zoneBtS, ip6zoneVal) +var TranscoderIPCIDR = NewTranscoderFromFunctions(ipcidrStB, ipcidrBtS, nil) + +func ipcidrBtS(b []byte) (string, error) { + if len(b) != 1 { + return "", fmt.Errorf("invalid length (should be == 1)") + } + return strconv.Itoa(int(b[0])), nil +} + +func ipcidrStB(s string) ([]byte, error) { + ipMask, err := strconv.ParseUint(s, 10, 8) + if err != nil { + return nil, err + } + return []byte{byte(uint8(ipMask))}, nil +} func ip4StB(s string) ([]byte, error) { i := net.ParseIP(s).To4()