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