[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