#!/usr/bin/python3 -cimport os, sys; os.execv(os.path.dirname(sys.argv[1]) + "/../common/pywrap", sys.argv)## Copyright (C) 2018 Red Hat, Inc.# SPDX-License-Identifier: LGPL-2.1-or-laterimport timeimport netlibimport testlibdef wait_unit_state(machine, unit, state):def active_state(unit):# HACK: don't use `systemctl is-active` here because of# https://bugzilla.redhat.com/show_bug.cgi?id=1073481# Also, use `systemctl --value` once that exists everywhereline = machine.execute(f"systemctl show -p ActiveState {unit}")return line.strip().split("=")[1]testlib.wait(lambda: active_state(unit) == state, delay=0.2)def get_active_rules(machine):active_zones = machine.execute("firewall-cmd --get-active-zones | grep -o '^[[:alnum:]]*'").split()active_rules = []for zone in active_zones:active_rules += machine.execute(f"firewall-cmd --zone '{zone}' --list-services").split()ports = machine.execute(f"firewall-cmd --zone '{zone}' --list-ports").strip()if ports:active_rules += [ports]return active_rules@testlib.skipOstree("no firewalld")@testlib.nondestructiveclass TestFirewall(netlib.NetworkCase):def setUp(self):super().setUp()m = self.machineself.restore_dir("/etc/firewalld", restart_unit="firewalld")m.execute("systemctl restart firewalld")testlib.wait(lambda: m.execute("firewall-cmd --state"))self.btn_danger = "pf-m-danger"self.btn_primary = "pf-m-primary"self.default_zone = "Public zone"# Arch Linux image has no active zones by defaultif m.image == "arch":m.execute("firewall-cmd --zone 'public' --change-interface='eth0'; firewall-cmd --runtime-to-permanent")def testNetworkingPage(self):b = self.browserm = self.machinedef get_num_zones():testlib.wait(lambda: m.execute("firewall-cmd --state"))return int(m.execute("firewall-cmd --get-active-zones | grep --count '^[a-zA-Z]'").strip())self.login_and_go("/network", superuser=False)active_zones = get_num_zones()# Zones should be visible as unprivileged userself.wait_onoff("#networking-firewall-summary", val=True)wait_unit_state(m, "firewalld", "active")b.wait_in_text("#networking-firewall-summary", f"{active_zones} active zone")m.execute("systemctl stop firewalld")b.relogin("/network", superuser=True)self.wait_onoff("#networking-firewall-summary", val=False)self.toggle_onoff("#networking-firewall-summary")self.wait_onoff("#networking-firewall-summary", val=True)wait_unit_state(m, "firewalld", "active")active_zones = get_num_zones()b.wait_in_text("#networking-firewall-summary", f"{active_zones} active zone")self.toggle_onoff("#networking-firewall-summary")self.wait_onoff("#networking-firewall-summary", val=False)wait_unit_state(m, "firewalld", "inactive")b.wait_in_text("#networking-firewall-summary", "0 active zones")# toggle the service from CLI, page should reacttry:m.execute("systemctl start firewalld")wait_unit_state(m, "firewalld", "active")except testlib.Error:print("====== firewalld.service =======")print(m.execute("systemctl status firewalld"))raiseself.wait_onoff("#networking-firewall-summary", val=True)try:testlib.wait(lambda: get_num_zones() == active_zones)b.wait_in_text("#networking-firewall-summary", f"{active_zones} active zone")except testlib.Error:m.execute("firewall-cmd --get-active-zones >&2")raisetry:m.execute("systemctl stop firewalld")wait_unit_state(m, "firewalld", "inactive")except testlib.Error:print("====== firewalld.service =======")print(m.execute("systemctl status firewalld"))raiseself.wait_onoff("#networking-firewall-summary", val=False)b.wait_in_text("#networking-firewall-summary", "0 active zones")b.click("#networking-firewall-link")b.enter_page("/network/firewall")b.click(".pf-v6-c-breadcrumb li:first-of-type")b.enter_page("/network")self.allow_journal_messages(".*The name org.fedoraproject.FirewallD1 was not provided by any .service files.*",".*org.fedoraproject.FirewallD1: .*: GDBus.Error:org.freedesktop.DBus.Error.NoReply.*")# test missing "pkcheck" binary, in that case fallback to the admin checkdef testPkcheckMissing(self):b = self.browserm = self.machine# Path to the "pkcheck" PolicyKit utilitypkcheck = m.execute("command -v pkcheck").strip()# "Hide" pkcheckm.execute(f"mount --bind /dev/null '{pkcheck}'")try:# Regular user is not allowed to change the firewall switchself.login_and_go("/network", superuser=False)b.wait_visible("#networking-firewall-switch:disabled")# Super user is allowed to change the firewall switchb.relogin("/network", superuser=True)b.wait_visible("#networking-firewall-switch:not([disabled])")finally:m.execute(f"umount '{pkcheck}'")def testFirewallPage(self):b = self.browserm = self.machine# Changed in #13686, remove all existing ports before testingfor port in m.execute("firewall-cmd --zone 'public' --list-ports").split():m.execute(f"firewall-cmd --remove-port='{port}' --zone 'public'")m.execute(f"firewall-cmd --permanent --remove-port='{port}' --zone 'public'")# Zones should be visible as unprivileged userself.login_and_go("/network/firewall", superuser=False)self.wait_onoff("#firewall-heading-title-group", val=True)wait_unit_state(m, "firewalld", "active")# Wait until the default zone is listedb.wait_in_text("#zones-listing .zone-section[data-id='public']", self.default_zone)b.wait_not_present("#add-zone-button")# "Add services" button should not be presentb.wait_not_present(".zone-section[data-id='public'] .add-services-button")m.execute("systemctl stop firewalld")b.relogin("/network/firewall", superuser=True)# "Add services" button should not be presentb.wait_not_present(".add-services-button")self.wait_onoff("#firewall-heading-title-group", val=False)self.toggle_onoff("#firewall-heading-title-group")self.wait_onoff("#firewall-heading-title-group", val=True)wait_unit_state(m, "firewalld", "active")# Wait until the default zone is listedb.wait_in_text("#zones-listing .zone-section[data-id='public']", self.default_zone)# "Add services" button should be enabledb.wait_visible(".zone-section[data-id='public'] .add-services-button:enabled")# ensure that pop3 is not enabled (shouldn't be on any of our images),# so that we can use it for testingb.wait_not_present(".zone-section[data-id='public'] tr[data-row-id='pop3']")m.execute("firewall-cmd --add-service=pop3")b.wait_visible(".zone-section[data-id='public'] tr[data-row-id='pop3']")# Check that all services are shown. This only works reliably since #12806active_rules = get_active_rules(m)b.wait_count(".zone-section table.ct-table tbody", len(active_rules))b.click(".zone-section[data-id='public'] tr[data-row-id='pop3'] [data-ouia-component-id='toggle-pop3']")b.wait_in_text(".zone-section[data-id='public'] tbody.pf-m-expanded tr.pf-v6-c-table__expandable-row", "Post Office Protocol")b.click(".zone-section[data-id='public'] tr[data-row-id='pop3'] button.pf-v6-c-menu-toggle")b.click(".pf-v6-c-menu__list-item.pf-m-danger button")b.wait_not_present(".zone-section[data-id='public'] tr[data-row-id='pop3']")self.assertNotIn('pop3', m.execute("firewall-cmd --list-services").split())# Test that service without name is shown properlym.execute("firewall-cmd --permanent --new-service=empty; firewall-cmd --reload; firewall-cmd --add-service=empty")b.wait_visible(".zone-section[data-id='public'] tr[data-row-id='empty']")m.execute("firewall-cmd --add-port=9998/udp --add-port=9999/udp --add-port=6666/tcp")b.wait_in_text(".zone-section[data-id='public'] tr[data-row-id='public-6666-tcp-port']", "6666")b.wait_in_text(".zone-section[data-id='public'] tr[data-row-id='public-9998-udp-port']", "9998")b.wait_in_text(".zone-section[data-id='public'] tr[data-row-id='public-9999-udp-port']", "9999")for (port, ptype) in [(6666, "tcp"), (9998, "udp")]:row_sel = f".zone-section[data-id='public'] tr[data-row-id='public-{port}-{ptype}-port']"b.click(f"{row_sel} button.pf-m-plain")b.click(".pf-v6-c-menu__list-item.pf-m-danger button")b.wait_not_present(row_sel)# Also via firewall-cmdm.execute("firewall-cmd --remove-port=9999/udp")b.wait_not_present(".zone-section[data-id='public'] tr[data-row-id='public-9999-udp-port']")# switch service off againself.toggle_onoff("#firewall-heading-title-group")self.wait_onoff("#firewall-heading-title-group", val=False)wait_unit_state(m, "firewalld", "inactive")# "Add services" button should be hidden againb.wait_not_present(".zone-section[data-id='public'] .add-services-button")def testAddServices(self):b = self.browserm = self.machineself.login_and_go("/network/firewall")b.wait_in_text("#zones-listing .zone-section[data-id='public']", self.default_zone)# add a service to the runtime configuration via the cli, after all the# operations it should still be there, indicating no reload took placem.execute("firewall-cmd --add-service=http")b.wait_visible(".zone-section[data-id='public'] tr[data-row-id='http']")b.assert_pixels("#zones-listing .zone-section[data-id='public']", "firewall-default-zone-card",# HACK: medium layout has unstable horizontal widthskip_layouts=["medium"])# click on the "Add services" buttonb.click(".zone-section[data-id='public'] .add-services-button")b.wait_visible(".pf-v6-c-modal-box .service-list #firewall-service-pop3")# check functionality of radio buttonsb.click("#add-services-dialog input[value='ports']")b.wait_not_present("#filter-services-input")b.click("#add-services-dialog input[value='services']")b.wait_visible("#filter-services-input")# filter for pop3b.wait_visible(".pf-v6-c-modal-box .service-list #firewall-service-imap")b.set_input_text("#filter-services-input", "pop")b.wait_not_present(".pf-v6-c-modal-box .service-list #firewall-service-imap")b.wait_visible(".pf-v6-c-modal-box .service-list #firewall-service-pop3")self.assertIn("TCP: 110", b.text(".pf-v6-c-modal-box .service-list li:first-child .service-ports.tcp"))b.wait_not_present(".pf-v6-c-modal-box .service-list li:first-child .service-ports.udp")# clear filterb.set_input_text("#filter-services-input", "")b.wait_visible(".pf-v6-c-modal-box .service-list #firewall-service-imap")b.wait_visible(".pf-v6-c-modal-box .service-list #firewall-service-pop3")# filter for port 110b.wait_visible(".pf-v6-c-modal-box .service-list #firewall-service-imap")b.set_input_text("#filter-services-input", "110")b.wait_not_present(".pf-v6-c-modal-box .service-list #firewall-service-imap")b.wait_visible(".pf-v6-c-modal-box .service-list #firewall-service-pop3")self.assertIn("TCP: 110", b.text(".pf-v6-c-modal-box .service-list li:first-child .service-ports.tcp"))b.wait_not_present(".pf-v6-c-modal-box .service-list li:first-child .service-ports.udp")# clear filterb.set_input_text("#filter-services-input", "")b.wait_visible(".pf-v6-c-modal-box .service-list #firewall-service-imap")b.wait_visible(".pf-v6-c-modal-box .service-list #firewall-service-pop3")# HACK: the dialog needs some time to set up after resizingb.assert_pixels(".pf-v6-c-modal-box", "firewall-add-services-to-zone-modal", wait_delay=3)# don't select anything in the dialogb.wait_visible(f"#add-services-dialog .{self.btn_primary}:disabled")b.click("#add-services-dialog footer .btn-cancel")b.wait_not_present(".pf-v6-c-modal-box")def addService(zone, service):b.click(f".zone-section[data-id='{zone}'] .add-services-button")b.click(f"#add-services-dialog .service-list #firewall-service-{service}")b.click(f"#add-services-dialog footer .{self.btn_primary}")b.wait_not_present(".pf-v6-c-modal-box")b.wait_visible(f".zone-section[data-id='{zone}'] tr[data-row-id='{service}']")self.assertIn(service, m.execute(f"firewall-cmd --zone={zone} --list-services"))# now add pop3addService('public', 'pop3')freeipa = "freeipa-4"addService("public", freeipa)b.click(f'.zone-section[data-id="public"] tr[data-row-id="{freeipa}"] [data-ouia-component-id="toggle-{freeipa}"]')b.wait_in_text(".zone-section[data-id='public'] tbody.pf-m-expanded .pf-v6-c-table__expandable-row", "Included Services")b.click(f'.zone-section[data-id="public"] tr[data-row-id="{freeipa}"] [data-ouia-component-id="toggle-{freeipa}"]')# pop3 should now not appear any more in Add Services dialogb.click(".zone-section[data-id='public'] .add-services-button")b.wait_visible(".pf-v6-c-modal-box .service-list #firewall-service-imap")b.wait_not_present(".pf-v6-c-modal-box .service-list #firewall-service-pop3")b.click("#add-services-dialog footer .btn-cancel")b.wait_not_present(".pf-v6-c-modal-box")# should still be hereb.wait_visible(".zone-section[data-id='public'] tr[data-row-id='http']")# Service without name should appear in the dialogm.execute("firewall-cmd --permanent --new-service=empty; firewall-cmd --reload")b.click(".zone-section[data-id='public'] .add-services-button")b.wait_visible(".pf-v6-c-modal-box .service-list #firewall-service-empty")b.click("#add-services-dialog footer .btn-cancel")b.wait_not_present(".pf-v6-c-modal-box")# remove all servicesservices = set(m.execute("firewall-cmd --list-services").strip().split(" "))# some images come with an extra preconfigured libvirt zone so remove all# the service which belong to both libvirt and public (and thus# requiring checkboxes to be clicked), then remove all the services# belonging either libvirt or publicfor service in services:b.click(f".zone-section[data-id='public'] tr[data-row-id='{service}'] button.pf-v6-c-menu-toggle")b.click(".pf-v6-c-menu__list-item.pf-m-danger button")# removing cockpit services requires confirmationif service == "cockpit":b.click(f"#delete-confirmation-dialog button.{self.btn_danger}")b.wait_not_present(f".zone-section[data-id='public'] tr[data-row-id='{service}']")self.assertEqual(m.execute("firewall-cmd --list-services").strip(), "")# Test that we show 'Additional ports' even when no service is presentm.execute("firewall-cmd --add-port=9998/tcp --zone public")b.wait_in_text(".zone-section[data-id='public'] tr[data-row-id='public-9998-tcp-port']", "9998")# test error handlingm.execute("firewall-cmd --add-service=pop3")b.wait_visible(".zone-section[data-id='public'] tr[data-row-id='pop3']")b.wait_in_text(".zone-section[data-id='public'] tr[data-row-id='public-9998-tcp-port']", "9998")# remove service in cockpitb.click(".zone-section[data-id='public'] tr[data-row-id='pop3'] button.pf-v6-c-menu-toggle")b.click(".pf-v6-c-menu__list-item.pf-m-danger button")b.wait_not_present(".zone-section[data-id='public'] tr[data-row-id='pop3']")def testAddCustomServices(self):b = self.browserm = self.machineself.login_and_go("/network/firewall")b.wait_in_text("#zones-listing .zone-section[data-id='public']", self.default_zone)# add a service to the runtime configuration via the cli, after all the# operations it should be removed, indicating a reload took placem.execute("firewall-cmd --add-service=http")b.wait_visible("tr[data-row-id='http']")def open_dialog():b.click(".zone-section[data-id='public'] .add-services-button")b.wait_visible("#add-services-dialog input[value='ports']:enabled")b.click("#add-services-dialog input[value='ports']")b.wait_visible("#tcp-ports")b.wait_visible("#tcp-ports")def set_field(sel, val, expected):b.set_input_text(sel, val)b.wait_val("#service-name", expected)def check_error(text):b.wait_in_text(".pf-v6-c-form__helper-text .pf-m-error", text)def save(identifier, tcp, udp, desc="", submit_text="Add ports"):b.click(f"button:contains('{submit_text}')")b.wait_not_present("#add-services-dialog")# expand-togglecustom--80-82-echo-83-123-snmpprint(identifier)# expand-togglefreeipa-4line_sel = f".zone-section[data-id='public'] tr[data-row-id='{identifier}']"b.wait_in_text(line_sel, "".join([identifier, tcp, udp]))if desc:# Click expand buttonb.click(f'{line_sel} [data-ouia-component-id="toggle-{identifier}"]')b.wait_text(line_sel + " + tr", desc)out = m.execute("firewall-cmd --zone public --list-services")self.assertIn(identifier, out)open_dialog()set_field("#tcp-ports", "80", "custom--http")set_field("#tcp-ports", "", "")set_field("#tcp-ports", "80,7", "custom--http-echo")set_field("#udp-ports", "123", "custom--http-echo-ntp")set_field("#udp-ports", "123, 50000", "custom--http-echo-ntp-50000")set_field("#tcp-ports", "", "custom--ntp-50000")set_field("#tcp-ports", "https", "custom--https-ntp-50000")save("custom--https-ntp-50000", "443", "123, 50000")open_dialog()set_field("#tcp-ports", "80-82", "custom--80-82")set_field("#tcp-ports", "80-82, echo", "custom--80-82-echo")set_field("#udp-ports", "ntp, snmp", "custom--80-82-echo-ntp-snmp")set_field("#udp-ports", "83-ntp", "custom--80-82-echo-83-123")set_field("#udp-ports", "83-ntp, snmp", "custom--80-82-echo-83-123-snmp")b.set_input_text("#service-description", "custom snmp firewall rule")# Test that the service name field is really optionalb.set_input_text("#service-name", "")save("custom--80-82-echo-83-123-snmp", "80-82, 7", "83-123, 161", "custom snmp firewall rule")out = m.execute("firewall-cmd --info-service custom--80-82-echo-83-123-snmp")self.assertIn("ports: 80-82/tcp 7/tcp 83-123/udp 161/udp", out)open_dialog()set_field("#tcp-ports", "80", "custom--http")b.set_input_text("#service-name", "I-am-persistent")b.set_input_text("#tcp-ports", "7")time.sleep(5) # We need to validate that the service name did not changeb.wait_val("#service-name", "I-am-persistent")save("I-am-persistent", "7", "")open_dialog()set_field("#tcp-ports", "500000", "")check_error("Invalid port number")set_field("#tcp-ports", "-1", "")check_error("Invalid port number")set_field("#tcp-ports", "8a", "")check_error("Unknown service name")set_field("#tcp-ports", "foobar", "")check_error("Unknown service name")set_field("#tcp-ports", "80-80", "")check_error("Range must be strictly ordered")set_field("#tcp-ports", "80-79", "")check_error("Range must be strictly ordered")set_field("#tcp-ports", "https-http", "")check_error("Range must be strictly ordered")set_field("#tcp-ports", "80-90-", "")check_error("Invalid range")set_field("#tcp-ports", "80-90-100", "")check_error("Invalid range")b.click("#add-services-dialog button.btn-cancel")# test error handling# attempt to create custom service which already existsm.execute("firewall-cmd --permanent --new-service=custom--19834")m.execute("firewall-cmd --permanent --service=custom--19834 --add-port=19834/udp")open_dialog()set_field("#udp-ports", "19834", "custom--19834")b.click(f"#add-services-dialog .{self.btn_primary}")b.wait_in_text("#add-services-dialog div.pf-m-danger", "org.fedoraproject.FirewallD1.Exception: NAME_CONFLICT: new_service(): 'custom--19834'")b.click("#add-services-dialog button.btn-cancel")# should have been removed in the reloadb.wait_not_present("tr[data-row-id='http']")b.click("tr[data-row-id='custom--80-82-echo-83-123-snmp'] button[aria-label=Details]")b.click("tr[data-row-id='custom--80-82-echo-83-123-snmp'] .pf-v6-c-menu-toggle.pf-m-plain")b.click("button.pf-v6-c-menu__item:contains('Edit')")b.wait_val("#tcp-ports", "80-82, 7")b.wait_val("#udp-ports", "83-123, 161")b.wait_val("#service-name", "custom--80-82-echo-83-123-snmp")b.wait_val("#service-description", "custom snmp firewall rule")b.set_input_text("#tcp-ports", "80-82, 8")b.set_input_text("#service-description", "edited snmp firewall rule")b.wait_val("#service-name", "custom--80-82-echo-83-123-snmp")save("custom--80-82-echo-83-123-snmp", "80-82, 8", "83-123, 161", "edited snmp firewall rule", 'Edit service')out = m.execute("firewall-cmd --info-service custom--80-82-echo-83-123-snmp")self.assertIn("ports: 80-82/tcp 8/tcp 83-123/udp 161/udp", out)def testMultipleZones(self):b = self.browserm = self.machine# create unused interface for adding a new zone# FIXME: Firewall page does not pick this up once it's already openhome_iface = "ethome"self.add_veth(home_iface)def addServiceToZone(service, zone):b.click(f".zone-section[data-id='{zone}'] .add-services-button")b.click(f"#add-services-dialog .service-list #firewall-service-{service}")b.click(f"#add-services-dialog footer .{self.btn_primary}")b.wait_not_present(".pf-v6-c-modal-box")b.wait_visible(f".zone-section[data-id='{zone}'] tr[data-row-id='{service}']")self.assertIn(service, m.execute(f"firewall-cmd --zone={zone} --list-services"))def addZone(zone, interfaces=None, sources=None, error=None, do_pixel=False):if interfaces is None:interfaces = []b.click("#add-zone-button")b.wait_visible("#add-zone-dialog")b.wait_visible(f"#add-zone-dialog footer button.{self.btn_primary}:disabled")b.click(f"#add-zone-dialog .add-zone-zones-firewalld input[value='{zone}']")if do_pixel:b.assert_pixels("#add-zone-dialog", "firewall-add-zone-dialog")for i in interfaces:b.click(f"#add-zone-dialog input[value='{i}']")if sources:b.click("#add-zone-dialog input[value='ip-range']")b.set_input_text("#add-zone-dialog #add-zone-ip", sources)b.click(f"#add-zone-dialog footer button.{self.btn_primary}:enabled")if error:b.wait_in_text("#add-zone-dialog div.pf-m-danger", error)b.click("#add-zone-dialog footer button.btn-cancel")b.wait_not_present("#add-zone-dialog")returnb.wait_not_present("#add-zone-dialog")for source in sources.split(",") if sources else []:b.wait_in_text(f"#zones-listing .zone-section[data-id='{zone}']", source)for i in interfaces:b.wait_in_text(f"#zones-listing .zone-section[data-id='{zone}']", i)b.wait_visible(f"#zones-listing .zone-section[data-id='{zone}']")b.wait_visible(f"#zones-listing .zone-section[data-id='{zone}'] tr[data-row-id='cockpit']")def removeZone(zone):b.click(f".zone-section[data-id='{zone}'] .zone-section-buttons button.pf-v6-c-menu-toggle")b.click(".pf-v6-c-menu__list-item.pf-m-danger button")b.click(f"#delete-confirmation-dialog button.{self.btn_danger}")b.wait_not_present(f".zone-section[data-id='{zone}']")self.login_and_go("/network/firewall")b.wait_in_text("#zones-listing .zone-section[data-id='public']", self.default_zone)# add predefined work zoneaddZone("work", sources="192.168.1.0/24", do_pixel=True)addServiceToZone("pop3", "work")b.wait_visible(".zone-section[data-id='work'] tr[data-row-id='pop3']")self.assertNotIn(self.default_zone.split(" ")[0], b.text("tr[data-row-id='pop3']"))addServiceToZone("pop3", "public")b.wait_visible(".zone-section[data-id='public'] tr[data-row-id='pop3']")# Remove the service from public zoneb.click(".zone-section[data-id='public'] tr[data-row-id='pop3'] [data-ouia-component-id='toggle-pop3']")b.click(".zone-section[data-id='public'] tr[data-row-id='pop3'] button.pf-v6-c-menu-toggle")b.click(".pf-v6-c-menu__list-item.pf-m-danger button")b.wait_not_present(".zone-section[data-id='public'] tr[data-row-id='pop3']")b.wait_visible(".zone-section[data-id='work'] tr[data-row-id='pop3']")# Remove the service from the work zoneb.click(".zone-section[data-id='work'] tr[data-row-id='pop3'] button.pf-v6-c-menu-toggle")b.click(".pf-v6-c-menu__list-item.pf-m-danger button")b.wait_not_present(".zone-section[data-id='work'] tr[data-row-id='pop3']")# remove predefined work zoneremoveZone("work")# add zone with previously unused interfaceaddZone("home", interfaces=[home_iface])# Interfaces which already belong to an active zone shouldn't show up in# the Add Zone dialog anymoreb.click("#add-zone-button")b.wait_visible("#add-zone-dialog")b.wait_not_present(f"#add-zone-body input[value='{home_iface}']")b.click("#add-zone-dialog footer button.btn-cancel")b.wait_not_present("#add-zone-dialog")addServiceToZone("pop3", "home")removeZone("home")addZone("work", sources="totally invalid address", error="org.fedoraproject.FirewallD1.Exception: INVALID_ADDR: totally invalid address")if __name__ == '__main__':testlib.test_main()