Skip to content
Snippets Groups Projects
Commit 88f59ebb authored by Thomas Bellman's avatar Thomas Bellman
Browse files

Split IPs by family in parse_host_mac_ip_list().

Change the return value of the parse_host_mac_ip_list() function to
separate IP addresses by address family (IPv4 and IPv6).  Instead of
just putting all the addresses in the 'ip' element of the returned
hash, we now split them into three lists: 'ipv4', 'ipv6' and 'ip_Name'.
The latter element is used for IP "addresses" that are actually names
within square brackets ("[" and "]").  For example, the line

    foo     123456-789abc   192.0.2.99  2001:db8:01::0:099  [fie]

will now generate the element

    'foo' => {
        'mac'      => [ '123456-789abc' ],
        'ipv4'     => [ '192.0.2.99' ],
        'ipv6'     => [ '2001:db8:1::99' ],
        'ip_Name'  => [ 'fie' ],
        'ip'       => [ '192.0.2.99', '2001:db8:1::99', 'fie' ],
    }

in the return value.  The 'ip' element is retained for backward com-
patibility, but users are recommended to stop using it, as it will
likely be deprecated in the future.

Separating the addresses by address family is helpful in many cases,
as they often need to be treated differently or handled separately,
and users would otherwise need to do that separation themselves.  In
particular, a primary use for this function is to provide data for
DHCP servers, and at least the ISC DHCP server needs entirely separate
config files for DHCPv4 and DHCPv6.

IP addresses will now also be canonicalized and duplicate addresses
for each host will be removed.  (MAC addresses are however at the
moment *not* canonicalized, although that may be added in the future.)

Words in the IP address part of a line that are not valid numerical
IP addresses or symbolic names within square brackets, will now cause
an error to be raised.
parent 045144ad
No related branches found
No related tags found
No related merge requests found
# Copyright (C) 2020 Thomas Bellman.
# Copyright (C) 2020-2022 Thomas Bellman.
# Licensed under the GNU LGPL v3+; see the README file for more information.
require 'ipaddr'
require 'socket'
module Puppet::Parser::Functions
newfunction(:parse_host_mac_ip_list, :type => :rvalue, :doc => '
Parse lines of hostnames, MAC addresses and IP addresses.
......@@ -17,11 +21,37 @@ module Puppet::Parser::Functions
i.e, a name, a MAC address, and zero, one or more IP addresses, all
separated by horizontal whitespace.
The special value "-" (a single minus sign/hyphen, ASCII 0x2d, and
nothing else) in the MAC address column means there is no MAC address,
and the "mac" element will be the empty list. Likewise, a "-" instead
of IP address can be used to explicitly indicate that there are no IP
addresses; this is equivalent to simply not listing any IP addresses.
An IP address can be a numeric IPv4 or IPv6 address, or a name within
square brackets ("[" and "]"). In the latter case, the name will be
returned with the brackets removed. Numeric IP addresses will be
canonicalized.
The return value is a hash with the host names as keys, and the
value for each host being a hash with two items, "ip" and "mac".
Each of those will be a list of strings. The special value "-" (a
single dash and nothing else) in the MAC address column means there
is no MAC address, and the "mac" element will be the empty list.
value for each host being a hash with the following items:
- "mac" The MAC addresses (if any) for the host
- "ipv4" All IPv4 addresses for the host
- "ipv6" All IPv6 addresses for the host
- "ip_Name" All symbolic names in the IP address segment
- "ip" All addresses from the IP address segment; the
concatenation of the ipv4, ipv6 and ip_Name lists.
The "ip" element is mostly for backward compatibility with older
versions of the function. Users are recommended to migrate away
from using it.
All item values are always lists, even the "mac" item (in the future
there might be a way to specify multiple MAC addresses for a host).
Hostnames are not validated for correctness, and currently nor are MAC
addresses. Duplicate addresses for a host are removed.
Each hostname must only be listed once.
Blank lines are ignored. Lines starting with a hash sign ("#") are
comments, and are also skipped
......@@ -35,52 +65,106 @@ module Puppet::Parser::Functions
Example input:
# A simple comment line
foo 012345-6789ab 192.0.2.6
fie - 192.0.2.7 2001:db8:1::7
foo 012345-6789ab 192.0.2.6 [xyzzy]
fie - 2001:db8:1::7 192.0.2.7 2001:db8:1::77
fum cdef-0123-4567
This will be parsed into the hash
{
"foo" => {
"mac" => [ "012345-6789ab" ],
"ip" => [ "192.0.2.6" ],
"mac" => [ "012345-6789ab" ],
"ipv4" => [ "192.0.2.6" ],
"ipv6" => [ ],
"ip_Name" => [ "xyzzy" ],
"ip" => [ "192.0.2.6", "xyzzy" ],
},
"fie" => {
"mac" => [ ],
"ip" => [ "192.0.2.7", "2001:db8:1::7" ],
"mac" => [ ],
"ipv4" => [ "192.0.2.7" ],
"ipv6" => [ "2001:db8:1::7", "2001:db8:1::77" ],
"ip_Name" => [ ],
"ip" => [ "192.0.2.7", "2001:db8:1::7", "2001:db8:1::77" ],
},
"fum" => {
"mac" => [ "cdef-0123-4567" ],
"ip" => [ ],
"mac" => [ "cdef-0123-4567" ],
"ipv4" => [ ],
"ipv6" => [ ],
"ip_Name" => [ "fum" ],
"ip" => [ "fum" ],
},
}
Hostnames, MAC addresses and IP addresses in the input are not
validated for correctness. Multiple lines using the same hostname
are not permitted.
') \
do |args|
if args.length != 1
raise(Puppet::ParseError,
"parse_host_mac_ip_list(): Wrong number of arguments")
end
lines = [args[0]].flatten.join("\n").split("\n")
hostlist_lines = [args[0]].flatten.join("\n").split("\n")
# Skip blank lines and comments
lines = lines.select {|l|
l = l.strip
l != "" and l[0] != "#"
hostlist_lines = hostlist_lines.select { |line|
line = line.strip
line != "" and line[0] != "#"
}
hosts = { }
lines.each do |l|
hostname,macs,*ips = l.split()
macs = (macs == "-") ? [] : [macs]
hostlist_lines.each do |hostline|
hostname,macs,*ips = hostline.split()
macaddrs = (macs == "-") ? [] : [macs]
if ips == ["-"]
ips = []
end
if hosts.has_key?(hostname)
raise(Puppet::ParseError,
"parse_host_mac_ip_list(): Duplicate host name, `#{hostname}'")
end
hosts[hostname] = { 'ip' => ips, 'mac' => [macs].flatten }
ipaddrs = {
Socket::AF_INET => [],
Socket::AF_INET6 => [],
:UNRESOLVED => [],
}
ips.each do |ip|
# If surrounded by square brackets ("[" and "]"), use what is
# inside without trying to resolve.
if m = /^\[(.*)\]$/.match(ip) # "[" address "]"
ipaddrs[:UNRESOLVED] << $1
next
end
# Check for numeric IP address.
# This will also canonicalize the representation.
begin
ipa = IPAddr.new(ip)
rescue ArgumentError => err
errmsg = "parse_host_mac_ip_list(): #{err.to_s}, `#{ip}'"
raise(err.class.new(errmsg))
else
ipaddrs[ipa.family] << ipa.to_s
next
end
# Catch-all
raise(Puppet::ParseError,
"parse_host_mac_ip_list(): Invalid IP format, `#{ip}'")
end
ipaddrs.each_value do |family_addrs|
family_addrs.uniq!
end
hosts[hostname] = {
'ipv4' => ipaddrs[Socket::AF_INET],
'ipv6' => ipaddrs[Socket::AF_INET6],
'ip_Name' => ipaddrs[:UNRESOLVED],
'ip' => ( ipaddrs[Socket::AF_INET] +
ipaddrs[Socket::AF_INET6] +
ipaddrs[:UNRESOLVED]
),
'mac' => [macaddrs].flatten,
}
end
return hosts
end
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment