github.com/opentofu/opentofu@v1.7.1/internal/lang/funcs/cidr.go (about) 1 // Copyright (c) The OpenTofu Authors 2 // SPDX-License-Identifier: MPL-2.0 3 // Copyright (c) 2023 HashiCorp, Inc. 4 // SPDX-License-Identifier: MPL-2.0 5 6 package funcs 7 8 import ( 9 "fmt" 10 "math/big" 11 12 "github.com/apparentlymart/go-cidr/cidr" 13 "github.com/opentofu/opentofu/internal/ipaddr" 14 "github.com/zclconf/go-cty/cty" 15 "github.com/zclconf/go-cty/cty/function" 16 "github.com/zclconf/go-cty/cty/gocty" 17 ) 18 19 // CidrHostFunc contructs a function that calculates a full host IP address 20 // within a given IP network address prefix. 21 var CidrHostFunc = function.New(&function.Spec{ 22 Params: []function.Parameter{ 23 { 24 Name: "prefix", 25 Type: cty.String, 26 }, 27 { 28 Name: "hostnum", 29 Type: cty.Number, 30 }, 31 }, 32 Type: function.StaticReturnType(cty.String), 33 RefineResult: refineNotNull, 34 Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { 35 var hostNum *big.Int 36 if err := gocty.FromCtyValue(args[1], &hostNum); err != nil { 37 return cty.UnknownVal(cty.String), err 38 } 39 _, network, err := ipaddr.ParseCIDR(args[0].AsString()) 40 if err != nil { 41 return cty.UnknownVal(cty.String), fmt.Errorf("invalid CIDR expression: %w", err) 42 } 43 44 ip, err := cidr.HostBig(network, hostNum) 45 if err != nil { 46 return cty.UnknownVal(cty.String), err 47 } 48 49 return cty.StringVal(ip.String()), nil 50 }, 51 }) 52 53 // CidrNetmaskFunc contructs a function that converts an IPv4 address prefix given 54 // in CIDR notation into a subnet mask address. 55 var CidrNetmaskFunc = function.New(&function.Spec{ 56 Params: []function.Parameter{ 57 { 58 Name: "prefix", 59 Type: cty.String, 60 }, 61 }, 62 Type: function.StaticReturnType(cty.String), 63 RefineResult: refineNotNull, 64 Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { 65 _, network, err := ipaddr.ParseCIDR(args[0].AsString()) 66 if err != nil { 67 return cty.UnknownVal(cty.String), fmt.Errorf("invalid CIDR expression: %w", err) 68 } 69 70 if network.IP.To4() == nil { 71 return cty.UnknownVal(cty.String), fmt.Errorf("IPv6 addresses cannot have a netmask: %s", args[0].AsString()) 72 } 73 74 return cty.StringVal(ipaddr.IP(network.Mask).String()), nil 75 }, 76 }) 77 78 // CidrSubnetFunc contructs a function that calculates a subnet address within 79 // a given IP network address prefix. 80 var CidrSubnetFunc = function.New(&function.Spec{ 81 Params: []function.Parameter{ 82 { 83 Name: "prefix", 84 Type: cty.String, 85 }, 86 { 87 Name: "newbits", 88 Type: cty.Number, 89 }, 90 { 91 Name: "netnum", 92 Type: cty.Number, 93 }, 94 }, 95 Type: function.StaticReturnType(cty.String), 96 RefineResult: refineNotNull, 97 Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { 98 var newbits int 99 if err := gocty.FromCtyValue(args[1], &newbits); err != nil { 100 return cty.UnknownVal(cty.String), err 101 } 102 var netnum *big.Int 103 if err := gocty.FromCtyValue(args[2], &netnum); err != nil { 104 return cty.UnknownVal(cty.String), err 105 } 106 107 _, network, err := ipaddr.ParseCIDR(args[0].AsString()) 108 if err != nil { 109 return cty.UnknownVal(cty.String), fmt.Errorf("invalid CIDR expression: %w", err) 110 } 111 112 newNetwork, err := cidr.SubnetBig(network, newbits, netnum) 113 if err != nil { 114 return cty.UnknownVal(cty.String), err 115 } 116 117 return cty.StringVal(newNetwork.String()), nil 118 }, 119 }) 120 121 // CidrSubnetsFunc is similar to CidrSubnetFunc but calculates many consecutive 122 // subnet addresses at once, rather than just a single subnet extension. 123 var CidrSubnetsFunc = function.New(&function.Spec{ 124 Params: []function.Parameter{ 125 { 126 Name: "prefix", 127 Type: cty.String, 128 }, 129 }, 130 VarParam: &function.Parameter{ 131 Name: "newbits", 132 Type: cty.Number, 133 }, 134 Type: function.StaticReturnType(cty.List(cty.String)), 135 RefineResult: refineNotNull, 136 Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { 137 _, network, err := ipaddr.ParseCIDR(args[0].AsString()) 138 if err != nil { 139 return cty.UnknownVal(cty.String), function.NewArgErrorf(0, "invalid CIDR expression: %s", err) 140 } 141 startPrefixLen, _ := network.Mask.Size() 142 143 prefixLengthArgs := args[1:] 144 if len(prefixLengthArgs) == 0 { 145 return cty.ListValEmpty(cty.String), nil 146 } 147 148 var firstLength int 149 if err := gocty.FromCtyValue(prefixLengthArgs[0], &firstLength); err != nil { 150 return cty.UnknownVal(cty.String), function.NewArgError(1, err) 151 } 152 firstLength += startPrefixLen 153 154 retVals := make([]cty.Value, len(prefixLengthArgs)) 155 156 current, _ := cidr.PreviousSubnet(network, firstLength) 157 for i, lengthArg := range prefixLengthArgs { 158 var length int 159 if err := gocty.FromCtyValue(lengthArg, &length); err != nil { 160 return cty.UnknownVal(cty.String), function.NewArgError(i+1, err) 161 } 162 163 if length < 1 { 164 return cty.UnknownVal(cty.String), function.NewArgErrorf(i+1, "must extend prefix by at least one bit") 165 } 166 // For portability with 32-bit systems where the subnet number 167 // will be a 32-bit int, we only allow extension of 32 bits in 168 // one call even if we're running on a 64-bit machine. 169 // (Of course, this is significant only for IPv6.) 170 if length > 32 { 171 return cty.UnknownVal(cty.String), function.NewArgErrorf(i+1, "may not extend prefix by more than 32 bits") 172 } 173 length += startPrefixLen 174 if length > (len(network.IP) * 8) { 175 protocol := "IP" 176 switch len(network.IP) * 8 { 177 case 32: 178 protocol = "IPv4" 179 case 128: 180 protocol = "IPv6" 181 } 182 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) 183 } 184 185 next, rollover := cidr.NextSubnet(current, length) 186 if rollover || !network.Contains(next.IP) { 187 // If we run out of suffix bits in the base CIDR prefix then 188 // NextSubnet will start incrementing the prefix bits, which 189 // we don't allow because it would then allocate addresses 190 // outside of the caller's given prefix. 191 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()) 192 } 193 194 current = next 195 retVals[i] = cty.StringVal(current.String()) 196 } 197 198 return cty.ListVal(retVals), nil 199 }, 200 }) 201 202 // CidrContainsFunc constructs a function that checks whether a given IP address 203 // is within a given IP network address prefix. 204 var CidrContainsFunc = function.New(&function.Spec{ 205 Params: []function.Parameter{ 206 { 207 Name: "containing_prefix", 208 Type: cty.String, 209 }, 210 { 211 Name: "contained_ip_or_prefix", 212 Type: cty.String, 213 }, 214 }, 215 Type: function.StaticReturnType(cty.Bool), 216 Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { 217 prefix := args[0].AsString() 218 addr := args[1].AsString() 219 220 // The first argument must be a CIDR prefix. 221 _, containing, err := ipaddr.ParseCIDR(prefix) 222 if err != nil { 223 return cty.UnknownVal(cty.Bool), err 224 } 225 226 // The second argument can be either an IP address or a CIDR prefix. 227 // We will try parsing it as an IP address first. 228 startIP := ipaddr.ParseIP(addr) 229 var endIP ipaddr.IP 230 231 // If the second argument did not parse as an IP, we will try parsing it 232 // as a CIDR prefix. 233 if startIP == nil { 234 _, contained, err := ipaddr.ParseCIDR(addr) 235 236 // If that also fails, we'll return an error. 237 if err != nil { 238 return cty.UnknownVal(cty.Bool), fmt.Errorf("invalid IP address or prefix: %s", addr) 239 } 240 241 // Otherwise, we will want to know the start and the end IP of the 242 // prefix, so that we can check whether both are contained in the 243 // containing prefix. 244 startIP, endIP = cidr.AddressRange(contained) 245 } 246 247 // We require that both addresses are of the same type, so that 248 // we can't accidentally compare an IPv4 address to an IPv6 prefix. 249 // The underlying Go function will always return false if this happens, 250 // but we want to return an error instead so that the caller can 251 // distinguish between a "legitimate" false result and an erroneous 252 // check. 253 if (startIP.To4() == nil) != (containing.IP.To4() == nil) { 254 return cty.UnknownVal(cty.Bool), fmt.Errorf("address family mismatch: %s vs. %s", prefix, addr) 255 } 256 257 // If the second argument was an IP address, we will check whether it 258 // is contained in the containing prefix, and that's our result. 259 result := containing.Contains(startIP) 260 261 // If the second argument was a CIDR prefix, we will also check whether 262 // the end IP of the prefix is contained in the containing prefix. 263 // Once CIDR is contained in another CIDR iff both the start and the 264 // end IP of the contained CIDR are contained in the containing CIDR. 265 if endIP != nil { 266 result = result && containing.Contains(endIP) 267 } 268 269 return cty.BoolVal(result), nil 270 }, 271 }) 272 273 // CidrHost calculates a full host IP address within a given IP network address prefix. 274 func CidrHost(prefix, hostnum cty.Value) (cty.Value, error) { 275 return CidrHostFunc.Call([]cty.Value{prefix, hostnum}) 276 } 277 278 // CidrNetmask converts an IPv4 address prefix given in CIDR notation into a subnet mask address. 279 func CidrNetmask(prefix cty.Value) (cty.Value, error) { 280 return CidrNetmaskFunc.Call([]cty.Value{prefix}) 281 } 282 283 // CidrSubnet calculates a subnet address within a given IP network address prefix. 284 func CidrSubnet(prefix, newbits, netnum cty.Value) (cty.Value, error) { 285 return CidrSubnetFunc.Call([]cty.Value{prefix, newbits, netnum}) 286 } 287 288 // CidrSubnets calculates a sequence of consecutive subnet prefixes that may 289 // be of different prefix lengths under a common base prefix. 290 func CidrSubnets(prefix cty.Value, newbits ...cty.Value) (cty.Value, error) { 291 args := make([]cty.Value, len(newbits)+1) 292 args[0] = prefix 293 copy(args[1:], newbits) 294 return CidrSubnetsFunc.Call(args) 295 } 296 297 // CidrContains checks whether a given IP address is within a given IP network address prefix. 298 func CidrContains(prefix, address cty.Value) (cty.Value, error) { 299 return CidrContainsFunc.Call([]cty.Value{prefix, address}) 300 }