netdev: Really set output values to 0 on failure in netdev_get_features().
[sliver-openvswitch.git] / vswitchd / mgmt.c
index f5dcd18..8da640f 100644 (file)
@@ -1,28 +1,16 @@
 /* Copyright (c) 2009 Nicira Networks
- * 
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
  *
- * This program 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 General Public License for more details.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
  *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- *
- * In addition, as a special exception, Nicira Networks gives permission
- * to link the code of its release of vswitchd with the OpenSSL project's
- * "OpenSSL" library (or with modified versions of it that use the same
- * license as the "OpenSSL" library), and distribute the linked
- * executables.  You must obey the GNU General Public License in all
- * respects for all of the code used other than "OpenSSL".  If you modify
- * this file, you may extend this exception to your version of the file,
- * but you are not obligated to do so.  If you do not wish to do so,
- * delete this exception statement from your version.
+ *     http://www.apache.org/licenses/LICENSE-2.0
  *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
 #include <config.h>
@@ -31,6 +19,9 @@
 #include <assert.h>
 #include <errno.h>
 #include <stdlib.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <sys/types.h>
 
 #include "bridge.h"
 #include "cfg.h"
@@ -47,6 +38,7 @@
 #include "svec.h"
 #include "vconn.h"
 #include "vconn-ssl.h"
+#include "xenserver.h"
 #include "xtoxll.h"
 
 #define THIS_MODULE VLM_mgmt
 
 static struct svec mgmt_cfg;
 static uint8_t cfg_cookie[CFG_COOKIE_LEN];
+static bool need_reconfigure = false;
 static struct rconn *mgmt_rconn;
 static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(60, 60);
 static struct svec capabilities;
+static struct ofpbuf ext_data_buffer;
+static uint32_t ext_data_xid = UINT32_MAX;
 uint64_t mgmt_id;
 
 
@@ -69,6 +64,7 @@ struct rconn_packet_counter *txqlen; /* # pkts queued for tx on mgmt_rconn. */
 static uint64_t pick_fallback_mgmt_id(void);
 static void send_config_update(uint32_t xid, bool use_xid);
 static void send_resources_update(uint32_t xid, bool use_xid);
+static int recv_ofmp(uint32_t xid, struct ofmp_header *ofmph, size_t len);
 
 void
 mgmt_init(void)
@@ -85,6 +81,8 @@ mgmt_init(void)
         /* Randomly generate a mgmt id */
         mgmt_id = pick_fallback_mgmt_id();
     }
+
+    ofpbuf_init(&ext_data_buffer, 0);
 }
 
 #ifdef HAVE_OPENSSL
@@ -107,6 +105,7 @@ mgmt_configure_ssl(void)
     static char *private_key_file;
     static char *certificate_file;
     static char *cacert_file;
+    struct stat s;
 
     /* XXX SSL should be configurable separate from the bridges.
      * XXX should be possible to de-configure SSL. */
@@ -118,7 +117,13 @@ mgmt_configure_ssl(void)
         vconn_ssl_set_certificate_file(certificate_file);
     }
 
-    if (config_string_change("ssl.ca-cert", &cacert_file)) {
+    /* We assume that even if the filename hasn't changed, if the CA cert 
+     * file has been removed, that we want to move back into
+     * boot-strapping mode.  This opens a small security hole, because
+     * the old certificate will still be trusted until vSwitch is
+     * restarted.  We may want to address this in vconn's SSL library. */
+    if (config_string_change("ssl.ca-cert", &cacert_file) 
+            || (stat(cacert_file, &s) && errno == ENOENT)) {
         vconn_ssl_set_ca_cert_file(cacert_file,
                 cfg_get_bool(0, "ssl.bootstrap-ca-cert"));
     }
@@ -137,6 +142,7 @@ mgmt_reconfigure(void)
     int retval;
 
     if (!cfg_has_section("mgmt")) {
+        svec_clear(&mgmt_cfg);
         if (mgmt_rconn) {
             rconn_destroy(mgmt_rconn);
             mgmt_rconn = NULL;
@@ -217,40 +223,10 @@ mgmt_reconfigure(void)
     if (retval == EAFNOSUPPORT) {
         VLOG_ERR("no support for %s vconn", controller_name);
     }
-}
-
-static int
-send_openflow_buffer(struct ofpbuf *buffer)
-{               
-    int retval;
-
-    if (!mgmt_rconn) {
-        VLOG_ERR("attempt to send openflow packet with no rconn\n");
-        return EINVAL;
-    }
-
-    update_openflow_length(buffer);
-    retval = rconn_send_with_limit(mgmt_rconn, buffer, txqlen, TXQ_LIMIT);
-    if (retval) {
-        VLOG_WARN_RL(&rl, "send to %s failed: %s",
-                     rconn_get_name(mgmt_rconn), strerror(retval));
-    }   
-    return retval;
-}   
-    
-static void
-send_features_reply(uint32_t xid)
-{
-    struct ofpbuf *buffer;
-    struct ofp_switch_features *ofr;
 
-    ofr = make_openflow_xid(sizeof *ofr, OFPT_FEATURES_REPLY, xid, &buffer);
-    ofr->datapath_id  = 0;
-    ofr->n_tables     = 0;
-    ofr->n_buffers    = 0;
-    ofr->capabilities = 0;
-    ofr->actions      = 0;
-    send_openflow_buffer(buffer);
+    /* Reset the extended message buffer when we create a new
+     * management connection. */
+    ofpbuf_clear(&ext_data_buffer);
 }
 
 static void *
@@ -280,6 +256,96 @@ make_ofmp(size_t ofmp_len, uint16_t type, struct ofpbuf **bufferp)
     return oh;
 }
 
+static int
+send_openflow_buffer(struct ofpbuf *buffer)
+{               
+    int retval;
+
+    if (!mgmt_rconn) {
+        VLOG_ERR("attempt to send openflow packet with no rconn\n");
+        return EINVAL;
+    }
+
+    /* Make sure there's room to transmit the data.  We don't want to
+     * fail part way through a send. */
+    if (rconn_packet_counter_read(txqlen) >= TXQ_LIMIT) {
+        return EAGAIN;
+    }
+
+    /* OpenFlow messages use a 16-bit length field, so messages over 64K
+     * must be broken into multiple pieces. 
+     */
+    if (buffer->size <= 65535) {
+        update_openflow_length(buffer);
+        retval = rconn_send(mgmt_rconn, buffer, txqlen);
+        if (retval) {
+            VLOG_WARN_RL(&rl, "send to %s failed: %s",
+                         rconn_get_name(mgmt_rconn), strerror(retval));
+        }   
+        return retval;
+    } else {
+        struct ofmp_header *header = (struct ofmp_header *)buffer->data;
+        uint32_t xid = header->header.header.xid;
+        size_t remain = buffer->size;
+        uint8_t *ptr = buffer->data;
+        
+        /* Mark the OpenFlow header with a zero length to indicate some
+         * funkiness. 
+         */
+        header->header.header.length = 0;
+
+        while (remain > 0) {
+            struct ofpbuf *new_buffer;
+            struct ofmp_extended_data *oed;
+            size_t new_len = MIN(65535 - sizeof *oed, remain);
+
+            oed = make_ofmp_xid(sizeof *oed, OFMPT_EXTENDED_DATA, xid, 
+                    &new_buffer);
+            oed->type = header->type;
+
+            if (remain > new_len) {
+                oed->flags |= OFMPEDF_MORE_DATA;
+            }
+
+            /* Copy the entire original message, including the OpenFlow
+             * header, since management protocol structure definitions
+             * include these headers.
+             */
+            ofpbuf_put(new_buffer, ptr, new_len);
+
+            update_openflow_length(new_buffer);
+            retval = rconn_send(mgmt_rconn, new_buffer, txqlen);
+            if (retval) {
+                VLOG_WARN_RL(&rl, "send to %s failed: %s",
+                             rconn_get_name(mgmt_rconn), strerror(retval));
+                ofpbuf_delete(buffer);
+                return retval;
+            }   
+
+            remain -= new_len;
+            ptr += new_len;
+        }
+
+        ofpbuf_delete(buffer);
+        return 0;
+    }
+}   
+    
+static void
+send_features_reply(uint32_t xid)
+{
+    struct ofpbuf *buffer;
+    struct ofp_switch_features *ofr;
+
+    ofr = make_openflow_xid(sizeof *ofr, OFPT_FEATURES_REPLY, xid, &buffer);
+    ofr->datapath_id  = 0;
+    ofr->n_tables     = 0;
+    ofr->n_buffers    = 0;
+    ofr->capabilities = 0;
+    ofr->actions      = 0;
+    send_openflow_buffer(buffer);
+}
+
 static void 
 send_capability_reply(uint32_t xid)
 {
@@ -305,6 +371,8 @@ send_resources_update(uint32_t xid, bool use_xid)
     struct ofmp_resources_update *ofmpru;
     struct ofmp_tlv *tlv;
     struct svec br_list;
+    struct svec port_list;
+    const char *host_uuid;
     int i;
 
     if (use_xid) {
@@ -314,11 +382,28 @@ send_resources_update(uint32_t xid, bool use_xid)
         ofmpru = make_ofmp(sizeof *ofmpru, OFMPT_RESOURCES_UPDATE, &buffer);
     }
 
+    /* On XenServer systems, each host has its own UUID, which we provide
+     * to the controller. 
+     */ 
+    host_uuid = xenserver_get_host_uuid();
+    if (host_uuid) {
+        struct ofmptsr_mgmt_uuid *mgmt_uuid_tlv;
+
+        mgmt_uuid_tlv = ofpbuf_put_zeros(buffer, sizeof(*mgmt_uuid_tlv));
+        mgmt_uuid_tlv->type = htons(OFMPTSR_MGMT_UUID);
+        mgmt_uuid_tlv->len = htons(sizeof(*mgmt_uuid_tlv));
+        mgmt_uuid_tlv->mgmt_id = htonll(mgmt_id);
+        memcpy(mgmt_uuid_tlv->uuid, host_uuid, OFMP_UUID_LEN);
+    }
+
     svec_init(&br_list);
     cfg_get_subsections(&br_list, "bridge");
     for (i=0; i < br_list.n; i++) {
         struct ofmptsr_dp *dp_tlv;
-        uint64_t dp_id = bridge_get_datapathid(br_list.names[i]);
+        uint64_t dp_id;
+        int n_uuid;
+
+        dp_id = bridge_get_datapathid(br_list.names[i]);
         if (!dp_id) {
             VLOG_WARN_RL(&rl, "bridge %s doesn't seem to exist", 
                     br_list.names[i]);
@@ -330,7 +415,81 @@ send_resources_update(uint32_t xid, bool use_xid)
 
         dp_tlv->dp_id = htonll(dp_id);
         memcpy(dp_tlv->name, br_list.names[i], strlen(br_list.names[i])+1);
+
+        /* On XenServer systems, each network has one or more UUIDs
+         * associated with it, which we provide to the controller. 
+         */
+        n_uuid = cfg_count("bridge.%s.xs-network-uuids", br_list.names[i]);
+        if (n_uuid) {
+            struct ofmptsr_dp_uuid *dp_uuid_tlv;
+            size_t tlv_len = sizeof(*dp_uuid_tlv) + n_uuid * OFMP_UUID_LEN;
+            int j;
+
+            dp_uuid_tlv = ofpbuf_put_zeros(buffer, sizeof(*dp_uuid_tlv));
+            dp_uuid_tlv->type = htons(OFMPTSR_DP_UUID);
+            dp_uuid_tlv->len = htons(tlv_len);
+            dp_uuid_tlv->dp_id = htonll(dp_id);
+
+            for (j=0; j<n_uuid; j++) {
+                const char *dp_uuid = cfg_get_string(j, 
+                        "bridge.%s.xs-network-uuids", br_list.names[i]);
+
+                /* The UUID list could change underneath us, so just
+                 * fill with zeros in that case.  Another update will be
+                 * initiated shortly, which should contain corrected data.
+                 */
+                if (dp_uuid) {
+                    ofpbuf_put(buffer, dp_uuid, OFMP_UUID_LEN);
+                } else {
+                    ofpbuf_put_zeros(buffer, OFMP_UUID_LEN);
+                }
+            }
+        }
     }
+    svec_destroy(&br_list);
+
+    /* On XenServer systems, extended information about virtual interfaces 
+     * (VIFs) is available, which is needed by the controller. 
+     */ 
+    svec_init(&port_list);
+    bridge_get_ifaces(&port_list);
+    for (i=0; i < port_list.n; i++) {
+        const char *vif_uuid, *vm_uuid, *net_uuid;
+        uint64_t vif_mac;
+        struct ofmptsr_vif *vif_tlv;
+
+        vif_uuid = cfg_get_string(0, "port.%s.vif-uuid", port_list.names[i]);
+        if (!vif_uuid) {
+            continue;
+        }
+
+        vif_tlv = ofpbuf_put_zeros(buffer, sizeof(*vif_tlv));
+        vif_tlv->type = htons(OFMPTSR_VIF);
+        vif_tlv->len = htons(sizeof(*vif_tlv));
+
+        memcpy(vif_tlv->name, port_list.names[i], strlen(port_list.names[i])+1);
+        memcpy(vif_tlv->vif_uuid, vif_uuid, sizeof(vif_tlv->vif_uuid));
+
+        vm_uuid = cfg_get_string(0, "port.%s.vm-uuid", port_list.names[i]);
+        if (vm_uuid) {
+            memcpy(vif_tlv->vm_uuid, vm_uuid, sizeof(vif_tlv->vm_uuid));
+        } else {
+            /* In case the vif disappeared underneath us. */
+            memset(vif_tlv->vm_uuid, '\0', sizeof(vif_tlv->vm_uuid));
+        }
+
+        net_uuid = cfg_get_string(0, "port.%s.net-uuid", port_list.names[i]);
+        if (net_uuid) {
+            memcpy(vif_tlv->net_uuid, net_uuid, sizeof(vif_tlv->net_uuid));
+        } else {
+            /* In case the vif disappeared underneath us. */
+            memset(vif_tlv->net_uuid, '\0', sizeof(vif_tlv->net_uuid));
+        }
+
+        vif_mac = cfg_get_mac(0, "port.%s.vif-mac", port_list.names[i]);
+        vif_tlv->vif_mac = htonll(vif_mac);
+    }
+    svec_destroy(&port_list);
 
     /* Put end marker. */
     tlv = ofpbuf_put_zeros(buffer, sizeof(*tlv));
@@ -375,20 +534,6 @@ send_config_update_ack(uint32_t xid, bool success)
     send_openflow_buffer(buffer);
 }
 
-static void
-send_ofmp_error_msg(uint32_t xid, uint16_t type, uint16_t code, 
-            const void *data, size_t len)
-{
-    struct ofpbuf *buffer;
-    struct ofmp_error_msg *oem;
-
-    oem = make_ofmp_xid(sizeof(*oem)+len, OFMPT_ERROR, xid, &buffer);
-    oem->type = htons(type);
-    oem->code = htons(code);
-    memcpy(oem->data, data, len);
-    send_openflow_buffer(buffer);
-}
-
 static void
 send_error_msg(uint32_t xid, uint16_t type, uint16_t code, 
             const void *data, size_t len)
@@ -426,11 +571,12 @@ recv_set_config(uint32_t xid UNUSED, const void *msg UNUSED)
 }
 
 static int
-recv_ofmp_capability_request(uint32_t xid, const struct ofmp_header *ofmph)
+recv_ofmp_capability_request(uint32_t xid, const struct ofmp_header *ofmph,
+        size_t len)
 {
     struct ofmp_capability_request *ofmpcr;
 
-    if (htons(ofmph->header.header.length) != sizeof(*ofmpcr)) {
+    if (len != sizeof(*ofmpcr)) {
         /* xxx Send error */
         return -EINVAL;
     }
@@ -447,18 +593,20 @@ recv_ofmp_capability_request(uint32_t xid, const struct ofmp_header *ofmph)
 }
 
 static int
-recv_ofmp_resources_request(uint32_t xid, const void *msg UNUSED)
+recv_ofmp_resources_request(uint32_t xid, const void *msg UNUSED, 
+        size_t len UNUSED)
 {
     send_resources_update(xid, true);
     return 0;
 }
 
 static int
-recv_ofmp_config_request(uint32_t xid, const struct ofmp_header *ofmph)
+recv_ofmp_config_request(uint32_t xid, const struct ofmp_header *ofmph, 
+        size_t len)
 {
     struct ofmp_config_request *ofmpcr;
 
-    if (htons(ofmph->header.header.length) != sizeof(*ofmpcr)) {
+    if (len != sizeof(*ofmpcr)) {
         /* xxx Send error */
         return -EINVAL;
     }
@@ -475,12 +623,13 @@ recv_ofmp_config_request(uint32_t xid, const struct ofmp_header *ofmph)
 }
 
 static int
-recv_ofmp_config_update(uint32_t xid, const struct ofmp_header *ofmph)
+recv_ofmp_config_update(uint32_t xid, const struct ofmp_header *ofmph,
+        size_t len)
 {
     struct ofmp_config_update *ofmpcu;
     int data_len;
 
-    data_len = htons(ofmph->header.header.length) - sizeof(*ofmpcu);
+    data_len = len - sizeof(*ofmpcu);
     if (data_len <= sizeof(*ofmpcu)) {
         /* xxx Send error. */
         return -EINVAL;
@@ -499,6 +648,14 @@ recv_ofmp_config_update(uint32_t xid, const struct ofmp_header *ofmph)
         /* xxx cfg_lock can fail for other reasons, such as being
          * xxx locked... */
         VLOG_WARN_RL(&rl, "config update failed due to bad cookie\n");
+
+        /* Check if our local view matches the controller, in which
+         * case, it is likely that there were local modifications
+         * without our being told to reread the config file. */
+        if (!memcmp(cfg_cookie, ofmpcu->cookie, sizeof cfg_cookie)) {
+            VLOG_WARN_RL(&rl, "config appears to have been locally modified "
+                              "without having told ovs-vswitchd to reload");
+        }
         send_config_update_ack(xid, false);
         return 0;
     }
@@ -512,26 +669,96 @@ recv_ofmp_config_update(uint32_t xid, const struct ofmp_header *ofmph)
      * connection settings may have changed. */
     send_config_update_ack(xid, true);
 
-    reconfigure();
+    need_reconfigure = true;
+
+    return 0;
+}
+
+static int
+recv_ofmp_extended_data(uint32_t xid, const struct ofmp_header *ofmph,
+        size_t len)
+{
+    int data_len;
+    struct ofmp_extended_data *ofmped;
+
+    if (len <= sizeof(*ofmped)) {
+        /* xxx Send error. */
+        return -EINVAL;
+    }
+
+    ext_data_xid = xid;
+    ofmped = (struct ofmp_extended_data *)ofmph;
+
+    data_len = len - sizeof(*ofmped);
+    ofpbuf_put(&ext_data_buffer, ofmped->data, data_len);
 
+    if (!(ofmped->flags & OFMPEDF_MORE_DATA)) {
+        struct ofmp_header *new_oh;
+        int error;
+
+        /* An embedded message must be greater than the size of an
+         * OpenFlow message. */
+        new_oh = ofpbuf_at(&ext_data_buffer, 0, 65536);
+        if (!new_oh) {
+            VLOG_WARN_RL(&rl, "received short embedded message: %zu\n",
+                    ext_data_buffer.size);
+            return -EINVAL;
+        }
+
+        /* Make sure that this is a management message and that there's
+         * not an embedded extended data message. */
+        if ((new_oh->header.vendor != htonl(NX_VENDOR_ID))
+                || (new_oh->header.subtype != htonl(NXT_MGMT))
+                || (new_oh->type == htonl(OFMPT_EXTENDED_DATA))) {
+            VLOG_WARN_RL(&rl, "received bad embedded message\n");
+            return -EINVAL;
+        }
+        new_oh->header.header.xid = ext_data_xid;
+        new_oh->header.header.length = 0;
+
+        error = recv_ofmp(xid, ext_data_buffer.data, ext_data_buffer.size);
+        ofpbuf_clear(&ext_data_buffer);
+
+        return error;
+    }
 
     return 0;
 }
 
+/* Handles receiving a management message.  Generally, this function
+ * will be called 'len' set to zero, and the length will be derived by
+ * the OpenFlow header.  With the extended data message, management
+ * messages are not constrained by OpenFlow's 64K message length limit.  
+ * The extended data handler calls this function with the 'len' set to
+ * the total message length and the OpenFlow header's length field is 
+ * ignored.
+ */
 static
-int recv_ofmp(uint32_t xid, struct ofmp_header *ofmph)
+int recv_ofmp(uint32_t xid, struct ofmp_header *ofmph, size_t len)
 {
+    if (!len) {
+        len = ntohs(ofmph->header.header.length);
+    }
+
+    /* Reset the extended data buffer if this isn't a continuation of an 
+     * existing extended data message. */
+    if (ext_data_xid != xid) {
+        ofpbuf_clear(&ext_data_buffer);
+    }
+
     /* xxx Should sanity-check for min/max length */
     switch (ntohs(ofmph->type)) 
     {
         case OFMPT_CAPABILITY_REQUEST:
-            return recv_ofmp_capability_request(xid, ofmph);
+            return recv_ofmp_capability_request(xid, ofmph, len);
         case OFMPT_RESOURCES_REQUEST:
-            return recv_ofmp_resources_request(xid, ofmph);
+            return recv_ofmp_resources_request(xid, ofmph, len);
         case OFMPT_CONFIG_REQUEST:
-            return recv_ofmp_config_request(xid, ofmph);
+            return recv_ofmp_config_request(xid, ofmph, len);
         case OFMPT_CONFIG_UPDATE:
-            return recv_ofmp_config_update(xid, ofmph);
+            return recv_ofmp_config_update(xid, ofmph, len);
+        case OFMPT_EXTENDED_DATA:
+            return recv_ofmp_extended_data(xid, ofmph, len);
         default:
             VLOG_WARN_RL(&rl, "unknown mgmt message: %d", 
                     ntohs(ofmph->type));
@@ -547,11 +774,11 @@ recv_nx_msg(uint32_t xid, const void *oh)
     switch (ntohl(nh->subtype)) {
 
     case NXT_MGMT:
-        return recv_ofmp(xid, (struct ofmp_header *)oh);
+        return recv_ofmp(xid, (struct ofmp_header *)oh, 0);
 
     default:
         send_error_msg(xid, OFPET_BAD_REQUEST, OFPBRC_BAD_SUBTYPE, 
-                oh, htons(nh->header.length));
+                oh, ntohs(nh->header.length));
         return -EINVAL;
     }
 }
@@ -624,15 +851,16 @@ handle_msg(uint32_t xid, const void *msg, size_t length)
     return handler(xid, msg);
 }
 
-void 
+bool 
 mgmt_run(void)
 {
     int i;
 
     if (!mgmt_rconn) {
-        return;
+        return false;
     }
 
+    need_reconfigure = false;
     rconn_run(mgmt_rconn);
 
     /* Do some processing, but cap it at a reasonable amount so that
@@ -654,6 +882,8 @@ mgmt_run(void)
             VLOG_WARN_RL(&rl, "received too-short OpenFlow message");
         }
     }
+
+    return need_reconfigure;
 }
 
 void