libvirt/src/util/viriptables.c

896 lines
27 KiB
C
Raw Normal View History

/*
* viriptables.c: helper APIs for managing iptables
*
* Copyright (C) 2007-2013 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/>.
*
* Authors:
* Mark McLoughlin <markmc@redhat.com>
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <errno.h>
#include <limits.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#ifdef HAVE_PATHS_H
# include <paths.h>
#endif
#include "internal.h"
#include "viriptables.h"
#include "vircommand.h"
2012-12-12 18:06:53 +00:00
#include "viralloc.h"
#include "virerror.h"
#include "virfile.h"
2012-12-12 17:59:27 +00:00
#include "virlog.h"
#include "virthread.h"
#include "virstring.h"
#include "virutil.h"
network: use firewalld instead of iptables, when available * configure.ac, spec file: firewalld defaults to enabled if dbus is available, otherwise is disabled. If --with_firewalld is explicitly requested and dbus is not available, configure will fail. * bridge_driver: add dbus filters to get the FirewallD1.Reloaded signal and DBus.NameOwnerChanged on org.fedoraproject.FirewallD1. When these are encountered, reload all the iptables reuls of all libvirt's virtual networks (similar to what happens when libvirtd is restarted). * iptables, ebtables: use firewall-cmd's direct passthrough interface when available, otherwise use iptables and ebtables commands. This decision is made once the first time libvirt calls iptables/ebtables, and that decision is maintained for the life of libvirtd. * Note that the nwfilter part of this patch was separated out into another patch by Stefan in V2, so that needs to be revised and re-reviewed as well. ================ All the configure.ac and specfile changes are unchanged from Thomas' V3. V3 re-ran "firewall-cmd --state" every time a new rule was added, which was extremely inefficient. V4 uses VIR_ONCE_GLOBAL_INIT to set up a one-time initialization function. The VIR_ONCE_GLOBAL_INIT(x) macro references a static function called vir(Ip|Eb)OnceInit(), which will then be called the first time that the static function vir(Ip|Eb)TablesInitialize() is called (that function is defined for you by the macro). This is thread-safe, so there is no chance of any race. IMPORTANT NOTE: I've left the VIR_DEBUG messages in these two init functions (one for iptables, on for ebtables) as VIR_WARN so that I don't have to turn on all the other debug message just to see these. Even if this patch doesn't need any other modification, those messages need to be changed to VIR_DEBUG before pushing. This one-time initialization works well. However, I've encountered problems with testing: 1) Whenever I have enabled the firewalld service, *all* attempts to call firewall-cmd from within libvirtd end with firewall-cmd hanging internally somewhere. This is *not* the case if firewall-cmd returns non-0 in response to "firewall-cmd --state" (i.e. *that* command runs and returns to libvirt successfully.) 2) If I start libvirtd while firewalld is stopped, then start firewalld later, this triggers libvirtd to reload its iptables rules, however it also spits out a *ton* of complaints about deletion failing (I suppose because firewalld has nuked all of libvirt's rules). I guess we need to suppress those messages (which is a more annoying problem to fix than you might think, but that's another story). 3) I noticed a few times during this long line of errors that firewalld made a complaint about "Resource Temporarily unavailable. Having libvirtd access iptables commands directly at the same time as firewalld is doing so is apparently problematic. 4) In general, I'm concerned about the "set it once and never change it" method - if firewalld is disabled at libvirtd startup, causing libvirtd to always use iptables/ebtables directly, this won't cause *terrible* problems, but if libvirtd decides to use firewall-cmd and firewalld is later disabled, libvirtd will not be able to recover.
2012-08-14 18:59:52 +00:00
#if HAVE_FIREWALLD
static char *firewall_cmd_path = NULL;
static int
virIpTablesOnceInit(void)
{
firewall_cmd_path = virFindFileInPath("firewall-cmd");
if (!firewall_cmd_path) {
VIR_INFO("firewall-cmd not found on system. "
network: use firewalld instead of iptables, when available * configure.ac, spec file: firewalld defaults to enabled if dbus is available, otherwise is disabled. If --with_firewalld is explicitly requested and dbus is not available, configure will fail. * bridge_driver: add dbus filters to get the FirewallD1.Reloaded signal and DBus.NameOwnerChanged on org.fedoraproject.FirewallD1. When these are encountered, reload all the iptables reuls of all libvirt's virtual networks (similar to what happens when libvirtd is restarted). * iptables, ebtables: use firewall-cmd's direct passthrough interface when available, otherwise use iptables and ebtables commands. This decision is made once the first time libvirt calls iptables/ebtables, and that decision is maintained for the life of libvirtd. * Note that the nwfilter part of this patch was separated out into another patch by Stefan in V2, so that needs to be revised and re-reviewed as well. ================ All the configure.ac and specfile changes are unchanged from Thomas' V3. V3 re-ran "firewall-cmd --state" every time a new rule was added, which was extremely inefficient. V4 uses VIR_ONCE_GLOBAL_INIT to set up a one-time initialization function. The VIR_ONCE_GLOBAL_INIT(x) macro references a static function called vir(Ip|Eb)OnceInit(), which will then be called the first time that the static function vir(Ip|Eb)TablesInitialize() is called (that function is defined for you by the macro). This is thread-safe, so there is no chance of any race. IMPORTANT NOTE: I've left the VIR_DEBUG messages in these two init functions (one for iptables, on for ebtables) as VIR_WARN so that I don't have to turn on all the other debug message just to see these. Even if this patch doesn't need any other modification, those messages need to be changed to VIR_DEBUG before pushing. This one-time initialization works well. However, I've encountered problems with testing: 1) Whenever I have enabled the firewalld service, *all* attempts to call firewall-cmd from within libvirtd end with firewall-cmd hanging internally somewhere. This is *not* the case if firewall-cmd returns non-0 in response to "firewall-cmd --state" (i.e. *that* command runs and returns to libvirt successfully.) 2) If I start libvirtd while firewalld is stopped, then start firewalld later, this triggers libvirtd to reload its iptables rules, however it also spits out a *ton* of complaints about deletion failing (I suppose because firewalld has nuked all of libvirt's rules). I guess we need to suppress those messages (which is a more annoying problem to fix than you might think, but that's another story). 3) I noticed a few times during this long line of errors that firewalld made a complaint about "Resource Temporarily unavailable. Having libvirtd access iptables commands directly at the same time as firewalld is doing so is apparently problematic. 4) In general, I'm concerned about the "set it once and never change it" method - if firewalld is disabled at libvirtd startup, causing libvirtd to always use iptables/ebtables directly, this won't cause *terrible* problems, but if libvirtd decides to use firewall-cmd and firewalld is later disabled, libvirtd will not be able to recover.
2012-08-14 18:59:52 +00:00
"firewalld support disabled for iptables.");
} else {
virCommandPtr cmd = virCommandNew(firewall_cmd_path);
int status;
virCommandAddArgList(cmd, "--state", NULL);
if (virCommandRun(cmd, &status) < 0 || status != 0) {
VIR_INFO("firewall-cmd found but disabled for iptables");
network: use firewalld instead of iptables, when available * configure.ac, spec file: firewalld defaults to enabled if dbus is available, otherwise is disabled. If --with_firewalld is explicitly requested and dbus is not available, configure will fail. * bridge_driver: add dbus filters to get the FirewallD1.Reloaded signal and DBus.NameOwnerChanged on org.fedoraproject.FirewallD1. When these are encountered, reload all the iptables reuls of all libvirt's virtual networks (similar to what happens when libvirtd is restarted). * iptables, ebtables: use firewall-cmd's direct passthrough interface when available, otherwise use iptables and ebtables commands. This decision is made once the first time libvirt calls iptables/ebtables, and that decision is maintained for the life of libvirtd. * Note that the nwfilter part of this patch was separated out into another patch by Stefan in V2, so that needs to be revised and re-reviewed as well. ================ All the configure.ac and specfile changes are unchanged from Thomas' V3. V3 re-ran "firewall-cmd --state" every time a new rule was added, which was extremely inefficient. V4 uses VIR_ONCE_GLOBAL_INIT to set up a one-time initialization function. The VIR_ONCE_GLOBAL_INIT(x) macro references a static function called vir(Ip|Eb)OnceInit(), which will then be called the first time that the static function vir(Ip|Eb)TablesInitialize() is called (that function is defined for you by the macro). This is thread-safe, so there is no chance of any race. IMPORTANT NOTE: I've left the VIR_DEBUG messages in these two init functions (one for iptables, on for ebtables) as VIR_WARN so that I don't have to turn on all the other debug message just to see these. Even if this patch doesn't need any other modification, those messages need to be changed to VIR_DEBUG before pushing. This one-time initialization works well. However, I've encountered problems with testing: 1) Whenever I have enabled the firewalld service, *all* attempts to call firewall-cmd from within libvirtd end with firewall-cmd hanging internally somewhere. This is *not* the case if firewall-cmd returns non-0 in response to "firewall-cmd --state" (i.e. *that* command runs and returns to libvirt successfully.) 2) If I start libvirtd while firewalld is stopped, then start firewalld later, this triggers libvirtd to reload its iptables rules, however it also spits out a *ton* of complaints about deletion failing (I suppose because firewalld has nuked all of libvirt's rules). I guess we need to suppress those messages (which is a more annoying problem to fix than you might think, but that's another story). 3) I noticed a few times during this long line of errors that firewalld made a complaint about "Resource Temporarily unavailable. Having libvirtd access iptables commands directly at the same time as firewalld is doing so is apparently problematic. 4) In general, I'm concerned about the "set it once and never change it" method - if firewalld is disabled at libvirtd startup, causing libvirtd to always use iptables/ebtables directly, this won't cause *terrible* problems, but if libvirtd decides to use firewall-cmd and firewalld is later disabled, libvirtd will not be able to recover.
2012-08-14 18:59:52 +00:00
VIR_FREE(firewall_cmd_path);
firewall_cmd_path = NULL;
} else {
VIR_INFO("using firewalld for iptables commands");
network: use firewalld instead of iptables, when available * configure.ac, spec file: firewalld defaults to enabled if dbus is available, otherwise is disabled. If --with_firewalld is explicitly requested and dbus is not available, configure will fail. * bridge_driver: add dbus filters to get the FirewallD1.Reloaded signal and DBus.NameOwnerChanged on org.fedoraproject.FirewallD1. When these are encountered, reload all the iptables reuls of all libvirt's virtual networks (similar to what happens when libvirtd is restarted). * iptables, ebtables: use firewall-cmd's direct passthrough interface when available, otherwise use iptables and ebtables commands. This decision is made once the first time libvirt calls iptables/ebtables, and that decision is maintained for the life of libvirtd. * Note that the nwfilter part of this patch was separated out into another patch by Stefan in V2, so that needs to be revised and re-reviewed as well. ================ All the configure.ac and specfile changes are unchanged from Thomas' V3. V3 re-ran "firewall-cmd --state" every time a new rule was added, which was extremely inefficient. V4 uses VIR_ONCE_GLOBAL_INIT to set up a one-time initialization function. The VIR_ONCE_GLOBAL_INIT(x) macro references a static function called vir(Ip|Eb)OnceInit(), which will then be called the first time that the static function vir(Ip|Eb)TablesInitialize() is called (that function is defined for you by the macro). This is thread-safe, so there is no chance of any race. IMPORTANT NOTE: I've left the VIR_DEBUG messages in these two init functions (one for iptables, on for ebtables) as VIR_WARN so that I don't have to turn on all the other debug message just to see these. Even if this patch doesn't need any other modification, those messages need to be changed to VIR_DEBUG before pushing. This one-time initialization works well. However, I've encountered problems with testing: 1) Whenever I have enabled the firewalld service, *all* attempts to call firewall-cmd from within libvirtd end with firewall-cmd hanging internally somewhere. This is *not* the case if firewall-cmd returns non-0 in response to "firewall-cmd --state" (i.e. *that* command runs and returns to libvirt successfully.) 2) If I start libvirtd while firewalld is stopped, then start firewalld later, this triggers libvirtd to reload its iptables rules, however it also spits out a *ton* of complaints about deletion failing (I suppose because firewalld has nuked all of libvirt's rules). I guess we need to suppress those messages (which is a more annoying problem to fix than you might think, but that's another story). 3) I noticed a few times during this long line of errors that firewalld made a complaint about "Resource Temporarily unavailable. Having libvirtd access iptables commands directly at the same time as firewalld is doing so is apparently problematic. 4) In general, I'm concerned about the "set it once and never change it" method - if firewalld is disabled at libvirtd startup, causing libvirtd to always use iptables/ebtables directly, this won't cause *terrible* problems, but if libvirtd decides to use firewall-cmd and firewalld is later disabled, libvirtd will not be able to recover.
2012-08-14 18:59:52 +00:00
}
virCommandFree(cmd);
}
return 0;
}
VIR_ONCE_GLOBAL_INIT(virIpTables)
#endif
#define VIR_FROM_THIS VIR_FROM_NONE
enum {
ADD = 0,
REMOVE
};
static virCommandPtr
iptablesCommandNew(const char *table, const char *chain, int family, int action)
{
network: use firewalld instead of iptables, when available * configure.ac, spec file: firewalld defaults to enabled if dbus is available, otherwise is disabled. If --with_firewalld is explicitly requested and dbus is not available, configure will fail. * bridge_driver: add dbus filters to get the FirewallD1.Reloaded signal and DBus.NameOwnerChanged on org.fedoraproject.FirewallD1. When these are encountered, reload all the iptables reuls of all libvirt's virtual networks (similar to what happens when libvirtd is restarted). * iptables, ebtables: use firewall-cmd's direct passthrough interface when available, otherwise use iptables and ebtables commands. This decision is made once the first time libvirt calls iptables/ebtables, and that decision is maintained for the life of libvirtd. * Note that the nwfilter part of this patch was separated out into another patch by Stefan in V2, so that needs to be revised and re-reviewed as well. ================ All the configure.ac and specfile changes are unchanged from Thomas' V3. V3 re-ran "firewall-cmd --state" every time a new rule was added, which was extremely inefficient. V4 uses VIR_ONCE_GLOBAL_INIT to set up a one-time initialization function. The VIR_ONCE_GLOBAL_INIT(x) macro references a static function called vir(Ip|Eb)OnceInit(), which will then be called the first time that the static function vir(Ip|Eb)TablesInitialize() is called (that function is defined for you by the macro). This is thread-safe, so there is no chance of any race. IMPORTANT NOTE: I've left the VIR_DEBUG messages in these two init functions (one for iptables, on for ebtables) as VIR_WARN so that I don't have to turn on all the other debug message just to see these. Even if this patch doesn't need any other modification, those messages need to be changed to VIR_DEBUG before pushing. This one-time initialization works well. However, I've encountered problems with testing: 1) Whenever I have enabled the firewalld service, *all* attempts to call firewall-cmd from within libvirtd end with firewall-cmd hanging internally somewhere. This is *not* the case if firewall-cmd returns non-0 in response to "firewall-cmd --state" (i.e. *that* command runs and returns to libvirt successfully.) 2) If I start libvirtd while firewalld is stopped, then start firewalld later, this triggers libvirtd to reload its iptables rules, however it also spits out a *ton* of complaints about deletion failing (I suppose because firewalld has nuked all of libvirt's rules). I guess we need to suppress those messages (which is a more annoying problem to fix than you might think, but that's another story). 3) I noticed a few times during this long line of errors that firewalld made a complaint about "Resource Temporarily unavailable. Having libvirtd access iptables commands directly at the same time as firewalld is doing so is apparently problematic. 4) In general, I'm concerned about the "set it once and never change it" method - if firewalld is disabled at libvirtd startup, causing libvirtd to always use iptables/ebtables directly, this won't cause *terrible* problems, but if libvirtd decides to use firewall-cmd and firewalld is later disabled, libvirtd will not be able to recover.
2012-08-14 18:59:52 +00:00
virCommandPtr cmd = NULL;
#if HAVE_FIREWALLD
virIpTablesInitialize();
if (firewall_cmd_path) {
cmd = virCommandNew(firewall_cmd_path);
virCommandAddArgList(cmd, "--direct", "--passthrough",
(family == AF_INET6) ? "ipv6" : "ipv4", NULL);
}
#endif
if (cmd == NULL) {
cmd = virCommandNew((family == AF_INET6)
? IP6TABLES_PATH : IPTABLES_PATH);
network: use firewalld instead of iptables, when available * configure.ac, spec file: firewalld defaults to enabled if dbus is available, otherwise is disabled. If --with_firewalld is explicitly requested and dbus is not available, configure will fail. * bridge_driver: add dbus filters to get the FirewallD1.Reloaded signal and DBus.NameOwnerChanged on org.fedoraproject.FirewallD1. When these are encountered, reload all the iptables reuls of all libvirt's virtual networks (similar to what happens when libvirtd is restarted). * iptables, ebtables: use firewall-cmd's direct passthrough interface when available, otherwise use iptables and ebtables commands. This decision is made once the first time libvirt calls iptables/ebtables, and that decision is maintained for the life of libvirtd. * Note that the nwfilter part of this patch was separated out into another patch by Stefan in V2, so that needs to be revised and re-reviewed as well. ================ All the configure.ac and specfile changes are unchanged from Thomas' V3. V3 re-ran "firewall-cmd --state" every time a new rule was added, which was extremely inefficient. V4 uses VIR_ONCE_GLOBAL_INIT to set up a one-time initialization function. The VIR_ONCE_GLOBAL_INIT(x) macro references a static function called vir(Ip|Eb)OnceInit(), which will then be called the first time that the static function vir(Ip|Eb)TablesInitialize() is called (that function is defined for you by the macro). This is thread-safe, so there is no chance of any race. IMPORTANT NOTE: I've left the VIR_DEBUG messages in these two init functions (one for iptables, on for ebtables) as VIR_WARN so that I don't have to turn on all the other debug message just to see these. Even if this patch doesn't need any other modification, those messages need to be changed to VIR_DEBUG before pushing. This one-time initialization works well. However, I've encountered problems with testing: 1) Whenever I have enabled the firewalld service, *all* attempts to call firewall-cmd from within libvirtd end with firewall-cmd hanging internally somewhere. This is *not* the case if firewall-cmd returns non-0 in response to "firewall-cmd --state" (i.e. *that* command runs and returns to libvirt successfully.) 2) If I start libvirtd while firewalld is stopped, then start firewalld later, this triggers libvirtd to reload its iptables rules, however it also spits out a *ton* of complaints about deletion failing (I suppose because firewalld has nuked all of libvirt's rules). I guess we need to suppress those messages (which is a more annoying problem to fix than you might think, but that's another story). 3) I noticed a few times during this long line of errors that firewalld made a complaint about "Resource Temporarily unavailable. Having libvirtd access iptables commands directly at the same time as firewalld is doing so is apparently problematic. 4) In general, I'm concerned about the "set it once and never change it" method - if firewalld is disabled at libvirtd startup, causing libvirtd to always use iptables/ebtables directly, this won't cause *terrible* problems, but if libvirtd decides to use firewall-cmd and firewalld is later disabled, libvirtd will not be able to recover.
2012-08-14 18:59:52 +00:00
}
virCommandAddArgList(cmd, "--table", table,
action == ADD ? "--insert" : "--delete",
chain, NULL);
return cmd;
}
static int
iptablesCommandRunAndFree(virCommandPtr cmd)
{
int ret;
ret = virCommandRun(cmd, NULL);
virCommandFree(cmd);
return ret;
}
static int ATTRIBUTE_SENTINEL
iptablesAddRemoveRule(const char *table, const char *chain, int family, int action,
const char *arg, ...)
{
va_list args;
virCommandPtr cmd = NULL;
const char *s;
cmd = iptablesCommandNew(table, chain, family, action);
virCommandAddArg(cmd, arg);
va_start(args, arg);
while ((s = va_arg(args, const char *)))
virCommandAddArg(cmd, s);
va_end(args);
return iptablesCommandRunAndFree(cmd);
}
static int
iptablesInput(int family,
const char *iface,
int port,
int action,
int tcp)
{
char portstr[32];
snprintf(portstr, sizeof(portstr), "%d", port);
portstr[sizeof(portstr) - 1] = '\0';
return iptablesAddRemoveRule("filter", "INPUT",
family,
2007-03-13 22:43:22 +00:00
action,
"--in-interface", iface,
"--protocol", tcp ? "tcp" : "udp",
"--destination-port", portstr,
"--jump", "ACCEPT",
NULL);
}
/**
* iptablesAddTcpInput:
* @ctx: pointer to the IP table context
* @iface: the interface name
* @port: the TCP port to add
*
* Add an input to the IP table allowing access to the given @port on
* the given @iface interface for TCP packets
*
* Returns 0 in case of success or an error code in case of error
*/
int
iptablesAddTcpInput(int family,
const char *iface,
int port)
{
return iptablesInput(family, iface, port, ADD, 1);
}
/**
* iptablesRemoveTcpInput:
* @ctx: pointer to the IP table context
* @iface: the interface name
* @port: the TCP port to remove
*
2008-02-27 10:37:19 +00:00
* Removes an input from the IP table, hence forbidding access to the given
* @port on the given @iface interface for TCP packets
*
* Returns 0 in case of success or an error code in case of error
*/
int
iptablesRemoveTcpInput(int family,
const char *iface,
int port)
{
return iptablesInput(family, iface, port, REMOVE, 1);
}
/**
* iptablesAddUdpInput:
* @ctx: pointer to the IP table context
* @iface: the interface name
* @port: the UDP port to add
*
* Add an input to the IP table allowing access to the given @port on
* the given @iface interface for UDP packets
*
* Returns 0 in case of success or an error code in case of error
*/
int
iptablesAddUdpInput(int family,
const char *iface,
int port)
{
return iptablesInput(family, iface, port, ADD, 0);
}
/**
* iptablesRemoveUdpInput:
* @ctx: pointer to the IP table context
* @iface: the interface name
* @port: the UDP port to remove
*
2008-02-27 10:37:19 +00:00
* Removes an input from the IP table, hence forbidding access to the given
* @port on the given @iface interface for UDP packets
*
* Returns 0 in case of success or an error code in case of error
*/
int
iptablesRemoveUdpInput(int family,
const char *iface,
int port)
{
return iptablesInput(family, iface, port, REMOVE, 0);
}
static char *iptablesFormatNetwork(virSocketAddr *netaddr,
unsigned int prefix)
{
virSocketAddr network;
char *netstr;
char *ret;
if (!(VIR_SOCKET_ADDR_IS_FAMILY(netaddr, AF_INET) ||
VIR_SOCKET_ADDR_IS_FAMILY(netaddr, AF_INET6))) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("Only IPv4 or IPv6 addresses can be used with iptables"));
return NULL;
}
if (virSocketAddrMaskByPrefix(netaddr, prefix, &network) < 0) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("Failure to mask address"));
return NULL;
}
netstr = virSocketAddrFormat(&network);
if (!netstr)
return NULL;
ignore_value(virAsprintf(&ret, "%s/%d", netstr, prefix));
VIR_FREE(netstr);
return ret;
}
/* Allow all traffic coming from the bridge, with a valid network address
* to proceed to WAN
*/
static int
iptablesForwardAllowOut(virSocketAddr *netaddr,
unsigned int prefix,
const char *iface,
const char *physdev,
int action)
{
int ret;
char *networkstr;
virCommandPtr cmd = NULL;
if (!(networkstr = iptablesFormatNetwork(netaddr, prefix)))
return -1;
cmd = iptablesCommandNew("filter", "FORWARD",
VIR_SOCKET_ADDR_FAMILY(netaddr),
action);
virCommandAddArgList(cmd,
"--source", networkstr,
"--in-interface", iface, NULL);
if (physdev && physdev[0])
virCommandAddArgList(cmd, "--out-interface", physdev, NULL);
virCommandAddArgList(cmd, "--jump", "ACCEPT", NULL);
ret = iptablesCommandRunAndFree(cmd);
VIR_FREE(networkstr);
return ret;
}
/**
* iptablesAddForwardAllowOut:
* @ctx: pointer to the IP table context
* @network: the source network name
* @iface: the source interface name
* @physdev: the physical output device
*
* Add a rule to the IP table context to allow the traffic for the
* network @network via interface @iface to be forwarded to
* @physdev device. This allow the outbound traffic on a bridge.
*
* Returns 0 in case of success or an error code otherwise
*/
int
iptablesAddForwardAllowOut(virSocketAddr *netaddr,
unsigned int prefix,
const char *iface,
const char *physdev)
{
return iptablesForwardAllowOut(netaddr, prefix, iface, physdev, ADD);
}
/**
* iptablesRemoveForwardAllowOut:
* @ctx: pointer to the IP table context
* @network: the source network name
* @iface: the source interface name
* @physdev: the physical output device
*
* Remove a rule from the IP table context hence forbidding forwarding
* of the traffic for the network @network via interface @iface
* to the @physdev device output. This stops the outbound traffic on a bridge.
*
* Returns 0 in case of success or an error code otherwise
*/
int
iptablesRemoveForwardAllowOut(virSocketAddr *netaddr,
unsigned int prefix,
const char *iface,
const char *physdev)
{
return iptablesForwardAllowOut(netaddr, prefix, iface, physdev, REMOVE);
}
/* Allow all traffic destined to the bridge, with a valid network address
* and associated with an existing connection
*/
static int
iptablesForwardAllowRelatedIn(virSocketAddr *netaddr,
unsigned int prefix,
const char *iface,
const char *physdev,
int action)
{
int ret;
char *networkstr;
if (!(networkstr = iptablesFormatNetwork(netaddr, prefix)))
return -1;
if (physdev && physdev[0]) {
ret = iptablesAddRemoveRule("filter", "FORWARD",
VIR_SOCKET_ADDR_FAMILY(netaddr),
action,
"--destination", networkstr,
"--in-interface", physdev,
"--out-interface", iface,
"--match", "conntrack",
"--ctstate", "ESTABLISHED,RELATED",
"--jump", "ACCEPT",
NULL);
2007-03-13 22:43:22 +00:00
} else {
ret = iptablesAddRemoveRule("filter", "FORWARD",
VIR_SOCKET_ADDR_FAMILY(netaddr),
action,
"--destination", networkstr,
"--out-interface", iface,
"--match", "conntrack",
"--ctstate", "ESTABLISHED,RELATED",
"--jump", "ACCEPT",
NULL);
2007-03-13 22:43:22 +00:00
}
VIR_FREE(networkstr);
return ret;
}
/**
* iptablesAddForwardAllowRelatedIn:
* @ctx: pointer to the IP table context
* @network: the source network name
* @iface: the output interface name
* @physdev: the physical input device or NULL
*
* Add rules to the IP table context to allow the traffic for the
* network @network on @physdev device to be forwarded to
* interface @iface, if it is part of an existing connection.
*
* Returns 0 in case of success or an error code otherwise
*/
int
iptablesAddForwardAllowRelatedIn(virSocketAddr *netaddr,
unsigned int prefix,
const char *iface,
const char *physdev)
{
return iptablesForwardAllowRelatedIn(netaddr, prefix, iface, physdev, ADD);
}
/**
* iptablesRemoveForwardAllowRelatedIn:
* @ctx: pointer to the IP table context
* @network: the source network name
* @iface: the output interface name
* @physdev: the physical input device or NULL
*
* Remove rules from the IP table context hence forbidding the traffic for
* network @network on @physdev device to be forwarded to
* interface @iface, if it is part of an existing connection.
*
* Returns 0 in case of success or an error code otherwise
*/
int
iptablesRemoveForwardAllowRelatedIn(virSocketAddr *netaddr,
unsigned int prefix,
const char *iface,
const char *physdev)
{
return iptablesForwardAllowRelatedIn(netaddr, prefix, iface, physdev, REMOVE);
}
/* Allow all traffic destined to the bridge, with a valid network address
*/
static int
iptablesForwardAllowIn(virSocketAddr *netaddr,
unsigned int prefix,
const char *iface,
const char *physdev,
int action)
{
int ret;
char *networkstr;
if (!(networkstr = iptablesFormatNetwork(netaddr, prefix)))
return -1;
if (physdev && physdev[0]) {
ret = iptablesAddRemoveRule("filter", "FORWARD",
VIR_SOCKET_ADDR_FAMILY(netaddr),
action,
"--destination", networkstr,
"--in-interface", physdev,
"--out-interface", iface,
"--jump", "ACCEPT",
NULL);
} else {
ret = iptablesAddRemoveRule("filter", "FORWARD",
VIR_SOCKET_ADDR_FAMILY(netaddr),
action,
"--destination", networkstr,
"--out-interface", iface,
"--jump", "ACCEPT",
NULL);
}
VIR_FREE(networkstr);
return ret;
}
/**
* iptablesAddForwardAllowIn:
* @ctx: pointer to the IP table context
* @network: the source network name
* @iface: the output interface name
* @physdev: the physical input device or NULL
*
* Add rules to the IP table context to allow the traffic for the
* network @network on @physdev device to be forwarded to
* interface @iface. This allow the inbound traffic on a bridge.
*
* Returns 0 in case of success or an error code otherwise
*/
int
iptablesAddForwardAllowIn(virSocketAddr *netaddr,
unsigned int prefix,
const char *iface,
const char *physdev)
{
return iptablesForwardAllowIn(netaddr, prefix, iface, physdev, ADD);
}
/**
* iptablesRemoveForwardAllowIn:
* @ctx: pointer to the IP table context
* @network: the source network name
* @iface: the output interface name
* @physdev: the physical input device or NULL
*
* Remove rules from the IP table context hence forbidding the traffic for
* network @network on @physdev device to be forwarded to
* interface @iface. This stops the inbound traffic on a bridge.
*
* Returns 0 in case of success or an error code otherwise
*/
int
iptablesRemoveForwardAllowIn(virSocketAddr *netaddr,
unsigned int prefix,
const char *iface,
const char *physdev)
{
return iptablesForwardAllowIn(netaddr, prefix, iface, physdev, REMOVE);
}
/* Allow all traffic between guests on the same bridge,
* with a valid network address
*/
static int
iptablesForwardAllowCross(int family,
const char *iface,
int action)
{
return iptablesAddRemoveRule("filter", "FORWARD",
family,
action,
"--in-interface", iface,
"--out-interface", iface,
"--jump", "ACCEPT",
NULL);
}
/**
* iptablesAddForwardAllowCross:
* @ctx: pointer to the IP table context
* @iface: the input/output interface name
*
* Add rules to the IP table context to allow traffic to cross that
* interface. It allows all traffic between guests on the same bridge
* represented by that interface.
*
* Returns 0 in case of success or an error code otherwise
*/
int
iptablesAddForwardAllowCross(int family,
const char *iface)
{
return iptablesForwardAllowCross(family, iface, ADD);
}
/**
* iptablesRemoveForwardAllowCross:
* @ctx: pointer to the IP table context
* @iface: the input/output interface name
*
* Remove rules to the IP table context to block traffic to cross that
* interface. It forbids traffic between guests on the same bridge
* represented by that interface.
*
* Returns 0 in case of success or an error code otherwise
*/
int
iptablesRemoveForwardAllowCross(int family,
const char *iface)
{
return iptablesForwardAllowCross(family, iface, REMOVE);
}
/* Drop all traffic trying to forward from the bridge.
* ie the bridge is the in interface
*/
static int
iptablesForwardRejectOut(int family,
const char *iface,
int action)
{
return iptablesAddRemoveRule("filter", "FORWARD",
family,
action,
"--in-interface", iface,
"--jump", "REJECT",
NULL);
}
/**
* iptablesAddForwardRejectOut:
* @ctx: pointer to the IP table context
* @iface: the output interface name
*
* Add rules to the IP table context to forbid all traffic to that
* interface. It forbids forwarding from the bridge to that interface.
*
* Returns 0 in case of success or an error code otherwise
*/
int
iptablesAddForwardRejectOut(int family,
const char *iface)
{
return iptablesForwardRejectOut(family, iface, ADD);
}
/**
* iptablesRemoveForwardRejectOut:
* @ctx: pointer to the IP table context
* @iface: the output interface name
*
* Remove rules from the IP table context forbidding all traffic to that
* interface. It reallow forwarding from the bridge to that interface.
*
* Returns 0 in case of success or an error code otherwise
*/
int
iptablesRemoveForwardRejectOut(int family,
const char *iface)
{
return iptablesForwardRejectOut(family, iface, REMOVE);
}
/* Drop all traffic trying to forward to the bridge.
* ie the bridge is the out interface
*/
static int
iptablesForwardRejectIn(int family,
2007-03-13 22:43:22 +00:00
const char *iface,
int action)
{
return iptablesAddRemoveRule("filter", "FORWARD",
family,
action,
"--out-interface", iface,
"--jump", "REJECT",
NULL);
}
/**
* iptablesAddForwardRejectIn:
* @ctx: pointer to the IP table context
* @iface: the input interface name
*
* Add rules to the IP table context to forbid all traffic from that
* interface. It forbids forwarding from that interface to the bridge.
*
* Returns 0 in case of success or an error code otherwise
*/
int
iptablesAddForwardRejectIn(int family,
const char *iface)
{
return iptablesForwardRejectIn(family, iface, ADD);
}
/**
* iptablesRemoveForwardRejectIn:
* @ctx: pointer to the IP table context
* @iface: the input interface name
*
* Remove rules from the IP table context forbidding all traffic from that
* interface. It allows forwarding from that interface to the bridge.
*
* Returns 0 in case of success or an error code otherwise
*/
int
iptablesRemoveForwardRejectIn(int family,
const char *iface)
{
return iptablesForwardRejectIn(family, iface, REMOVE);
}
/* Masquerade all traffic coming from the network associated
* with the bridge
*/
static int
iptablesForwardMasquerade(virSocketAddr *netaddr,
unsigned int prefix,
const char *physdev,
virSocketAddrRangePtr addr,
virPortRangePtr port,
const char *protocol,
int action)
{
int ret = -1;
char *networkstr = NULL;
char *addrStartStr = NULL;
char *addrEndStr = NULL;
char *portRangeStr = NULL;
char *natRangeStr = NULL;
virCommandPtr cmd = NULL;
if (!(networkstr = iptablesFormatNetwork(netaddr, prefix)))
return -1;
if (!VIR_SOCKET_ADDR_IS_FAMILY(netaddr, AF_INET)) {
/* Higher level code *should* guaranteee it's impossible to get here. */
virReportError(VIR_ERR_INTERNAL_ERROR,
_("Attempted to NAT '%s'. NAT is only supported for IPv4."),
networkstr);
goto cleanup;
}
if (VIR_SOCKET_ADDR_IS_FAMILY(&addr->start, AF_INET)) {
if (!(addrStartStr = virSocketAddrFormat(&addr->start)))
goto cleanup;
if (VIR_SOCKET_ADDR_IS_FAMILY(&addr->end, AF_INET)) {
if (!(addrEndStr = virSocketAddrFormat(&addr->end)))
goto cleanup;
}
}
cmd = iptablesCommandNew("nat", "POSTROUTING", AF_INET, action);
virCommandAddArgList(cmd, "--source", networkstr, NULL);
if (protocol && protocol[0])
virCommandAddArgList(cmd, "-p", protocol, NULL);
virCommandAddArgList(cmd, "!", "--destination", networkstr, NULL);
if (physdev && physdev[0])
virCommandAddArgList(cmd, "--out-interface", physdev, NULL);
if (protocol && protocol[0]) {
if (port->start == 0 && port->end == 0) {
port->start = 1024;
port->end = 65535;
}
if (port->start < port->end && port->end < 65536) {
if (virAsprintf(&portRangeStr, ":%u-%u",
port->start, port->end) < 0)
goto cleanup;
} else {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("Invalid port range '%u-%u'."),
port->start, port->end);
}
}
/* Use --jump SNAT if public addr is specified */
if (addrStartStr && addrStartStr[0]) {
int r = 0;
if (addrEndStr && addrEndStr[0]) {
r = virAsprintf(&natRangeStr, "%s-%s%s", addrStartStr, addrEndStr,
portRangeStr ? portRangeStr : "");
} else {
r = virAsprintf(&natRangeStr, "%s%s", addrStartStr,
portRangeStr ? portRangeStr : "");
}
if (r < 0)
goto cleanup;
virCommandAddArgList(cmd, "--jump", "SNAT",
"--to-source", natRangeStr, NULL);
} else {
virCommandAddArgList(cmd, "--jump", "MASQUERADE", NULL);
if (portRangeStr && portRangeStr[0])
virCommandAddArgList(cmd, "--to-ports", &portRangeStr[1], NULL);
}
ret = virCommandRun(cmd, NULL);
cleanup:
virCommandFree(cmd);
VIR_FREE(networkstr);
VIR_FREE(addrStartStr);
VIR_FREE(addrEndStr);
VIR_FREE(portRangeStr);
VIR_FREE(natRangeStr);
return ret;
}
/**
* iptablesAddForwardMasquerade:
* @ctx: pointer to the IP table context
* @network: the source network name
* @physdev: the physical input device or NULL
* @protocol: the network protocol or NULL
*
* Add rules to the IP table context to allow masquerading
* network @network on @physdev. This allow the bridge to
* masquerade for that network (on @physdev).
*
* Returns 0 in case of success or an error code otherwise
*/
int
iptablesAddForwardMasquerade(virSocketAddr *netaddr,
unsigned int prefix,
const char *physdev,
virSocketAddrRangePtr addr,
virPortRangePtr port,
const char *protocol)
{
return iptablesForwardMasquerade(netaddr, prefix, physdev, addr, port,
protocol, ADD);
}
/**
* iptablesRemoveForwardMasquerade:
* @ctx: pointer to the IP table context
* @network: the source network name
* @physdev: the physical input device or NULL
* @protocol: the network protocol or NULL
*
* Remove rules from the IP table context to stop masquerading
* network @network on @physdev. This stops the bridge from
* masquerading for that network (on @physdev).
*
* Returns 0 in case of success or an error code otherwise
*/
int
iptablesRemoveForwardMasquerade(virSocketAddr *netaddr,
unsigned int prefix,
const char *physdev,
virSocketAddrRangePtr addr,
virPortRangePtr port,
const char *protocol)
{
return iptablesForwardMasquerade(netaddr, prefix, physdev, addr, port,
protocol, REMOVE);
}
static int
iptablesOutputFixUdpChecksum(const char *iface,
int port,
int action)
{
char portstr[32];
snprintf(portstr, sizeof(portstr), "%d", port);
portstr[sizeof(portstr) - 1] = '\0';
return iptablesAddRemoveRule("mangle", "POSTROUTING",
AF_INET,
action,
"--out-interface", iface,
"--protocol", "udp",
"--destination-port", portstr,
"--jump", "CHECKSUM", "--checksum-fill",
NULL);
}
/**
* iptablesAddOutputFixUdpChecksum:
* @ctx: pointer to the IP table context
* @iface: the interface name
* @port: the UDP port to match
*
* Add a rule to the mangle table's POSTROUTING chain that fixes up the
* checksum of packets with the given destination @port.
* the given @iface interface for TCP packets.
*
* Returns 0 in case of success or an error code in case of error.
* (NB: if the system's iptables does not support checksum mangling,
* this will return an error, which should be ignored.)
*/
int
iptablesAddOutputFixUdpChecksum(const char *iface,
int port)
{
return iptablesOutputFixUdpChecksum(iface, port, ADD);
}
/**
* iptablesRemoveOutputFixUdpChecksum:
* @ctx: pointer to the IP table context
* @iface: the interface name
* @port: the UDP port of the rule to remove
*
* Removes the checksum fixup rule that was previous added with
* iptablesAddOutputFixUdpChecksum.
*
* Returns 0 in case of success or an error code in case of error
* (again, if iptables doesn't support checksum fixup, this will
* return an error, which should be ignored)
*/
int
iptablesRemoveOutputFixUdpChecksum(const char *iface,
int port)
{
return iptablesOutputFixUdpChecksum(iface, port, REMOVE);
}