/* * bridge_driver_linux.c: Linux implementation of bridge driver * * Copyright (C) 2006-2013 Red Hat, Inc. * Copyright (C) 2006 Daniel P. Berrange * * 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 * . * * Author: Daniel P. Berrange */ #include #include "viralloc.h" #include "virfile.h" #include "viriptables.h" #include "virstring.h" #define VIR_FROM_THIS VIR_FROM_NONE #define PROC_NET_ROUTE "/proc/net/route" /* XXX: This function can be a lot more exhaustive, there are certainly * other scenarios where we can ruin host network connectivity. * XXX: Using a proper library is preferred over parsing /proc */ int networkCheckRouteCollision(virNetworkObjPtr network) { int ret = 0, len; char *cur, *buf = NULL; /* allow for up to 100000 routes (each line is 128 bytes) */ enum {MAX_ROUTE_SIZE = 128*100000}; /* Read whole routing table into memory */ if ((len = virFileReadAll(PROC_NET_ROUTE, MAX_ROUTE_SIZE, &buf)) < 0) goto out; /* Dropping the last character shouldn't hurt */ if (len > 0) buf[len-1] = '\0'; VIR_DEBUG("%s output:\n%s", PROC_NET_ROUTE, buf); if (!STRPREFIX(buf, "Iface")) goto out; /* First line is just headings, skip it */ cur = strchr(buf, '\n'); if (cur) cur++; while (cur) { char iface[17], dest[128], mask[128]; unsigned int addr_val, mask_val; virNetworkIpDefPtr ipdef; int num; size_t i; /* NUL-terminate the line, so sscanf doesn't go beyond a newline. */ char *nl = strchr(cur, '\n'); if (nl) { *nl++ = '\0'; } num = sscanf(cur, "%16s %127s %*s %*s %*s %*s %*s %127s", iface, dest, mask); cur = nl; if (num != 3) { VIR_DEBUG("Failed to parse %s", PROC_NET_ROUTE); continue; } if (virStrToLong_ui(dest, NULL, 16, &addr_val) < 0) { VIR_DEBUG("Failed to convert network address %s to uint", dest); continue; } if (virStrToLong_ui(mask, NULL, 16, &mask_val) < 0) { VIR_DEBUG("Failed to convert network mask %s to uint", mask); continue; } addr_val &= mask_val; for (i = 0; (ipdef = virNetworkDefGetIpByIndex(network->def, AF_INET, i)); i++) { unsigned int net_dest; virSocketAddr netmask; if (virNetworkIpDefNetmask(ipdef, &netmask) < 0) { VIR_WARN("Failed to get netmask of '%s'", network->def->bridge); continue; } net_dest = (ipdef->address.data.inet4.sin_addr.s_addr & netmask.data.inet4.sin_addr.s_addr); if ((net_dest == addr_val) && (netmask.data.inet4.sin_addr.s_addr == mask_val)) { virReportError(VIR_ERR_INTERNAL_ERROR, _("Network is already in use by interface %s"), iface); ret = -1; goto out; } } } out: VIR_FREE(buf); return ret; } static const char networkLocalMulticast[] = "224.0.0.0/24"; static const char networkLocalBroadcast[] = "255.255.255.255/32"; int networkAddMasqueradingFirewallRules(virNetworkObjPtr network, virNetworkIpDefPtr ipdef) { int prefix = virNetworkIpDefPrefix(ipdef); const char *forwardIf = virNetworkDefForwardIf(network->def, 0); if (prefix < 0) { virReportError(VIR_ERR_INTERNAL_ERROR, _("Invalid prefix or netmask for '%s'"), network->def->bridge); goto masqerr1; } /* allow forwarding packets from the bridge interface */ if (iptablesAddForwardAllowOut(&ipdef->address, prefix, network->def->bridge, forwardIf) < 0) { virReportError(VIR_ERR_SYSTEM_ERROR, _("failed to add iptables rule to allow forwarding from '%s'"), network->def->bridge); goto masqerr1; } /* allow forwarding packets to the bridge interface if they are * part of an existing connection */ if (iptablesAddForwardAllowRelatedIn(&ipdef->address, prefix, network->def->bridge, forwardIf) < 0) { virReportError(VIR_ERR_SYSTEM_ERROR, _("failed to add iptables rule to allow forwarding to '%s'"), network->def->bridge); goto masqerr2; } /* * Enable masquerading. * * We need to end up with 5 rules in the table in this order * * 1. do not masquerade packets targeting 224.0.0.0/24 * 2. do not masquerade packets targeting 255.255.255.255/32 * 3. masquerade protocol=tcp with sport mapping restriction * 4. masquerade protocol=udp with sport mapping restriction * 5. generic, masquerade any protocol * * 224.0.0.0/24 is the local network multicast range. Packets are not * forwarded outside. * * 255.255.255.255/32 is the broadcast address of any local network. Again, * such packets are never forwarded, but strict DHCP clients don't accept * DHCP replies with changed source ports. * * The sport mappings are required, because default IPtables * MASQUERADE maintain port numbers unchanged where possible. * * NFS can be configured to only "trust" port numbers < 1023. * * Guests using NAT thus need to be prevented from having port * numbers < 1023, otherwise they can bypass the NFS "security" * check on the source port number. * * Since we use '--insert' to add rules to the header of the * chain, we actually need to add them in the reverse of the * order just mentioned ! */ /* First the generic masquerade rule for other protocols */ if (iptablesAddForwardMasquerade(&ipdef->address, prefix, forwardIf, &network->def->forward.addr, &network->def->forward.port, NULL) < 0) { if (forwardIf) virReportError(VIR_ERR_SYSTEM_ERROR, _("failed to add iptables rule to enable masquerading to %s"), forwardIf); else virReportError(VIR_ERR_SYSTEM_ERROR, "%s", _("failed to add iptables rule to enable masquerading")); goto masqerr3; } /* UDP with a source port restriction */ if (iptablesAddForwardMasquerade(&ipdef->address, prefix, forwardIf, &network->def->forward.addr, &network->def->forward.port, "udp") < 0) { if (forwardIf) virReportError(VIR_ERR_SYSTEM_ERROR, _("failed to add iptables rule to enable UDP masquerading to %s"), forwardIf); else virReportError(VIR_ERR_SYSTEM_ERROR, "%s", _("failed to add iptables rule to enable UDP masquerading")); goto masqerr4; } /* TCP with a source port restriction */ if (iptablesAddForwardMasquerade(&ipdef->address, prefix, forwardIf, &network->def->forward.addr, &network->def->forward.port, "tcp") < 0) { if (forwardIf) virReportError(VIR_ERR_SYSTEM_ERROR, _("failed to add iptables rule to enable TCP masquerading to %s"), forwardIf); else virReportError(VIR_ERR_SYSTEM_ERROR, "%s", _("failed to add iptables rule to enable TCP masquerading")); goto masqerr5; } /* exempt local network broadcast address as destination */ if (iptablesAddDontMasquerade(&ipdef->address, prefix, forwardIf, networkLocalBroadcast) < 0) { if (forwardIf) virReportError(VIR_ERR_SYSTEM_ERROR, _("failed to add iptables rule to prevent local broadcast masquerading on %s"), forwardIf); else virReportError(VIR_ERR_SYSTEM_ERROR, "%s", _("failed to add iptables rule to prevent local broadcast masquerading")); goto masqerr6; } /* exempt local multicast range as destination */ if (iptablesAddDontMasquerade(&ipdef->address, prefix, forwardIf, networkLocalMulticast) < 0) { if (forwardIf) virReportError(VIR_ERR_SYSTEM_ERROR, _("failed to add iptables rule to prevent local multicast masquerading on %s"), forwardIf); else virReportError(VIR_ERR_SYSTEM_ERROR, "%s", _("failed to add iptables rule to prevent local multicast masquerading")); goto masqerr7; } return 0; masqerr7: iptablesRemoveDontMasquerade(&ipdef->address, prefix, forwardIf, networkLocalBroadcast); masqerr6: iptablesRemoveForwardMasquerade(&ipdef->address, prefix, forwardIf, &network->def->forward.addr, &network->def->forward.port, "tcp"); masqerr5: iptablesRemoveForwardMasquerade(&ipdef->address, prefix, forwardIf, &network->def->forward.addr, &network->def->forward.port, "udp"); masqerr4: iptablesRemoveForwardMasquerade(&ipdef->address, prefix, forwardIf, &network->def->forward.addr, &network->def->forward.port, NULL); masqerr3: iptablesRemoveForwardAllowRelatedIn(&ipdef->address, prefix, network->def->bridge, forwardIf); masqerr2: iptablesRemoveForwardAllowOut(&ipdef->address, prefix, network->def->bridge, forwardIf); masqerr1: return -1; } void networkRemoveMasqueradingFirewallRules(virNetworkObjPtr network, virNetworkIpDefPtr ipdef) { int prefix = virNetworkIpDefPrefix(ipdef); const char *forwardIf = virNetworkDefForwardIf(network->def, 0); if (prefix >= 0) { iptablesRemoveDontMasquerade(&ipdef->address, prefix, forwardIf, networkLocalMulticast); iptablesRemoveDontMasquerade(&ipdef->address, prefix, forwardIf, networkLocalBroadcast); iptablesRemoveForwardMasquerade(&ipdef->address, prefix, forwardIf, &network->def->forward.addr, &network->def->forward.port, "tcp"); iptablesRemoveForwardMasquerade(&ipdef->address, prefix, forwardIf, &network->def->forward.addr, &network->def->forward.port, "udp"); iptablesRemoveForwardMasquerade(&ipdef->address, prefix, forwardIf, &network->def->forward.addr, &network->def->forward.port, NULL); iptablesRemoveForwardAllowRelatedIn(&ipdef->address, prefix, network->def->bridge, forwardIf); iptablesRemoveForwardAllowOut(&ipdef->address, prefix, network->def->bridge, forwardIf); } } int networkAddRoutingFirewallRules(virNetworkObjPtr network, virNetworkIpDefPtr ipdef) { int prefix = virNetworkIpDefPrefix(ipdef); const char *forwardIf = virNetworkDefForwardIf(network->def, 0); if (prefix < 0) { virReportError(VIR_ERR_INTERNAL_ERROR, _("Invalid prefix or netmask for '%s'"), network->def->bridge); goto routeerr1; } /* allow routing packets from the bridge interface */ if (iptablesAddForwardAllowOut(&ipdef->address, prefix, network->def->bridge, forwardIf) < 0) { virReportError(VIR_ERR_SYSTEM_ERROR, _("failed to add iptables rule to allow routing from '%s'"), network->def->bridge); goto routeerr1; } /* allow routing packets to the bridge interface */ if (iptablesAddForwardAllowIn(&ipdef->address, prefix, network->def->bridge, forwardIf) < 0) { virReportError(VIR_ERR_SYSTEM_ERROR, _("failed to add iptables rule to allow routing to '%s'"), network->def->bridge); goto routeerr2; } return 0; routeerr2: iptablesRemoveForwardAllowOut(&ipdef->address, prefix, network->def->bridge, forwardIf); routeerr1: return -1; } void networkRemoveRoutingFirewallRules(virNetworkObjPtr network, virNetworkIpDefPtr ipdef) { int prefix = virNetworkIpDefPrefix(ipdef); const char *forwardIf = virNetworkDefForwardIf(network->def, 0); if (prefix >= 0) { iptablesRemoveForwardAllowIn(&ipdef->address, prefix, network->def->bridge, forwardIf); iptablesRemoveForwardAllowOut(&ipdef->address, prefix, network->def->bridge, forwardIf); } } /* Add all once/network rules required for IPv6. * If no IPv6 addresses are defined and is * specified, then allow IPv6 commuinications between virtual systems. * If any IPv6 addresses are defined, then add the rules for regular operation. */ static int networkAddGeneralIp6tablesRules(virNetworkObjPtr network) { if (!virNetworkDefGetIpByIndex(network->def, AF_INET6, 0) && !network->def->ipv6nogw) { return 0; } /* Catch all rules to block forwarding to/from bridges */ if (iptablesAddForwardRejectOut(AF_INET6, network->def->bridge) < 0) { virReportError(VIR_ERR_SYSTEM_ERROR, _("failed to add ip6tables rule to block outbound traffic from '%s'"), network->def->bridge); goto err1; } if (iptablesAddForwardRejectIn(AF_INET6, network->def->bridge) < 0) { virReportError(VIR_ERR_SYSTEM_ERROR, _("failed to add ip6tables rule to block inbound traffic to '%s'"), network->def->bridge); goto err2; } /* Allow traffic between guests on the same bridge */ if (iptablesAddForwardAllowCross(AF_INET6, network->def->bridge) < 0) { virReportError(VIR_ERR_SYSTEM_ERROR, _("failed to add ip6tables rule to allow cross bridge traffic on '%s'"), network->def->bridge); goto err3; } /* if no IPv6 addresses are defined, we are done. */ if (!virNetworkDefGetIpByIndex(network->def, AF_INET6, 0)) return 0; /* allow DNS over IPv6 */ if (iptablesAddTcpInput(AF_INET6, network->def->bridge, 53) < 0) { virReportError(VIR_ERR_SYSTEM_ERROR, _("failed to add ip6tables rule to allow DNS requests from '%s'"), network->def->bridge); goto err4; } if (iptablesAddUdpInput(AF_INET6, network->def->bridge, 53) < 0) { virReportError(VIR_ERR_SYSTEM_ERROR, _("failed to add ip6tables rule to allow DNS requests from '%s'"), network->def->bridge); goto err5; } if (iptablesAddUdpInput(AF_INET6, network->def->bridge, 547) < 0) { virReportError(VIR_ERR_SYSTEM_ERROR, _("failed to add ip6tables rule to allow DHCP6 requests from '%s'"), network->def->bridge); goto err6; } return 0; /* unwind in reverse order from the point of failure */ err6: iptablesRemoveUdpInput(AF_INET6, network->def->bridge, 53); err5: iptablesRemoveTcpInput(AF_INET6, network->def->bridge, 53); err4: iptablesRemoveForwardAllowCross(AF_INET6, network->def->bridge); err3: iptablesRemoveForwardRejectIn(AF_INET6, network->def->bridge); err2: iptablesRemoveForwardRejectOut(AF_INET6, network->def->bridge); err1: return -1; } static void networkRemoveGeneralIp6tablesRules(virNetworkObjPtr network) { if (!virNetworkDefGetIpByIndex(network->def, AF_INET6, 0) && !network->def->ipv6nogw) { return; } if (virNetworkDefGetIpByIndex(network->def, AF_INET6, 0)) { iptablesRemoveUdpInput(AF_INET6, network->def->bridge, 547); iptablesRemoveUdpInput(AF_INET6, network->def->bridge, 53); iptablesRemoveTcpInput(AF_INET6, network->def->bridge, 53); } /* the following rules are there if no IPv6 address has been defined * but network->def->ipv6nogw == true */ iptablesRemoveForwardAllowCross(AF_INET6, network->def->bridge); iptablesRemoveForwardRejectIn(AF_INET6, network->def->bridge); iptablesRemoveForwardRejectOut(AF_INET6, network->def->bridge); } int networkAddGeneralFirewallRules(virNetworkObjPtr network) { size_t i; virNetworkIpDefPtr ipv4def; /* First look for first IPv4 address that has dhcp or tftpboot defined. */ /* We support dhcp config on 1 IPv4 interface only. */ for (i = 0; (ipv4def = virNetworkDefGetIpByIndex(network->def, AF_INET, i)); i++) { if (ipv4def->nranges || ipv4def->nhosts || ipv4def->tftproot) break; } /* allow DHCP requests through to dnsmasq */ if (iptablesAddTcpInput(AF_INET, network->def->bridge, 67) < 0) { virReportError(VIR_ERR_SYSTEM_ERROR, _("failed to add iptables rule to allow DHCP requests from '%s'"), network->def->bridge); goto err1; } if (iptablesAddUdpInput(AF_INET, network->def->bridge, 67) < 0) { virReportError(VIR_ERR_SYSTEM_ERROR, _("failed to add iptables rule to allow DHCP requests from '%s'"), network->def->bridge); goto err2; } if (iptablesAddUdpOutput(AF_INET, network->def->bridge, 68) < 0) { virReportError(VIR_ERR_SYSTEM_ERROR, _("failed to add iptables rule to allow DHCP replies to '%s'"), network->def->bridge); goto err3; } /* If we are doing local DHCP service on this network, attempt to * add a rule that will fixup the checksum of DHCP response * packets back to the guests (but report failure without * aborting, since not all iptables implementations support it). */ if (ipv4def && (ipv4def->nranges || ipv4def->nhosts) && (iptablesAddOutputFixUdpChecksum(network->def->bridge, 68) < 0)) { VIR_WARN("Could not add rule to fixup DHCP response checksums " "on network '%s'.", network->def->name); VIR_WARN("May need to update iptables package & kernel to support CHECKSUM rule."); } /* allow DNS requests through to dnsmasq */ if (iptablesAddTcpInput(AF_INET, network->def->bridge, 53) < 0) { virReportError(VIR_ERR_SYSTEM_ERROR, _("failed to add iptables rule to allow DNS requests from '%s'"), network->def->bridge); goto err4; } if (iptablesAddUdpInput(AF_INET, network->def->bridge, 53) < 0) { virReportError(VIR_ERR_SYSTEM_ERROR, _("failed to add iptables rule to allow DNS requests from '%s'"), network->def->bridge); goto err5; } /* allow TFTP requests through to dnsmasq if necessary */ if (ipv4def && ipv4def->tftproot && iptablesAddUdpInput(AF_INET, network->def->bridge, 69) < 0) { virReportError(VIR_ERR_SYSTEM_ERROR, _("failed to add iptables rule to allow TFTP requests from '%s'"), network->def->bridge); goto err6; } /* Catch all rules to block forwarding to/from bridges */ if (iptablesAddForwardRejectOut(AF_INET, network->def->bridge) < 0) { virReportError(VIR_ERR_SYSTEM_ERROR, _("failed to add iptables rule to block outbound traffic from '%s'"), network->def->bridge); goto err7; } if (iptablesAddForwardRejectIn(AF_INET, network->def->bridge) < 0) { virReportError(VIR_ERR_SYSTEM_ERROR, _("failed to add iptables rule to block inbound traffic to '%s'"), network->def->bridge); goto err8; } /* Allow traffic between guests on the same bridge */ if (iptablesAddForwardAllowCross(AF_INET, network->def->bridge) < 0) { virReportError(VIR_ERR_SYSTEM_ERROR, _("failed to add iptables rule to allow cross bridge traffic on '%s'"), network->def->bridge); goto err9; } /* add IPv6 general rules, if needed */ if (networkAddGeneralIp6tablesRules(network) < 0) { goto err10; } return 0; /* unwind in reverse order from the point of failure */ err10: iptablesRemoveForwardAllowCross(AF_INET, network->def->bridge); err9: iptablesRemoveForwardRejectIn(AF_INET, network->def->bridge); err8: iptablesRemoveForwardRejectOut(AF_INET, network->def->bridge); err7: if (ipv4def && ipv4def->tftproot) { iptablesRemoveUdpInput(AF_INET, network->def->bridge, 69); } err6: iptablesRemoveUdpInput(AF_INET, network->def->bridge, 53); err5: iptablesRemoveTcpInput(AF_INET, network->def->bridge, 53); err4: iptablesRemoveUdpOutput(AF_INET, network->def->bridge, 68); err3: iptablesRemoveUdpInput(AF_INET, network->def->bridge, 67); err2: iptablesRemoveTcpInput(AF_INET, network->def->bridge, 67); err1: return -1; } void networkRemoveGeneralFirewallRules(virNetworkObjPtr network) { size_t i; virNetworkIpDefPtr ipv4def; networkRemoveGeneralIp6tablesRules(network); for (i = 0; (ipv4def = virNetworkDefGetIpByIndex(network->def, AF_INET, i)); i++) { if (ipv4def->nranges || ipv4def->nhosts || ipv4def->tftproot) break; } iptablesRemoveForwardAllowCross(AF_INET, network->def->bridge); iptablesRemoveForwardRejectIn(AF_INET, network->def->bridge); iptablesRemoveForwardRejectOut(AF_INET, network->def->bridge); if (ipv4def && ipv4def->tftproot) { iptablesRemoveUdpInput(AF_INET, network->def->bridge, 69); } iptablesRemoveUdpInput(AF_INET, network->def->bridge, 53); iptablesRemoveTcpInput(AF_INET, network->def->bridge, 53); if (ipv4def && (ipv4def->nranges || ipv4def->nhosts)) { iptablesRemoveOutputFixUdpChecksum(network->def->bridge, 68); } iptablesRemoveUdpOutput(AF_INET, network->def->bridge, 68); iptablesRemoveUdpInput(AF_INET, network->def->bridge, 67); iptablesRemoveTcpInput(AF_INET, network->def->bridge, 67); } int networkAddIpSpecificFirewallRules(virNetworkObjPtr network, virNetworkIpDefPtr ipdef) { /* NB: in the case of IPv6, routing rules are added when the * forward mode is NAT. This is because IPv6 has no NAT. */ if (network->def->forward.type == VIR_NETWORK_FORWARD_NAT) { if (VIR_SOCKET_ADDR_IS_FAMILY(&ipdef->address, AF_INET)) return networkAddMasqueradingFirewallRules(network, ipdef); else if (VIR_SOCKET_ADDR_IS_FAMILY(&ipdef->address, AF_INET6)) return networkAddRoutingFirewallRules(network, ipdef); } else if (network->def->forward.type == VIR_NETWORK_FORWARD_ROUTE) { return networkAddRoutingFirewallRules(network, ipdef); } return 0; } void networkRemoveIpSpecificFirewallRules(virNetworkObjPtr network, virNetworkIpDefPtr ipdef) { if (network->def->forward.type == VIR_NETWORK_FORWARD_NAT) { if (VIR_SOCKET_ADDR_IS_FAMILY(&ipdef->address, AF_INET)) networkRemoveMasqueradingFirewallRules(network, ipdef); else if (VIR_SOCKET_ADDR_IS_FAMILY(&ipdef->address, AF_INET6)) networkRemoveRoutingFirewallRules(network, ipdef); } else if (network->def->forward.type == VIR_NETWORK_FORWARD_ROUTE) { networkRemoveRoutingFirewallRules(network, ipdef); } } /* Add all rules for all ip addresses (and general rules) on a network */ int networkAddFirewallRules(virNetworkObjPtr network) { size_t i, j; virNetworkIpDefPtr ipdef; virErrorPtr orig_error; /* Add "once per network" rules */ if (networkAddGeneralFirewallRules(network) < 0) return -1; for (i = 0; (ipdef = virNetworkDefGetIpByIndex(network->def, AF_UNSPEC, i)); i++) { /* Add address-specific iptables rules */ if (networkAddIpSpecificFirewallRules(network, ipdef) < 0) { goto err; } } return 0; err: /* store the previous error message before attempting removal of rules */ orig_error = virSaveLastError(); /* The final failed call to networkAddIpSpecificFirewallRules will * have removed any rules it created, but we need to remove those * added for previous IP addresses. */ for (j = 0; j < i; j++) { if ((ipdef = virNetworkDefGetIpByIndex(network->def, AF_UNSPEC, j))) networkRemoveIpSpecificFirewallRules(network, ipdef); } networkRemoveGeneralFirewallRules(network); /* return the original error */ virSetError(orig_error); virFreeError(orig_error); return -1; } /* Remove all rules for all ip addresses (and general rules) on a network */ void networkRemoveFirewallRules(virNetworkObjPtr network) { size_t i; virNetworkIpDefPtr ipdef; for (i = 0; (ipdef = virNetworkDefGetIpByIndex(network->def, AF_UNSPEC, i)); i++) { networkRemoveIpSpecificFirewallRules(network, ipdef); } networkRemoveGeneralFirewallRules(network); }