2018-05-24 00:42:04 +02:00
package funcs
import (
"fmt"
2020-07-08 16:58:06 +02:00
"math/big"
2018-05-24 00:42:04 +02:00
"github.com/apparentlymart/go-cidr/cidr"
2021-08-17 20:30:18 +02:00
"github.com/hashicorp/terraform/internal/ipaddr"
2018-05-24 00:42:04 +02:00
"github.com/zclconf/go-cty/cty"
"github.com/zclconf/go-cty/cty/function"
"github.com/zclconf/go-cty/cty/gocty"
)
2018-05-25 00:20:22 +02:00
// CidrHostFunc contructs a function that calculates a full host IP address
2018-05-24 00:42:04 +02:00
// within a given IP network address prefix.
var CidrHostFunc = function . New ( & function . Spec {
Params : [ ] function . Parameter {
{
Name : "prefix" ,
Type : cty . String ,
} ,
{
Name : "hostnum" ,
Type : cty . Number ,
} ,
} ,
Type : function . StaticReturnType ( cty . String ) ,
Impl : func ( args [ ] cty . Value , retType cty . Type ) ( ret cty . Value , err error ) {
2020-07-08 16:58:06 +02:00
var hostNum * big . Int
2018-05-24 00:42:04 +02:00
if err := gocty . FromCtyValue ( args [ 1 ] , & hostNum ) ; err != nil {
return cty . UnknownVal ( cty . String ) , err
}
2021-08-17 20:30:18 +02:00
_ , network , err := ipaddr . ParseCIDR ( args [ 0 ] . AsString ( ) )
2018-05-24 00:42:04 +02:00
if err != nil {
return cty . UnknownVal ( cty . String ) , fmt . Errorf ( "invalid CIDR expression: %s" , err )
}
2020-07-08 16:58:06 +02:00
ip , err := cidr . HostBig ( network , hostNum )
2018-05-24 00:42:04 +02:00
if err != nil {
return cty . UnknownVal ( cty . String ) , err
}
return cty . StringVal ( ip . String ( ) ) , nil
} ,
} )
2018-05-25 00:20:22 +02:00
// CidrNetmaskFunc contructs a function that converts an IPv4 address prefix given
2018-05-24 00:42:04 +02:00
// in CIDR notation into a subnet mask address.
var CidrNetmaskFunc = function . New ( & function . Spec {
Params : [ ] function . Parameter {
{
Name : "prefix" ,
Type : cty . String ,
} ,
} ,
Type : function . StaticReturnType ( cty . String ) ,
Impl : func ( args [ ] cty . Value , retType cty . Type ) ( ret cty . Value , err error ) {
2021-08-17 20:30:18 +02:00
_ , network , err := ipaddr . ParseCIDR ( args [ 0 ] . AsString ( ) )
2018-05-24 00:42:04 +02:00
if err != nil {
return cty . UnknownVal ( cty . String ) , fmt . Errorf ( "invalid CIDR expression: %s" , err )
}
2021-08-17 20:30:18 +02:00
return cty . StringVal ( ipaddr . IP ( network . Mask ) . String ( ) ) , nil
2018-05-24 00:42:04 +02:00
} ,
} )
2018-05-25 00:20:22 +02:00
// CidrSubnetFunc contructs a function that calculates a subnet address within
2018-05-24 00:42:04 +02:00
// a given IP network address prefix.
var CidrSubnetFunc = function . New ( & function . Spec {
Params : [ ] function . Parameter {
{
Name : "prefix" ,
Type : cty . String ,
} ,
{
Name : "newbits" ,
Type : cty . Number ,
} ,
{
Name : "netnum" ,
Type : cty . Number ,
} ,
} ,
Type : function . StaticReturnType ( cty . String ) ,
Impl : func ( args [ ] cty . Value , retType cty . Type ) ( ret cty . Value , err error ) {
var newbits int
if err := gocty . FromCtyValue ( args [ 1 ] , & newbits ) ; err != nil {
return cty . UnknownVal ( cty . String ) , err
}
2020-07-08 16:58:06 +02:00
var netnum * big . Int
2018-05-24 00:42:04 +02:00
if err := gocty . FromCtyValue ( args [ 2 ] , & netnum ) ; err != nil {
return cty . UnknownVal ( cty . String ) , err
}
2021-08-17 20:30:18 +02:00
_ , network , err := ipaddr . ParseCIDR ( args [ 0 ] . AsString ( ) )
2018-05-24 00:42:04 +02:00
if err != nil {
return cty . UnknownVal ( cty . String ) , fmt . Errorf ( "invalid CIDR expression: %s" , err )
}
2020-07-08 16:58:06 +02:00
newNetwork , err := cidr . SubnetBig ( network , newbits , netnum )
2018-05-24 00:42:04 +02:00
if err != nil {
return cty . UnknownVal ( cty . String ) , err
}
return cty . StringVal ( newNetwork . String ( ) ) , nil
} ,
} )
2019-09-20 04:06:15 +02:00
// CidrSubnetsFunc is similar to CidrSubnetFunc but calculates many consecutive
// subnet addresses at once, rather than just a single subnet extension.
var CidrSubnetsFunc = function . New ( & function . Spec {
Params : [ ] function . Parameter {
{
Name : "prefix" ,
Type : cty . String ,
} ,
} ,
VarParam : & function . Parameter {
Name : "newbits" ,
Type : cty . Number ,
} ,
Type : function . StaticReturnType ( cty . List ( cty . String ) ) ,
Impl : func ( args [ ] cty . Value , retType cty . Type ) ( ret cty . Value , err error ) {
2021-08-17 20:30:18 +02:00
_ , network , err := ipaddr . ParseCIDR ( args [ 0 ] . AsString ( ) )
2019-09-20 04:06:15 +02:00
if err != nil {
return cty . UnknownVal ( cty . String ) , function . NewArgErrorf ( 0 , "invalid CIDR expression: %s" , err )
}
startPrefixLen , _ := network . Mask . Size ( )
prefixLengthArgs := args [ 1 : ]
if len ( prefixLengthArgs ) == 0 {
return cty . ListValEmpty ( cty . String ) , nil
}
var firstLength int
if err := gocty . FromCtyValue ( prefixLengthArgs [ 0 ] , & firstLength ) ; err != nil {
return cty . UnknownVal ( cty . String ) , function . NewArgError ( 1 , err )
}
firstLength += startPrefixLen
retVals := make ( [ ] cty . Value , len ( prefixLengthArgs ) )
current , _ := cidr . PreviousSubnet ( network , firstLength )
for i , lengthArg := range prefixLengthArgs {
var length int
if err := gocty . FromCtyValue ( lengthArg , & length ) ; err != nil {
return cty . UnknownVal ( cty . String ) , function . NewArgError ( i + 1 , err )
}
if length < 1 {
return cty . UnknownVal ( cty . String ) , function . NewArgErrorf ( i + 1 , "must extend prefix by at least one bit" )
}
// For portability with 32-bit systems where the subnet number
// will be a 32-bit int, we only allow extension of 32 bits in
// one call even if we're running on a 64-bit machine.
// (Of course, this is significant only for IPv6.)
if length > 32 {
return cty . UnknownVal ( cty . String ) , function . NewArgErrorf ( i + 1 , "may not extend prefix by more than 32 bits" )
}
length += startPrefixLen
if length > ( len ( network . IP ) * 8 ) {
protocol := "IP"
switch len ( network . IP ) * 8 {
case 32 :
protocol = "IPv4"
case 128 :
protocol = "IPv6"
}
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 )
}
next , rollover := cidr . NextSubnet ( current , length )
if rollover || ! network . Contains ( next . IP ) {
// If we run out of suffix bits in the base CIDR prefix then
// NextSubnet will start incrementing the prefix bits, which
// we don't allow because it would then allocate addresses
// outside of the caller's given prefix.
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 ( ) )
}
current = next
retVals [ i ] = cty . StringVal ( current . String ( ) )
}
return cty . ListVal ( retVals ) , nil
} ,
} )
2018-05-24 00:42:04 +02:00
// CidrHost calculates a full host IP address within a given IP network address prefix.
func CidrHost ( prefix , hostnum cty . Value ) ( cty . Value , error ) {
return CidrHostFunc . Call ( [ ] cty . Value { prefix , hostnum } )
}
// CidrNetmask converts an IPv4 address prefix given in CIDR notation into a subnet mask address.
func CidrNetmask ( prefix cty . Value ) ( cty . Value , error ) {
return CidrNetmaskFunc . Call ( [ ] cty . Value { prefix } )
}
// CidrSubnet calculates a subnet address within a given IP network address prefix.
func CidrSubnet ( prefix , newbits , netnum cty . Value ) ( cty . Value , error ) {
return CidrSubnetFunc . Call ( [ ] cty . Value { prefix , newbits , netnum } )
}
2019-09-20 04:06:15 +02:00
// CidrSubnets calculates a sequence of consecutive subnet prefixes that may
// be of different prefix lengths under a common base prefix.
func CidrSubnets ( prefix cty . Value , newbits ... cty . Value ) ( cty . Value , error ) {
args := make ( [ ] cty . Value , len ( newbits ) + 1 )
args [ 0 ] = prefix
copy ( args [ 1 : ] , newbits )
return CidrSubnetsFunc . Call ( args )
}