Repository: brimsec/zq
Pull request #1700 (2020-11-26 18:20:44)

add network_of() function
This commit adds a function to return a network from an IP address.
If one argument is given, then its an IP address and that IP must be
an IPv4 address, which determines the default netmask to use via
the IP class A-B-C system.  If two arguments are given, then the
second argument is a netmask given either as a integer (indicating
the number of bits to use in the netmask, e.g., a /28 subnet)
or it's a network value in which case the network number must have
all network bits set to 1 and host bits set to 0, as in, 10.1.2.128/25.


Patch:
diff --git a/expr/function/function.go b/expr/function/function.go
index 67df1e198..e789d3b31 100644
--- a/expr/function/function.go
+++ b/expr/function/function.go
@@ -130,6 +130,9 @@ func New(name string, narg int) (Interface, error) {
 		f = &toBase64{}
 	case "from_base64":
 		f = &fromBase64{}
+	case "network_of":
+		argmax = 2
+		f = &networkOf{}
 	}
 	if argmin != -1 && narg < argmin {
 		return nil, ErrTooFewArgs
diff --git a/expr/function/ip.go b/expr/function/ip.go
new file mode 100644
index 000000000..7637b6353
--- /dev/null
+++ b/expr/function/ip.go
@@ -0,0 +1,72 @@
+package function
+
+import (
+	"bytes"
+	"net"
+
+	"github.com/brimsec/zq/expr/result"
+	"github.com/brimsec/zq/zng"
+)
+
+type networkOf struct {
+	result.Buffer
+}
+
+func (n *networkOf) Call(args []zng.Value) (zng.Value, error) {
+	id := args[0].Type.ID()
+	if id != zng.IdIP {
+		return zng.NewErrorf("not an IP"), nil
+	}
+	// XXX GC
+	ip, err := zng.DecodeIP(args[0].Bytes)
+	if err != nil {
+		return zng.NewError(err), nil
+	}
+	var mask net.IPMask
+	if len(args) == 1 {
+		mask = ip.DefaultMask()
+		if mask == nil {
+			return zng.NewErrorf("not an IPv4"), nil
+		}
+	} else {
+		// two args
+		id := args[1].Type.ID()
+		body := args[1].Bytes
+		if id == zng.IdNet {
+			var err error
+			cidrMask, err := zng.DecodeNet(body)
+			if err != nil {
+				return zng.NewError(err), nil
+			}
+			if !bytes.Equal(cidrMask.IP, cidrMask.Mask) {
+				return zng.NewErrorf("network arg not a cidr mask"), nil
+			}
+			mask = cidrMask.Mask
+		} else if zng.IsInteger(id) {
+			var nbits uint
+			if zng.IsSigned(id) {
+				v, err := zng.DecodeInt(body)
+				if err != nil {
+					return zng.NewError(err), nil
+				}
+				nbits = uint(v)
+			} else {
+				v, err := zng.DecodeUint(body)
+				if err != nil {
+					return zng.NewError(err), nil
+				}
+				nbits = uint(v)
+			}
+			if nbits > 64 {
+				return zng.NewErrorf("cidr bit count out of range"), nil
+			}
+			mask = net.CIDRMask(int(nbits), 8*len(ip))
+		} else {
+			return zng.NewErrorf("bad arg for cidr mask"), nil
+		}
+	}
+	// XXX GC
+	netIP := ip.Mask(mask)
+	v := &net.IPNet{netIP, mask}
+	return zng.Value{zng.TypeNet, zng.EncodeNet(v)}, nil
+}


Test patch:
diff --git a/expr/ztests/network-of.yaml b/expr/ztests/network-of.yaml
new file mode 100644
index 000000000..462fbe0a3
--- /dev/null
+++ b/expr/ztests/network-of.yaml
@@ -0,0 +1,9 @@
+zql: put n1=network_of(a,mask),n2=network_of(a,24)
+
+input: |
+  #0:record[a:ip,mask:net]
+  0:[10.1.2.129;255.255.255.128/25;]
+
+output: |
+  #0:record[a:ip,mask:net,n1:net,n2:net]
+  0:[10.1.2.129;255.255.255.128/25;10.1.2.128/25;10.1.2.0/24;]