[ovs-discuss] [ACLv2 19/19] vswitchd: Implement local ACL functionality.

Jesse Gross jesse at nicira.com
Sat Aug 15 02:13:27 UTC 2009


Allows ACL's to be locally configured and applied when the switch is
not connected to a controller.  ACL's may be added to the configuration
file and will applied to specified switch ports.  Ingress rules generate
OpenFlow entries, while egress rules utilize a new filter to remove output
ports after normal processing has taken place.  Also allows ACL's over ARP
payload fields, which is not supported by OpenFlow.
---
 README                          |    1 +
 lib/vlog-modules.def            |    1 +
 vswitchd/acl.c                  | 1134 +++++++++++++++++++++++++++++++++++++++
 vswitchd/acl.h                  |   54 ++
 vswitchd/automake.mk            |    2 +
 vswitchd/bridge.c               |  112 ++++-
 vswitchd/ovs-vswitchd.8.in      |    3 +
 vswitchd/ovs-vswitchd.conf.5.in |  181 +++++++
 8 files changed, 1475 insertions(+), 13 deletions(-)
 create mode 100644 vswitchd/acl.c
 create mode 100644 vswitchd/acl.h

diff --git a/README b/README
index 146795b..6bfd5be 100644
--- a/README
+++ b/README
@@ -24,6 +24,7 @@ vSwitch supports the following features:
     * Standard 802.1Q VLAN model with trunking
     * Per VM policing
     * NIC bonding with source-MAC load balancing
+    * Access control lists
     * Kernel-based forwarding
     * Support for OpenFlow
     * Compatibility layer for the Linux bridging code
diff --git a/lib/vlog-modules.def b/lib/vlog-modules.def
index 849c867..7a8ced5 100644
--- a/lib/vlog-modules.def
+++ b/lib/vlog-modules.def
@@ -15,6 +15,7 @@
  */
 
 /* Modules that can emit log messages. */
+VLOG_MODULE(acl)
 VLOG_MODULE(backtrace)
 VLOG_MODULE(brcompatd)
 VLOG_MODULE(bridge)
diff --git a/vswitchd/acl.c b/vswitchd/acl.c
new file mode 100644
index 0000000..80d5ea1
--- /dev/null
+++ b/vswitchd/acl.c
@@ -0,0 +1,1134 @@
+/*
+ * Copyright (c) 2009 Nicira Networks.
+ *
+ * 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:
+ *
+ *     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>
+#include "acl.h"
+
+#include <arpa/inet.h>
+#include <ctype.h>
+#include <errno.h>
+#include <fnmatch.h>
+#include <linux/if_arp.h>
+#include <netinet/in.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "cfg.h"
+#include "classifier.h"
+#include "flow.h"
+#include "hmap.h"
+#include "ofp-print.h"
+#include "ofp-util.h"
+#include "packets.h"
+#include "shash.h"
+#include "socket-util.h"
+#include "svec.h"
+#include "util.h"
+#include "vlog.h"
+
+#define THIS_MODULE VLM_acl
+
+struct acl_engine {
+    bool acls_active;           /* Whether ACL's are currently enabled. */
+    struct hmap out_flows;      /* Flows seen on ports with out rules. */
+};
+
+struct acl_rule {
+    flow_t flow;                /* Flow entry for this rule */
+    uint32_t wildcards;         /* Wildcards for this rule */
+    unsigned int priority;      /* Rule priority */
+    struct cls_rule cr;         /* Classifier rule for out ACL's */
+    bool permit;                /* Allow further processing or drop packets */
+};
+
+struct acl_group {
+    int size;                   /* Size of group */
+    struct acl_rule **rules;    /* Rules in group */
+};
+
+struct stats {
+    uint64_t packets;           /* Count of packets */
+    uint64_t bytes;             /* Count of bytes */
+};
+
+struct flow_tracker {
+    struct hmap_node node;      /* hmap node */
+    flow_t flow;                /* Flow that is being tracked */
+    struct stats *stats;        /* Pointer to stats for 1 rule on 1 port */
+};
+
+struct rules_stats {
+    size_t n_rules;             /* Number of rules in this set */
+    struct acl_rule **rules;    /* Array of pointers to rules in global table */
+    struct stats *stats;        /* Array of stats for rules on this port */
+};
+
+struct acl {
+    struct acl_engine *bridge_info; /* Bridge-specific ACL information */
+    char *port_name;            /* Name of port */
+    struct ofproto *ofproto;    /* Bridge OpenFlow switch */
+    uint16_t cur_ofp_port;      /* The port in rules are installed on */
+    struct classifier out_rules; /* Lookup table for applying egress ACL's */
+    struct rules_stats in_stats; /* Stats for in rules on this port */
+    struct rules_stats out_stats; /* Stats for out rules on this port */
+    bool arp_userspace;         /* Rules require ARP userspace processing */
+};
+
+struct acl_stats {
+    struct rules_stats in;      /* Collected rules and stats for in rules */
+    struct rules_stats out;     /* Collected rules and stats for out rules */
+};
+
+struct acl_field {
+    const char *name;           /* Name of the field in the config file */
+    uint32_t wildcard;          /* Wildcard value */
+    enum { F_U8, F_U16, F_MAC, F_IP, F_PROTO, F_ARPOP } type; /* Data type */
+    size_t offset;              /* Offset in the flow_t */
+    size_t shift;               /* Wildcard shift */
+};
+
+/* Mapping of group name to struct acl_group *. */
+static struct shash acls = SHASH_INITIALIZER(&acls);
+
+/* Old configuration, used to check if we need to update. */
+static struct svec old_cfg = SVEC_EMPTY_INITIALIZER;
+
+static bool has_in_rules(struct acl *info);
+static bool has_out_rules(struct acl *info);
+static struct acl_rule *permit_rule(void);
+static void sort_svec_numeric(struct svec *svec);
+static int compare_priorities(const void *a, const void *b);
+static void install_rules(const char *group_name, uint16_t ofp_port,
+                          bool in_rule, struct acl *info);
+static void init_rules_stats(struct rules_stats *rules_stats, size_t n_rules);
+static void destroy_rules_stats(struct rules_stats *rules_stats);
+static bool need_arp_userspace(const struct acl_rule *rule);
+static void update_rules(void);
+static bool parse_acl(const char *acl, unsigned int priority,
+                          struct acl_rule **rule);
+static bool parse_acl_field(const struct acl_field *f, const char *value,
+                            struct acl_rule *rule);
+static bool parse_int(const char *value, unsigned int max_value, void *out);
+static bool parse_proto(const char *value, struct acl_rule *rule);
+static bool parse_arpop(const char *value, uint8_t *out);
+static void add_default_acl(struct acl_rule **rule);
+static void init_stats_output(struct rules_stats *iface_stats,
+                              struct rules_stats *out);
+static void get_port_tx_stats(struct acl *info, struct stats *baseline,
+                              struct stats *stats);
+static void format_stats_output(struct rules_stats *stats, struct ds *out);
+
+void
+acl_reconfigure(void)
+{
+    struct svec new_cfg = SVEC_EMPTY_INITIALIZER;
+    struct shash_node *old_node;
+    struct acl_group *old_rule_set;
+    int i;
+
+    /* Only reconfigure if something actually changed. */
+    cfg_get_section(&new_cfg, "acl");
+    if (svec_equal(&old_cfg, &new_cfg)) {
+        svec_destroy(&new_cfg);
+        return;
+    } else {
+        svec_swap(&old_cfg, &new_cfg);
+        svec_destroy(&new_cfg);
+    }
+
+    /* Remove old entries. */
+    HMAP_FOR_EACH(old_node, struct shash_node, node, &acls.map) {
+        old_rule_set = old_node->data;
+        for (i = 0; i < old_rule_set->size; i++) {
+            free(old_rule_set->rules[i]);
+        }
+        free(old_rule_set);
+    }
+    shash_clear(&acls);
+
+    /* Add new entries. */
+    update_rules();
+}
+
+void
+acl_bridge_init(struct acl_engine **bridge_info)
+{
+    *bridge_info = xmalloc(sizeof **bridge_info);
+
+    hmap_init(&(*bridge_info)->out_flows);
+}
+
+void
+acl_bridge_reconfigure(struct acl_engine *bridge_info, bool acls_active)
+{
+    if (cfg_has_section("acl")) {
+        if (!acls_active) {
+            VLOG_ERR("acls configured but are disabled");
+        }
+    } else {
+        acls_active = false;
+    }
+
+    bridge_info->acls_active = acls_active;
+}
+
+bool
+acl_bridge_is_active(struct acl_engine *bridge_info)
+{
+    return bridge_info->acls_active;
+}
+
+void
+acl_bridge_destroy(struct acl_engine *bridge_info)
+{
+    struct flow_tracker *tracker, *next;
+
+    HMAP_FOR_EACH_SAFE(tracker, next, struct flow_tracker, node,
+                       &bridge_info->out_flows) {
+        hmap_remove(&bridge_info->out_flows, &tracker->node);
+        free(tracker);
+    }
+    hmap_destroy(&bridge_info->out_flows);
+
+    free(bridge_info);
+}
+
+void
+acl_iface_init(struct acl_engine *bridge_info, const char *port_name,
+               struct ofproto *ofproto, struct acl **info)
+{
+    struct acl *new_iface;
+
+    *info = xcalloc(1, sizeof **info);
+    new_iface = *info;
+
+    new_iface->bridge_info = bridge_info;
+    new_iface->port_name = xstrdup(port_name);
+    new_iface->ofproto = ofproto;
+    classifier_init(&new_iface->out_rules);
+}
+
+void
+acl_iface_reconfigure(struct acl *info, uint16_t ofp_port)
+{
+    flow_t flow;
+    uint32_t wildcards;
+    const char *acl_group_name;
+    int i;
+
+    info->cur_ofp_port = ofp_port;
+
+    memset(&flow, 0, sizeof flow);
+    flow.in_port = ofp_port;
+    wildcards = OFPFW_ALL & ~OFPFW_IN_PORT;
+
+    /* Clear out old data and flows associated with ACL's.  Since ACL's are
+     * only active when there is no controller, only our flows should be
+     * in here. */
+
+    destroy_rules_stats(&info->in_stats);
+    destroy_rules_stats(&info->out_stats);
+    info->arp_userspace = false;
+
+    if (has_out_rules(info)) {
+        classifier_destroy(&info->out_rules);
+        classifier_init(&info->out_rules);
+    }
+
+    /* We should delete the out rules first, since deleting the flows will
+     * cause expiration messages to be sent, which will invoke the out rules
+     * code.  If we run this with the old rules (in the event that the rules
+     * also changed), bad things will happen. */
+    if (info->bridge_info->acls_active) {
+        ofproto_delete_flows_wildcarded(info->ofproto, &flow, wildcards);
+    }
+
+    if (!info->bridge_info->acls_active) {
+        init_rules_stats(&info->in_stats, 1);
+        init_rules_stats(&info->out_stats, 1);
+
+        info->in_stats.rules[0] = permit_rule();
+        info->out_stats.rules[0] = permit_rule();
+
+        /* If ACL's aren't active on this port, we store baseline statistics
+         * instead.  The counters for the active ACL's are zeroed when
+         * a reconfigure is done and this emulates that behavior for the
+         * inactive ones as well. */
+        get_port_tx_stats(info, NULL, &info->out_stats.stats[0]);
+
+        return;
+    }
+
+    /* Add flows for ingress rules. */
+    acl_group_name = cfg_get_string(0, "acl.port.%s.in", info->port_name);
+    if (acl_group_name) {
+        install_rules(acl_group_name, ofp_port, true, info);
+    }
+
+    /* Add to classifier for egress rules. */
+    acl_group_name = cfg_get_string(0, "acl.port.%s.out", info->port_name);
+    if (acl_group_name) {
+        install_rules(acl_group_name, ofp_port, false, info);
+    }
+
+    /* Wildcarded rules. */
+    if (!has_in_rules(info) || !has_out_rules(info)) {
+        struct svec priorities = SVEC_EMPTY_INITIALIZER;
+        const char *glob;
+        uint16_t defaults_priority;
+
+        cfg_get_subsections(&priorities, "acl.default");
+        sort_svec_numeric(&priorities);
+
+        for (i = 0; i < priorities.n; i++) {
+            if (!parse_int(priorities.names[i], UINT16_MAX,
+                &defaults_priority)) {
+                VLOG_WARN("invalid priority when processing default acl's,"
+                          " skipping; bad key: acl.default.%s",
+                          priorities.names[i]);
+                continue;
+            }
+
+            glob = cfg_get_string(0, "acl.default.%s.match",
+                                  priorities.names[i]);
+            if (glob) {
+                if (fnmatch(glob, info->port_name, 0) == 0) {
+                    if (!has_in_rules(info)) {
+                        acl_group_name = cfg_get_string(0,
+                                                        "acl.default.%s.in",
+                                                        priorities.names[i]);
+                        if (acl_group_name) {
+                            install_rules(acl_group_name, ofp_port, true, info);
+                        }
+                    }
+                    if (!has_out_rules(info)) {
+                        acl_group_name = cfg_get_string(0,
+                                                        "acl.default.%s.out",
+                                                        priorities.names[i]);
+                        if (acl_group_name) {
+                            install_rules(acl_group_name, ofp_port, false,
+                                          info);
+                        }
+                    }
+                    if (has_in_rules(info) && has_out_rules(info)) {
+                        break;
+                    }
+                }
+            } else {
+                VLOG_WARN("missing glob when processing default acl's,"
+                          " skipping; bad key: acl.default.%s.match",
+                          priorities.names[i]);
+            }
+        }
+        svec_destroy(&priorities);
+    }
+
+    /* No match, install a default ingress flow. */
+    if (!has_in_rules(info)) {
+        ofproto_add_flow(info->ofproto, &flow, wildcards, 0,
+                         ofp_normal_action(), 1, 0, true);
+        init_rules_stats(&info->in_stats, 1);
+        info->in_stats.rules[0] = permit_rule();
+    }
+
+    if (!has_out_rules(info)) {
+        init_rules_stats(&info->out_stats, 1);
+        info->out_stats.rules[0] = permit_rule();
+        get_port_tx_stats(info, NULL, &info->out_stats.stats[0]);
+    }
+}
+
+static bool
+has_in_rules(struct acl *info)
+{
+    flow_t flow;
+
+    memset(&flow, 0, sizeof flow);
+    flow.in_port = info->cur_ofp_port;
+
+    return ofproto_has_matching_flow(info->ofproto, &flow,
+                                     OFPFW_ALL & ~OFPFW_IN_PORT);
+}
+
+static bool
+has_out_rules(struct acl *info)
+{
+    return !classifier_is_empty(&info->out_rules);
+}
+
+static struct acl_rule *
+permit_rule(void)
+{
+    static struct acl_rule permit_rule;
+    static bool inited;
+
+    if (!inited) {
+        permit_rule.permit = true;
+        permit_rule.priority = 0;
+        permit_rule.wildcards = OFPFW_ALL & ~OFPFW_IN_PORT;
+
+        cls_rule_from_flow(&permit_rule.cr, &permit_rule.flow,
+                            permit_rule.wildcards, permit_rule.priority);
+    }
+
+    return &permit_rule;
+}
+
+static void
+sort_svec_numeric(struct svec *svec)
+{
+    qsort(svec->names, svec->n, sizeof *svec->names, compare_priorities);
+}
+
+static int
+compare_priorities(const void *a_, const void *b_)
+{
+    char *const *a = a_;
+    char *const *b = b_;
+    unsigned int a_priority = 0, b_priority = 0;
+
+    sscanf(*a, "%u", &a_priority);
+    sscanf(*b, "%u", &b_priority);
+
+    return a_priority - b_priority;
+}
+
+static void
+install_rules(const char *group_name, uint16_t ofp_port, bool in_rule,
+              struct acl *info)
+{
+    struct acl_group *access_rules;
+    int i;
+    flow_t flow;
+    struct rules_stats *stats;
+    bool in_arp_userspace = false;
+
+    access_rules = shash_find_data(&acls, group_name);
+    if (access_rules) {
+        stats = in_rule ? &info->in_stats : &info->out_stats;
+        init_rules_stats(stats, access_rules->size);
+
+        /* If any of the rules on a port require processing ARP payloads in
+         * userspace, then none of the rules relating to ARP's are
+         * installable. */
+        for (i = 0; i < access_rules->size; i++) {
+            if (access_rules->rules[i]) {
+                if (need_arp_userspace(access_rules->rules[i])) {
+                    if (in_rule) {
+                        in_arp_userspace = true;
+                    } else {
+                        info->arp_userspace = true;
+                    }
+                    break;
+                }
+            }
+        }
+
+        for (i = 0; i < access_rules->size; i++) {
+            if (access_rules->rules[i]) {
+                if (in_rule) {
+                    bool may_install = true;
+
+                    flow = access_rules->rules[i]->flow;
+                    flow.in_port = ofp_port;
+
+                    if (flow.dl_type == htons(ETH_TYPE_ARP) &&
+                        in_arp_userspace) {
+                        may_install = false;
+                    }
+
+                    ofproto_add_flow(info->ofproto, &flow,
+                                     access_rules->rules[i]->wildcards,
+                                     access_rules->rules[i]->priority,
+                                     access_rules->rules[i]->permit ?
+                                     ofp_normal_action() : NULL,
+                                     access_rules->rules[i]->permit ? 1 : 0, 0,
+                                     may_install);
+                } else {
+                    classifier_insert(&info->out_rules,
+                                      &access_rules->rules[i]->cr);
+                }
+
+                stats->rules[i] = access_rules->rules[i];
+            }
+        }
+    } else {
+        /* The user configured an acl that does not exist. */
+        VLOG_WARN("acl group '%s' not found", group_name);
+    }
+}
+
+static void
+init_rules_stats(struct rules_stats *rules_stats, size_t n_rules)
+{
+    rules_stats->n_rules = n_rules;
+    rules_stats->rules = xcalloc(n_rules, sizeof *rules_stats->rules);
+    rules_stats->stats = xcalloc(n_rules, sizeof *rules_stats->stats);
+}
+
+static void
+destroy_rules_stats(struct rules_stats *rules_stats)
+{
+    free(rules_stats->rules);
+    free(rules_stats->stats);
+}
+
+void
+acl_iface_destroy(struct acl *info)
+{
+    flow_t flow;
+
+    /* Delete in rules */
+    memset(&flow, 0, sizeof flow);
+    flow.in_port = info->cur_ofp_port;
+    ofproto_delete_flows_wildcarded(info->ofproto, &flow,
+                                    OFPFW_ALL & ~OFPFW_IN_PORT);
+
+    destroy_rules_stats(&info->in_stats);
+    destroy_rules_stats(&info->out_stats);
+    classifier_destroy(&info->out_rules);
+    free(info->port_name);
+    free(info);
+}
+
+static bool
+need_arp_userspace(const struct acl_rule *rule)
+{
+    uint32_t wildcard_mask;
+
+    if (rule->flow.dl_type == htons(ETH_TYPE_ARP)) {
+        wildcard_mask = NICFW_AR_SHA | NICFW_AR_THA;
+        if ((rule->wildcards & wildcard_mask) !=
+            (OFPFW_ALL & wildcard_mask)) {
+            return true;
+        }
+    }
+
+    return false;
+}
+
+bool
+acl_iface_output_filter(struct acl *info, const flow_t *flow, bool *userspace)
+{
+    flow_t flow_copy;
+    struct cls_rule *cls_rule;
+    struct acl_rule *rule;
+    struct flow_tracker *tracker;
+    int i;
+    struct stats *stats = NULL;
+    bool exists_in_hash = false;
+
+    if (!has_out_rules(info)) {
+        return true;
+    }
+
+    /* For performance, zero out the in port since we never look at it. */
+    memcpy(&flow_copy, flow, sizeof flow_copy);
+    flow_copy.in_port = 0;
+
+    cls_rule = classifier_lookup(&info->out_rules, &flow_copy);
+    assert(cls_rule);   /* All rule sets should end with a wildcarded entry. */
+
+    rule = CONTAINER_OF(cls_rule, struct acl_rule, cr);
+
+    for (i = 0; i < info->out_stats.n_rules; i++) {
+        if (info->out_stats.rules[i] == rule) {
+            stats = &info->out_stats.stats[i];
+            break;
+        }
+    }
+
+    assert(stats);
+
+    HMAP_FOR_EACH_WITH_HASH(tracker, struct flow_tracker, node,
+                            flow_hash(flow, 0), &info->bridge_info->out_flows) {
+        if (flow_equal(flow, &tracker->flow) && tracker->stats == stats) {
+            exists_in_hash = true;
+        }
+    }
+
+    if (!exists_in_hash) {
+        tracker = xcalloc(1, sizeof *tracker);
+        memcpy(&tracker->flow, flow, sizeof tracker->flow);
+        tracker->stats = stats;
+        hmap_insert(&info->bridge_info->out_flows, &tracker->node,
+                    flow_hash(flow, 0));
+    }
+
+    if (flow->dl_type == htons(ETH_TYPE_ARP) && info->arp_userspace) {
+        *userspace = true;
+    }
+
+    return rule->permit;
+}
+
+void
+acl_flow_expire_cb(struct acl_engine *bridge_info, const flow_t *flow,
+                   uint64_t packets, uint64_t bytes)
+{
+    struct flow_tracker *tracker, *next;
+
+    HMAP_FOR_EACH_WITH_HASH_SAFE(tracker, next, struct flow_tracker, node,
+                                 flow_hash(flow, 0), &bridge_info->out_flows) {
+        if (flow_equal(flow, &tracker->flow)) {
+            /* Could have multiple hits here if there was more than one
+             * output port. */
+            tracker->stats->packets += packets;
+            tracker->stats->bytes += bytes;
+
+            hmap_remove(&bridge_info->out_flows, &tracker->node);
+            free(tracker);
+        }
+    }
+}
+
+static void
+update_rules(void)
+{
+    struct svec groups = SVEC_EMPTY_INITIALIZER;
+    struct svec priorities = SVEC_EMPTY_INITIALIZER;
+    int i, j;
+    const char *acl;
+    struct acl_group *rule_set;
+    bool final_rule;
+    unsigned short int rule_priority;
+
+    /* Create parsed flow entries for each ACL. */
+    cfg_get_subsections(&groups, "acl.group");
+
+    for (i = 0; i < groups.n; i++) {
+        svec_clear(&priorities);
+
+        cfg_get_subsections(&priorities, "acl.group.%s", groups.names[i]);
+        sort_svec_numeric(&priorities);
+
+        rule_set = xmalloc(sizeof *rule_set);
+        rule_set->size = priorities.n + 1;
+        rule_set->rules = xcalloc(rule_set->size, sizeof *rule_set->rules);
+
+        for (j = 0; j < priorities.n; j++) {
+            if (!parse_int(priorities.names[i], UINT16_MAX, &rule_priority)) {
+                VLOG_WARN("invalid priority when processing acl's, skipping "
+                          "rule; bad key: acl.group.%s.%s", groups.names[i],
+                          priorities.names[j]);
+                continue;
+            }
+
+            acl = cfg_get_string(0, "acl.group.%s.%s", groups.names[i],
+                                 priorities.names[j]);
+            if (acl) {
+                final_rule = parse_acl(acl, priorities.n - j,
+                                           &rule_set->rules[j]);
+
+                if (final_rule) {
+                    if (j < priorities.n - 1) {
+                        VLOG_WARN("rules after acl.group.%s.%s are ignored "
+                                  "because they have no effect",
+                                  groups.names[i], priorities.names[j] );
+                    }
+
+                    break;
+                }
+            } else {
+                VLOG_WARN("missing rule when processing acl's, skipping; bad "
+                          "key: acl.group.%s.%s", groups.names[i],
+                          priorities.names[j]);
+            }
+        }
+
+        if (!final_rule) {
+            add_default_acl(&rule_set->rules[j]);
+        }
+
+        shash_add(&acls, groups.names[i], rule_set);
+    }
+
+    svec_destroy(&priorities);
+    svec_destroy(&groups);
+}
+
+/* Returns true if this is a completely wildcarded rule. */
+static bool
+parse_acl(const char *acl, unsigned int priority, struct acl_rule **rule)
+{
+    struct acl_rule *new_rule;
+    char *parse_string, *save_ptr = NULL;
+    const char *name, *value;
+    const struct acl_field *f;
+    bool retval;
+    bool match;
+    uint32_t wildcard_mask;
+
+#define F_OFS(MEMBER) offsetof(flow_t, MEMBER)
+    static const struct acl_field fields[] = {
+        { "dl_vlan", OFPFW_DL_VLAN, F_U16, F_OFS(dl_vlan), 0 },
+        { "dl_src", OFPFW_DL_SRC, F_MAC, F_OFS(dl_src), 0 },
+        { "dl_dst", OFPFW_DL_DST, F_MAC, F_OFS(dl_dst), 0 },
+        { "proto", OFPFW_DL_TYPE, F_PROTO, 0, 0 },
+        { "dl_type", OFPFW_DL_TYPE, F_U16, F_OFS(dl_type), 0 },
+        { "nw_src", OFPFW_NW_SRC_MASK, F_IP, F_OFS(nw_src),
+          OFPFW_NW_SRC_SHIFT },
+        { "nw_dst", OFPFW_NW_DST_MASK, F_IP, F_OFS(nw_dst),
+          OFPFW_NW_DST_SHIFT },
+        { "nw_proto", OFPFW_NW_PROTO, F_U8, F_OFS(nw_proto), 0 },
+        { "tp_src", OFPFW_TP_SRC, F_U16, F_OFS(tp_src), 0 },
+        { "tp_dst", OFPFW_TP_DST, F_U16, F_OFS(tp_dst), 0 },
+        { "icmp_type", OFPFW_ICMP_TYPE, F_U16, F_OFS(icmp_type), 0 },
+        { "icmp_code", OFPFW_ICMP_CODE, F_U16, F_OFS(icmp_code), 0 },
+        { "ar_op", OFPFW_NW_PROTO, F_ARPOP, F_OFS(nw_proto), 0 },
+        { "ar_spa", OFPFW_NW_SRC_MASK, F_IP, F_OFS(nw_src),
+          OFPFW_NW_SRC_SHIFT },
+        { "ar_tpa", OFPFW_NW_DST_MASK, F_IP, F_OFS(nw_dst),
+          OFPFW_NW_DST_SHIFT },
+        { "ar_sha", NICFW_AR_SHA, F_MAC, F_OFS(ar_sha), 0 },
+        { "ar_tha", NICFW_AR_THA, F_MAC, F_OFS(ar_tha), 0 }
+    };
+
+    *rule = xcalloc(1, sizeof **rule);
+    new_rule = *rule;
+
+    new_rule->priority = priority;
+    new_rule->wildcards = OFPFW_ALL; /* Wildcard all fields unless overridden.*/
+
+    parse_string = xstrdup(acl);
+
+    /* Rule action */
+    name = strtok_r(parse_string, " ", &save_ptr);
+    if (!strcasecmp(name, "permit") ||
+        !strcasecmp(name, "allow")) {
+        new_rule->permit = true;
+    } else if (!strcasecmp(name, "deny")) {
+        new_rule->permit = false;
+    } else {
+        VLOG_WARN("unknown action when processing acl, skipping; "
+                  "bad rule: %s", acl);
+        goto error;
+    }
+
+    /* Flow fields */
+    while ((name = strtok_r(NULL, " ", &save_ptr))) {
+        match = false;
+
+        for (f = fields; f < &fields[ARRAY_SIZE(fields)]; f++) {
+            if (!strcasecmp(f->name, name)) {
+
+                match = true;
+
+                if ((new_rule->wildcards & f->wildcard) !=
+                    (OFPFW_ALL & f->wildcard)) {
+                    VLOG_WARN("duplicate values specified for %s, "
+                              "skipping rule; bad acl: %s", f->name, acl);
+
+                    goto error;
+                }
+
+                value = strtok_r(NULL, " ", &save_ptr);
+                if (!value) {
+                    VLOG_WARN("missing value specified for %s, "
+                              "skipping rule; bad acl: %s", f->name, acl);
+
+                    goto error;
+                }
+
+                retval = parse_acl_field(f, value, new_rule);
+
+                if (!retval) {
+                    VLOG_WARN("unparsable %s '%s' when processing acl, "
+                              "skipping rule; bad acl: %s", f->name, value,
+                               acl);
+
+                    goto error;
+                }
+            }
+        }
+
+        if (!match) {
+            VLOG_WARN("unknown keyword '%s' when processing acl, skipping rule;"
+                      " bad acl: %s", name, acl);
+            goto error;
+        }
+    }
+
+    free(parse_string);
+
+    /* Basic sanity checking */
+    if (new_rule->flow.dl_type != htons(ETH_TYPE_IP) &&
+        new_rule->flow.dl_type != htons(ETH_TYPE_ARP)) {
+
+        wildcard_mask = OFPFW_NW_SRC_MASK | OFPFW_NW_DST_MASK | OFPFW_NW_PROTO;
+        if ((new_rule->wildcards & wildcard_mask) !=
+            (OFPFW_ALL & wildcard_mask)) {
+            VLOG_WARN("invalid fields specified for acl that is not ip or arp,"
+                      " skipping rule; bad acl: %s", acl);
+            goto error;
+        }
+    }
+
+    if (new_rule->flow.nw_proto != IP_TYPE_ICMP &&
+        new_rule->flow.nw_proto != IP_TYPE_TCP &&
+        new_rule->flow.nw_proto != IP_TYPE_UDP) {
+
+        wildcard_mask = OFPFW_TP_SRC | OFPFW_TP_DST;
+        if ((new_rule->wildcards & wildcard_mask) !=
+            (OFPFW_ALL & wildcard_mask)) {
+            VLOG_WARN("invalid fields specified for acl that is not icmp, tcp,"
+                      " or udp, skipping rule; bad acl: %s", acl);
+            goto error;
+        }
+    }
+
+    if (new_rule->flow.dl_type != htons(ETH_TYPE_ARP)) {
+        wildcard_mask = NICFW_AR_SHA | NICFW_AR_THA;
+        if ((new_rule->wildcards & wildcard_mask) !=
+            (OFPFW_ALL & wildcard_mask)) {
+            VLOG_WARN("invalid fields specified for acl that is not arp,"
+                      " skipping rule; bad acl: %s", acl);
+            goto error;
+        }
+    }
+
+    /* Finalize rule */
+    cls_rule_from_flow(&new_rule->cr, &new_rule->flow, new_rule->wildcards,
+                       priority);
+
+    retval = (new_rule->wildcards == OFPFW_ALL);
+
+    /* Don't wildcard fields we never look at, for performance reasons. */
+    new_rule->wildcards &= ~OFPFW_IN_PORT;
+
+    return retval;
+
+error:
+    free(parse_string);
+    free(new_rule);
+    *rule = NULL;
+    return false;
+}
+
+static bool
+parse_acl_field(const struct acl_field *f, const char *value,
+                struct acl_rule *rule)
+{
+    int retval;
+    void *data;
+    uint32_t new_wild;
+    uint16_t *short_data;
+
+    rule->wildcards &= ~f->wildcard;
+    data = (char *)&rule->flow + f->offset;
+
+    switch (f->type) {
+    case F_U8:
+        return parse_int(value, UINT8_MAX, data);
+
+    case F_U16:
+        retval = parse_int(value, UINT16_MAX, data);
+        short_data = data;
+        *short_data = htons(*short_data);
+        return retval;
+
+    case F_MAC:
+        return sscanf(value, ETH_ADDR_SCAN_FMT,
+                      ETH_ADDR_SCAN_ARGS((uint8_t *)data)) ==
+                ETH_ADDR_SCAN_COUNT;
+
+    case F_IP:
+        retval = str_to_ip(value, data, &new_wild);
+        rule->wildcards |= new_wild << f->shift;
+        return !retval;
+
+    case F_PROTO:
+        return parse_proto(value, rule);
+
+    case F_ARPOP:
+        return parse_arpop(value, data);
+
+    default:
+        NOT_REACHED();
+    };
+}
+
+static bool
+parse_int(const char *value, unsigned int max_value, void *out)
+{
+    char *tail;
+    unsigned int result;
+
+    errno = 0;
+    result = strtoul(value, &tail, 0);
+    if (errno == EINVAL || errno == ERANGE || *tail) {
+        return false;
+    }
+
+    if (result > max_value) {
+        return false;
+    }
+
+    *(uint32_t *)out = result;
+    return true;
+}
+
+static bool
+parse_proto(const char *value, struct acl_rule *rule)
+{
+    struct protocol {
+        const char *name;
+        uint16_t dl_type;
+        uint8_t nw_proto;
+    };
+
+    static const struct protocol protocols[] = {
+        { "ip", ETH_TYPE_IP, 0 },
+        { "arp", ETH_TYPE_ARP, 0 },
+        { "icmp", ETH_TYPE_IP, IP_TYPE_ICMP },
+        { "tcp", ETH_TYPE_IP, IP_TYPE_TCP },
+        { "udp", ETH_TYPE_IP, IP_TYPE_UDP },
+    };
+
+    const struct protocol *p;
+
+    for (p = protocols; p < &protocols[ARRAY_SIZE(protocols)]; p++) {
+        if (!strcasecmp(p->name, value)) {
+
+            rule->flow.dl_type = htons(p->dl_type);
+
+            if (p->nw_proto) {
+                rule->wildcards &= ~OFPFW_NW_PROTO;
+                rule->flow.nw_proto = p->nw_proto;
+            }
+            return true;
+        }
+    }
+
+    return false;
+}
+
+static bool
+parse_arpop(const char *value, uint8_t *out)
+{
+    /* We have only a single byte for the opcode (in the spec it is 2 bytes) */
+    if (!strcasecmp(value, "request")) {
+        *out = ARP_OP_REQUEST;
+        return true;
+    } else if (!strcasecmp(value, "reply")) {
+        *out = ARP_OP_REPLY;
+        return true;
+    }
+
+    return false;
+}
+
+static void
+add_default_acl(struct acl_rule **rule)
+{
+    struct acl_rule *new_rule;
+
+    /* Create a lowest priority default rule that denies all traffic. */
+    new_rule = *rule = xcalloc(1, sizeof **rule);
+
+    new_rule->permit = false;
+    new_rule->priority = 0;
+    new_rule->wildcards = OFPFW_ALL & ~OFPFW_IN_PORT;
+
+    cls_rule_from_flow(&new_rule->cr, &new_rule->flow, new_rule->wildcards,
+                       new_rule->priority);
+}
+
+void
+acl_stats_init(struct acl_stats **stats)
+{
+    *stats = xcalloc(1, sizeof **stats);
+}
+
+void
+acl_stats_add_iface(struct acl *info, struct acl_stats *stats)
+{
+    int i, j;
+    uint64_t packets;
+    uint64_t bytes;
+    flow_t flow;
+
+    /* The stats for the in rules can be easily collected from ofproto
+     * since these correspond directly to OpenFlow flows and we already
+     * track stats for those. */
+
+    init_stats_output(&info->in_stats, &stats->in);
+
+    for (i = 0; i < stats->in.n_rules; i++) {
+        if (stats->in.rules[i]) {
+            flow = stats->in.rules[i]->flow;
+            flow.in_port = info->cur_ofp_port;
+
+            ofproto_get_flow_stats(info->ofproto, &flow,
+                                   stats->in.rules[i]->wildcards, false,
+                                   &packets, &bytes);
+
+            /* When we get stats, it will be for all rules matching that
+             * wildcarded flow.  This removes counts for packets that actually
+             * hit earlier rules. */
+            for (j = 0; j < i; j++) {
+                if (flow_equal_wildcarded(&stats->in.rules[j]->flow,
+                                          &stats->in.rules[i]->flow,
+                                          stats->in.rules[i]->wildcards)) {
+                    packets -= stats->in.stats[j].packets;
+                    bytes -= stats->in.stats[j].bytes;
+                }
+            }
+
+            stats->in.stats[i].packets += packets;
+            stats->in.stats[i].bytes += bytes;
+        }
+    }
+
+    /* Out rules are more complicated because they do not correspond to
+     * directly to OpenFlow flows.  Normally, when the exact match flows
+     * expire the associated stats get aggregated and the out rules are
+     * lost.  As a result, we record the stats as these flows expire
+     * and then add the current data for unexpired flows. */
+
+    init_stats_output(&info->out_stats, &stats->out);
+
+    if (has_out_rules(info)) {
+        for (i = 0; i < stats->out.n_rules; i++) {
+            if (stats->out.rules[i]) {
+                struct flow_tracker *tracker;
+
+                /* Add stats collected from flow expiry events. */
+                stats->out.stats[i].packets += info->out_stats.stats[i].packets;
+                stats->out.stats[i].bytes += info->out_stats.stats[i].bytes;
+
+                /* Get the stats for flows that haven't expired yet. */
+                HMAP_FOR_EACH(tracker, struct flow_tracker, node,
+                              &info->bridge_info->out_flows) {
+
+                    if (tracker->stats == &info->out_stats.stats[i]) {
+                        ofproto_get_flow_stats(info->ofproto, &tracker->flow, 0,
+                                               true, &packets, &bytes);
+
+                        stats->out.stats[i].packets += packets;
+                        stats->out.stats[i].bytes += bytes;
+                    }
+                }
+            }
+        }
+    } else {
+        /* If we are just permitting all traffic then use the port transmit
+         * statistics.  This avoids the overhead of tracking flows. */
+
+        get_port_tx_stats(info, &info->out_stats.stats[0],
+                          &stats->out.stats[0]);
+    }
+}
+
+static void
+init_stats_output(struct rules_stats *iface_stats, struct rules_stats *out)
+{
+    if (!out->stats) {
+        memcpy(out, iface_stats, sizeof *out);
+        out->stats = xcalloc(out->n_rules, sizeof *out->stats);
+    }
+}
+
+static void
+get_port_tx_stats(struct acl *info, struct stats *baseline, struct stats *stats)
+{
+    struct netdev_stats netdev_stats;
+    int retval;
+
+    /* If we can't retrieve any stats, it's because the port doesn't exist
+     * yet.  This will happen the first time that a port is created.  If
+     * the port doesn't exist yet then the counters are 0, so it is fine
+     * to ignore this error. */
+    retval = ofproto_get_port_stats(info->ofproto, info->cur_ofp_port,
+                                    &netdev_stats);
+    if (retval == 0) {
+        if (netdev_stats.tx_packets != UINT64_MAX) {
+            stats->packets += netdev_stats.tx_packets;
+            if (baseline) {
+                stats->packets -= baseline->packets;
+            }
+        } else {
+            VLOG_WARN("could not retrieve packet counts, statistics may be "
+                      "inaccurate; port: %s", info->port_name);
+        }
+        if (netdev_stats.tx_bytes != UINT64_MAX) {
+            stats->bytes += netdev_stats.tx_bytes;
+            if (baseline) {
+                stats->bytes -= baseline->bytes;
+            }
+        } else {
+            VLOG_WARN("could not retrieve byte counts, statistics may be "
+                      "inaccurate; port: %s", info->port_name);
+        }
+    }
+}
+
+void
+acl_stats_to_string(const char *port_name, struct acl_stats *stats,
+                    struct ds *out)
+{
+    ds_put_format(out, "%s:\n", port_name);
+
+    ds_put_cstr(out, " Ingress rules:\n");
+    format_stats_output(&stats->in, out);
+
+    ds_put_cstr(out, " Egress rules:\n");
+    format_stats_output(&stats->out, out);
+}
+
+static void
+format_stats_output(struct rules_stats *stats, struct ds *out)
+{
+    int i;
+    uint32_t wildcards;
+    char *flow_str;
+    int str_len;
+
+    for (i = 0; i < stats->n_rules; i++) {
+        if (stats->rules[i]) {
+            ds_put_cstr(out, stats->rules[i]->permit ? "  permit" : "  deny");
+
+            /* We implicitly wildcard in_port. */
+            wildcards = stats->rules[i]->wildcards;
+            wildcards |= OFPFW_IN_PORT;
+
+            if (wildcards != OFPFW_ALL) {
+                flow_str = flow_wildcard_to_string(&stats->rules[i]->flow,
+                                                   wildcards, 0);
+
+                /* Knock off the final comma. */
+                str_len = strlen(flow_str);
+                if (str_len > 0) {
+                    flow_str[str_len - 1] = '\0';
+                }
+
+                ds_put_format(out, " %s", flow_str);
+                free(flow_str);
+            }
+
+            ds_put_format(out, "; packets: %"PRIu64"; bytes: %"PRIu64"\n",
+                          stats->stats[i].packets, stats->stats[i].bytes);
+        }
+    }
+}
+
+void
+acl_stats_destroy(struct acl_stats *stats)
+{
+    free(stats->in.stats);
+    free(stats->out.stats);
+    free(stats);
+}
diff --git a/vswitchd/acl.h b/vswitchd/acl.h
new file mode 100644
index 0000000..55f6a61
--- /dev/null
+++ b/vswitchd/acl.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2009 Nicira Networks.
+ *
+ * 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:
+ *
+ *     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.
+ */
+
+#ifndef ACL_H
+#define ACL_H 1
+
+#include <stdint.h>
+
+#include "dynamic-string.h"
+#include "flow.h"
+#include "ofproto/ofproto.h"
+
+struct acl;
+struct acl_engine;
+struct acl_stats;
+
+void acl_reconfigure(void);
+
+void acl_bridge_init(struct acl_engine **bridge_info);
+void acl_bridge_reconfigure(struct acl_engine *bridge_info, bool acls_active);
+bool acl_bridge_is_active(struct acl_engine *bridge_info);
+void acl_bridge_destroy(struct acl_engine *bridge_info);
+
+void acl_iface_init(struct acl_engine *bridge_info, const char *port_name,
+                    struct ofproto *ofproto, struct acl **info);
+void acl_iface_reconfigure(struct acl *info, uint16_t ofp_port);
+void acl_iface_destroy(struct acl *info);
+
+bool acl_iface_output_filter(struct acl *info, const flow_t *flow,
+                             bool *userspace);
+
+void acl_stats_init(struct acl_stats **stats);
+void acl_stats_add_iface(struct acl *info, struct acl_stats *stats);
+void acl_stats_to_string(const char *port_name, struct acl_stats *stats,
+                         struct ds *out);
+void acl_stats_destroy(struct acl_stats *stats);
+
+void acl_flow_expire_cb(struct acl_engine *bridge_info, const flow_t *flow,
+                        uint64_t packets, uint64_t bytes);
+
+#endif /* acl.h */
diff --git a/vswitchd/automake.mk b/vswitchd/automake.mk
index 8e27fc2..1abce6f 100644
--- a/vswitchd/automake.mk
+++ b/vswitchd/automake.mk
@@ -9,6 +9,8 @@ DISTCLEANFILES += \
 	vswitchd/ovs-brcompatd.8
 
 vswitchd_ovs_vswitchd_SOURCES = \
+	vswitchd/acl.c \
+	vswitchd/acl.h \
 	vswitchd/bridge.c \
 	vswitchd/bridge.h \
 	vswitchd/mgmt.c \
diff --git a/vswitchd/bridge.c b/vswitchd/bridge.c
index 8a2bf42..3e4eefe 100644
--- a/vswitchd/bridge.c
+++ b/vswitchd/bridge.c
@@ -29,6 +29,7 @@
 #include <sys/socket.h>
 #include <sys/types.h>
 #include <unistd.h>
+#include "acl.h"
 #include "bitmap.h"
 #include "cfg.h"
 #include "coverage.h"
@@ -78,6 +79,7 @@ struct iface {
     char *name;                 /* Host network device name. */
     tag_type tag;               /* Tag associated with this interface. */
     long long delay_expires;    /* Time after which 'enabled' may change. */
+    struct acl *acl;            /* ACL state information for this interface. */
 
     /* These members are valid only after bridge_reconfigure() causes them to
      * be initialized.*/
@@ -183,6 +185,9 @@ struct bridge {
     /* Spanning tree. */
     struct stp *stp;
     long long int stp_last_tick;
+
+    /* ACL's */
+    struct acl_engine *acl;
 };
 
 /* List of all bridges. */
@@ -209,6 +214,7 @@ static uint64_t bridge_pick_datapath_id(struct bridge *,
 static struct iface *bridge_get_local_iface(struct bridge *);
 static uint64_t dpid_from_hash(const void *, size_t nbytes);
 
+static void bridge_unixctl_acl_show(struct unixctl_conn *, const char *args);
 static void bridge_unixctl_fdb_show(struct unixctl_conn *, const char *args);
 
 static void bond_init(void);
@@ -284,6 +290,7 @@ bridge_init(void)
     struct svec dpif_names;
     size_t i;
 
+    unixctl_command_register("acl/show", bridge_unixctl_acl_show);
     unixctl_command_register("fdb/show", bridge_unixctl_fdb_show);
 
     svec_init(&dpif_names);
@@ -442,7 +449,7 @@ bridge_reconfigure(void)
 {
     struct svec old_br, new_br;
     struct bridge *br, *next;
-    size_t i;
+    size_t i, j;
 
     COVERAGE_INC(bridge_reconfigure);
 
@@ -478,6 +485,8 @@ bridge_reconfigure(void)
     bridge_configure_ssl();
 #endif
 
+    acl_reconfigure();
+
     /* Reconfigure all bridges. */
     LIST_FOR_EACH (br, struct bridge, node, &all_bridges) {
         bridge_reconfigure_one(br);
@@ -626,6 +635,18 @@ bridge_reconfigure(void)
         brstp_reconfigure(br);
         iterate_and_prune_ifaces(br, set_iface_policing, NULL);
     }
+    LIST_FOR_EACH (br, struct bridge, node, &all_bridges) {
+        for (i = 0; i < br->n_ports; i++) {
+            struct port *port = br->ports[i];
+            if (!port->is_mirror_output_port) {
+                for (j = 0; j < port->n_ifaces; j++) {
+                    struct iface *iface = port->ifaces[j];
+                    acl_iface_reconfigure(iface->acl,
+                                         odp_port_to_ofp_port(iface->dp_ifidx));
+                }
+            }
+        }
+    }
 }
 
 static void
@@ -859,6 +880,41 @@ bridge_get_local_iface(struct bridge *br)
 
 /* Bridge unixctl user interface functions. */
 static void
+bridge_unixctl_acl_show(struct unixctl_conn *conn, const char *args)
+{
+    struct ds ds = DS_EMPTY_INITIALIZER;
+    const struct bridge *br;
+    struct acl_stats *stats;
+    int i, j;
+
+    br = bridge_lookup(args);
+    if (!br) {
+        unixctl_command_reply(conn, 501, "no such bridge");
+        return;
+    }
+
+    if (!acl_bridge_is_active(br->acl)) {
+        unixctl_command_reply(conn, 501, "acls not active");
+        return;
+    }
+
+    for (i = 0; i < br->n_ports; i++) {
+        struct port *port = br->ports[i];
+        acl_stats_init(&stats);
+        for (j = 0; j < port->n_ifaces; j++) {
+            struct iface *iface = port->ifaces[j];
+            acl_stats_add_iface(iface->acl, stats);
+        }
+        acl_stats_to_string(port->name, stats, &ds);
+        acl_stats_destroy(stats);
+    }
+
+    unixctl_command_reply(conn, 200, ds_cstr(&ds));
+
+    ds_destroy(&ds);
+}
+
+static void
 bridge_unixctl_fdb_show(struct unixctl_conn *conn, const char *args)
 {
     struct ds ds = DS_EMPTY_INITIALIZER;
@@ -932,6 +988,8 @@ bridge_create(const char *name)
     br->flush = false;
     br->bond_next_rebalance = time_msec() + 10000;
 
+    acl_bridge_init(&br->acl);
+
     list_push_back(&all_bridges, &br->node);
 
     VLOG_INFO("created bridge %s on %s", br->name, dpif_name(br->dpif));
@@ -945,6 +1003,8 @@ bridge_destroy(struct bridge *br)
     if (br) {
         int error;
 
+        acl_bridge_destroy(br->acl);
+
         while (br->n_ports > 0) {
             port_destroy(br->ports[br->n_ports - 1]);
         }
@@ -1155,6 +1215,7 @@ bridge_reconfigure_one(struct bridge *br)
     svec_destroy(&old_snoops);
 
     mirror_reconfigure(br);
+    acl_bridge_reconfigure(br->acl, !bridge_get_controller(br));
 }
 
 static void
@@ -1286,8 +1347,12 @@ bridge_reconfigure_controller(struct bridge *br)
         /* Set up a flow that matches every packet and directs them to
          * OFPP_NORMAL (which goes to us). */
         memset(&flow, 0, sizeof flow);
-        ofproto_add_flow(br->ofproto, &flow, OFPFW_ALL, 0,
-                         ofp_normal_action(), 1, 0, true);
+        if (!acl_bridge_is_active(br->acl)) {
+            ofproto_add_flow(br->ofproto, &flow, OFPFW_ALL, 0,
+                             ofp_normal_action(), 1, 0, true);
+        } else {
+            ofproto_delete_flow(br->ofproto, &flow, OFPFW_ALL, 0);
+        }
 
         ofproto_set_in_band(br->ofproto, false);
         ofproto_set_max_backoff(br->ofproto, 1);
@@ -1641,7 +1706,7 @@ port_includes_vlan(const struct port *port, uint16_t vlan)
 static size_t
 compose_dsts(const struct bridge *br, const flow_t *flow, uint16_t vlan,
              const struct port *in_port, const struct port *out_port,
-             struct dst dsts[], tag_type *tags)
+             struct dst dsts[], tag_type *tags, bool *userspace_processing)
 {
     mirror_mask_t mirrors = in_port->src_mirrors;
     struct dst *dst = dsts;
@@ -1649,18 +1714,22 @@ compose_dsts(const struct bridge *br, const flow_t *flow, uint16_t vlan,
 
     *tags |= in_port->stp_state_tag;
     if (out_port == FLOOD_PORT) {
-        /* XXX use ODP_FLOOD if no vlans or bonding. */
-        /* XXX even better, define each VLAN as a datapath port group */
         for (i = 0; i < br->n_ports; i++) {
             struct port *port = br->ports[i];
             if (port != in_port && port_includes_vlan(port, vlan)
                 && !port->is_mirror_output_port
-                && set_dst(dst, flow, in_port, port, tags)) {
+                && set_dst(dst, flow, in_port, port, tags)
+                && acl_iface_output_filter(iface_from_dp_ifidx(br,
+                                           dst->dp_ifidx)->acl, flow,
+                                           userspace_processing)) {
                 mirrors |= port->dst_mirrors;
                 dst++;
             }
         }
-    } else if (out_port && set_dst(dst, flow, in_port, out_port, tags)) {
+    } else if (out_port && set_dst(dst, flow, in_port, out_port, tags)
+               && acl_iface_output_filter(iface_from_dp_ifidx(br,
+                                          dst->dp_ifidx)->acl, flow,
+                                          userspace_processing)) {
         mirrors |= out_port->dst_mirrors;
         dst++;
     }
@@ -1714,14 +1783,16 @@ print_dsts(const struct dst *dsts, size_t n)
 static void
 compose_actions(struct bridge *br, const flow_t *flow, uint16_t vlan,
                 const struct port *in_port, const struct port *out_port,
-                tag_type *tags, struct odp_actions *actions)
+                tag_type *tags, struct odp_actions *actions,
+                bool *userspace_processing)
 {
     struct dst dsts[DP_MAX_PORTS * (MAX_MIRRORS + 1)];
     size_t n_dsts;
     const struct dst *p;
     uint16_t cur_vlan;
 
-    n_dsts = compose_dsts(br, flow, vlan, in_port, out_port, dsts, tags);
+    n_dsts = compose_dsts(br, flow, vlan, in_port, out_port, dsts, tags,
+                          userspace_processing);
 
     cur_vlan = ntohs(flow->dl_vlan);
     for (p = dsts; p < &dsts[n_dsts]; p++) {
@@ -1762,6 +1833,7 @@ process_flow(struct bridge *br, const flow_t *flow,
     struct port *in_port;
     struct port *out_port = NULL; /* By default, drop the packet/flow. */
     int vlan;
+    bool userspace_processing = false;
 
     /* Find the interface and port structure for the received packet. */
     in_iface = iface_from_dp_ifidx(br, flow->in_port);
@@ -1913,9 +1985,10 @@ process_flow(struct bridge *br, const flow_t *flow,
     }
 
 done:
-    compose_actions(br, flow, vlan, in_port, out_port, tags, actions);
+    compose_actions(br, flow, vlan, in_port, out_port, tags, actions,
+                    &userspace_processing);
 
-    return true;
+    return !userspace_processing;
 }
 
 /* Careful: 'opp' is in host byte order and opp->port_no is an OFP port
@@ -2022,12 +2095,21 @@ bridge_account_checkpoint_ofhook_cb(void *br_)
     }
 }
 
+static void
+bridge_flow_expire_ofhook_cb(const flow_t *flow, uint64_t packets,
+                             uint64_t bytes, void *br_)
+{
+    struct bridge *br = br_;
+
+    acl_flow_expire_cb(br->acl, flow, packets, bytes);
+}
+
 static struct ofhooks bridge_ofhooks = {
     bridge_port_changed_ofhook_cb,
     bridge_normal_ofhook_cb,
     bridge_account_flow_ofhook_cb,
     bridge_account_checkpoint_ofhook_cb,
-    NULL
+    bridge_flow_expire_ofhook_cb
 };
 
 /* Bonding functions. */
@@ -2990,6 +3072,8 @@ iface_create(struct port *port, const char *name)
 
     VLOG_DBG("attached network device %s to port %s", iface->name, port->name);
 
+    acl_iface_init(port->bridge->acl, port->name, port->bridge->ofproto,
+                   &iface->acl);
     bridge_flush(port->bridge);
 }
 
@@ -3002,6 +3086,8 @@ iface_destroy(struct iface *iface)
         bool del_active = port->active_iface == iface->port_ifidx;
         struct iface *del;
 
+        acl_iface_destroy(iface->acl);
+
         if (iface->dp_ifidx >= 0) {
             port_array_set(&br->ifaces, iface->dp_ifidx, NULL);
         }
diff --git a/vswitchd/ovs-vswitchd.8.in b/vswitchd/ovs-vswitchd.8.in
index 6941bdf..2cc13e5 100644
--- a/vswitchd/ovs-vswitchd.8.in
+++ b/vswitchd/ovs-vswitchd.8.in
@@ -33,6 +33,9 @@ NIC bonding with automatic fail-over and source MAC-based TX load
 balancing ("SLB").
 .
 .IP \(bu
+Access control lists.
+.
+.IP \(bu
 802.1Q VLAN support.
 .
 .IP \(bu
diff --git a/vswitchd/ovs-vswitchd.conf.5.in b/vswitchd/ovs-vswitchd.conf.5.in
index 665d3d0..17bc6a3 100644
--- a/vswitchd/ovs-vswitchd.conf.5.in
+++ b/vswitchd/ovs-vswitchd.conf.5.in
@@ -271,6 +271,187 @@ mirror.mybr.a.output.port=eth3
         
 .fi
 .RE
+.SS "Access Control Lists"
+Access control lists allow restrictions to be placed on the types of
+traffic that may flow through the switch.  These restrictions can be
+applied to ports or groups of ports on either switch ingress or egress.
+Note that ACLs are in effect only when remote management is not
+configured.  If a controller is in use, then it can be used to apply
+any necessary restrictions.
+
+.ST "Rules"
+A rule defines an action to be taken when a particular flow is encountered.
+These rules can be aggregated into groups to form access control lists.
+A rule consists of a command plus zero or more pairs of network protocol
+fields and values.  Any fields not specified are wildcarded.  See the next
+section for an example of a rule.
+
+The available components of a rule are:
+
+.IP \fIcommand\fR
+The action to be taken when this flow is encountered.  The allowed
+values are \fBpermit\fR, \fBallow\fR, and \fBdeny\fR.  \fBpermit\fR
+and \fBallow\fR cause the packet to be handled according to normal
+packet processing rules.  \fBdeny\fR drops the packet.
+
+.IP \fBdl_vlan\ \fIvlan\fR
+Matches IEEE 802.1q Virtual LAN tag \fIvlan\fR.  Specify \fB0xffff\fR
+as \fIvlan\fR to match packets that are not tagged with a Virtual LAN;
+otherwise, specify a number between 0 and 4095, inclusive, as the
+12-bit VLAN ID to match.
+
+.IP \fBdl_src\ \fImac\fR
+Matches Ethernet source address \fImac\fR, which is specified as 6 pairs 
+of hexadecimal digits delimited by colons (e.g. \fB00:0A:E4:25:6B:B0\fR).
+
+.IP \fBdl_dst\ \fImac\fR
+Matches Ethernet destination address \fImac\fR.
+
+.IP \fBdl_type\ \fIethertype\fR
+Matches Ethernet protocol type \fIethertype\fR, which is specified as an
+integer between 0 and 65535, inclusive, either in decimal or as a 
+hexadecimal number prefixed by \fB0x\fR (e.g. \fB0x0806\fR to match ARP 
+packets).
+
+.IP \fBnw_src\ \fIip\fR[\fB/\fInetmask\fR]
+Matches IPv4 source address \fIip\fR, which may be specified as an
+IP address (e.g. \fB192.168.1.1\fR).  The optional \fInetmask\fR allows
+restricting a match to an IPv4 address prefix.  The netmask may be
+specified as a dotted  quad (e.g. \fB192.168.1.0/255.255.255.0\fR) or
+as a CIDR block (e.g. \fB192.168.1.0/24\fR).
+
+.IP \fBnw_dst\ \fIip\fR[\fB/\fInetmask\fR]
+Matches IPv4 destination address \fIip\fR.
+
+.IP \fBnw_proto\ \fIproto\fR
+Matches IP protocol type \fIproto\fR, which is specified as a decimal 
+number between 0 and 255, inclusive (e.g. 6 to match TCP packets).
+
+.IP \fBtp_src\ \fIport\fR
+Matches UDP or TCP source port \fIport\fR, which is specified as a decimal 
+number between 0 and 65535, inclusive (e.g. 80 to match packets originating 
+from a HTTP server).
+
+.IP \fBtp_dst\ \fIport\fR
+Matches UDP or TCP destination port \fIport\fR.
+
+.IP \fBicmp_type\ \fItype\fR
+Matches ICMP message with \fItype\fR, which is specified as a decimal 
+number between 0 and 255, inclusive.
+
+.IP \fBicmp_code\ \fIcode\fR
+Matches ICMP messages with \fIcode\fR.
+
+.IP \fBar_op\ \fIop\fR
+Matches ARP messages with operation \fIop\fR, which may be either
+\fBrequest\fR or \fBreply\fR.
+
+.IP \fBar_spa\ \fIip\fR[\fB/\fInetmask\fR]
+Matches ARP messages with IPv4 sender protocol address \fIip\fR.
+
+.IP \fBar_tpa\ \fIip\fR[\fB/\fInetmask\fR]
+Matches ARP messages with IPv4 target protocol address \fIip\fR.
+
+.IP \fBar_sha\ \fImac\fR
+Matches ARP messages with Ethernet source hardware address \fImac\fR.
+
+.IP \fBar_tha\ \fImac\fR
+Matches ARP messages with Ethernet target hardware address \fImac\fR.
+
+.IP \fBproto\ \fIprotocol\fR
+Provides a shorthand notations for common protocols.  The available values
+for \fIprotocol\fR are:
+
+.RS
+
+.IP \fBip\fR
+Same as \fBdl_type=0x0800\fR.
+
+.IP \fBicmp\fR
+Same as \fBdl_type=0x0800,nw_proto=1\fR.
+
+.IP \fBtcp\fR
+Same as \fBdl_type=0x0800,nw_proto=6\fR.
+
+.IP \fBudp\fR
+Same as \fBdl_type=0x0800,nw_proto=17\fR.
+
+.IP \fBarp\fR
+Same as \fBdl_type=0x0806\fR.
+
+.RE
+.PP
+
+.ST "Rule Groups"
+Access control lists can be configured as groups of rules that
+are matched against switch traffic.  These groups are ordered
+lists that are executed on a first match basis.
+
+A rule group is created by specifying rules as values for the key
+\fBacl.group.\fIname\fB.\fIorder\fR.  Each key defines a single rule
+in group \fIname\fR that is executed in \fIorder\fR from lowest to highest.
+In addition to the listed rules, each group ends with an implicit deny rule
+that drops all traffic not otherwise matched.
+
+The following rule group allows TCP/IP port \fB80\fR and \fBARP\fR
+traffic while dropping everything else:
+.PP
+.RS
+.nf
+
+acl.group.http.1=permit proto tcp tp_dst 80
+acl.group.http.2=permit proto arp
+acl.group.http.3=deny
+
+.RE
+.fi
+.ST "Ports"
+Before an ACL will filter any traffic, the rule group must be applied to
+one or more ports.  To apply an ACL to a port, set the key
+\fBacl.port.\fIport\fB.\fIdirection\fR to a rule group.  \fIport\fR is
+an interface that is attached to the bridge and \fIdirection\fR is
+either \fBin\fR or \fBout\fR, which respectively filter traffic either
+upon ingress or egress from the switch.  One \fBin\fR and one \fBout\fR
+rule group may be defined for each port.  If no rule group is defined
+for a port then it will by default pass all traffic.
+
+The following applies the rule group from the previous section to
+\fBeth0\fR.  This could be used to filter traffic for an HTTP server
+attached to \fBeth0\fR.
+.PP
+.RS
+.nf
+
+acl.port.eth0.out=http
+
+.RE
+.fi
+.ST "Default Rules"
+Sometimes it is more convenient to specify rules that apply to groups
+of ports.  Default rules may be used to apply ACLs to all ports with
+names matching a given pattern.  These are called default rules
+because they will only be applied if no other rule is assigned
+to the port.  Default rules consist of an ordered set of directives
+that specify a glob to match against port names and rule groups to
+apply in the ingress and egress directions.  For a given port,
+default rules are applied on a first match basis.  A maximum of one
+ingress and one egress rule is applied to each port.  A default rule
+can be defined by setting the key
+\fBacl.default.\fIorder\fB.match\fR to a \fIglob\fR.  It is then
+possible to apply rule groups with the key
+\fBacl.default.\fIorder\fB.\fIdirection\fR.
+
+The following applies the rule group from the previous section to all
+ethernet devices:
+.PP
+.RS
+.nf
+
+acl.default.1.match=eth*
+acl.default.1.out=http
+
+.fi
+.RE
 .SS "Port Rate-Limiting"
 Traffic policing and shaping are configured on physical ports.  Policing
 defines a hard limit at which traffic that exceeds the specified rate is
-- 
1.6.0.4





More information about the discuss mailing list