From 3481ef83efa3b0130d13fedcd3d191e7053a4e0e Mon Sep 17 00:00:00 2001
From: Thomas Bellman <bellman@nsc.liu.se>
Date: Thu, 9 Feb 2023 21:02:26 +0100
Subject: [PATCH] Add 'trunc_to_net' flag to resolve_ipnets() function.

This new flag will cause resolve_ipnets() to mask the resolved IP
addresses with the specified netmask, thus "truncating" the address
down to the network address for the subnet.  For example, if the
hostname "exempel.se" resolves to 2001:db8:17:23::69:3:4711, then
"exempel.se/64" will return 2001:db8:17:23::/64, "exempel.se/112"
will return 2001:db8:17:23::69:3:0/112, and "exempel.se/96" will
yield the address 2001:db8:17:23:0:69::/96.

This can be useful if you have an "example" host, and want to specify
the entire subnet on which that host resides, but your application
requires the address part of the CIDR specification given to it to be
the network address, i.e. with all bits in the host part to be zero.
---
 lib/puppet/parser/functions/resolve_ipnets.rb | 27 ++++++++++++++++---
 1 file changed, 23 insertions(+), 4 deletions(-)

diff --git a/lib/puppet/parser/functions/resolve_ipnets.rb b/lib/puppet/parser/functions/resolve_ipnets.rb
index 61f408e..3405867 100644
--- a/lib/puppet/parser/functions/resolve_ipnets.rb
+++ b/lib/puppet/parser/functions/resolve_ipnets.rb
@@ -7,7 +7,8 @@ require 'ipaddr'
 module Puppet::Parser::Functions
 
     def resolve_ipnets__netspec(
-		addr, ipfamilies, on_error, do_partsort, forcemask)
+		addr, ipfamilies, on_error, do_partsort, forcemask,
+		truncate_to_network)
 
 	familyname_sockaf_map = {
 	    :__ANY__	=> Socket::AF_UNSPEC,	# Internal use
@@ -36,7 +37,7 @@ module Puppet::Parser::Functions
 	end
 
 	if m = /^\[(.*)\]$/.match(hostname)	# "[" address "]"
-	    if forcemask && maskspec == ""
+	    if (forcemask && maskspec == "") || truncate_to_network
 		raise(Puppet::ParseError,
 		      ("resolve_ipnets(): Can't force a netmask on bracketed" +
 		       " address, #{addr}"))
@@ -71,6 +72,9 @@ module Puppet::Parser::Functions
 			       "Unsupported IP address, #{hostname}"))
 		    end
 		end
+		if truncate_to_network && maskspec != ""
+		    ip = ip.mask(maskspec[1..-1])
+		end
 		return [ip.to_s + maskspec]
 	    end
 	end
@@ -114,7 +118,12 @@ module Puppet::Parser::Functions
 	    else
 		family_maskspec = maskspec
 	    end
-	    ipaddrs += ips[family].collect {|ip| ip + family_maskspec }
+	    ipaddrs += ips[family].collect { |ip|
+		if truncate_to_network && family_maskspec != ""
+		    ip = IPAddr.new(ip + family_maskspec).to_s
+		end
+		ip + family_maskspec
+	    }
 	end
 	if ipaddrs.length == 0
 	    # Note: this will only show the last error message
@@ -144,6 +153,12 @@ module Puppet::Parser::Functions
 		    Always add a host mask (/32 or /128) to the result if no
 		    mask was specified in the input.
 
+	    "trunc_to_net"
+		    Truncate the resolved address to the network address based
+		    on the specified netmask.  If e.g. "example.org" resolves
+		    to 198.51.100.123, then "example.org/28" will result in
+		    198.51.100.112/28 with this flag in effect.
+
 	    "ignoreerrors"
 		    If an address cannot be resolved, return it unchanged.
 
@@ -215,6 +230,7 @@ module Puppet::Parser::Functions
 	netspecs,*flags = args
 	ipfamilies = []
 	forcemask = false
+	truncate_to_network = false
 	on_error = :fail
 	do_partsort = false	# Sort addresses for each input netspec
 	[flags].flatten.each do |f|
@@ -223,6 +239,8 @@ module Puppet::Parser::Functions
 		ipfamilies << f
 	    when 'forcemask'
 		forcemask = true
+	    when 'trunc_to_net'
+		truncate_to_network = true
 	    when 'ignoreerrors'
 		on_error = :ignore
 	    when 'failerrors'
@@ -239,7 +257,8 @@ module Puppet::Parser::Functions
 	res = []
 	[netspecs].flatten.each do |netspec|
 	    res += Puppet::Parser::Functions::resolve_ipnets__netspec(
-		netspec, ipfamilies, on_error, do_partsort, forcemask)
+		netspec, ipfamilies, on_error, do_partsort, forcemask,
+		truncate_to_network)
 	end
 	return res.uniq
     end
-- 
GitLab