2019-01-09 14:11:32 -05:00
/*
* virfirewalld . c : support for firewalld ( https : //firewalld.org)
*
* Copyright ( C ) 2019 Red Hat , Inc .
*
* This library is free software ; you can redistribute it and / or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation ; either
* version 2.1 of the License , or ( at your option ) any later version .
*
* This library is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the GNU
* Lesser General Public License for more details .
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library . If not , see
* < http : //www.gnu.org/licenses/>.
*/
# include <config.h>
# include <stdarg.h>
# include "virfirewall.h"
# include "virfirewalld.h"
# define LIBVIRT_VIRFIREWALLDPRIV_H_ALLOW
# include "virfirewalldpriv.h"
2019-04-01 16:28:05 +02:00
# include "viralloc.h"
2019-01-09 14:11:32 -05:00
# include "virerror.h"
# include "virlog.h"
2020-09-15 14:00:53 +02:00
# include "virgdbus.h"
2019-04-01 12:14:26 +02:00
# include "virenum.h"
2020-02-24 00:43:36 +01:00
# include "virstring.h"
2019-01-09 14:11:32 -05:00
# define VIR_FROM_THIS VIR_FROM_FIREWALLD
VIR_LOG_INIT ( " util.firewalld " ) ;
/* used to convert virFirewallLayer enum values to strings
* understood by the firewalld . direct " passthrough " method
*/
VIR_ENUM_DECL ( virFirewallLayerFirewallD ) ;
2019-03-16 14:20:32 -04:00
VIR_ENUM_IMPL ( virFirewallLayerFirewallD ,
VIR_FIREWALL_LAYER_LAST ,
2019-01-09 14:11:32 -05:00
" eb " ,
" ipv4 " ,
" ipv6 " ,
) ;
2019-01-09 14:40:51 -05:00
VIR_ENUM_DECL ( virFirewallDBackend ) ;
2019-03-16 14:20:32 -04:00
VIR_ENUM_IMPL ( virFirewallDBackend ,
VIR_FIREWALLD_BACKEND_LAST ,
2019-01-09 14:40:51 -05:00
" " ,
" iptables " ,
" nftables " ,
) ;
2019-01-09 14:11:32 -05:00
/**
* virFirewallDIsRegistered :
*
* Returns 0 if service is registered , - 1 on fatal error , or - 2 if service is not registered
*/
int
virFirewallDIsRegistered ( void )
{
2020-09-15 14:00:53 +02:00
return virGDBusIsServiceRegistered ( VIR_FIREWALL_FIREWALLD_SERVICE ) ;
2019-01-09 14:11:32 -05:00
}
2019-01-09 14:40:51 -05:00
/**
* virFirewallDGetVersion :
* @ version : pointer to location to save version in the form of :
* 1000000 * major + 1000 * minor + micro
*
* queries the firewalld version property from dbus , and converts it
* from a string into a number .
*
* Returns 0 if version was successfully retrieved , or - 1 on error
*/
int
2023-03-07 16:18:51 +01:00
virFirewallDGetVersion ( unsigned long long * version )
2019-01-09 14:40:51 -05:00
{
2020-09-15 14:00:53 +02:00
GDBusConnection * sysbus = virGDBusGetSystemBus ( ) ;
g_autoptr ( GVariant ) message = NULL ;
g_autoptr ( GVariant ) reply = NULL ;
g_autoptr ( GVariant ) gvar = NULL ;
char * versionStr ;
2019-01-09 14:40:51 -05:00
if ( ! sysbus )
return - 1 ;
2020-09-15 14:00:53 +02:00
message = g_variant_new ( " (ss) " , " org.fedoraproject.FirewallD1 " , " version " ) ;
2019-01-09 14:40:51 -05:00
2020-09-15 14:00:53 +02:00
if ( virGDBusCallMethod ( sysbus ,
& reply ,
2020-09-21 15:42:00 +02:00
G_VARIANT_TYPE ( " (v) " ) ,
2020-09-15 14:00:53 +02:00
NULL ,
VIR_FIREWALL_FIREWALLD_SERVICE ,
" /org/fedoraproject/FirewallD1 " ,
" org.freedesktop.DBus.Properties " ,
" Get " ,
message ) < 0 )
return - 1 ;
g_variant_get ( reply , " (v) " , & gvar ) ;
g_variant_get ( gvar , " &s " , & versionStr ) ;
2019-01-09 14:40:51 -05:00
2020-02-24 00:47:18 +01:00
if ( virStringParseVersion ( version , versionStr , false ) < 0 ) {
2019-01-09 14:40:51 -05:00
virReportError ( VIR_ERR_INTERNAL_ERROR ,
2023-03-09 15:13:35 +01:00
_ ( " Failed to parse firewalld version '%1$s' " ) ,
2019-01-09 14:40:51 -05:00
versionStr ) ;
2020-09-15 14:00:53 +02:00
return - 1 ;
2019-01-09 14:40:51 -05:00
}
2023-03-07 16:18:51 +01:00
VIR_DEBUG ( " FirewallD version: %s - %llu " , versionStr , * version ) ;
2019-01-09 14:40:51 -05:00
2020-09-15 14:00:53 +02:00
return 0 ;
2019-01-09 14:40:51 -05:00
}
/**
* virFirewallDGetBackend :
*
* Returns virVirewallDBackendType value representing which packet
* filtering backend is currently in use by firewalld , or - 1 on error .
*/
int
virFirewallDGetBackend ( void )
{
2020-09-15 14:00:53 +02:00
GDBusConnection * sysbus = virGDBusGetSystemBus ( ) ;
g_autoptr ( GVariant ) message = NULL ;
g_autoptr ( GVariant ) reply = NULL ;
g_autoptr ( GVariant ) gvar = NULL ;
g_autoptr ( virError ) error = NULL ;
char * backendStr = NULL ;
2019-01-09 14:40:51 -05:00
int backend = - 1 ;
if ( ! sysbus )
return - 1 ;
2020-10-05 19:09:12 +02:00
error = g_new0 ( virError , 1 ) ;
2019-01-09 14:40:51 -05:00
2020-09-15 14:00:53 +02:00
message = g_variant_new ( " (ss) " ,
" org.fedoraproject.FirewallD1.config " ,
" FirewallBackend " ) ;
if ( virGDBusCallMethod ( sysbus ,
& reply ,
2020-09-21 15:42:00 +02:00
G_VARIANT_TYPE ( " (v) " ) ,
2020-09-15 14:00:53 +02:00
error ,
VIR_FIREWALL_FIREWALLD_SERVICE ,
" /org/fedoraproject/FirewallD1/config " ,
" org.freedesktop.DBus.Properties " ,
" Get " ,
message ) < 0 )
return - 1 ;
2019-01-09 14:40:51 -05:00
2020-09-15 14:00:53 +02:00
if ( error - > level = = VIR_ERR_ERROR ) {
2019-01-09 14:40:51 -05:00
/* we don't want to log any error in the case that
* FirewallBackend isn ' t implemented in this firewalld , since
* that just means that it is an old version , and only has an
* iptables backend .
*/
VIR_DEBUG ( " Failed to get FirewallBackend setting, assuming 'iptables' " ) ;
2020-09-15 14:00:53 +02:00
return VIR_FIREWALLD_BACKEND_IPTABLES ;
2019-01-09 14:40:51 -05:00
}
2020-09-15 14:00:53 +02:00
g_variant_get ( reply , " (v) " , & gvar ) ;
g_variant_get ( gvar , " &s " , & backendStr ) ;
2019-01-09 14:40:51 -05:00
VIR_DEBUG ( " FirewallD backend: %s " , backendStr ) ;
if ( ( backend = virFirewallDBackendTypeFromString ( backendStr ) ) < 0 ) {
virReportError ( VIR_ERR_INTERNAL_ERROR ,
2023-03-09 15:13:35 +01:00
_ ( " Unrecognized firewalld backend type: %1$s " ) ,
2019-01-09 14:40:51 -05:00
backendStr ) ;
2020-09-15 14:00:53 +02:00
return - 1 ;
2019-01-09 14:40:51 -05:00
}
return backend ;
}
/**
* virFirewallDGetZones :
* @ zones : array of char * , each entry is a null - terminated zone name
* @ nzones : number of entries in @ zones
*
* Get the number of currently active firewalld zones , and their names
* in an array of null - terminated strings . The memory pointed to by
* @ zones will belong to the caller , and must be freed .
*
* Returns 0 on success , - 1 ( and failure logged ) on error
*/
int
virFirewallDGetZones ( char * * * zones , size_t * nzones )
{
2020-09-15 14:00:53 +02:00
GDBusConnection * sysbus = virGDBusGetSystemBus ( ) ;
g_autoptr ( GVariant ) reply = NULL ;
g_autoptr ( GVariant ) array = NULL ;
2019-01-09 14:40:51 -05:00
* nzones = 0 ;
* zones = NULL ;
if ( ! sysbus )
return - 1 ;
2020-09-15 14:00:53 +02:00
if ( virGDBusCallMethod ( sysbus ,
& reply ,
2020-09-21 15:42:00 +02:00
G_VARIANT_TYPE ( " (as) " ) ,
2020-09-15 14:00:53 +02:00
NULL ,
VIR_FIREWALL_FIREWALLD_SERVICE ,
" /org/fedoraproject/FirewallD1 " ,
" org.fedoraproject.FirewallD1.zone " ,
" getZones " ,
NULL ) < 0 )
return - 1 ;
2019-01-09 14:40:51 -05:00
2020-09-21 14:39:49 +02:00
g_variant_get ( reply , " (@as) " , & array ) ;
2020-09-15 14:00:53 +02:00
* zones = g_variant_dup_strv ( array , nzones ) ;
2019-01-09 14:40:51 -05:00
2020-09-15 14:00:53 +02:00
return 0 ;
2019-01-09 14:40:51 -05:00
}
2022-09-22 11:13:20 -04:00
/**
* virFirewallDGetPolicies :
* @ policies : array of char * , each entry is a null - terminated policy name
* @ npolicies : number of entries in @ policies
*
* Get the number of currently active firewalld policies , and their names
* in an array of null - terminated strings . The memory pointed to by
* @ policies will belong to the caller , and must be freed .
*
* Returns 0 on success , - 1 ( and failure logged ) on error
*/
int
virFirewallDGetPolicies ( char * * * policies , size_t * npolicies )
{
GDBusConnection * sysbus = virGDBusGetSystemBus ( ) ;
g_autoptr ( GVariant ) reply = NULL ;
g_autoptr ( GVariant ) array = NULL ;
2022-11-10 11:31:45 -05:00
g_autoptr ( virError ) error = NULL ;
2022-09-22 11:13:20 -04:00
* npolicies = 0 ;
* policies = NULL ;
if ( ! sysbus )
return - 1 ;
2022-11-10 11:31:45 -05:00
error = g_new0 ( virError , 1 ) ;
2022-09-22 11:13:20 -04:00
if ( virGDBusCallMethod ( sysbus ,
& reply ,
G_VARIANT_TYPE ( " (as) " ) ,
2022-11-10 11:31:45 -05:00
error ,
2022-09-22 11:13:20 -04:00
VIR_FIREWALL_FIREWALLD_SERVICE ,
" /org/fedoraproject/FirewallD1 " ,
" org.fedoraproject.FirewallD1.policy " ,
" getPolicies " ,
NULL ) < 0 )
return - 1 ;
2022-11-10 11:31:45 -05:00
if ( error - > level = = VIR_ERR_ERROR ) {
if ( ! virGDBusErrorIsUnknownMethod ( error ) )
virReportErrorObject ( error ) ;
return - 1 ;
}
2022-09-22 11:13:20 -04:00
g_variant_get ( reply , " (@as) " , & array ) ;
* policies = g_variant_dup_strv ( array , npolicies ) ;
return 0 ;
}
2019-01-09 14:40:51 -05:00
/**
* virFirewallDZoneExists :
* @ match : name of zone to look for
*
* Returns true if the requested zone exists , or false if it doesn ' t exist
*/
bool
virFirewallDZoneExists ( const char * match )
{
size_t nzones = 0 , i ;
char * * zones = NULL ;
bool result = false ;
if ( virFirewallDGetZones ( & zones , & nzones ) < 0 )
goto cleanup ;
for ( i = 0 ; i < nzones ; i + + ) {
if ( STREQ_NULLABLE ( zones [ i ] , match ) )
result = true ;
}
cleanup :
VIR_DEBUG ( " Requested zone '%s' %s exist " ,
match , result ? " does " : " doesn't " ) ;
for ( i = 0 ; i < nzones ; i + + )
VIR_FREE ( zones [ i ] ) ;
VIR_FREE ( zones ) ;
return result ;
}
2019-01-09 14:11:32 -05:00
2022-09-22 11:13:21 -04:00
/**
* virFirewallDPolicyExists :
* @ match : name of policy to look for
*
* Returns true if the requested policy exists , or false if it doesn ' t exist
*/
bool
virFirewallDPolicyExists ( const char * match )
{
size_t npolicies = 0 , i ;
char * * policies = NULL ;
bool result = false ;
if ( virFirewallDGetPolicies ( & policies , & npolicies ) < 0 )
goto cleanup ;
for ( i = 0 ; i < npolicies ; i + + ) {
if ( STREQ_NULLABLE ( policies [ i ] , match ) )
result = true ;
}
cleanup :
VIR_DEBUG ( " Requested policy '%s' %s exist " ,
match , result ? " does " : " doesn't " ) ;
for ( i = 0 ; i < npolicies ; i + + )
VIR_FREE ( policies [ i ] ) ;
VIR_FREE ( policies ) ;
return result ;
}
2019-01-09 14:11:32 -05:00
/**
* virFirewallDApplyRule :
* @ layer : which layer to apply the rule to
* @ args : list of args to send to this layer ' s passthrough command .
* @ argsLen : number of items in @ args
* @ ignoreErrors : true to suppress logging of errors and return success
* false to log errors and return actual status
* @ output : output of the direct passthrough command , if it was successful
*/
int
virFirewallDApplyRule ( virFirewallLayer layer ,
char * * args , size_t argsLen ,
bool ignoreErrors ,
char * * output )
{
const char * ipv = virFirewallLayerFirewallDTypeToString ( layer ) ;
2020-09-15 14:00:53 +02:00
GDBusConnection * sysbus = virGDBusGetSystemBus ( ) ;
g_autoptr ( GVariant ) message = NULL ;
g_autoptr ( GVariant ) reply = NULL ;
g_autoptr ( virError ) error = NULL ;
2019-01-09 14:11:32 -05:00
if ( ! sysbus )
return - 1 ;
if ( ! ipv ) {
virReportError ( VIR_ERR_INTERNAL_ERROR ,
2023-03-09 15:13:35 +01:00
_ ( " Unknown firewall layer %1$d " ) ,
2019-01-09 14:11:32 -05:00
layer ) ;
2020-09-15 14:00:53 +02:00
return - 1 ;
2019-01-09 14:11:32 -05:00
}
2020-10-05 19:09:12 +02:00
error = g_new0 ( virError , 1 ) ;
2020-09-15 14:00:53 +02:00
message = g_variant_new ( " (s@as) " ,
ipv ,
g_variant_new_strv ( ( const char * const * ) args , argsLen ) ) ;
if ( virGDBusCallMethod ( sysbus ,
& reply ,
2020-09-21 15:42:00 +02:00
G_VARIANT_TYPE ( " (s) " ) ,
2020-09-15 14:00:53 +02:00
error ,
VIR_FIREWALL_FIREWALLD_SERVICE ,
" /org/fedoraproject/FirewallD1 " ,
" org.fedoraproject.FirewallD1.direct " ,
" passthrough " ,
message ) < 0 )
return - 1 ;
2019-01-09 14:11:32 -05:00
2020-09-15 14:00:53 +02:00
if ( error - > level = = VIR_ERR_ERROR ) {
2019-01-09 14:11:32 -05:00
/*
* As of firewalld - 0.3 .9 .3 - 1.f c20 . noarch the name and
* message fields in the error look like
*
* name = " org.freedesktop.DBus.Python.dbus.exceptions.DBusException "
* message = " COMMAND_FAILED: '/sbin/iptables --table filter --delete
* INPUT - - in - interface virbr0 - - protocol udp - - destination - port 53
* - - jump ACCEPT ' failed : iptables : Bad rule ( does a matching rule
* exist in that chain ? ) . "
*
* We ' d like to only ignore DBus errors precisely related to the failure
* of iptables / ebtables commands . A well designed DBus interface would
* return specific named exceptions not the top level generic python dbus
* exception name . With this current scheme our only option is todo a
* sub - string match for ' COMMAND_FAILED ' on the message . eg like
*
* if ( ignoreErrors & &
* STREQ ( error . name ,
* " org.freedesktop.DBus.Python.dbus.exceptions.DBusException " ) & &
* STRPREFIX ( error . message , " COMMAND_FAILED " ) )
* . . .
*
* But this risks our error detecting code being broken if firewalld changes
* ever alter the message string , so we ' re avoiding doing that .
*/
if ( ignoreErrors ) {
VIR_DEBUG ( " Ignoring error '%s': '%s' " ,
2020-09-15 14:00:53 +02:00
error - > str1 , error - > message ) ;
2019-01-09 14:11:32 -05:00
} else {
2020-09-15 14:00:53 +02:00
virReportErrorObject ( error ) ;
return - 1 ;
2019-01-09 14:11:32 -05:00
}
} else {
2020-09-15 14:00:53 +02:00
g_variant_get ( reply , " (s) " , output ) ;
2019-01-09 14:11:32 -05:00
}
2020-09-15 14:00:53 +02:00
return 0 ;
2019-01-09 14:11:32 -05:00
}
2019-01-09 14:40:51 -05:00
int
virFirewallDInterfaceSetZone ( const char * iface ,
const char * zone )
{
2020-09-15 14:00:53 +02:00
GDBusConnection * sysbus = virGDBusGetSystemBus ( ) ;
g_autoptr ( GVariant ) message = NULL ;
2019-01-09 14:40:51 -05:00
if ( ! sysbus )
return - 1 ;
2020-09-15 14:00:53 +02:00
message = g_variant_new ( " (ss) " , zone , iface ) ;
return virGDBusCallMethod ( sysbus ,
2020-09-21 15:42:00 +02:00
NULL ,
2019-02-13 10:57:57 -05:00
NULL ,
2019-01-09 14:40:51 -05:00
NULL ,
VIR_FIREWALL_FIREWALLD_SERVICE ,
" /org/fedoraproject/FirewallD1 " ,
" org.fedoraproject.FirewallD1.zone " ,
" changeZoneOfInterface " ,
2020-09-15 14:00:53 +02:00
message ) ;
2019-01-09 14:40:51 -05:00
}
2021-11-17 16:58:40 -05:00
void
virFirewallDSynchronize ( void )
{
const char * arg = " -V " ;
g_autofree char * output = NULL ;
int firewallDRegistered = virFirewallDIsRegistered ( ) ;
/*
* virFirewallDSynchronize ( ) should be called after receiving an
* ownership - change event or reload event for firewalld from dbus ,
* prior to performing any operations on the default table
* " filter " .
*
* Our iptables filter rules are added to ( private chains within )
* the default table named " filter " , which is flushed by firewalld
* any time it is restarted or reloads its rules . libvirt watches
* for notifications that firewalld has been restarted / its rules
* reloaded , and then reloads the libvirt rules . But it ' s possible
* for libvirt to be notified that firewalld has restarted prior
* to firewalld completing initialization , and when that race
* happens , firewalld can potentially flush out rules that libvirt
* has just added !
*
* To prevent this , we send a simple command ( " iptables -V " ) via
* firewalld ' s passthrough iptables API , and wait until it ' s
* finished before sending our own directly - executed iptables
* commands . This assures that firewalld has fully initialized and
* caught up with its internal queue of iptables commands , and
* won ' t stomp all over the new rules we subsequently add .
*
*/
VIR_DEBUG ( " Firewalld is registered ? %d " , firewallDRegistered ) ;
if ( firewallDRegistered < 0 )
return ; /* firewalld (or dbus?) not functional, don't sync */
ignore_value ( virFirewallDApplyRule ( VIR_FIREWALL_LAYER_IPV4 ,
( char * * ) & arg , 1 , true , & output ) ) ;
VIR_DEBUG ( " Result of 'iptables -V' via firewalld: %s " , NULLSTR ( output ) ) ;
}