github.com/eliastor/durgaform@v0.0.0-20220816172711-d0ab2d17673e/internal/lang/funcs/cidr.go (about)

     1  package funcs
     2  
     3  import (
     4  	"fmt"
     5  	"math/big"
     6  
     7  	"github.com/apparentlymart/go-cidr/cidr"
     8  	"github.com/eliastor/durgaform/internal/ipaddr"
     9  	"github.com/zclconf/go-cty/cty"
    10  	"github.com/zclconf/go-cty/cty/function"
    11  	"github.com/zclconf/go-cty/cty/gocty"
    12  )
    13  
    14  // CidrHostFunc contructs a function that calculates a full host IP address
    15  // within a given IP network address prefix.
    16  var CidrHostFunc = function.New(&function.Spec{
    17  	Params: []function.Parameter{
    18  		{
    19  			Name: "prefix",
    20  			Type: cty.String,
    21  		},
    22  		{
    23  			Name: "hostnum",
    24  			Type: cty.Number,
    25  		},
    26  	},
    27  	Type: function.StaticReturnType(cty.String),
    28  	Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
    29  		var hostNum *big.Int
    30  		if err := gocty.FromCtyValue(args[1], &hostNum); err != nil {
    31  			return cty.UnknownVal(cty.String), err
    32  		}
    33  		_, network, err := ipaddr.ParseCIDR(args[0].AsString())
    34  		if err != nil {
    35  			return cty.UnknownVal(cty.String), fmt.Errorf("invalid CIDR expression: %s", err)
    36  		}
    37  
    38  		ip, err := cidr.HostBig(network, hostNum)
    39  		if err != nil {
    40  			return cty.UnknownVal(cty.String), err
    41  		}
    42  
    43  		return cty.StringVal(ip.String()), nil
    44  	},
    45  })
    46  
    47  // CidrNetmaskFunc contructs a function that converts an IPv4 address prefix given
    48  // in CIDR notation into a subnet mask address.
    49  var CidrNetmaskFunc = function.New(&function.Spec{
    50  	Params: []function.Parameter{
    51  		{
    52  			Name: "prefix",
    53  			Type: cty.String,
    54  		},
    55  	},
    56  	Type: function.StaticReturnType(cty.String),
    57  	Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
    58  		_, network, err := ipaddr.ParseCIDR(args[0].AsString())
    59  		if err != nil {
    60  			return cty.UnknownVal(cty.String), fmt.Errorf("invalid CIDR expression: %s", err)
    61  		}
    62  
    63  		if network.IP.To4() == nil {
    64  			return cty.UnknownVal(cty.String), fmt.Errorf("IPv6 addresses cannot have a netmask: %s", args[0].AsString())
    65  		}
    66  
    67  		return cty.StringVal(ipaddr.IP(network.Mask).String()), nil
    68  	},
    69  })
    70  
    71  // CidrSubnetFunc contructs a function that calculates a subnet address within
    72  // a given IP network address prefix.
    73  var CidrSubnetFunc = function.New(&function.Spec{
    74  	Params: []function.Parameter{
    75  		{
    76  			Name: "prefix",
    77  			Type: cty.String,
    78  		},
    79  		{
    80  			Name: "newbits",
    81  			Type: cty.Number,
    82  		},
    83  		{
    84  			Name: "netnum",
    85  			Type: cty.Number,
    86  		},
    87  	},
    88  	Type: function.StaticReturnType(cty.String),
    89  	Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
    90  		var newbits int
    91  		if err := gocty.FromCtyValue(args[1], &newbits); err != nil {
    92  			return cty.UnknownVal(cty.String), err
    93  		}
    94  		var netnum *big.Int
    95  		if err := gocty.FromCtyValue(args[2], &netnum); err != nil {
    96  			return cty.UnknownVal(cty.String), err
    97  		}
    98  
    99  		_, network, err := ipaddr.ParseCIDR(args[0].AsString())
   100  		if err != nil {
   101  			return cty.UnknownVal(cty.String), fmt.Errorf("invalid CIDR expression: %s", err)
   102  		}
   103  
   104  		newNetwork, err := cidr.SubnetBig(network, newbits, netnum)
   105  		if err != nil {
   106  			return cty.UnknownVal(cty.String), err
   107  		}
   108  
   109  		return cty.StringVal(newNetwork.String()), nil
   110  	},
   111  })
   112  
   113  // CidrSubnetsFunc is similar to CidrSubnetFunc but calculates many consecutive
   114  // subnet addresses at once, rather than just a single subnet extension.
   115  var CidrSubnetsFunc = function.New(&function.Spec{
   116  	Params: []function.Parameter{
   117  		{
   118  			Name: "prefix",
   119  			Type: cty.String,
   120  		},
   121  	},
   122  	VarParam: &function.Parameter{
   123  		Name: "newbits",
   124  		Type: cty.Number,
   125  	},
   126  	Type: function.StaticReturnType(cty.List(cty.String)),
   127  	Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
   128  		_, network, err := ipaddr.ParseCIDR(args[0].AsString())
   129  		if err != nil {
   130  			return cty.UnknownVal(cty.String), function.NewArgErrorf(0, "invalid CIDR expression: %s", err)
   131  		}
   132  		startPrefixLen, _ := network.Mask.Size()
   133  
   134  		prefixLengthArgs := args[1:]
   135  		if len(prefixLengthArgs) == 0 {
   136  			return cty.ListValEmpty(cty.String), nil
   137  		}
   138  
   139  		var firstLength int
   140  		if err := gocty.FromCtyValue(prefixLengthArgs[0], &firstLength); err != nil {
   141  			return cty.UnknownVal(cty.String), function.NewArgError(1, err)
   142  		}
   143  		firstLength += startPrefixLen
   144  
   145  		retVals := make([]cty.Value, len(prefixLengthArgs))
   146  
   147  		current, _ := cidr.PreviousSubnet(network, firstLength)
   148  		for i, lengthArg := range prefixLengthArgs {
   149  			var length int
   150  			if err := gocty.FromCtyValue(lengthArg, &length); err != nil {
   151  				return cty.UnknownVal(cty.String), function.NewArgError(i+1, err)
   152  			}
   153  
   154  			if length < 1 {
   155  				return cty.UnknownVal(cty.String), function.NewArgErrorf(i+1, "must extend prefix by at least one bit")
   156  			}
   157  			// For portability with 32-bit systems where the subnet number
   158  			// will be a 32-bit int, we only allow extension of 32 bits in
   159  			// one call even if we're running on a 64-bit machine.
   160  			// (Of course, this is significant only for IPv6.)
   161  			if length > 32 {
   162  				return cty.UnknownVal(cty.String), function.NewArgErrorf(i+1, "may not extend prefix by more than 32 bits")
   163  			}
   164  			length += startPrefixLen
   165  			if length > (len(network.IP) * 8) {
   166  				protocol := "IP"
   167  				switch len(network.IP) * 8 {
   168  				case 32:
   169  					protocol = "IPv4"
   170  				case 128:
   171  					protocol = "IPv6"
   172  				}
   173  				return cty.UnknownVal(cty.String), function.NewArgErrorf(i+1, "would extend prefix to %d bits, which is too long for an %s address", length, protocol)
   174  			}
   175  
   176  			next, rollover := cidr.NextSubnet(current, length)
   177  			if rollover || !network.Contains(next.IP) {
   178  				// If we run out of suffix bits in the base CIDR prefix then
   179  				// NextSubnet will start incrementing the prefix bits, which
   180  				// we don't allow because it would then allocate addresses
   181  				// outside of the caller's given prefix.
   182  				return cty.UnknownVal(cty.String), function.NewArgErrorf(i+1, "not enough remaining address space for a subnet with a prefix of %d bits after %s", length, current.String())
   183  			}
   184  
   185  			current = next
   186  			retVals[i] = cty.StringVal(current.String())
   187  		}
   188  
   189  		return cty.ListVal(retVals), nil
   190  	},
   191  })
   192  
   193  // CidrHost calculates a full host IP address within a given IP network address prefix.
   194  func CidrHost(prefix, hostnum cty.Value) (cty.Value, error) {
   195  	return CidrHostFunc.Call([]cty.Value{prefix, hostnum})
   196  }
   197  
   198  // CidrNetmask converts an IPv4 address prefix given in CIDR notation into a subnet mask address.
   199  func CidrNetmask(prefix cty.Value) (cty.Value, error) {
   200  	return CidrNetmaskFunc.Call([]cty.Value{prefix})
   201  }
   202  
   203  // CidrSubnet calculates a subnet address within a given IP network address prefix.
   204  func CidrSubnet(prefix, newbits, netnum cty.Value) (cty.Value, error) {
   205  	return CidrSubnetFunc.Call([]cty.Value{prefix, newbits, netnum})
   206  }
   207  
   208  // CidrSubnets calculates a sequence of consecutive subnet prefixes that may
   209  // be of different prefix lengths under a common base prefix.
   210  func CidrSubnets(prefix cty.Value, newbits ...cty.Value) (cty.Value, error) {
   211  	args := make([]cty.Value, len(newbits)+1)
   212  	args[0] = prefix
   213  	copy(args[1:], newbits)
   214  	return CidrSubnetsFunc.Call(args)
   215  }