Implement new OpenFlow "switch statistics" feature.
authorBen Pfaff <blp@nicira.com>
Tue, 26 Aug 2008 18:53:51 +0000 (11:53 -0700)
committerBen Pfaff <blp@nicira.com>
Tue, 26 Aug 2008 18:53:51 +0000 (11:53 -0700)
This should be useful for reporting the status of a switch on its
front-panel display (for those switches that have one) or in the
controller UI.

include/openflow.h
lib/ofp-print.c
secchan/secchan.c
utilities/dpctl.8
utilities/dpctl.c

index de23072..7456a6e 100644 (file)
@@ -467,7 +467,15 @@ enum ofp_stats_types {
     /* Physical port statistics.
      * The request body is empty.
      * The reply body is an array of struct ofp_port_stats. */
     /* Physical port statistics.
      * The request body is empty.
      * The reply body is an array of struct ofp_port_stats. */
-    OFPST_PORT
+    OFPST_PORT,
+
+    /* Switch status.
+     * The request body is an ASCII string that specifies a prefix of the key
+     * names to include in the output; if it is the null string, then all
+     * key-value pairs are included.
+     * The reply body is an ASCII string of key-value pairs in the form
+     * "key=value\n". */
+    OFPST_SWITCH
 };
 
 struct ofp_stats_request {
 };
 
 struct ofp_stats_request {
index 73b5df1..d758680 100644 (file)
@@ -815,6 +815,22 @@ ofp_table_stats_reply(struct ds *string, const void *body, size_t len,
      }
 }
 
      }
 }
 
+static void
+switch_status_reply(struct ds *string, const void *body, size_t len,
+                    int verbosity UNUSED)
+{
+    char *save_ptr = NULL;
+    char *s, *line;
+
+    s = xmemdup0(body, len);
+    for (line = strtok_r(s, "\n\n", &save_ptr); line != NULL;
+         line = strtok_r(NULL, "\n\n", &save_ptr)) {
+        ds_put_printable(string, line, strlen(line));
+        ds_put_char(string, '\n');
+    }
+    free(s);
+}
+
 enum stats_direction {
     REQUEST,
     REPLY
 enum stats_direction {
     REQUEST,
     REPLY
@@ -867,6 +883,11 @@ print_stats(struct ds *string, int type, const void *body, size_t body_len,
             { 0, 0, NULL, },
             { 0, SIZE_MAX, ofp_port_stats_reply },
         },
             { 0, 0, NULL, },
             { 0, SIZE_MAX, ofp_port_stats_reply },
         },
+        [OFPST_SWITCH] = {
+            "switch status",
+            { 0, 0, NULL, },
+            { 0, SIZE_MAX, switch_status_reply },
+        },
     };
 
     const struct stats_type *s;
     };
 
     const struct stats_type *s;
index 3cb1006..f3396ff 100644 (file)
@@ -143,19 +143,39 @@ static struct hook make_hook(bool (*packet_cb)(struct relay *, int, void *),
                              void (*wait_cb)(void *),
                              void *aux);
 
                              void (*wait_cb)(void *),
                              void *aux);
 
-static struct discovery *discovery_init(const struct settings *);
+struct switch_status;
+struct status_reply;
+static struct hook switch_status_hook_create(const struct settings *,
+                                             struct switch_status **);
+static void switch_status_register_category(struct switch_status *,
+                                            const char *category,
+                                            void (*cb)(struct status_reply *,
+                                                       void *aux),
+                                            void *aux);
+static void status_reply_put(struct status_reply *, const char *, ...)
+    PRINTF_FORMAT(2, 3);
+
+static void rconn_status_cb(struct status_reply *, void *rconn_);
+
+static struct discovery *discovery_init(const struct settings *,
+                                        struct switch_status *);
 static void discovery_question_connectivity(struct discovery *);
 static bool discovery_run(struct discovery *, char **controller_name);
 static void discovery_wait(struct discovery *);
 
 static void discovery_question_connectivity(struct discovery *);
 static bool discovery_run(struct discovery *, char **controller_name);
 static void discovery_wait(struct discovery *);
 
-static struct hook in_band_hook_create(const struct settings *);
+static struct hook in_band_hook_create(const struct settings *,
+                                       struct switch_status *,
+                                       struct rconn *remote);
 static struct hook fail_open_hook_create(const struct settings *,
 static struct hook fail_open_hook_create(const struct settings *,
+                                         struct switch_status *,
                                          struct rconn *local,
                                          struct rconn *remote);
 static struct hook rate_limit_hook_create(const struct settings *,
                                          struct rconn *local,
                                          struct rconn *remote);
 static struct hook rate_limit_hook_create(const struct settings *,
+                                          struct switch_status *,
                                           struct rconn *local,
                                           struct rconn *remote);
 
                                           struct rconn *local,
                                           struct rconn *remote);
 
+
 static void modify_dhcp_request(struct dhcp_msg *, void *aux);
 static bool validate_dhcp_offer(const struct dhcp_msg *, void *aux);
 
 static void modify_dhcp_request(struct dhcp_msg *, void *aux);
 static bool validate_dhcp_offer(const struct dhcp_msg *, void *aux);
 
@@ -166,13 +186,14 @@ main(int argc, char *argv[])
 
     struct list relays = LIST_INITIALIZER(&relays);
 
 
     struct list relays = LIST_INITIALIZER(&relays);
 
-    struct hook hooks[4];
-    size_t n_hooks;
+    struct hook hooks[8];
+    size_t n_hooks = 0;
 
     struct rconn *local_rconn, *remote_rconn;
     struct vconn *listen_vconn;
     struct relay *controller_relay;
     struct discovery *discovery;
 
     struct rconn *local_rconn, *remote_rconn;
     struct vconn *listen_vconn;
     struct relay *controller_relay;
     struct discovery *discovery;
+    struct switch_status *switch_status;
     int retval;
 
     set_program_name(argv[0]);
     int retval;
 
     set_program_name(argv[0]);
@@ -195,8 +216,11 @@ main(int argc, char *argv[])
         listen_vconn = NULL;
     }
 
         listen_vconn = NULL;
     }
 
+    /* Initialize switch status hook. */
+    hooks[n_hooks++] = switch_status_hook_create(&s, &switch_status);
+
     /* Start controller discovery. */
     /* Start controller discovery. */
-    discovery = s.discovery ? discovery_init(&s) : NULL;
+    discovery = s.discovery ? discovery_init(&s, switch_status) : NULL;
 
     /* Start listening for vlogconf requests. */
     retval = vlog_server_listen(NULL, NULL);
 
     /* Start listening for vlogconf requests. */
     retval = vlog_server_listen(NULL, NULL);
@@ -212,6 +236,8 @@ main(int argc, char *argv[])
     /* Connect to datapath. */
     local_rconn = rconn_create(0, s.max_backoff);
     rconn_connect(local_rconn, s.nl_name);
     /* Connect to datapath. */
     local_rconn = rconn_create(0, s.max_backoff);
     rconn_connect(local_rconn, s.nl_name);
+    switch_status_register_category(switch_status, "local",
+                                    rconn_status_cb, local_rconn);
 
     /* Connect to controller. */
     remote_rconn = rconn_create(s.probe_interval, s.max_backoff);
 
     /* Connect to controller. */
     remote_rconn = rconn_create(s.probe_interval, s.max_backoff);
@@ -221,22 +247,24 @@ main(int argc, char *argv[])
             fatal(0, "No support for %s vconn", s.controller_name);
         }
     }
             fatal(0, "No support for %s vconn", s.controller_name);
         }
     }
+    switch_status_register_category(switch_status, "remote",
+                                    rconn_status_cb, remote_rconn);
 
     /* Start relaying. */
     controller_relay = relay_create(local_rconn, remote_rconn, false);
     list_push_back(&relays, &controller_relay->node);
 
     /* Set up hooks. */
 
     /* Start relaying. */
     controller_relay = relay_create(local_rconn, remote_rconn, false);
     list_push_back(&relays, &controller_relay->node);
 
     /* Set up hooks. */
-    n_hooks = 0;
     if (s.in_band) {
     if (s.in_band) {
-        hooks[n_hooks++] = in_band_hook_create(&s);
+        hooks[n_hooks++] = in_band_hook_create(&s, switch_status,
+                                               remote_rconn);
     }
     if (s.fail_mode == FAIL_OPEN) {
     }
     if (s.fail_mode == FAIL_OPEN) {
-        hooks[n_hooks++] = fail_open_hook_create(&s,
+        hooks[n_hooks++] = fail_open_hook_create(&s, switch_status,
                                                  local_rconn, remote_rconn);
     }
     if (s.rate_limit) {
                                                  local_rconn, remote_rconn);
     }
     if (s.rate_limit) {
-        hooks[n_hooks++] = rate_limit_hook_create(&s,
+        hooks[n_hooks++] = rate_limit_hook_create(&s, switch_status,
                                                   local_rconn, remote_rconn);
     }
     assert(n_hooks <= ARRAY_SIZE(hooks));
                                                   local_rconn, remote_rconn);
     }
     assert(n_hooks <= ARRAY_SIZE(hooks));
@@ -463,6 +491,7 @@ struct in_band_data {
     const struct settings *s;
     struct mac_learning *ml;
     struct netdev *of_device;
     const struct settings *s;
     struct mac_learning *ml;
     struct netdev *of_device;
+    struct rconn *controller;
     uint8_t mac[ETH_ADDR_LEN];
     int n_queued;
 };
     uint8_t mac[ETH_ADDR_LEN];
     int n_queued;
 };
@@ -474,7 +503,7 @@ queue_tx(struct rconn *rc, struct in_band_data *in_band, struct buffer *b)
 }
 
 static const uint8_t *
 }
 
 static const uint8_t *
-get_controller_mac(struct netdev *netdev, struct rconn *controller)
+get_controller_mac(struct in_band_data *in_band)
 {
     static uint32_t ip, last_nonzero_ip;
     static uint8_t mac[ETH_ADDR_LEN], last_nonzero_mac[ETH_ADDR_LEN];
 {
     static uint32_t ip, last_nonzero_ip;
     static uint8_t mac[ETH_ADDR_LEN], last_nonzero_mac[ETH_ADDR_LEN];
@@ -484,14 +513,14 @@ get_controller_mac(struct netdev *netdev, struct rconn *controller)
 
     time_t now = time_now();
 
 
     time_t now = time_now();
 
-    ip = rconn_get_ip(controller);
+    ip = rconn_get_ip(in_band->controller);
     if (last_ip != ip || !next_refresh || now >= next_refresh) {
         bool have_mac;
 
         /* Look up MAC address. */
         memset(mac, 0, sizeof mac);
         if (ip) {
     if (last_ip != ip || !next_refresh || now >= next_refresh) {
         bool have_mac;
 
         /* Look up MAC address. */
         memset(mac, 0, sizeof mac);
         if (ip) {
-            int retval = netdev_arp_lookup(netdev, ip, mac);
+            int retval = netdev_arp_lookup(in_band->of_device, ip, mac);
             if (retval) {
                 VLOG_DBG("cannot look up controller hw address ("IP_FMT"): %s",
                          IP_ARGS(&ip), strerror(retval));
             if (retval) {
                 VLOG_DBG("cannot look up controller hw address ("IP_FMT"): %s",
                          IP_ARGS(&ip), strerror(retval));
@@ -523,10 +552,11 @@ get_controller_mac(struct netdev *netdev, struct rconn *controller)
 }
 
 static bool
 }
 
 static bool
-is_controller_mac(const uint8_t mac[ETH_ADDR_LEN],
-                  const uint8_t *controller_mac)
+is_controller_mac(const uint8_t dl_addr[ETH_ADDR_LEN],
+                  struct in_band_data *in_band)
 {
 {
-    return controller_mac && eth_addr_equals(mac, controller_mac);
+    const uint8_t *mac = get_controller_mac(in_band);
+    return mac && eth_addr_equals(mac, dl_addr);
 }
 
 static bool
 }
 
 static bool
@@ -551,7 +581,7 @@ in_band_packet_cb(struct relay *r, int half, void *in_band_)
     if (oh->type != OFPT_PACKET_IN) {
         return false;
     }
     if (oh->type != OFPT_PACKET_IN) {
         return false;
     }
-    if (msg->size < offsetof (struct ofp_packet_in, data)) {
+    if (msg->size < offsetof(struct ofp_packet_in, data)) {
         VLOG_WARN("packet too short (%zu bytes) for packet_in", msg->size);
         return false;
     }
         VLOG_WARN("packet too short (%zu bytes) for packet_in", msg->size);
         return false;
     }
@@ -566,8 +596,7 @@ in_band_packet_cb(struct relay *r, int half, void *in_band_)
     flow_extract(&pkt, in_port, &flow);
 
     /* Deal with local stuff. */
     flow_extract(&pkt, in_port, &flow);
 
     /* Deal with local stuff. */
-    controller_mac = get_controller_mac(in_band->of_device,
-                                        r->halves[HALF_REMOTE].rconn);
+    controller_mac = get_controller_mac(in_band);
     if (in_port == OFPP_LOCAL) {
         /* Sent by secure channel. */
         out_port = mac_learning_lookup(in_band->ml, flow.dl_dst);
     if (in_port == OFPP_LOCAL) {
         /* Sent by secure channel. */
         out_port = mac_learning_lookup(in_band->ml, flow.dl_dst);
@@ -580,10 +609,10 @@ in_band_packet_cb(struct relay *r, int half, void *in_band_)
         }
     } else if (flow.dl_type == htons(ETH_TYPE_ARP)
                && eth_addr_is_broadcast(flow.dl_dst)
         }
     } else if (flow.dl_type == htons(ETH_TYPE_ARP)
                && eth_addr_is_broadcast(flow.dl_dst)
-               && is_controller_mac(flow.dl_src, controller_mac)) {
+               && is_controller_mac(flow.dl_src, in_band)) {
         /* ARP sent by controller. */
         out_port = OFPP_FLOOD;
         /* ARP sent by controller. */
         out_port = OFPP_FLOOD;
-    } else if (is_controller_mac(flow.dl_dst, controller_mac)
+    } else if (is_controller_mac(flow.dl_dst, in_band)
                && in_port == mac_learning_lookup(in_band->ml,
                                                  controller_mac)) {
         /* Drop controller traffic that arrives on the controller port. */
                && in_port == mac_learning_lookup(in_band->ml,
                                                  controller_mac)) {
         /* Drop controller traffic that arrives on the controller port. */
@@ -620,8 +649,35 @@ in_band_packet_cb(struct relay *r, int half, void *in_band_)
     return true;
 }
 
     return true;
 }
 
+static void
+in_band_status_cb(struct status_reply *sr, void *in_band_)
+{
+    struct in_band_data *in_band = in_band_;
+    struct in_addr local_ip;
+    uint32_t controller_ip;
+    const uint8_t *controller_mac;
+
+    if (netdev_get_in4(in_band->of_device, &local_ip)) {
+        status_reply_put(sr, "local-ip="IP_FMT, IP_ARGS(&local_ip.s_addr));
+    }
+    status_reply_put(sr, "local-mac="ETH_ADDR_FMT,
+                     ETH_ADDR_ARGS(in_band->mac));
+
+    controller_ip = rconn_get_ip(in_band->controller);
+    if (controller_ip) {
+        status_reply_put(sr, "controller-ip="IP_FMT,
+                      IP_ARGS(&controller_ip));
+    }
+    controller_mac = get_controller_mac(in_band);
+    if (controller_mac) {
+        status_reply_put(sr, "controller-mac="ETH_ADDR_FMT,
+                      ETH_ADDR_ARGS(controller_mac));
+    }
+}
+
 static struct hook
 static struct hook
-in_band_hook_create(const struct settings *s)
+in_band_hook_create(const struct settings *s, struct switch_status *ss,
+                    struct rconn *remote)
 {
     struct in_band_data *in_band;
     int retval;
 {
     struct in_band_data *in_band;
     int retval;
@@ -636,7 +692,8 @@ in_band_hook_create(const struct settings *s)
     }
     memcpy(in_band->mac, netdev_get_etheraddr(in_band->of_device),
            ETH_ADDR_LEN);
     }
     memcpy(in_band->mac, netdev_get_etheraddr(in_band->of_device),
            ETH_ADDR_LEN);
-
+    in_band->controller = remote;
+    switch_status_register_category(ss, "in-band", in_band_status_cb, in_band);
     return make_hook(in_band_packet_cb, NULL, NULL, in_band);
 }
 \f
     return make_hook(in_band_packet_cb, NULL, NULL, in_band);
 }
 \f
@@ -693,15 +750,32 @@ fail_open_packet_cb(struct relay *r, int half, void *fail_open_)
     }
 }
 
     }
 }
 
+static void
+fail_open_status_cb(struct status_reply *sr, void *fail_open_)
+{
+    struct fail_open_data *fail_open = fail_open_;
+    const struct settings *s = fail_open->s;
+    int trigger_duration = s->probe_interval * 3;
+    int cur_duration = rconn_disconnected_duration(fail_open->remote_rconn);
+
+    status_reply_put(sr, "trigger-duration=%d", trigger_duration);
+    status_reply_put(sr, "current-duration=%d", cur_duration);
+    status_reply_put(sr, "triggered=%s",
+                     cur_duration >= trigger_duration ? "true" : "false");
+    status_reply_put(sr, "max-idle=%d", s->max_idle);
+}
+
 static struct hook
 static struct hook
-fail_open_hook_create(const struct settings *s, struct rconn *local_rconn,
-                      struct rconn *remote_rconn)
+fail_open_hook_create(const struct settings *s, struct switch_status *ss,
+                      struct rconn *local_rconn, struct rconn *remote_rconn)
 {
     struct fail_open_data *fail_open = xmalloc(sizeof *fail_open);
     fail_open->s = s;
     fail_open->local_rconn = local_rconn;
     fail_open->remote_rconn = remote_rconn;
     fail_open->lswitch = NULL;
 {
     struct fail_open_data *fail_open = xmalloc(sizeof *fail_open);
     fail_open->s = s;
     fail_open->local_rconn = local_rconn;
     fail_open->remote_rconn = remote_rconn;
     fail_open->lswitch = NULL;
+    switch_status_register_category(ss, "fail-open",
+                                    fail_open_status_cb, fail_open);
     return make_hook(fail_open_packet_cb, fail_open_periodic_cb, NULL,
                      fail_open);
 }
     return make_hook(fail_open_packet_cb, fail_open_periodic_cb, NULL,
                      fail_open);
 }
@@ -726,6 +800,12 @@ struct rate_limiter {
 
     /* Transmission queue. */
     int n_txq;                  /* No. of packets waiting in rconn for tx. */
 
     /* Transmission queue. */
     int n_txq;                  /* No. of packets waiting in rconn for tx. */
+
+    /* Statistics reporting. */
+    unsigned long long n_normal;        /* # txed w/o rate limit queuing. */
+    unsigned long long n_limited;       /* # queued for rate limiting. */
+    unsigned long long n_queue_dropped; /* # dropped due to queue overflow. */
+    unsigned long long n_tx_dropped;    /* # dropped due to tx overflow. */
 };
 
 /* Drop a packet from the longest queue in 'rl'. */
 };
 
 /* Drop a packet from the longest queue in 'rl'. */
@@ -827,6 +907,7 @@ rate_limit_packet_cb(struct relay *r, int half, void *rl_)
     if (!rl->n_queued && get_token(rl)) {
         /* In the common case where we are not constrained by the rate limit,
          * let the packet take the normal path. */
     if (!rl->n_queued && get_token(rl)) {
         /* In the common case where we are not constrained by the rate limit,
          * let the packet take the normal path. */
+        rl->n_normal++;
         return false;
     } else {
         /* Otherwise queue it up for the periodic callback to drain out. */
         return false;
     } else {
         /* Otherwise queue it up for the periodic callback to drain out. */
@@ -837,10 +918,22 @@ rate_limit_packet_cb(struct relay *r, int half, void *rl_)
         }
         queue_push_tail(&rl->queues[port], buffer_clone(msg));
         rl->n_queued++;
         }
         queue_push_tail(&rl->queues[port], buffer_clone(msg));
         rl->n_queued++;
+        rl->n_limited++;
         return true;
     }
 }
 
         return true;
     }
 }
 
+static void
+rate_limit_status_cb(struct status_reply *sr, void *rl_)
+{
+    struct rate_limiter *rl = rl_;
+
+    status_reply_put(sr, "normal=%llu", rl->n_normal);
+    status_reply_put(sr, "limited=%llu", rl->n_limited);
+    status_reply_put(sr, "queue-dropped=%llu", rl->n_queue_dropped);
+    status_reply_put(sr, "tx-dropped=%llu", rl->n_tx_dropped);
+}
+
 static void
 rate_limit_periodic_cb(void *rl_)
 {
 static void
 rate_limit_periodic_cb(void *rl_)
 {
@@ -856,7 +949,9 @@ rate_limit_periodic_cb(void *rl_)
          * no point in trying to transmit faster than the TCP connection can
          * handle. */
         struct buffer *b = dequeue_packet(rl);
          * no point in trying to transmit faster than the TCP connection can
          * handle. */
         struct buffer *b = dequeue_packet(rl);
-        rconn_send_with_limit(rl->remote_rconn, b, &rl->n_txq, 10);
+        if (rconn_send_with_limit(rl->remote_rconn, b, &rl->n_txq, 10)) {
+            rl->n_tx_dropped++;
+        }
     }
 }
 
     }
 }
 
@@ -877,9 +972,8 @@ rate_limit_wait_cb(void *rl_)
 }
 
 static struct hook
 }
 
 static struct hook
-rate_limit_hook_create(const struct settings *s,
-                       struct rconn *local,
-                       struct rconn *remote)
+rate_limit_hook_create(const struct settings *s, struct switch_status *ss,
+                       struct rconn *local, struct rconn *remote)
 {
     struct rate_limiter *rl;
     size_t i;
 {
     struct rate_limiter *rl;
     size_t i;
@@ -892,9 +986,191 @@ rate_limit_hook_create(const struct settings *s,
     }
     rl->last_fill = time_msec();
     rl->tokens = s->rate_limit * 100;
     }
     rl->last_fill = time_msec();
     rl->tokens = s->rate_limit * 100;
+    switch_status_register_category(ss, "rate-limit",
+                                    rate_limit_status_cb, rl);
     return make_hook(rate_limit_packet_cb, rate_limit_periodic_cb,
                      rate_limit_wait_cb, rl);
 }
     return make_hook(rate_limit_packet_cb, rate_limit_periodic_cb,
                      rate_limit_wait_cb, rl);
 }
+\f
+/* OFPST_SWITCH statistics. */
+
+struct switch_status_category {
+    char *name;
+    void (*cb)(struct status_reply *, void *aux);
+    void *aux;
+};
+
+struct switch_status {
+    const struct settings *s;
+    time_t booted;
+    struct switch_status_category categories[8];
+    int n_categories;
+};
+
+struct status_reply {
+    struct switch_status_category *category;
+    struct ds request;
+    struct ds output;
+};
+
+static bool
+switch_status_packet_cb(struct relay *r, int half, void *ss_)
+{
+    struct switch_status *ss = ss_;
+    struct rconn *rc = r->halves[HALF_REMOTE].rconn;
+    struct buffer *msg = r->halves[HALF_REMOTE].rxbuf;
+    struct switch_status_category *c;
+    struct ofp_stats_request *osr;
+    struct ofp_stats_reply *reply;
+    struct status_reply sr;
+    struct ofp_header *oh;
+    struct buffer *b;
+    int retval;
+
+    if (half == HALF_LOCAL) {
+        return false;
+    }
+
+    oh = msg->data;
+    if (oh->type != OFPT_STATS_REQUEST) {
+        return false;
+    }
+    if (msg->size < sizeof(struct ofp_stats_request)) {
+        VLOG_WARN("packet too short (%zu bytes) for stats_request", msg->size);
+        return false;
+    }
+
+    osr = msg->data;
+    if (osr->type != htons(OFPST_SWITCH)) {
+        return false;
+    }
+
+    sr.request.string = (void *) (osr + 1);
+    sr.request.length = msg->size - sizeof *osr;
+    ds_init(&sr.output);
+    for (c = ss->categories; c < &ss->categories[ss->n_categories]; c++) {
+        if (!memcmp(c->name, sr.request.string,
+                    MIN(strlen(c->name), sr.request.length))) {
+            sr.category = c;
+            c->cb(&sr, c->aux);
+        }
+    }
+    reply = make_openflow_xid((offsetof(struct ofp_stats_reply, body)
+                               + sr.output.length),
+                              OFPT_STATS_REPLY, osr->header.xid, &b);
+    reply->type = htons(OFPST_SWITCH);
+    reply->flags = 0;
+    memcpy(reply->body, sr.output.string, sr.output.length);
+    retval = rconn_send(rc, b, NULL);
+    if (retval && retval != EAGAIN) {
+        VLOG_WARN("send failed (%s)", strerror(retval));
+    }
+    ds_destroy(&sr.output);
+    return true;
+}
+
+static void
+rconn_status_cb(struct status_reply *sr, void *rconn_)
+{
+    struct rconn *rconn = rconn_;
+    time_t now = time_now();
+
+    status_reply_put(sr, "name=%s", rconn_get_name(rconn));
+    status_reply_put(sr, "state=%s", rconn_get_state(rconn));
+    status_reply_put(sr, "is-connected=%s",
+                     rconn_is_connected(rconn) ? "true" : "false");
+    status_reply_put(sr, "sent-msgs=%u", rconn_packets_sent(rconn));
+    status_reply_put(sr, "received-msgs=%u", rconn_packets_received(rconn));
+    status_reply_put(sr, "attempted-connections=%u",
+                     rconn_get_attempted_connections(rconn));
+    status_reply_put(sr, "successful-connections=%u",
+                     rconn_get_successful_connections(rconn));
+    status_reply_put(sr, "last-connection=%ld",
+                     (long int) (now - rconn_get_last_connection(rconn)));
+    status_reply_put(sr, "time-connected=%lu",
+                     rconn_get_total_time_connected(rconn));
+}
+
+static void
+config_status_cb(struct status_reply *sr, void *s_)
+{
+     const struct settings *s = s_;
+
+    if (s->listen_vconn_name) {
+        status_reply_put(sr, "management=%s", s->listen_vconn_name);
+    }
+    if (s->probe_interval) {
+        status_reply_put(sr, "probe-interval=%d", s->probe_interval);
+    }
+    if (s->max_backoff) {
+        status_reply_put(sr, "max-backoff=%d", s->max_backoff);
+    }
+}
+
+static void
+switch_status_cb(struct status_reply *sr, void *ss_)
+{
+    struct switch_status *ss = ss_;
+    time_t now = time_now();
+
+    status_reply_put(sr, "now=%ld", (long int) now);
+    status_reply_put(sr, "uptime=%ld", (long int) (now - ss->booted));
+    status_reply_put(sr, "pid=%ld", (long int) getpid());
+}
+
+static struct hook
+switch_status_hook_create(const struct settings *s, struct switch_status **ssp)
+{
+    struct switch_status *ss = xcalloc(1, sizeof *ss);
+    ss->s = s;
+    ss->booted = time_now();
+    switch_status_register_category(ss, "config",
+                                    config_status_cb, (void *) s);
+    switch_status_register_category(ss, "switch", switch_status_cb, ss);
+    *ssp = ss;
+    return make_hook(switch_status_packet_cb, NULL, NULL, ss);
+}
+
+static void
+switch_status_register_category(struct switch_status *ss,
+                                const char *category,
+                                void (*cb)(struct status_reply *,
+                                           void *aux),
+                                void *aux)
+{
+    struct switch_status_category *c;
+    assert(ss->n_categories < ARRAY_SIZE(ss->categories));
+    c = &ss->categories[ss->n_categories++];
+    c->cb = cb;
+    c->aux = aux;
+    c->name = xstrdup(category);
+}
+
+static void
+status_reply_put(struct status_reply *sr, const char *content, ...)
+{
+    size_t old_length = sr->output.length;
+    size_t added;
+    va_list args;
+
+    /* Append the status reply to the output. */
+    ds_put_format(&sr->output, "%s.", sr->category->name);
+    va_start(args, content);
+    ds_put_format_valist(&sr->output, content, args);
+    va_end(args);
+    if (ds_last(&sr->output) != '\n') {
+        ds_put_char(&sr->output, '\n');
+    }
+
+    /* Drop what we just added if it doesn't match the request. */
+    added = sr->output.length - old_length;
+    if (added < sr->request.length
+        || memcmp(&sr->output.string[old_length],
+                  sr->request.string, sr->request.length)) {
+        ds_truncate(&sr->output, old_length);
+    }
+}
+
 \f
 /* Controller discovery. */
 
 \f
 /* Controller discovery. */
 
@@ -902,11 +1178,55 @@ struct discovery
 {
     const struct settings *s;
     struct dhclient *dhcp;
 {
     const struct settings *s;
     struct dhclient *dhcp;
-    bool ever_successful;
+    int n_changes;
 };
 
 };
 
+static void
+discovery_status_cb(struct status_reply *sr, void *d_)
+{
+    struct discovery *d = d_;
+
+    status_reply_put(sr, "discovery.accept-remote=%s",
+                     d->s->accept_controller_re);
+    status_reply_put(sr, "discovery.n-changes=%d", d->n_changes);
+    status_reply_put(sr, "discovery.state=%s", dhclient_get_state(d->dhcp));
+    status_reply_put(sr, "discovery.state-elapsed=%u",
+                     dhclient_get_state_elapsed(d->dhcp));
+    if (dhclient_is_bound(d->dhcp)) {
+        uint32_t ip = dhclient_get_ip(d->dhcp);
+        uint32_t netmask = dhclient_get_netmask(d->dhcp);
+        uint32_t router = dhclient_get_router(d->dhcp);
+
+        const struct dhcp_msg *cfg = dhclient_get_config(d->dhcp);
+        uint32_t dns_server;
+        char *domain_name;
+        int i;
+
+        status_reply_put(sr, "discovery.ip="IP_FMT, IP_ARGS(&ip));
+        status_reply_put(sr, "discovery.netmask="IP_FMT, IP_ARGS(&netmask));
+        if (router) {
+            status_reply_put(sr, "discovery.router="IP_FMT, IP_ARGS(&router));
+        }
+
+        for (i = 0; dhcp_msg_get_ip(cfg, DHCP_CODE_DNS_SERVER, i, &dns_server);
+             i++) {
+            status_reply_put(sr, "discovery.dns%d="IP_FMT,
+                             i, IP_ARGS(&dns_server));
+        }
+
+        domain_name = dhcp_msg_get_string(cfg, DHCP_CODE_DOMAIN_NAME);
+        if (domain_name) {
+            status_reply_put(sr, "discovery.domain=%s", domain_name);
+            free(domain_name);
+        }
+
+        status_reply_put(sr, "discovery.lease-remaining=%u",
+                         dhclient_get_lease_remaining(d->dhcp));
+    }
+}
+
 static struct discovery *
 static struct discovery *
-discovery_init(const struct settings *s)
+discovery_init(const struct settings *s, struct switch_status *ss)
 {
     struct netdev *netdev;
     struct discovery *d;
 {
     struct netdev *netdev;
     struct discovery *d;
@@ -935,7 +1255,10 @@ discovery_init(const struct settings *s)
     d = xmalloc(sizeof *d);
     d->s = s;
     d->dhcp = dhcp;
     d = xmalloc(sizeof *d);
     d->s = s;
     d->dhcp = dhcp;
-    d->ever_successful = false;
+    d->n_changes = 0;
+
+    switch_status_register_category(ss, "discovery", discovery_status_cb, d);
+
     return d;
 }
 
     return d;
 }
 
@@ -962,11 +1285,12 @@ discovery_run(struct discovery *d, char **controller_name)
         *controller_name = dhcp_msg_get_string(dhclient_get_config(d->dhcp),
                                                DHCP_CODE_OFP_CONTROLLER_VCONN);
         VLOG_WARN("%s: discovered controller", *controller_name);
         *controller_name = dhcp_msg_get_string(dhclient_get_config(d->dhcp),
                                                DHCP_CODE_OFP_CONTROLLER_VCONN);
         VLOG_WARN("%s: discovered controller", *controller_name);
-        d->ever_successful = true;
-    } else if (controller_name) {
+        d->n_changes++;
+    } else {
         *controller_name = NULL;
         *controller_name = NULL;
-        if (d->ever_successful) {
+        if (d->n_changes) {
             VLOG_WARN("discovered controller no longer available");
             VLOG_WARN("discovered controller no longer available");
+            d->n_changes++;
         }
     }
     return true;
         }
     }
     return true;
index 5a1181e..c91166a 100644 (file)
@@ -98,6 +98,20 @@ the connection method.
 Prints to the console information on datapath \fIswitch\fR including
 information on its flow tables and ports.
 
 Prints to the console information on datapath \fIswitch\fR including
 information on its flow tables and ports.
 
+.TP
+\fBstatus \fIswitch\fR [\fIkey\fR]
+Prints to the console a series of key-value pairs that report the
+status of \fIswitch\fR.  If \fIkey\fR is specified, only the key-value
+pairs whose key names begin with \fIkey\fR are printed.  If \fIkey\fR is
+omitted, all key-value pairs are printed.
+
+(In the OpenFlow reference implementation, the \fBstatus\fR command is
+implemented in \fBsecchan\fR(8), not in the kernel module, so the
+\fBnl:\fIdp_idx\fR connection method should not be used with this
+command.  Instead, specify \fB-l\fR or \fB--listen\fR on the
+\fBsecchan\fR command line and tell \fBdpctl\fR to use the connection
+method specified there.)
+
 .TP
 \fBdump-tables \fIswitch\fR
 Prints to the console statistics for each of the flow tables used by
 .TP
 \fBdump-tables \fIswitch\fR
 Prints to the console statistics for each of the flow tables used by
index 2601ac9..7c50200 100644 (file)
@@ -192,7 +192,8 @@ usage(void)
            "  monitor nl:DP_ID            print packets received\n"
 #endif
            "\nFor local datapaths and remote switches:\n"
            "  monitor nl:DP_ID            print packets received\n"
 #endif
            "\nFor local datapaths and remote switches:\n"
-           "  show SWITCH                 show information\n"
+           "  show SWITCH                 show basic information\n"
+           "  status SWITCH [KEY]         report statistics (about KEY)\n"
            "  dump-version SWITCH         print version information\n"
            "  dump-tables SWITCH          print table stats\n"
            "  mod-port SWITCH IFACE ACT   modify port behavior\n"
            "  dump-version SWITCH         print version information\n"
            "  dump-tables SWITCH          print table stats\n"
            "  mod-port SWITCH IFACE ACT   modify port behavior\n"
@@ -396,6 +397,16 @@ do_show(int argc UNUSED, char *argv[])
     dump_trivial_transaction(argv[1], OFPT_GET_CONFIG_REQUEST);
 }
 
     dump_trivial_transaction(argv[1], OFPT_GET_CONFIG_REQUEST);
 }
 
+static void
+do_status(int argc, char *argv[])
+{
+    struct buffer *request;
+    alloc_stats_request(0, OFPST_SWITCH, &request);
+    if (argc > 2) {
+        buffer_put(request, argv[2], strlen(argv[2]));
+    }
+    dump_stats_transaction(argv[1], request);
+}
 
 static void
 do_dump_version(int argc, char *argv[])
 
 static void
 do_dump_version(int argc, char *argv[])
@@ -1054,6 +1065,7 @@ static struct command all_commands[] = {
 #endif
 
     { "show", 1, 1, do_show },
 #endif
 
     { "show", 1, 1, do_show },
+    { "status", 1, 2, do_status },
 
     { "help", 0, INT_MAX, do_help },
     { "monitor", 1, 1, do_monitor },
 
     { "help", 0, INT_MAX, do_help },
     { "monitor", 1, 1, do_monitor },