Skip to content

Commit 3af5a23

Browse files
committed
feat: add IPv6 ipset support
Feature: Add support for IPv6 addresses to ipsets. You can now specify IPv6 addresses when using `ipset` in the `ipset_entries` list when using `hash:ip` or `hash:net`. Reason: Users need to be able to specify IPv6 addresses in `ipset` definitions. Result: Users can specify IPv6 addresses in `ipset` definitions. NOTE: You cannot mix IPv4, IPv6, and MAC addresses in the same `ipset_entries` list. This is a limitation of the underlying firewalld implementation. Signed-off-by: Rich Megginson <[email protected]>
1 parent 0513cd1 commit 3af5a23

File tree

5 files changed

+325
-49
lines changed

5 files changed

+325
-49
lines changed

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -384,6 +384,10 @@ Use `source` to add and remove ipsets from a zone
384384
When creating an ipset, you must also specify `ipset_type`,
385385
and optionally `short`, `description`, `ipset_entries`
386386

387+
**NOTE**: You cannot mix IPv4, IPv6, and MAC addresses in the same
388+
`ipset_entries` list. All addresses must be the same IP type. This is a
389+
limitation of the underlying firewalld implementation.
390+
387391
Defining an ipset with all optional fields:
388392

389393
```yaml
@@ -478,6 +482,10 @@ Used with `ipset`
478482
Entries must be compatible with the ipset type of the `ipset`
479483
being created or modified.
480484

485+
**NOTE**: You cannot mix IPv4, IPv6, and MAC addresses in the same
486+
`ipset_entries` list. All addresses must be the same IP type. This is a
487+
limitation of the underlying firewalld implementation.
488+
481489
```yaml
482490
ipset: customipset
483491
ipset_entries:

library/firewall_lib.py

Lines changed: 56 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@
2121
# along with this program. If not, see <http://www.gnu.org/licenses/>.
2222

2323
from __future__ import absolute_import, division, print_function
24+
import ipaddress
25+
26+
from firewall.functions import check_mac
2427

2528
__metaclass__ = type
2629

@@ -343,6 +346,39 @@ def try_set_zone_of_interface(module, _zone, interface):
343346
return (False, False)
344347

345348

349+
# Check that all of the ipset entries are ipv4, ipv6, or mac addresses
350+
# if not, fail the module
351+
# if they are, return "ipv4", "ipv6", or "mac"
352+
def validate_ipset_entries(ipset_entries, module):
353+
addr_type = None
354+
is_mixed_addr_types = False
355+
for entry in ipset_entries:
356+
if check_mac(entry):
357+
if addr_type is None:
358+
addr_type = "mac"
359+
elif addr_type != "mac":
360+
is_mixed_addr_types = True
361+
else:
362+
addr = ipaddress.ip_interface(entry)
363+
if addr.version == 4:
364+
if addr_type is None:
365+
addr_type = "ipv4"
366+
elif addr_type != "ipv4":
367+
is_mixed_addr_types = True
368+
elif addr.version == 6:
369+
if addr_type is None:
370+
addr_type = "ipv6"
371+
elif addr_type != "ipv6":
372+
is_mixed_addr_types = True
373+
else:
374+
module.fail_json(msg="Invalid IP address - " + entry)
375+
if is_mixed_addr_types:
376+
module.fail_json(
377+
msg="Address types cannot be mixed in ipset entries - " + str(ipset_entries)
378+
)
379+
return addr_type
380+
381+
346382
# Above: adapted from firewall-cmd source code
347383

348384

@@ -613,13 +649,14 @@ def set_service(
613649
else:
614650
self.module.fail_json(msg="INVALID SERVICE - " + item)
615651

616-
def _create_ipset(self, ipset, ipset_type):
652+
def _create_ipset(self, ipset, ipset_type, ipset_options):
617653
if not ipset_type:
618654
self.module.fail_json(msg="ipset_type needed when creating a new ipset")
619655

620656
fw_ipset = None
621657
fw_ipset_settings = FirewallClientIPSetSettings()
622658
fw_ipset_settings.setType(ipset_type)
659+
fw_ipset_settings.setOptions(ipset_options)
623660
if not self.module.check_mode:
624661
self.fw.config().addIPSet(ipset, fw_ipset_settings)
625662
fw_ipset = self.fw.config().getIPSetByName(ipset)
@@ -628,6 +665,14 @@ def _create_ipset(self, ipset, ipset_type):
628665
return fw_ipset, fw_ipset_settings
629666

630667
def set_ipset(self, ipset, description, short, ipset_type, ipset_entries):
668+
addr_type = validate_ipset_entries(ipset_entries, self.module)
669+
if addr_type is None and ipset_entries:
670+
self.module.fail_json(msg="Invalid IP address - " + str(ipset_entries))
671+
ipset_options = {}
672+
if addr_type == "ipv6":
673+
ipset_options = {
674+
"family": "inet6",
675+
}
631676
ipset_exists = ipset in self.fw.config().getIPSetNames()
632677
fw_ipset = None
633678
fw_ipset_settings = None
@@ -641,7 +686,9 @@ def set_ipset(self, ipset, description, short, ipset_type, ipset_entries):
641686
% (ipset, fw_ipset_settings.getType())
642687
)
643688
elif self.state == "present":
644-
fw_ipset, fw_ipset_settings = self._create_ipset(ipset, ipset_type)
689+
fw_ipset, fw_ipset_settings = self._create_ipset(
690+
ipset, ipset_type, ipset_options
691+
)
645692
self.changed = True
646693
ipset_exists = True
647694
if self.state == "present":
@@ -1256,6 +1303,12 @@ def set_service(
12561303
self.change("--zone", self.zone, op + item)
12571304

12581305
def set_ipset(self, ipset, description, short, ipset_type, ipset_entries):
1306+
addr_type = validate_ipset_entries(ipset_entries, self.module)
1307+
if addr_type is None and ipset_entries:
1308+
self.module.fail_json(msg="Invalid IP address - " + str(ipset_entries))
1309+
ipset_options = []
1310+
if addr_type == "ipv6":
1311+
ipset_options = ["--option", "family=inet6"]
12591312
present = self.check_state(["present", "absent"], "ipset")
12601313
known_ipsets = self.cmd("--get-ipsets").split()
12611314
ipset_exists = ipset in known_ipsets
@@ -1280,7 +1333,7 @@ def set_ipset(self, ipset, description, short, ipset_type, ipset_entries):
12801333
self.module.fail_json(
12811334
msg="ipset_type needed when creating a new ipset"
12821335
)
1283-
self.change("--new-ipset", ipset, "--type=%s" % ipset_type)
1336+
self.change("--new-ipset", ipset, "--type=%s" % ipset_type, *ipset_options)
12841337

12851338
existing_description = self.cmd("--ipset", ipset, "--get-description")
12861339
if description is not None and description != existing_description:

0 commit comments

Comments
 (0)