nicira-ext: Support matching IPv6 Neighbor Discovery messages.
[sliver-openvswitch.git] / lib / ofp-parse.c
index 1c7ce4f..3fac474 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2010 Nicira Networks.
+ * Copyright (c) 2010, 2011 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -25,6 +25,7 @@
 #include "byte-order.h"
 #include "dynamic-string.h"
 #include "netdev.h"
+#include "multipath.h"
 #include "nx-match.h"
 #include "ofp-util.h"
 #include "ofpbuf.h"
@@ -127,6 +128,75 @@ str_to_ip(const char *str_, ovs_be32 *ip, ovs_be32 *maskp)
     free(str);
 }
 
+static void
+str_to_tun_id(const char *str, ovs_be64 *tun_idp, ovs_be64 *maskp)
+{
+    uint64_t tun_id, mask;
+    char *tail;
+
+    errno = 0;
+    tun_id = strtoull(str, &tail, 0);
+    if (errno || (*tail != '\0' && *tail != '/')) {
+        goto error;
+    }
+
+    if (*tail == '/') {
+        mask = strtoull(tail + 1, &tail, 0);
+        if (errno || *tail != '\0') {
+            goto error;
+        }
+    } else {
+        mask = UINT64_MAX;
+    }
+
+    *tun_idp = htonll(tun_id);
+    *maskp = htonll(mask);
+    return;
+
+error:
+    ovs_fatal(0, "%s: bad syntax for tunnel id", str);
+}
+
+static void
+str_to_ipv6(const char *str_, struct in6_addr *addrp, struct in6_addr *maskp)
+{
+    char *str = xstrdup(str_);
+    char *save_ptr = NULL;
+    const char *name, *netmask;
+    struct in6_addr addr, mask;
+    int retval;
+
+    name = strtok_r(str, "/", &save_ptr);
+    retval = name ? lookup_ipv6(name, &addr) : EINVAL;
+    if (retval) {
+        ovs_fatal(0, "%s: could not convert to IPv6 address", str);
+    }
+
+    netmask = strtok_r(NULL, "/", &save_ptr);
+    if (netmask) {
+        int prefix = atoi(netmask);
+        if (prefix <= 0 || prefix > 128) {
+            ovs_fatal(0, "%s: network prefix bits not between 1 and 128",
+                      str);
+        } else {
+            mask = ipv6_create_mask(prefix);
+        }
+    } else {
+        mask = in6addr_exact;
+    }
+    *addrp = ipv6_addr_bitand(&addr, &mask);
+
+    if (maskp) {
+        *maskp = mask;
+    } else {
+        if (!ipv6_mask_is_exact(&mask)) {
+            ovs_fatal(0, "%s: netmask not allowed here", str_);
+        }
+    }
+
+    free(str);
+}
+
 static void *
 put_action(struct ofpbuf *b, size_t size, uint16_t type)
 {
@@ -194,26 +264,66 @@ parse_port_name(const char *name, uint16_t *port)
 static void
 str_to_action(char *str, struct ofpbuf *b)
 {
-    char *act, *arg;
-    char *saveptr = NULL;
     bool drop = false;
     int n_actions;
+    char *pos;
 
-    for (act = strtok_r(str, ", \t\r\n", &saveptr), n_actions = 0; act;
-         act = strtok_r(NULL, ", \t\r\n", &saveptr), n_actions++)
-    {
+    pos = str;
+    n_actions = 0;
+    for (;;) {
+        char *act, *arg;
+        size_t actlen;
         uint16_t port;
 
+        pos += strspn(pos, ", \t\r\n");
+        if (*pos == '\0') {
+            break;
+        }
+
         if (drop) {
             ovs_fatal(0, "Drop actions must not be followed by other actions");
         }
 
-        /* Arguments are separated by colons */
-        arg = strchr(act, ':');
-        if (arg) {
-            *arg = '\0';
-            arg++;
+        act = pos;
+        actlen = strcspn(pos, ":(, \t\r\n");
+        if (act[actlen] == ':') {
+            /* The argument can be separated by a colon. */
+            size_t arglen;
+
+            arg = act + actlen + 1;
+            arglen = strcspn(arg, ", \t\r\n");
+            pos = arg + arglen + (arg[arglen] != '\0');
+            arg[arglen] = '\0';
+        } else if (act[actlen] == '(') {
+            /* The argument can be surrounded by balanced parentheses.  The
+             * outermost set of parentheses is removed. */
+            int level = 1;
+            size_t arglen;
+
+            arg = act + actlen + 1;
+            for (arglen = 0; level > 0; arglen++) {
+                switch (arg[arglen]) {
+                case '\0':
+                    ovs_fatal(0, "unbalanced parentheses in argument to %s "
+                              "action", act);
+
+                case '(':
+                    level++;
+                    break;
+
+                case ')':
+                    level--;
+                    break;
+                }
+            }
+            arg[arglen - 1] = '\0';
+            pos = arg + arglen;
+        } else {
+            /* There might be no argument at all. */
+            arg = NULL;
+            pos = act + actlen + (act[actlen] != '\0');
         }
+        act[actlen] = '\0';
 
         if (!strcasecmp(act, "mod_vlan_vid")) {
             struct ofp_action_vlan_vid *va;
@@ -257,12 +367,22 @@ str_to_action(char *str, struct ofpbuf *b)
             nar->vendor = htonl(NX_VENDOR_ID);
             nar->subtype = htons(NXAST_RESUBMIT);
             nar->in_port = htons(str_to_u32(arg));
-        } else if (!strcasecmp(act, "set_tunnel")) {
-            struct nx_action_set_tunnel *nast;
-            nast = put_action(b, sizeof *nast, OFPAT_VENDOR);
-            nast->vendor = htonl(NX_VENDOR_ID);
-            nast->subtype = htons(NXAST_SET_TUNNEL);
-            nast->tun_id = htonl(str_to_u32(arg));
+        } else if (!strcasecmp(act, "set_tunnel")
+                   || !strcasecmp(act, "set_tunnel64")) {
+            uint64_t tun_id = str_to_u64(arg);
+            if (!strcasecmp(act, "set_tunnel64") || tun_id > UINT32_MAX) {
+                struct nx_action_set_tunnel64 *nast64;
+                nast64 = put_action(b, sizeof *nast64, OFPAT_VENDOR);
+                nast64->vendor = htonl(NX_VENDOR_ID);
+                nast64->subtype = htons(NXAST_SET_TUNNEL64);
+                nast64->tun_id = htonll(tun_id);
+            } else {
+                struct nx_action_set_tunnel *nast;
+                nast = put_action(b, sizeof *nast, OFPAT_VENDOR);
+                nast->vendor = htonl(NX_VENDOR_ID);
+                nast->subtype = htons(NXAST_SET_TUNNEL);
+                nast->tun_id = htonl(tun_id);
+            }
         } else if (!strcasecmp(act, "drop_spoofed_arp")) {
             struct nx_action_header *nah;
             nah = put_action(b, sizeof *nah, OFPAT_VENDOR);
@@ -324,6 +444,10 @@ str_to_action(char *str, struct ofpbuf *b)
             struct nx_action_reg_load *load;
             load = ofpbuf_put_uninit(b, sizeof *load);
             nxm_parse_reg_load(load, arg);
+        } else if (!strcasecmp(act, "multipath")) {
+            struct nx_action_multipath *nam;
+            nam = ofpbuf_put_uninit(b, sizeof *nam);
+            multipath_parse(nam, arg);
         } else if (!strcasecmp(act, "output")) {
             put_output_action(b, str_to_u32(arg));
         } else if (!strcasecmp(act, "enqueue")) {
@@ -360,6 +484,7 @@ str_to_action(char *str, struct ofpbuf *b)
         } else {
             ovs_fatal(0, "Unknown action: %s", act);
         }
+        n_actions++;
     }
 }
 
@@ -375,9 +500,14 @@ parse_protocol(const char *name, const struct protocol **p_out)
     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 },
+        { "icmp", ETH_TYPE_IP, IPPROTO_ICMP },
+        { "tcp", ETH_TYPE_IP, IPPROTO_TCP },
+        { "udp", ETH_TYPE_IP, IPPROTO_UDP },
+        { "ipv6", ETH_TYPE_IPV6, 0 },
+        { "ip6", ETH_TYPE_IPV6, 0 },
+        { "icmp6", ETH_TYPE_IPV6, IPPROTO_ICMPV6 },
+        { "tcp6", ETH_TYPE_IPV6, IPPROTO_TCP },
+        { "udp6", ETH_TYPE_IPV6, IPPROTO_UDP },
     };
     const struct protocol *p;
 
@@ -392,7 +522,7 @@ parse_protocol(const char *name, const struct protocol **p_out)
 }
 
 #define FIELDS                                              \
-    FIELD(F_TUN_ID,      "tun_id",      FWW_TUN_ID)         \
+    FIELD(F_TUN_ID,      "tun_id",      0)                  \
     FIELD(F_IN_PORT,     "in_port",     FWW_IN_PORT)        \
     FIELD(F_DL_VLAN,     "dl_vlan",     0)                  \
     FIELD(F_DL_VLAN_PCP, "dl_vlan_pcp", 0)                  \
@@ -406,7 +536,14 @@ parse_protocol(const char *name, const struct protocol **p_out)
     FIELD(F_TP_SRC,      "tp_src",      FWW_TP_SRC)         \
     FIELD(F_TP_DST,      "tp_dst",      FWW_TP_DST)         \
     FIELD(F_ICMP_TYPE,   "icmp_type",   FWW_TP_SRC)         \
-    FIELD(F_ICMP_CODE,   "icmp_code",   FWW_TP_DST)
+    FIELD(F_ICMP_CODE,   "icmp_code",   FWW_TP_DST)         \
+    FIELD(F_ARP_SHA,     "arp_sha",     FWW_ARP_SHA)        \
+    FIELD(F_ARP_THA,     "arp_tha",     FWW_ARP_THA)        \
+    FIELD(F_IPV6_SRC,    "ipv6_src",    0)                  \
+    FIELD(F_IPV6_DST,    "ipv6_dst",    0)                  \
+    FIELD(F_ND_TARGET,   "nd_target",   FWW_ND_TARGET)      \
+    FIELD(F_ND_SLL,      "nd_sll",      FWW_ARP_SHA)        \
+    FIELD(F_ND_TLL,      "nd_tll",      FWW_ARP_THA)
 
 enum field_index {
 #define FIELD(ENUM, NAME, WILDCARD) ENUM,
@@ -446,12 +583,15 @@ parse_field_value(struct cls_rule *rule, enum field_index index,
                   const char *value)
 {
     uint8_t mac[ETH_ADDR_LEN];
+    ovs_be64 tun_id, tun_mask;
     ovs_be32 ip, mask;
+    struct in6_addr ipv6, ipv6_mask;
     uint16_t port_no;
 
     switch (index) {
     case F_TUN_ID:
-        cls_rule_set_tun_id(rule, htonl(str_to_u32(value)));
+        str_to_tun_id(value, &tun_id, &tun_mask);
+        cls_rule_set_tun_id_masked(rule, tun_id, tun_mask);
         break;
 
     case F_IN_PORT:
@@ -520,6 +660,41 @@ parse_field_value(struct cls_rule *rule, enum field_index index,
         cls_rule_set_icmp_code(rule, str_to_u32(value));
         break;
 
+    case F_ARP_SHA:
+        str_to_mac(value, mac);
+        cls_rule_set_arp_sha(rule, mac);
+        break;
+
+    case F_ARP_THA:
+        str_to_mac(value, mac);
+        cls_rule_set_arp_tha(rule, mac);
+        break;
+
+    case F_IPV6_SRC:
+        str_to_ipv6(value, &ipv6, &ipv6_mask);
+        cls_rule_set_ipv6_src_masked(rule, &ipv6, &ipv6_mask);
+        break;
+
+    case F_IPV6_DST:
+        str_to_ipv6(value, &ipv6, &ipv6_mask);
+        cls_rule_set_ipv6_dst_masked(rule, &ipv6, &ipv6_mask);
+        break;
+
+    case F_ND_TARGET:
+        str_to_ipv6(value, &ipv6, NULL);
+        cls_rule_set_nd_target(rule, ipv6);
+        break;
+
+    case F_ND_SLL:
+        str_to_mac(value, mac);
+        cls_rule_set_arp_sha(rule, mac);
+        break;
+
+    case F_ND_TLL:
+        str_to_mac(value, mac);
+        cls_rule_set_arp_tha(rule, mac);
+        break;
+
     case N_FIELDS:
         NOT_REACHED();
     }
@@ -624,6 +799,12 @@ parse_ofp_str(struct flow_mod *fm, uint8_t *table_idx,
                         cls_rule_set_nw_src_masked(&fm->cr, 0, 0);
                     } else if (f->index == F_NW_DST) {
                         cls_rule_set_nw_dst_masked(&fm->cr, 0, 0);
+                    } else if (f->index == F_IPV6_SRC) {
+                        cls_rule_set_ipv6_src_masked(&fm->cr,
+                                &in6addr_any, &in6addr_any);
+                    } else if (f->index == F_IPV6_DST) {
+                        cls_rule_set_ipv6_dst_masked(&fm->cr,
+                                &in6addr_any, &in6addr_any);
                     } else if (f->index == F_DL_VLAN) {
                         cls_rule_set_any_vid(&fm->cr);
                     } else if (f->index == F_DL_VLAN_PCP) {