Make in-band communication work.
authorBen Pfaff <blp@nicira.com>
Wed, 21 May 2008 21:31:33 +0000 (14:31 -0700)
committerBen Pfaff <blp@nicira.com>
Wed, 28 May 2008 17:46:54 +0000 (10:46 -0700)
INSTALL
include/rconn.h
lib/rconn.c
secchan/secchan.c

diff --git a/INSTALL b/INSTALL
index 0e488cf..5aa65e8 100644 (file)
--- a/INSTALL
+++ b/INSTALL
@@ -124,11 +124,11 @@ switch are running on the same machine.  This is an easy configuration
 for testing, but a more conventional setup would run a controller on
 one machine and one or more switches on different machines.  To do so,
 simply specify the IP address of the controller as the first argument
-to the switch program (in place of 127.0.0.1).  (Note: The current
-version of the switch and controller requires that they be connected
-through a "control network" that is physically separate from the one
-that they are controlling.  Future releases will support in-band
-control communication.)
+to the switch program (in place of 127.0.0.1). (Note: The userspace
+switch must be connected to the controller over a "control network"
+that is physically separate from the one that the switch and
+controller are controlling.  The kernel-based switch does not have
+this limitation.)
 
 Secure operation over SSL
 -------------------------
@@ -381,11 +381,6 @@ following instructions to set up remote switches:
 1. Start the datapath and attach it to two or more physical ports as
    described in the previous section.
 
-   Note: The current version of the switch and controller requires
-   that they be connected through a "control network" that is
-   physically separate from the one that they are controlling.  Future
-   releases will support in-band control communication.
-
 2. Run the controller in passive tcp mode on the host which will act as
    the controller. In the example below, the controller will bind to
    port 975 (the default) awaiting connections from secure channels. 
@@ -396,7 +391,38 @@ following instructions to set up remote switches:
    
    Make sure the machine hosting the controller is reachable by the switch.  
 
-3. Run secchan on the datapath host to start the secure channel
+3. Arrange so that the switch can reach the controller over the
+   network.  There are two ways to do this:
+
+      - Use a "control network" that is completely separate from the
+        "data network" to be controlled.  To do so, configure a
+        network device (one that has not been added to the datapath
+        with "dpctl addif") to access the control network in the usual
+        way.
+
+      - Use the same network for control and for data.  For this
+        purpose, each datapath nl:K has a corresponding virtual
+        network device named ofK.  Start by bringing up of0 before you
+        start the secure channel:
+
+           # ifconfig of0 up
+
+        Before the secure channel starts up, the of0 device cannot
+        send or receive any packets, so the next step depends on
+        whether connectivity is required to configure the device's IP
+        address:
+
+           . If the switch has a static IP address, you may configure
+             its IP address now, e.g.:
+
+                # ifconfig of0 192.168.1.1
+
+           . If the switch does not have a static IP address, e.g. its
+             IP address is obtained dynamically via DHCP, then proceed
+             to step 4.  The DHCP client will not be able to contact
+             the DHCP server until the secure channel has started up.
+
+4. Run secchan on the datapath host to start the secure channel
    connecting the datapath to a remote controller.  (See secchan(8)
    for usage details).  The channel should be configured to connect to
    the controller's IP address on the port configured in step 2.
@@ -407,6 +433,17 @@ following instructions to set up remote switches:
 
       # secchan -v nl:0 tcp:192.168.1.2
 
+   If you are using separate control and data networks, or if the
+   networks are combined and the switch has a static IP address, the
+   secure channel should quickly connect to the controller.  Setup is
+   now complete.  Otherwise, proceed to step 5.
+
+5. If you are using the same network for control and data, and the
+   switch obtains its IP address dynamically, then you may now obtain
+   the switch's IP address, e.g. by invoking a DHCP client.  The
+   secure channel will only be able to connect to the controller after
+   an IP address has been obtained.
+
 Bug Reporting
 -------------
 
index 3e72da0..14889bd 100644 (file)
@@ -62,9 +62,11 @@ void rconn_run_wait(struct rconn *);
 struct buffer *rconn_recv(struct rconn *);
 void rconn_recv_wait(struct rconn *);
 int rconn_send(struct rconn *, struct buffer *);
+int rconn_force_send(struct rconn *, struct buffer *);
 bool rconn_is_full(const struct rconn *);
 
 const char *rconn_get_name(const struct rconn *);
 bool rconn_is_alive(const struct rconn *);
+bool rconn_is_connected(const struct rconn *);
 
 #endif /* rconn.h */
index 7fd4405..4bfd5a4 100644 (file)
@@ -186,14 +186,13 @@ rconn_recv_wait(struct rconn *rc)
     }
 }
 
-/* There is no rconn_send_wait() function: an rconn has a send queue that it
- * takes care of sending if you call rconn_wait(), which will have the side
- * effect of waking up poll_block(). */
+/* Sends 'b' on 'rc'.  Returns 0 if successful, EAGAIN if at least 'txq_limit'
+ * packets are already queued, otherwise a positive errno value. */
 int
-rconn_send(struct rconn *rc, struct buffer *b) 
+do_send(struct rconn *rc, struct buffer *b, int txq_limit)
 {
     if (rc->vconn) {
-        if (rc->txq.n < rc->txq_limit) {
+        if (rc->txq.n < txq_limit) {
             queue_push_tail(&rc->txq, b);
             if (rc->txq.n == 1) {
                 try_send(rc);
@@ -207,6 +206,29 @@ rconn_send(struct rconn *rc, struct buffer *b)
     }
 }
 
+/* Sends 'b' on 'rc'.  Returns 0 if successful, EAGAIN if the send queue is
+ * full, otherwise a positive errno value.
+ *
+ * There is no rconn_send_wait() function: an rconn has a send queue that it
+ * takes care of sending if you call rconn_wait(), which will have the side
+ * effect of waking up poll_block(). */
+int
+rconn_send(struct rconn *rc, struct buffer *b)
+{
+    return do_send(rc, b, rc->txq_limit);
+}
+
+/* Sends 'b' on 'rc'.  Returns 0 if successful, EAGAIN if the send queue is
+ * full, otherwise a positive errno value.
+ *
+ * Compared to rconn_send(), this function relaxes the queue limit, allowing
+ * more packets than usual to be queued. */
+int
+rconn_force_send(struct rconn *rc, struct buffer *b)
+{
+    return do_send(rc, b, 2 * rc->txq_limit);
+}
+
 /* Returns true if 'rc''s send buffer is full,
  * false if it has room for at least one more packet. */
 bool
@@ -229,6 +251,13 @@ rconn_is_alive(const struct rconn *rconn)
 {
     return rconn->reliable || rconn->vconn;
 }
+
+/* Returns true if 'rconn' is connected, false otherwise. */
+bool
+rconn_is_connected(const struct rconn *rconn)
+{
+    return rconn->vconn && !vconn_connect(rconn->vconn);
+}
 \f
 static struct rconn *
 create_rconn(const char *name, int txq_limit, struct vconn *vconn)
index 25bd794..6dd6302 100644 (file)
@@ -33,6 +33,8 @@
 
 #include <errno.h>
 #include <getopt.h>
+#include <inttypes.h>
+#include <netinet/in.h>
 #include <poll.h>
 #include <stdlib.h>
 #include <string.h>
 #include "command-line.h"
 #include "compiler.h"
 #include "fault.h"
+#include "flow.h"
 #include "list.h"
-#include "util.h"
-#include "rconn.h"
-#include "vconn-ssl.h"
-#include "vlog-socket.h"
+#include "mac-learning.h"
+#include "netdev.h"
 #include "openflow.h"
+#include "packets.h"
 #include "poll-loop.h"
+#include "rconn.h"
+#include "util.h"
+#include "vconn-ssl.h"
 #include "vconn.h"
+#include "vlog-socket.h"
 
 #include "vlog.h"
 #define THIS_MODULE VLM_secchan
 
-static void parse_options(int argc, char *argv[]);
-static void usage(void) NO_RETURN;
+#include "ofp-print.h"
 
 static const char *listen_vconn_name;
 
@@ -67,22 +72,41 @@ struct half {
 
 struct relay {
     struct list node;
+
+#define HALF_LOCAL 0
+#define HALF_REMOTE 1
     struct half halves[2];
 };
 
 static struct list relays = LIST_INITIALIZER(&relays);
 
+/* Enable the local port? */
+static int local_port;
+
+/* MAC address of local port. */
+static uint8_t local_mac[ETH_ADDR_LEN];
+
+/* MAC learning table for local port. */
+static struct mac_learning *local_ml;
+
+static void parse_options(int argc, char *argv[]);
+static void usage(void) NO_RETURN;
+
 static void new_management_connection(const char *nl_name, struct vconn *new_remote);
-static void relay_create(struct rconn *, struct rconn *);
+static void relay_create(struct rconn *local, struct rconn *remote);
 static void relay_run(struct relay *);
 static void relay_wait(struct relay *);
 static void relay_destroy(struct relay *);
 
+static bool local_hook(struct relay *r);
+
 int
 main(int argc, char *argv[])
 {
     struct vconn *listen_vconn;
+    struct netdev *of_device;
     const char *nl_name;
+    char of_name[16];
     int retval;
 
     set_program_name(argv[0]);
@@ -113,6 +137,33 @@ main(int argc, char *argv[])
         listen_vconn = NULL;
     }
 
+
+    snprintf(of_name, sizeof of_name, "of%s", nl_name + 3);
+    retval = netdev_open(of_name, &of_device);
+    if (!retval) {
+        enum netdev_flags flags;
+        retval = netdev_get_flags(of_device, &flags);
+        if (!retval) {
+            if (flags & NETDEV_UP) {
+                struct in6_addr in6;
+
+                local_port = true;
+                memcpy(local_mac, netdev_get_etheraddr(of_device),
+                       ETH_ADDR_LEN);
+                if (netdev_get_in6(of_device, &in6)) {
+                    VLOG_WARN("Ignoring IPv6 address on %s device: "
+                              "IPv6 not supported", of_name);
+                }
+                local_ml = mac_learning_create();
+            }
+        } else {
+            error(retval, "Could not get flags for %s device", of_name);
+        }
+        netdev_close(of_device);
+    } else {
+        error(retval, "Could not open %s device", of_name);
+    }
+
     retval = vlog_server_listen(NULL, NULL);
     if (retval) {
         fatal(retval, "Could not listen for vlog connections");
@@ -184,14 +235,15 @@ new_management_connection(const char *nl_name, struct vconn *new_remote)
 }
 
 static void
-relay_create(struct rconn *a, struct rconn *b)
+relay_create(struct rconn *local, struct rconn *remote)
 {
     struct relay *r;
     int i;
 
     r = xmalloc(sizeof *r);
+    r->halves[HALF_LOCAL].rconn = local;
+    r->halves[HALF_REMOTE].rconn = remote;
     for (i = 0; i < 2; i++) {
-        r->halves[i].rconn = i ? b : a;
         r->halves[i].rxbuf = NULL;
     }
     list_push_back(&relays, &r->node);
@@ -216,6 +268,10 @@ relay_run(struct relay *r)
 
             if (!this->rxbuf) {
                 this->rxbuf = rconn_recv(this->rconn);
+                if (this->rxbuf && i == HALF_LOCAL && local_hook(r)) {
+                    buffer_delete(this->rxbuf);
+                    this->rxbuf = NULL;
+                }
             }
 
             if (this->rxbuf) {
@@ -273,6 +329,74 @@ relay_destroy(struct relay *r)
     free(r);
 }
 
+static bool
+local_hook(struct relay *r)
+{
+    struct rconn *rc = r->halves[HALF_LOCAL].rconn;
+    struct buffer *msg = r->halves[HALF_LOCAL].rxbuf;
+    struct ofp_packet_in *opi;
+    struct ofp_header *oh;
+    size_t pkt_ofs, pkt_len;
+    struct buffer pkt, *b;
+    struct flow flow;
+    uint16_t in_port, out_port;
+
+    if (!local_port) {
+        return false;
+    }
+
+    oh = msg->data;
+    if (oh->type != OFPT_PACKET_IN) {
+        return false;
+    }
+    if (msg->size < offsetof (struct ofp_packet_in, data)) {
+        VLOG_WARN("packet too short (%zu bytes) for packet_in", msg->size);
+        return false;
+    }
+
+    /* Extract flow data from 'opi' into 'flow'. */
+    opi = msg->data;
+    in_port = ntohs(opi->in_port);
+    pkt_ofs = offsetof(struct ofp_packet_in, data);
+    pkt_len = ntohs(opi->header.length) - pkt_ofs;
+    pkt.data = opi->data;
+    pkt.size = pkt_len;
+    flow_extract(&pkt, in_port, &flow);
+
+    /* Deal with local stuff. */
+    if (!rconn_is_connected(r->halves[HALF_REMOTE].rconn)
+        && eth_addr_is_broadcast(flow.dl_dst)) {
+        out_port = OFPP_FLOOD;
+    } else if (in_port == OFPP_LOCAL) {
+        out_port = mac_learning_lookup(local_ml, flow.dl_dst);
+    } else if (eth_addr_equals(flow.dl_dst, local_mac)) {
+        out_port = OFPP_LOCAL;
+        if (mac_learning_learn(local_ml, flow.dl_src, in_port)) {
+            VLOG_DBG("learned that "ETH_ADDR_FMT" is on port %"PRIu16,
+                     ETH_ADDR_ARGS(flow.dl_src), in_port);
+        }
+    } else {
+        return false;
+    }
+
+    /* Add new flow. */
+    if (out_port != OFPP_FLOOD) {
+        b = make_add_simple_flow(&flow, ntohl(opi->buffer_id), out_port);
+        if (rconn_force_send(rc, b)) {
+            buffer_delete(b);
+        }
+    }
+
+    /* If the switch didn't buffer the packet, we need to send a copy. */
+    if (out_port == OFPP_FLOOD || ntohl(opi->buffer_id) == UINT32_MAX) {
+        b = make_unbuffered_packet_out(&pkt, in_port, out_port);
+        if (rconn_force_send(rc, b)) {
+            buffer_delete(b);
+        }
+    }
+    return true;
+}
+
 static void
 parse_options(int argc, char *argv[]) 
 {