Merge citrix branch into master.
[sliver-openvswitch.git] / vswitchd / bridge.c
index c41c132..7081512 100644 (file)
@@ -131,6 +131,7 @@ struct port {
     tag_type active_iface_tag;  /* Tag for bcast flows. */
     tag_type no_ifaces_tag;     /* Tag for flows when all ifaces disabled. */
     int updelay, downdelay;     /* Delay before iface goes up/down, in ms. */
+    bool bond_compat_is_stale;  /* Need to call port_update_bond_compat()? */
 
     /* Port mirroring info. */
     mirror_mask_t src_mirrors;  /* Mirrors triggered when packet received. */
@@ -193,6 +194,7 @@ enum { DP_MAX = 256 };
 static struct bridge *bridge_create(const char *name);
 static void bridge_destroy(struct bridge *);
 static struct bridge *bridge_lookup(const char *name);
+static void bridge_unixctl_dump_flows(struct unixctl_conn *, const char *);
 static int bridge_run_one(struct bridge *);
 static void bridge_reconfigure_one(struct bridge *);
 static void bridge_reconfigure_controller(struct bridge *);
@@ -201,10 +203,11 @@ static void bridge_fetch_dp_ifaces(struct bridge *);
 static void bridge_flush(struct bridge *);
 static void bridge_pick_local_hw_addr(struct bridge *,
                                       uint8_t ea[ETH_ADDR_LEN],
-                                      const char **devname);
+                                      struct iface **hw_addr_iface);
 static uint64_t bridge_pick_datapath_id(struct bridge *,
                                         const uint8_t bridge_ea[ETH_ADDR_LEN],
-                                        const char *devname);
+                                        struct iface *hw_addr_iface);
+static struct iface *bridge_get_local_iface(struct bridge *);
 static uint64_t dpid_from_hash(const void *, size_t nbytes);
 
 static void bridge_unixctl_fdb_show(struct unixctl_conn *, const char *args);
@@ -284,6 +287,7 @@ bridge_init(void)
 
     unixctl_command_register("fdb/show", bridge_unixctl_fdb_show);
 
+    svec_init(&dpif_names);
     dp_enumerate(&dpif_names);
     for (i = 0; i < dpif_names.n; i++) {
         const char *dpif_name = dpif_names.names[i];
@@ -309,6 +313,8 @@ bridge_init(void)
         }
     }
 
+    unixctl_command_register("bridge/dump-flows", bridge_unixctl_dump_flows);
+
     bond_init();
     bridge_reconfigure();
 }
@@ -379,15 +385,9 @@ init_iface_netdev(struct bridge *br UNUSED, struct iface *iface,
 }
 
 static bool
-check_iface_dp_ifidx(struct bridge *br, struct iface *iface,
-                     void *local_ifacep_)
+check_iface_dp_ifidx(struct bridge *br, struct iface *iface, void *aux UNUSED)
 {
-    struct iface **local_ifacep = local_ifacep_;
-
     if (iface->dp_ifidx >= 0) {
-        if (iface->dp_ifidx == ODPP_LOCAL) {
-            *local_ifacep = iface;
-        }
         VLOG_DBG("%s has interface %s on port %d",
                  dpif_name(br->dpif),
                  iface->name, iface->dp_ifidx);
@@ -399,6 +399,16 @@ check_iface_dp_ifidx(struct bridge *br, struct iface *iface,
     }
 }
 
+static bool
+set_iface_policing(struct bridge *br UNUSED, struct iface *iface,
+                   void *aux UNUSED)
+{
+    int rate = cfg_get_int(0, "port.%s.ingress.policing-rate", iface->name);
+    int burst = cfg_get_int(0, "port.%s.ingress.policing-burst", iface->name);
+    netdev_set_policing(iface->netdev, rate, burst);
+    return true;
+}
+
 /* Calls 'cb' for each interfaces in 'br', passing along the 'aux' argument.
  * Deletes from 'br' all the interfaces for which 'cb' returns false, and then
  * deletes from 'br' any ports that no longer have any interfaces. */
@@ -520,10 +530,24 @@ bridge_reconfigure(void)
 
         for (i = 0; i < add_ifaces.n; i++) {
             const char *if_name = add_ifaces.names[i];
-            int internal = cfg_get_bool(0, "iface.%s.internal", if_name);
-            int flags = internal ? ODP_PORT_INTERNAL : 0;
-            int error = dpif_port_add(br->dpif, if_name, flags, NULL);
-            if (error == EXFULL) {
+            bool internal;
+            int error;
+
+            /* It's an internal interface if it's marked that way, or if
+             * it's a bonded interface for which we're faking up a network
+             * device. */
+            internal = cfg_get_bool(0, "iface.%s.internal", if_name);
+            if (cfg_get_bool(0, "bonding.%s.fake-iface", if_name)) {
+                struct port *port = port_lookup(br, if_name);
+                if (port && port->n_ifaces > 1) {
+                    internal = true;
+                }
+            }
+
+            /* Add to datapath. */
+            error = dpif_port_add(br->dpif, if_name,
+                                  internal ? ODP_PORT_INTERNAL : 0, NULL);
+            if (error == EFBIG) {
                 VLOG_ERR("ran out of valid port numbers on %s",
                          dpif_name(br->dpif));
                 break;
@@ -539,8 +563,8 @@ bridge_reconfigure(void)
     LIST_FOR_EACH (br, struct bridge, node, &all_bridges) {
         uint8_t ea[8];
         uint64_t dpid;
-        struct iface *local_iface = NULL;
-        const char *devname;
+        struct iface *local_iface;
+        struct iface *hw_addr_iface;
         uint8_t engine_type, engine_id;
         bool add_id_to_iface = false;
         struct svec nf_hosts;
@@ -548,13 +572,13 @@ bridge_reconfigure(void)
         bridge_fetch_dp_ifaces(br);
         iterate_and_prune_ifaces(br, init_iface_netdev, NULL);
 
-        local_iface = NULL;
-        iterate_and_prune_ifaces(br, check_iface_dp_ifidx, &local_iface);
+        iterate_and_prune_ifaces(br, check_iface_dp_ifidx, NULL);
 
         /* Pick local port hardware address, datapath ID. */
-        bridge_pick_local_hw_addr(br, ea, &devname);
+        bridge_pick_local_hw_addr(br, ea, &hw_addr_iface);
+        local_iface = bridge_get_local_iface(br);
         if (local_iface) {
-            int error = netdev_nodev_set_etheraddr(local_iface->name, ea);
+            int error = netdev_set_etheraddr(local_iface->netdev, ea);
             if (error) {
                 static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
                 VLOG_ERR_RL(&rl, "bridge %s: failed to set bridge "
@@ -563,7 +587,7 @@ bridge_reconfigure(void)
             }
         }
 
-        dpid = bridge_pick_datapath_id(br, ea, devname);
+        dpid = bridge_pick_datapath_id(br, ea, hw_addr_iface);
         ofproto_set_datapath_id(br->ofproto, dpid);
 
         /* Set NetFlow configuration on this bridge. */
@@ -617,18 +641,19 @@ bridge_reconfigure(void)
     }
     LIST_FOR_EACH (br, struct bridge, node, &all_bridges) {
         brstp_reconfigure(br);
+        iterate_and_prune_ifaces(br, set_iface_policing, NULL);
     }
 }
 
 static void
 bridge_pick_local_hw_addr(struct bridge *br, uint8_t ea[ETH_ADDR_LEN],
-                          const char **devname)
+                          struct iface **hw_addr_iface)
 {
     uint64_t requested_ea;
     size_t i, j;
     int error;
 
-    *devname = NULL;
+    *hw_addr_iface = NULL;
 
     /* Did the user request a particular MAC? */
     requested_ea = cfg_get_mac(0, "bridge.%s.mac", br->name);
@@ -650,35 +675,79 @@ bridge_pick_local_hw_addr(struct bridge *br, uint8_t ea[ETH_ADDR_LEN],
     memset(ea, 0xff, sizeof ea);
     for (i = 0; i < br->n_ports; i++) {
         struct port *port = br->ports[i];
+        uint8_t iface_ea[ETH_ADDR_LEN];
+        uint64_t iface_ea_u64;
+        struct iface *iface;
+
+        /* Mirror output ports don't participate. */
         if (port->is_mirror_output_port) {
             continue;
         }
-        for (j = 0; j < port->n_ifaces; j++) {
-            struct iface *iface = port->ifaces[j];
-            uint8_t iface_ea[ETH_ADDR_LEN];
+
+        /* Choose the MAC address to represent the port. */
+        iface_ea_u64 = cfg_get_mac(0, "port.%s.mac", port->name);
+        if (iface_ea_u64) {
+            /* User specified explicitly. */
+            eth_addr_from_uint64(iface_ea_u64, iface_ea);
+
+            /* Find the interface with this Ethernet address (if any) so that
+             * we can provide the correct devname to the caller. */
+            iface = NULL;
+            for (j = 0; j < port->n_ifaces; j++) {
+                struct iface *candidate = port->ifaces[j];
+                uint8_t candidate_ea[ETH_ADDR_LEN];
+                if (!netdev_get_etheraddr(candidate->netdev, candidate_ea)
+                    && eth_addr_equals(iface_ea, candidate_ea)) {
+                    iface = candidate;
+                }
+            }
+        } else {
+            /* Choose the interface whose MAC address will represent the port.
+             * The Linux kernel bonding code always chooses the MAC address of
+             * the first slave added to a bond, and the Fedora networking
+             * scripts always add slaves to a bond in alphabetical order, so
+             * for compatibility we choose the interface with the name that is
+             * first in alphabetical order. */
+            iface = port->ifaces[0];
+            for (j = 1; j < port->n_ifaces; j++) {
+                struct iface *candidate = port->ifaces[j];
+                if (strcmp(candidate->name, iface->name) < 0) {
+                    iface = candidate;
+                }
+            }
+
+            /* The local port doesn't count (since we're trying to choose its
+             * MAC address anyway).  Other internal ports don't count because
+             * we really want a physical MAC if we can get it, and internal
+             * ports typically have randomly generated MACs. */
             if (iface->dp_ifidx == ODPP_LOCAL
                 || cfg_get_bool(0, "iface.%s.internal", iface->name)) {
                 continue;
             }
-            error = netdev_nodev_get_etheraddr(iface->name, iface_ea);
-            if (!error) {
-                if (!eth_addr_is_multicast(iface_ea) &&
-                    !eth_addr_is_reserved(iface_ea) &&
-                    !eth_addr_is_zero(iface_ea) &&
-                    memcmp(iface_ea, ea, ETH_ADDR_LEN) < 0) {
-                    memcpy(ea, iface_ea, ETH_ADDR_LEN);
-                    *devname = iface->name;
-                }
-            } else {
+
+            /* Grab MAC. */
+            error = netdev_get_etheraddr(iface->netdev, iface_ea);
+            if (error) {
                 static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
                 VLOG_ERR_RL(&rl, "failed to obtain Ethernet address of %s: %s",
                             iface->name, strerror(error));
+                continue;
             }
         }
+
+        /* Compare against our current choice. */
+        if (!eth_addr_is_multicast(iface_ea) &&
+            !eth_addr_is_reserved(iface_ea) &&
+            !eth_addr_is_zero(iface_ea) &&
+            memcmp(iface_ea, ea, ETH_ADDR_LEN) < 0)
+        {
+            memcpy(ea, iface_ea, ETH_ADDR_LEN);
+            *hw_addr_iface = iface;
+        }
     }
     if (eth_addr_is_multicast(ea) || eth_addr_is_vif(ea)) {
         memcpy(ea, br->default_ea, ETH_ADDR_LEN);
-        *devname = NULL;
+        *hw_addr_iface = NULL;
         VLOG_WARN("bridge %s: using default bridge Ethernet "
                   "address "ETH_ADDR_FMT, br->name, ETH_ADDR_ARGS(ea));
     } else {
@@ -689,13 +758,13 @@ bridge_pick_local_hw_addr(struct bridge *br, uint8_t ea[ETH_ADDR_LEN],
 
 /* Choose and returns the datapath ID for bridge 'br' given that the bridge
  * Ethernet address is 'bridge_ea'.  If 'bridge_ea' is the Ethernet address of
- * a network device, then that network device's name must be passed in as
- * 'devname'; if 'bridge_ea' was derived some other way, then 'devname' must be
- * passed in as a null pointer. */
+ * an interface on 'br', then that interface must be passed in as
+ * 'hw_addr_iface'; if 'bridge_ea' was derived some other way, then
+ * 'hw_addr_iface' must be passed in as a null pointer. */
 static uint64_t
 bridge_pick_datapath_id(struct bridge *br,
                         const uint8_t bridge_ea[ETH_ADDR_LEN],
-                        const char *devname)
+                        struct iface *hw_addr_iface)
 {
     /*
      * The procedure for choosing a bridge MAC address will, in the most
@@ -716,9 +785,9 @@ bridge_pick_datapath_id(struct bridge *br,
         return dpid;
     }
 
-    if (devname) {
+    if (hw_addr_iface) {
         int vlan;
-        if (!netdev_get_vlan_vid(devname, &vlan)) {
+        if (!netdev_get_vlan_vid(hw_addr_iface->netdev, &vlan)) {
             /*
              * A bridge whose MAC address is taken from a VLAN network device
              * (that is, a network device created with vconfig(8) or similar
@@ -828,6 +897,26 @@ bridge_flush(struct bridge *br)
         mac_learning_flush(br->ml);
     }
 }
+
+/* Returns the 'br' interface for the ODPP_LOCAL port, or null if 'br' has no
+ * such interface. */
+static struct iface *
+bridge_get_local_iface(struct bridge *br)
+{
+    size_t i, j;
+
+    for (i = 0; i < br->n_ports; i++) {
+        struct port *port = br->ports[i];
+        for (j = 0; j < port->n_ifaces; j++) {
+            struct iface *iface = port->ifaces[j];
+            if (iface->dp_ifidx == ODPP_LOCAL) {
+                return iface;
+            }
+        }
+    }
+
+    return NULL;
+}
 \f
 /* Bridge unixctl user interface functions. */
 static void
@@ -963,6 +1052,27 @@ bridge_get_datapathid(const char *name)
     return br ? ofproto_get_datapath_id(br->ofproto) : 0;
 }
 
+/* Handle requests for a listing of all flows known by the OpenFlow
+ * stack, including those normally hidden. */
+static void
+bridge_unixctl_dump_flows(struct unixctl_conn *conn, const char *args)
+{
+    struct bridge *br;
+    struct ds results;
+    
+    br = bridge_lookup(args);
+    if (!br) {
+        unixctl_command_reply(conn, 501, "Unknown bridge");
+        return;
+    }
+
+    ds_init(&results);
+    ofproto_get_all_flows(br->ofproto, &results);
+
+    unixctl_command_reply(conn, 200, ds_cstr(&results));
+    ds_destroy(&results);
+}
+
 static int
 bridge_run_one(struct bridge *br)
 {
@@ -1158,10 +1268,8 @@ bridge_reconfigure_controller(struct bridge *br)
                                   cfg_get_string(0, "%s.accept-regex", pfx),
                                   update_resolv_conf);
         } else {
-            char local_name[IF_NAMESIZE];
-            struct netdev *netdev;
+            struct iface *local_iface;
             bool in_band;
-            int error;
 
             in_band = (!cfg_is_valid(CFG_BOOL | CFG_REQUIRED,
                                      "%s.in-band", pfx)
@@ -1169,37 +1277,32 @@ bridge_reconfigure_controller(struct bridge *br)
             ofproto_set_discovery(br->ofproto, false, NULL, NULL);
             ofproto_set_in_band(br->ofproto, in_band);
 
-            error = dpif_port_get_name(br->dpif, ODPP_LOCAL,
-                                       local_name, sizeof local_name);
-            if (!error) {
-                error = netdev_open(local_name, NETDEV_ETH_TYPE_NONE, &netdev);
-            }
-            if (!error) {
-                if (cfg_is_valid(CFG_IP | CFG_REQUIRED, "%s.ip", pfx)) {
-                    struct in_addr ip, mask, gateway;
-                    ip.s_addr = cfg_get_ip(0, "%s.ip", pfx);
-                    mask.s_addr = cfg_get_ip(0, "%s.netmask", pfx);
-                    gateway.s_addr = cfg_get_ip(0, "%s.gateway", pfx);
-
-                    netdev_turn_flags_on(netdev, NETDEV_UP, true);
-                    if (!mask.s_addr) {
-                        mask.s_addr = guess_netmask(ip.s_addr);
-                    }
-                    if (!netdev_set_in4(netdev, ip, mask)) {
-                        VLOG_INFO("bridge %s: configured IP address "IP_FMT", "
-                                  "netmask "IP_FMT,
-                                  br->name, IP_ARGS(&ip.s_addr),
-                                  IP_ARGS(&mask.s_addr));
-                    }
+            local_iface = bridge_get_local_iface(br);
+            if (local_iface
+                && cfg_is_valid(CFG_IP | CFG_REQUIRED, "%s.ip", pfx)) {
+                struct netdev *netdev = local_iface->netdev;
+                struct in_addr ip, mask, gateway;
+                ip.s_addr = cfg_get_ip(0, "%s.ip", pfx);
+                mask.s_addr = cfg_get_ip(0, "%s.netmask", pfx);
+                gateway.s_addr = cfg_get_ip(0, "%s.gateway", pfx);
+
+                netdev_turn_flags_on(netdev, NETDEV_UP, true);
+                if (!mask.s_addr) {
+                    mask.s_addr = guess_netmask(ip.s_addr);
+                }
+                if (!netdev_set_in4(netdev, ip, mask)) {
+                    VLOG_INFO("bridge %s: configured IP address "IP_FMT", "
+                              "netmask "IP_FMT,
+                              br->name, IP_ARGS(&ip.s_addr),
+                              IP_ARGS(&mask.s_addr));
+                }
 
-                    if (gateway.s_addr) {
-                        if (!netdev_add_router(netdev, gateway)) {
-                            VLOG_INFO("bridge %s: configured gateway "IP_FMT,
-                                      br->name, IP_ARGS(&gateway.s_addr));
-                        }
+                if (gateway.s_addr) {
+                    if (!netdev_add_router(netdev, gateway)) {
+                        VLOG_INFO("bridge %s: configured gateway "IP_FMT,
+                                  br->name, IP_ARGS(&gateway.s_addr));
                     }
                 }
-                netdev_close(netdev);
             }
         }
 
@@ -1216,7 +1319,7 @@ bridge_reconfigure_controller(struct bridge *br)
         if (probe < 5) {
             probe = cfg_get_int(0, "mgmt.inactivity-probe");
             if (probe < 5) {
-                probe = 15;
+                probe = 5;
             }
         }
         ofproto_set_probe_interval(br->ofproto, probe);
@@ -1225,7 +1328,7 @@ bridge_reconfigure_controller(struct bridge *br)
         if (!max_backoff) {
             max_backoff = cfg_get_int(0, "mgmt.max-backoff");
             if (!max_backoff) {
-                max_backoff = 15;
+                max_backoff = 8;
             }
         }
         ofproto_set_max_backoff(br->ofproto, max_backoff);
@@ -1296,9 +1399,12 @@ bridge_get_all_ifaces(const struct bridge *br, struct svec *ifaces)
             struct iface *iface = port->ifaces[j];
             svec_add(ifaces, iface->name);
         }
+        if (port->n_ifaces > 1
+            && cfg_get_bool(0, "bonding.%s.fake-iface", port->name)) {
+            svec_add(ifaces, port->name);
+        }
     }
-    svec_sort(ifaces);
-    assert(svec_is_unique(ifaces));
+    svec_sort_unique(ifaces);
 }
 
 /* For robustness, in case the administrator moves around datapath ports behind
@@ -1393,6 +1499,7 @@ choose_output_iface(const struct port *port, const uint8_t *dl_src,
                 return false;
             }
             e->iface_tag = tag_create_random();
+            ((struct port *) port)->bond_compat_is_stale = true;
         }
         *tags |= e->iface_tag;
         iface = port->ifaces[e->iface_idx];
@@ -1491,6 +1598,12 @@ bond_run(struct bridge *br)
 
     for (i = 0; i < br->n_ports; i++) {
         struct port *port = br->ports[i];
+
+        if (port->bond_compat_is_stale) {
+            port->bond_compat_is_stale = false;
+            port_update_bond_compat(port);
+        }
+
         if (port->n_ifaces < 2) {
             continue;
         }
@@ -1661,12 +1774,14 @@ compose_dsts(const struct bridge *br, const flow_t *flow, uint16_t vlan,
                 for (i = 0; i < br->n_ports; i++) {
                     struct port *port = br->ports[i];
                     if (port_includes_vlan(port, m->out_vlan)
-                        && set_dst(dst, flow, in_port, port, tags)
-                        && !dst_is_duplicate(dsts, dst - dsts, dst))
+                        && set_dst(dst, flow, in_port, port, tags))
                     {
                         if (port->vlan < 0) {
                             dst->vlan = m->out_vlan;
                         }
+                        if (dst_is_duplicate(dsts, dst - dsts, dst)) {
+                            continue;
+                        }
                         if (dst->dp_ifidx == flow->in_port
                             && dst->vlan == vlan) {
                             /* Don't send out input port on same VLAN. */
@@ -2272,6 +2387,7 @@ bond_rebalance_port(struct port *port)
             } else {
                 from++;
             }
+            port->bond_compat_is_stale = true;
         }
     }
 
@@ -2538,6 +2654,7 @@ bond_unixctl_migrate(struct unixctl_conn *conn, const char *args_)
     ofproto_revalidate(port->bridge->ofproto, entry->iface_tag);
     entry->iface_idx = iface->port_ifidx;
     entry->iface_tag = tag_create_random();
+    port->bond_compat_is_stale = true;
     unixctl_command_reply(conn, 200, "migrated");
 }
 
@@ -2802,6 +2919,7 @@ port_destroy(struct port *port)
         size_t i;
 
         proc_net_compat_update_vlan(port->name, NULL, 0);
+        proc_net_compat_update_bond(port->name, NULL);
 
         for (i = 0; i < MAX_MIRRORS; i++) {
             struct mirror *m = br->mirrors[i];
@@ -2868,7 +2986,7 @@ port_update_bonding(struct port *port)
         if (port->bond_hash) {
             free(port->bond_hash);
             port->bond_hash = NULL;
-            proc_net_compat_update_bond(port->name, NULL);
+            port->bond_compat_is_stale = true;
         }
     } else {
         if (!port->bond_hash) {
@@ -2883,23 +3001,39 @@ port_update_bonding(struct port *port)
             port->no_ifaces_tag = tag_create_random();
             bond_choose_active_iface(port);
         }
-        port_update_bond_compat(port);
+        port->bond_compat_is_stale = true;
     }
 }
 
 static void
 port_update_bond_compat(struct port *port)
 {
+    struct compat_bond_hash compat_hashes[BOND_MASK + 1];
     struct compat_bond bond;
     size_t i;
 
     if (port->n_ifaces < 2) {
+        proc_net_compat_update_bond(port->name, NULL);
         return;
     }
 
     bond.up = false;
     bond.updelay = port->updelay;
     bond.downdelay = port->downdelay;
+
+    bond.n_hashes = 0;
+    bond.hashes = compat_hashes;
+    if (port->bond_hash) {
+        const struct bond_entry *e;
+        for (e = port->bond_hash; e <= &port->bond_hash[BOND_MASK]; e++) {
+            if (e->iface_idx >= 0 && e->iface_idx < port->n_ifaces) {
+                struct compat_bond_hash *cbh = &bond.hashes[bond.n_hashes++];
+                cbh->hash = e - port->bond_hash;
+                cbh->netdev_name = port->ifaces[e->iface_idx]->name;
+            }
+        }
+    }
+
     bond.n_slaves = port->n_ifaces;
     bond.slaves = xmalloc(port->n_ifaces * sizeof *bond.slaves);
     for (i = 0; i < port->n_ifaces; i++) {
@@ -2913,6 +3047,7 @@ port_update_bond_compat(struct port *port)
         }
         netdev_get_etheraddr(iface->netdev, slave->mac);
     }
+
     proc_net_compat_update_bond(port->name, &bond);
     free(bond.slaves);
 }
@@ -3236,6 +3371,7 @@ mirror_reconfigure_one(struct mirror *m)
     int *vlans;
     size_t i;
     bool mirror_all_ports;
+    bool any_ports_specified;
 
     /* Get output port. */
     out_port_name = cfg_get_key(0, "mirror.%s.%s.output.port",
@@ -3274,11 +3410,18 @@ mirror_reconfigure_one(struct mirror *m)
     cfg_get_all_keys(&src_ports, "%s.select.src-port", pfx);
     cfg_get_all_keys(&dst_ports, "%s.select.dst-port", pfx);
     cfg_get_all_keys(&ports, "%s.select.port", pfx);
+    any_ports_specified = src_ports.n || dst_ports.n || ports.n;
     svec_append(&src_ports, &ports);
     svec_append(&dst_ports, &ports);
     svec_destroy(&ports);
     prune_ports(m, &src_ports);
     prune_ports(m, &dst_ports);
+    if (any_ports_specified && !src_ports.n && !dst_ports.n) {
+        VLOG_ERR("%s: none of the specified ports exist; "
+                 "disabling port mirror %s", pfx, pfx);
+        mirror_destroy(m);
+        goto exit;
+    }
 
     /* Get all the vlans, and drop duplicate and invalid vlans. */
     svec_init(&vlan_strings);
@@ -3330,6 +3473,7 @@ mirror_reconfigure_one(struct mirror *m)
     }
 
     /* Clean up. */
+exit:
     svec_destroy(&src_ports);
     svec_destroy(&dst_ports);
     free(pfx);