/* * 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" #include "virlog.h" #include "virfirewall.h" #define VIR_FROM_THIS VIR_FROM_NONE VIR_LOG_INIT("network.bridge_driver_linux"); #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(virNetworkDefPtr def) { 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; virNetworkRouteDefPtr routedef; 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(def, AF_INET, i)); i++) { unsigned int net_dest; virSocketAddr netmask; if (virNetworkIpDefNetmask(ipdef, &netmask) < 0) { VIR_WARN("Failed to get netmask of '%s'", 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; } } for (i = 0; (routedef = virNetworkDefGetRouteByIndex(def, AF_INET, i)); i++) { virSocketAddr r_mask, r_addr; virSocketAddrPtr tmp_addr = virNetworkRouteDefGetAddress(routedef); int r_prefix = virNetworkRouteDefGetPrefix(routedef); if (!tmp_addr || virSocketAddrMaskByPrefix(tmp_addr, r_prefix, &r_addr) < 0 || virSocketAddrPrefixToNetmask(r_prefix, &r_mask, AF_INET) < 0) continue; if ((r_addr.data.inet4.sin_addr.s_addr == addr_val) && (r_mask.data.inet4.sin_addr.s_addr == mask_val)) { char *addr_str = virSocketAddrFormat(&r_addr); if (!addr_str) virResetLastError(); virReportError(VIR_ERR_INTERNAL_ERROR, _("Route address '%s' conflicts " "with IP address for '%s'"), NULLSTR(addr_str), iface); VIR_FREE(addr_str); 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"; static int networkAddMasqueradingFirewallRules(virFirewallPtr fw, virNetworkDefPtr def, virNetworkIpDefPtr ipdef) { int prefix = virNetworkIpDefPrefix(ipdef); const char *forwardIf = virNetworkDefForwardIf(def, 0); if (prefix < 0) { virReportError(VIR_ERR_INTERNAL_ERROR, _("Invalid prefix or netmask for '%s'"), def->bridge); return -1; } /* allow forwarding packets from the bridge interface */ if (iptablesAddForwardAllowOut(fw, &ipdef->address, prefix, def->bridge, forwardIf) < 0) return -1; /* allow forwarding packets to the bridge interface if they are * part of an existing connection */ if (iptablesAddForwardAllowRelatedIn(fw, &ipdef->address, prefix, def->bridge, forwardIf) < 0) return -1; /* * 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(fw, &ipdef->address, prefix, forwardIf, &def->forward.addr, &def->forward.port, NULL) < 0) return -1; /* UDP with a source port restriction */ if (iptablesAddForwardMasquerade(fw, &ipdef->address, prefix, forwardIf, &def->forward.addr, &def->forward.port, "udp") < 0) return -1; /* TCP with a source port restriction */ if (iptablesAddForwardMasquerade(fw, &ipdef->address, prefix, forwardIf, &def->forward.addr, &def->forward.port, "tcp") < 0) return -1; /* exempt local network broadcast address as destination */ if (iptablesAddDontMasquerade(fw, &ipdef->address, prefix, forwardIf, networkLocalBroadcast) < 0) return -1; /* exempt local multicast range as destination */ if (iptablesAddDontMasquerade(fw, &ipdef->address, prefix, forwardIf, networkLocalMulticast) < 0) return -1; return 0; } static int networkRemoveMasqueradingFirewallRules(virFirewallPtr fw, virNetworkDefPtr def, virNetworkIpDefPtr ipdef) { int prefix = virNetworkIpDefPrefix(ipdef); const char *forwardIf = virNetworkDefForwardIf(def, 0); if (prefix < 0) return 0; if (iptablesRemoveDontMasquerade(fw, &ipdef->address, prefix, forwardIf, networkLocalMulticast) < 0) return -1; if (iptablesRemoveDontMasquerade(fw, &ipdef->address, prefix, forwardIf, networkLocalBroadcast) < 0) return -1; if (iptablesRemoveForwardMasquerade(fw, &ipdef->address, prefix, forwardIf, &def->forward.addr, &def->forward.port, "tcp") < 0) return -1; if (iptablesRemoveForwardMasquerade(fw, &ipdef->address, prefix, forwardIf, &def->forward.addr, &def->forward.port, "udp") < 0) return -1; if (iptablesRemoveForwardMasquerade(fw, &ipdef->address, prefix, forwardIf, &def->forward.addr, &def->forward.port, NULL) < 0) return -1; if (iptablesRemoveForwardAllowRelatedIn(fw, &ipdef->address, prefix, def->bridge, forwardIf) < 0) return -1; if (iptablesRemoveForwardAllowOut(fw, &ipdef->address, prefix, def->bridge, forwardIf) < 0) return -1; return 0; } static int networkAddRoutingFirewallRules(virFirewallPtr fw, virNetworkDefPtr def, virNetworkIpDefPtr ipdef) { int prefix = virNetworkIpDefPrefix(ipdef); const char *forwardIf = virNetworkDefForwardIf(def, 0); if (prefix < 0) { virReportError(VIR_ERR_INTERNAL_ERROR, _("Invalid prefix or netmask for '%s'"), def->bridge); return -1; } /* allow routing packets from the bridge interface */ if (iptablesAddForwardAllowOut(fw, &ipdef->address, prefix, def->bridge, forwardIf) < 0) return -1; /* allow routing packets to the bridge interface */ if (iptablesAddForwardAllowIn(fw, &ipdef->address, prefix, def->bridge, forwardIf) < 0) return -1; return 0; } static int networkRemoveRoutingFirewallRules(virFirewallPtr fw, virNetworkDefPtr def, virNetworkIpDefPtr ipdef) { int prefix = virNetworkIpDefPrefix(ipdef); const char *forwardIf = virNetworkDefForwardIf(def, 0); if (prefix < 0) return 0; if (iptablesRemoveForwardAllowIn(fw, &ipdef->address, prefix, def->bridge, forwardIf) < 0) return -1; if (iptablesRemoveForwardAllowOut(fw, &ipdef->address, prefix, def->bridge, forwardIf) < 0) return -1; return 0; } static void networkAddGeneralIPv4FirewallRules(virFirewallPtr fw, virNetworkDefPtr def) { 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(def, AF_INET, i)); i++) { if (ipv4def->nranges || ipv4def->nhosts || ipv4def->tftproot) break; } /* allow DHCP requests through to dnsmasq */ iptablesAddTcpInput(fw, VIR_FIREWALL_LAYER_IPV4, def->bridge, 67); iptablesAddUdpInput(fw, VIR_FIREWALL_LAYER_IPV4, def->bridge, 67); iptablesAddUdpOutput(fw, VIR_FIREWALL_LAYER_IPV4, def->bridge, 68); /* allow DNS requests through to dnsmasq */ iptablesAddTcpInput(fw, VIR_FIREWALL_LAYER_IPV4, def->bridge, 53); iptablesAddUdpInput(fw, VIR_FIREWALL_LAYER_IPV4, def->bridge, 53); /* allow TFTP requests through to dnsmasq if necessary */ if (ipv4def && ipv4def->tftproot) iptablesAddUdpInput(fw, VIR_FIREWALL_LAYER_IPV4, def->bridge, 69); /* Catch all rules to block forwarding to/from bridges */ iptablesAddForwardRejectOut(fw, VIR_FIREWALL_LAYER_IPV4, def->bridge); iptablesAddForwardRejectIn(fw, VIR_FIREWALL_LAYER_IPV4, def->bridge); /* Allow traffic between guests on the same bridge */ iptablesAddForwardAllowCross(fw, VIR_FIREWALL_LAYER_IPV4, def->bridge); } static void networkRemoveGeneralIPv4FirewallRules(virFirewallPtr fw, virNetworkDefPtr def) { size_t i; virNetworkIpDefPtr ipv4def; for (i = 0; (ipv4def = virNetworkDefGetIpByIndex(def, AF_INET, i)); i++) { if (ipv4def->nranges || ipv4def->nhosts || ipv4def->tftproot) break; } iptablesRemoveForwardAllowCross(fw, VIR_FIREWALL_LAYER_IPV4, def->bridge); iptablesRemoveForwardRejectIn(fw, VIR_FIREWALL_LAYER_IPV4, def->bridge); iptablesRemoveForwardRejectOut(fw, VIR_FIREWALL_LAYER_IPV4, def->bridge); if (ipv4def && ipv4def->tftproot) iptablesRemoveUdpInput(fw, VIR_FIREWALL_LAYER_IPV4, def->bridge, 69); iptablesRemoveUdpInput(fw, VIR_FIREWALL_LAYER_IPV4, def->bridge, 53); iptablesRemoveTcpInput(fw, VIR_FIREWALL_LAYER_IPV4, def->bridge, 53); iptablesRemoveUdpOutput(fw, VIR_FIREWALL_LAYER_IPV4, def->bridge, 68); iptablesRemoveUdpInput(fw, VIR_FIREWALL_LAYER_IPV4, def->bridge, 67); iptablesRemoveTcpInput(fw, VIR_FIREWALL_LAYER_IPV4, def->bridge, 67); } /* Add all once/network rules required for IPv6. * If no IPv6 addresses are defined and is * specified, then allow IPv6 communications between virtual systems. * If any IPv6 addresses are defined, then add the rules for regular operation. */ static void networkAddGeneralIPv6FirewallRules(virFirewallPtr fw, virNetworkDefPtr def) { if (!virNetworkDefGetIpByIndex(def, AF_INET6, 0) && !def->ipv6nogw) { return; } /* Catch all rules to block forwarding to/from bridges */ iptablesAddForwardRejectOut(fw, VIR_FIREWALL_LAYER_IPV6, def->bridge); iptablesAddForwardRejectIn(fw, VIR_FIREWALL_LAYER_IPV6, def->bridge); /* Allow traffic between guests on the same bridge */ iptablesAddForwardAllowCross(fw, VIR_FIREWALL_LAYER_IPV6, def->bridge); if (virNetworkDefGetIpByIndex(def, AF_INET6, 0)) { /* allow DNS over IPv6 */ iptablesAddTcpInput(fw, VIR_FIREWALL_LAYER_IPV6, def->bridge, 53); iptablesAddUdpInput(fw, VIR_FIREWALL_LAYER_IPV6, def->bridge, 53); iptablesAddUdpInput(fw, VIR_FIREWALL_LAYER_IPV6, def->bridge, 547); } } static void networkRemoveGeneralIPv6FirewallRules(virFirewallPtr fw, virNetworkDefPtr def) { if (!virNetworkDefGetIpByIndex(def, AF_INET6, 0) && !def->ipv6nogw) { return; } if (virNetworkDefGetIpByIndex(def, AF_INET6, 0)) { iptablesRemoveUdpInput(fw, VIR_FIREWALL_LAYER_IPV6, def->bridge, 547); iptablesRemoveUdpInput(fw, VIR_FIREWALL_LAYER_IPV6, def->bridge, 53); iptablesRemoveTcpInput(fw, VIR_FIREWALL_LAYER_IPV6, def->bridge, 53); } /* the following rules are there if no IPv6 address has been defined * but def->ipv6nogw == true */ iptablesRemoveForwardAllowCross(fw, VIR_FIREWALL_LAYER_IPV6, def->bridge); iptablesRemoveForwardRejectIn(fw, VIR_FIREWALL_LAYER_IPV6, def->bridge); iptablesRemoveForwardRejectOut(fw, VIR_FIREWALL_LAYER_IPV6, def->bridge); } static void networkAddGeneralFirewallRules(virFirewallPtr fw, virNetworkDefPtr def) { networkAddGeneralIPv4FirewallRules(fw, def); networkAddGeneralIPv6FirewallRules(fw, def); } static void networkRemoveGeneralFirewallRules(virFirewallPtr fw, virNetworkDefPtr def) { networkRemoveGeneralIPv4FirewallRules(fw, def); networkRemoveGeneralIPv6FirewallRules(fw, def); } static void networkAddChecksumFirewallRules(virFirewallPtr fw, virNetworkDefPtr def) { 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(def, AF_INET, i)); i++) { if (ipv4def->nranges || ipv4def->nhosts) break; } /* 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) iptablesAddOutputFixUdpChecksum(fw, def->bridge, 68); } static void networkRemoveChecksumFirewallRules(virFirewallPtr fw, virNetworkDefPtr def) { 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(def, AF_INET, i)); i++) { if (ipv4def->nranges || ipv4def->nhosts) break; } if (ipv4def) iptablesRemoveOutputFixUdpChecksum(fw, def->bridge, 68); } static int networkAddIpSpecificFirewallRules(virFirewallPtr fw, virNetworkDefPtr def, 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 (def->forward.type == VIR_NETWORK_FORWARD_NAT) { if (VIR_SOCKET_ADDR_IS_FAMILY(&ipdef->address, AF_INET)) return networkAddMasqueradingFirewallRules(fw, def, ipdef); else if (VIR_SOCKET_ADDR_IS_FAMILY(&ipdef->address, AF_INET6)) return networkAddRoutingFirewallRules(fw, def, ipdef); } else if (def->forward.type == VIR_NETWORK_FORWARD_ROUTE) { return networkAddRoutingFirewallRules(fw, def, ipdef); } return 0; } static int networkRemoveIpSpecificFirewallRules(virFirewallPtr fw, virNetworkDefPtr def, virNetworkIpDefPtr ipdef) { if (def->forward.type == VIR_NETWORK_FORWARD_NAT) { if (VIR_SOCKET_ADDR_IS_FAMILY(&ipdef->address, AF_INET)) return networkRemoveMasqueradingFirewallRules(fw, def, ipdef); else if (VIR_SOCKET_ADDR_IS_FAMILY(&ipdef->address, AF_INET6)) return networkRemoveRoutingFirewallRules(fw, def, ipdef); } else if (def->forward.type == VIR_NETWORK_FORWARD_ROUTE) { return networkRemoveRoutingFirewallRules(fw, def, ipdef); } return 0; } /* Add all rules for all ip addresses (and general rules) on a network */ int networkAddFirewallRules(virNetworkDefPtr def) { size_t i; virNetworkIpDefPtr ipdef; virFirewallPtr fw = NULL; int ret = -1; fw = virFirewallNew(); virFirewallStartTransaction(fw, 0); networkAddGeneralFirewallRules(fw, def); for (i = 0; (ipdef = virNetworkDefGetIpByIndex(def, AF_UNSPEC, i)); i++) { if (networkAddIpSpecificFirewallRules(fw, def, ipdef) < 0) goto cleanup; } virFirewallStartRollback(fw, 0); for (i = 0; (ipdef = virNetworkDefGetIpByIndex(def, AF_UNSPEC, i)); i++) { if (networkRemoveIpSpecificFirewallRules(fw, def, ipdef) < 0) goto cleanup; } networkRemoveGeneralFirewallRules(fw, def); virFirewallStartTransaction(fw, VIR_FIREWALL_TRANSACTION_IGNORE_ERRORS); networkAddChecksumFirewallRules(fw, def); if (virFirewallApply(fw) < 0) goto cleanup; ret = 0; cleanup: virFirewallFree(fw); return ret; } /* Remove all rules for all ip addresses (and general rules) on a network */ void networkRemoveFirewallRules(virNetworkDefPtr def) { size_t i; virNetworkIpDefPtr ipdef; virFirewallPtr fw = NULL; fw = virFirewallNew(); virFirewallStartTransaction(fw, VIR_FIREWALL_TRANSACTION_IGNORE_ERRORS); networkRemoveChecksumFirewallRules(fw, def); virFirewallStartTransaction(fw, VIR_FIREWALL_TRANSACTION_IGNORE_ERRORS); for (i = 0; (ipdef = virNetworkDefGetIpByIndex(def, AF_UNSPEC, i)); i++) { if (networkRemoveIpSpecificFirewallRules(fw, def, ipdef) < 0) goto cleanup; } networkRemoveGeneralFirewallRules(fw, def); virFirewallApply(fw); cleanup: virFirewallFree(fw); }