openvswitch: 802.1AD Flow handling, actions, vlan parsing, netlink attributes
[cascardo/linux.git] / net / openvswitch / flow_netlink.c
index c78a6a1..8efa718 100644 (file)
@@ -808,6 +808,167 @@ int ovs_nla_put_tunnel_info(struct sk_buff *skb,
                                  ip_tunnel_info_af(tun_info));
 }
 
+static int encode_vlan_from_nlattrs(struct sw_flow_match *match,
+                                   const struct nlattr *a[],
+                                   bool is_mask, bool inner)
+{
+       __be16 tci = 0;
+       __be16 tpid = 0;
+
+       if (a[OVS_KEY_ATTR_VLAN])
+               tci = nla_get_be16(a[OVS_KEY_ATTR_VLAN]);
+
+       if (a[OVS_KEY_ATTR_ETHERTYPE])
+               tpid = nla_get_be16(a[OVS_KEY_ATTR_ETHERTYPE]);
+
+       if (likely(!inner)) {
+               SW_FLOW_KEY_PUT(match, eth.vlan.tpid, tpid, is_mask);
+               SW_FLOW_KEY_PUT(match, eth.vlan.tci, tci, is_mask);
+       } else {
+               SW_FLOW_KEY_PUT(match, eth.cvlan.tpid, tpid, is_mask);
+               SW_FLOW_KEY_PUT(match, eth.cvlan.tci, tci, is_mask);
+       }
+       return 0;
+}
+
+static int validate_vlan_from_nlattrs(const struct sw_flow_match *match,
+                                     u64 key_attrs, bool inner,
+                                     const struct nlattr **a, bool log)
+{
+       __be16 tci = 0;
+
+       if (!((key_attrs & (1 << OVS_KEY_ATTR_ETHERNET)) &&
+             (key_attrs & (1 << OVS_KEY_ATTR_ETHERTYPE)) &&
+              eth_type_vlan(nla_get_be16(a[OVS_KEY_ATTR_ETHERTYPE])))) {
+               /* Not a VLAN. */
+               return 0;
+       }
+
+       if (!((key_attrs & (1 << OVS_KEY_ATTR_VLAN)) &&
+             (key_attrs & (1 << OVS_KEY_ATTR_ENCAP)))) {
+               OVS_NLERR(log, "Invalid %s frame", (inner) ? "C-VLAN" : "VLAN");
+               return -EINVAL;
+       }
+
+       if (a[OVS_KEY_ATTR_VLAN])
+               tci = nla_get_be16(a[OVS_KEY_ATTR_VLAN]);
+
+       if (!(tci & htons(VLAN_TAG_PRESENT))) {
+               if (tci) {
+                       OVS_NLERR(log, "%s TCI does not have VLAN_TAG_PRESENT bit set.",
+                                 (inner) ? "C-VLAN" : "VLAN");
+                       return -EINVAL;
+               } else if (nla_len(a[OVS_KEY_ATTR_ENCAP])) {
+                       /* Corner case for truncated VLAN header. */
+                       OVS_NLERR(log, "Truncated %s header has non-zero encap attribute.",
+                                 (inner) ? "C-VLAN" : "VLAN");
+                       return -EINVAL;
+               }
+       }
+
+       return 1;
+}
+
+static int validate_vlan_mask_from_nlattrs(const struct sw_flow_match *match,
+                                          u64 key_attrs, bool inner,
+                                          const struct nlattr **a, bool log)
+{
+       __be16 tci = 0;
+       __be16 tpid = 0;
+       bool encap_valid = !!(match->key->eth.vlan.tci &
+                             htons(VLAN_TAG_PRESENT));
+       bool i_encap_valid = !!(match->key->eth.cvlan.tci &
+                               htons(VLAN_TAG_PRESENT));
+
+       if (!(key_attrs & (1 << OVS_KEY_ATTR_ENCAP))) {
+               /* Not a VLAN. */
+               return 0;
+       }
+
+       if ((!inner && !encap_valid) || (inner && !i_encap_valid)) {
+               OVS_NLERR(log, "Encap mask attribute is set for non-%s frame.",
+                         (inner) ? "C-VLAN" : "VLAN");
+               return -EINVAL;
+       }
+
+       if (a[OVS_KEY_ATTR_VLAN])
+               tci = nla_get_be16(a[OVS_KEY_ATTR_VLAN]);
+
+       if (a[OVS_KEY_ATTR_ETHERTYPE])
+               tpid = nla_get_be16(a[OVS_KEY_ATTR_ETHERTYPE]);
+
+       if (tpid != htons(0xffff)) {
+               OVS_NLERR(log, "Must have an exact match on %s TPID (mask=%x).",
+                         (inner) ? "C-VLAN" : "VLAN", ntohs(tpid));
+               return -EINVAL;
+       }
+       if (!(tci & htons(VLAN_TAG_PRESENT))) {
+               OVS_NLERR(log, "%s TCI mask does not have exact match for VLAN_TAG_PRESENT bit.",
+                         (inner) ? "C-VLAN" : "VLAN");
+               return -EINVAL;
+       }
+
+       return 1;
+}
+
+static int __parse_vlan_from_nlattrs(struct sw_flow_match *match,
+                                    u64 *key_attrs, bool inner,
+                                    const struct nlattr **a, bool is_mask,
+                                    bool log)
+{
+       int err;
+       const struct nlattr *encap;
+
+       if (!is_mask)
+               err = validate_vlan_from_nlattrs(match, *key_attrs, inner,
+                                                a, log);
+       else
+               err = validate_vlan_mask_from_nlattrs(match, *key_attrs, inner,
+                                                     a, log);
+       if (err <= 0)
+               return err;
+
+       err = encode_vlan_from_nlattrs(match, a, is_mask, inner);
+       if (err)
+               return err;
+
+       *key_attrs &= ~(1 << OVS_KEY_ATTR_ENCAP);
+       *key_attrs &= ~(1 << OVS_KEY_ATTR_VLAN);
+       *key_attrs &= ~(1 << OVS_KEY_ATTR_ETHERTYPE);
+
+       encap = a[OVS_KEY_ATTR_ENCAP];
+
+       if (!is_mask)
+               err = parse_flow_nlattrs(encap, a, key_attrs, log);
+       else
+               err = parse_flow_mask_nlattrs(encap, a, key_attrs, log);
+
+       return err;
+}
+
+static int parse_vlan_from_nlattrs(struct sw_flow_match *match,
+                                  u64 *key_attrs, const struct nlattr **a,
+                                  bool is_mask, bool log)
+{
+       int err;
+       bool encap_valid = false;
+
+       err = __parse_vlan_from_nlattrs(match, key_attrs, false, a,
+                                       is_mask, log);
+       if (err)
+               return err;
+
+       encap_valid = !!(match->key->eth.vlan.tci & htons(VLAN_TAG_PRESENT));
+       if (encap_valid) {
+               err = __parse_vlan_from_nlattrs(match, key_attrs, true, a,
+                                               is_mask, log);
+               if (err)
+                       return err;
+       }
+
+       return 0;
+}
+
 static int metadata_from_nlattrs(struct net *net, struct sw_flow_match *match,
                                 u64 *attrs, const struct nlattr **a,
                                 bool is_mask, bool log)
@@ -923,20 +1084,11 @@ static int ovs_key_from_nlattrs(struct net *net, struct sw_flow_match *match,
        }
 
        if (attrs & (1 << OVS_KEY_ATTR_VLAN)) {
-               __be16 tci;
-
-               tci = nla_get_be16(a[OVS_KEY_ATTR_VLAN]);
-               if (!(tci & htons(VLAN_TAG_PRESENT))) {
-                       if (is_mask)
-                               OVS_NLERR(log, "VLAN TCI mask does not have exact match for VLAN_TAG_PRESENT bit.");
-                       else
-                               OVS_NLERR(log, "VLAN TCI does not have VLAN_TAG_PRESENT bit set.");
-
-                       return -EINVAL;
-               }
-
-               SW_FLOW_KEY_PUT(match, eth.tci, tci, is_mask);
-               attrs &= ~(1 << OVS_KEY_ATTR_VLAN);
+               /* VLAN attribute is always parsed before getting here since it
+                * may occur multiple times.
+                */
+               OVS_NLERR(log, "VLAN attribute unexpected.");
+               return -EINVAL;
        }
 
        if (attrs & (1 << OVS_KEY_ATTR_ETHERTYPE)) {
@@ -1182,49 +1334,18 @@ int ovs_nla_get_match(struct net *net, struct sw_flow_match *match,
                      bool log)
 {
        const struct nlattr *a[OVS_KEY_ATTR_MAX + 1];
-       const struct nlattr *encap;
        struct nlattr *newmask = NULL;
        u64 key_attrs = 0;
        u64 mask_attrs = 0;
-       bool encap_valid = false;
        int err;
 
        err = parse_flow_nlattrs(nla_key, a, &key_attrs, log);
        if (err)
                return err;
 
-       if ((key_attrs & (1 << OVS_KEY_ATTR_ETHERNET)) &&
-           (key_attrs & (1 << OVS_KEY_ATTR_ETHERTYPE)) &&
-           (nla_get_be16(a[OVS_KEY_ATTR_ETHERTYPE]) == htons(ETH_P_8021Q))) {
-               __be16 tci;
-
-               if (!((key_attrs & (1 << OVS_KEY_ATTR_VLAN)) &&
-                     (key_attrs & (1 << OVS_KEY_ATTR_ENCAP)))) {
-                       OVS_NLERR(log, "Invalid Vlan frame.");
-                       return -EINVAL;
-               }
-
-               key_attrs &= ~(1 << OVS_KEY_ATTR_ETHERTYPE);
-               tci = nla_get_be16(a[OVS_KEY_ATTR_VLAN]);
-               encap = a[OVS_KEY_ATTR_ENCAP];
-               key_attrs &= ~(1 << OVS_KEY_ATTR_ENCAP);
-               encap_valid = true;
-
-               if (tci & htons(VLAN_TAG_PRESENT)) {
-                       err = parse_flow_nlattrs(encap, a, &key_attrs, log);
-                       if (err)
-                               return err;
-               } else if (!tci) {
-                       /* Corner case for truncated 802.1Q header. */
-                       if (nla_len(encap)) {
-                               OVS_NLERR(log, "Truncated 802.1Q header has non-zero encap attribute.");
-                               return -EINVAL;
-                       }
-               } else {
-                       OVS_NLERR(log, "Encap attr is set for non-VLAN frame");
-                       return  -EINVAL;
-               }
-       }
+       err = parse_vlan_from_nlattrs(match, &key_attrs, a, false, log);
+       if (err)
+               return err;
 
        err = ovs_key_from_nlattrs(net, match, key_attrs, a, false, log);
        if (err)
@@ -1265,46 +1386,12 @@ int ovs_nla_get_match(struct net *net, struct sw_flow_match *match,
                        goto free_newmask;
 
                /* Always match on tci. */
-               SW_FLOW_KEY_PUT(match, eth.tci, htons(0xffff), true);
-
-               if (mask_attrs & 1 << OVS_KEY_ATTR_ENCAP) {
-                       __be16 eth_type = 0;
-                       __be16 tci = 0;
-
-                       if (!encap_valid) {
-                               OVS_NLERR(log, "Encap mask attribute is set for non-VLAN frame.");
-                               err = -EINVAL;
-                               goto free_newmask;
-                       }
-
-                       mask_attrs &= ~(1 << OVS_KEY_ATTR_ENCAP);
-                       if (a[OVS_KEY_ATTR_ETHERTYPE])
-                               eth_type = nla_get_be16(a[OVS_KEY_ATTR_ETHERTYPE]);
-
-                       if (eth_type == htons(0xffff)) {
-                               mask_attrs &= ~(1 << OVS_KEY_ATTR_ETHERTYPE);
-                               encap = a[OVS_KEY_ATTR_ENCAP];
-                               err = parse_flow_mask_nlattrs(encap, a,
-                                                             &mask_attrs, log);
-                               if (err)
-                                       goto free_newmask;
-                       } else {
-                               OVS_NLERR(log, "VLAN frames must have an exact match on the TPID (mask=%x).",
-                                         ntohs(eth_type));
-                               err = -EINVAL;
-                               goto free_newmask;
-                       }
-
-                       if (a[OVS_KEY_ATTR_VLAN])
-                               tci = nla_get_be16(a[OVS_KEY_ATTR_VLAN]);
+               SW_FLOW_KEY_PUT(match, eth.vlan.tci, htons(0xffff), true);
+               SW_FLOW_KEY_PUT(match, eth.cvlan.tci, htons(0xffff), true);
 
-                       if (!(tci & htons(VLAN_TAG_PRESENT))) {
-                               OVS_NLERR(log, "VLAN tag present bit must have an exact match (tci_mask=%x).",
-                                         ntohs(tci));
-                               err = -EINVAL;
-                               goto free_newmask;
-                       }
-               }
+               err = parse_vlan_from_nlattrs(match, &mask_attrs, a, true, log);
+               if (err)
+                       goto free_newmask;
 
                err = ovs_key_from_nlattrs(net, match, mask_attrs, a, true,
                                           log);
@@ -1410,12 +1497,25 @@ int ovs_nla_get_flow_metadata(struct net *net, const struct nlattr *attr,
        return metadata_from_nlattrs(net, &match, &attrs, a, false, log);
 }
 
+static int ovs_nla_put_vlan(struct sk_buff *skb, const struct vlan_head *vh,
+                           bool is_mask)
+{
+       __be16 eth_type = !is_mask ? vh->tpid : htons(0xffff);
+
+       if (nla_put_be16(skb, OVS_KEY_ATTR_ETHERTYPE, eth_type) ||
+           nla_put_be16(skb, OVS_KEY_ATTR_VLAN, vh->tci))
+               return -EMSGSIZE;
+       return 0;
+}
+
 static int __ovs_nla_put_key(const struct sw_flow_key *swkey,
                             const struct sw_flow_key *output, bool is_mask,
                             struct sk_buff *skb)
 {
        struct ovs_key_ethernet *eth_key;
-       struct nlattr *nla, *encap;
+       struct nlattr *nla;
+       struct nlattr *encap = NULL;
+       struct nlattr *in_encap = NULL;
 
        if (nla_put_u32(skb, OVS_KEY_ATTR_RECIRC_ID, output->recirc_id))
                goto nla_put_failure;
@@ -1464,17 +1564,21 @@ static int __ovs_nla_put_key(const struct sw_flow_key *swkey,
        ether_addr_copy(eth_key->eth_src, output->eth.src);
        ether_addr_copy(eth_key->eth_dst, output->eth.dst);
 
-       if (swkey->eth.tci || swkey->eth.type == htons(ETH_P_8021Q)) {
-               __be16 eth_type;
-               eth_type = !is_mask ? htons(ETH_P_8021Q) : htons(0xffff);
-               if (nla_put_be16(skb, OVS_KEY_ATTR_ETHERTYPE, eth_type) ||
-                   nla_put_be16(skb, OVS_KEY_ATTR_VLAN, output->eth.tci))
+       if (swkey->eth.vlan.tci || eth_type_vlan(swkey->eth.type)) {
+               if (ovs_nla_put_vlan(skb, &output->eth.vlan, is_mask))
                        goto nla_put_failure;
                encap = nla_nest_start(skb, OVS_KEY_ATTR_ENCAP);
-               if (!swkey->eth.tci)
+               if (!swkey->eth.vlan.tci)
                        goto unencap;
-       } else
-               encap = NULL;
+
+               if (swkey->eth.cvlan.tci || eth_type_vlan(swkey->eth.type)) {
+                       if (ovs_nla_put_vlan(skb, &output->eth.cvlan, is_mask))
+                               goto nla_put_failure;
+                       in_encap = nla_nest_start(skb, OVS_KEY_ATTR_ENCAP);
+                       if (!swkey->eth.cvlan.tci)
+                               goto unencap;
+               }
+       }
 
        if (swkey->eth.type == htons(ETH_P_802_2)) {
                /*
@@ -1493,6 +1597,14 @@ static int __ovs_nla_put_key(const struct sw_flow_key *swkey,
        if (nla_put_be16(skb, OVS_KEY_ATTR_ETHERTYPE, output->eth.type))
                goto nla_put_failure;
 
+       if (eth_type_vlan(swkey->eth.type)) {
+               /* There are 3 VLAN tags, we don't know anything about the rest
+                * of the packet, so truncate here.
+                */
+               WARN_ON_ONCE(!(encap && in_encap));
+               goto unencap;
+       }
+
        if (swkey->eth.type == htons(ETH_P_IP)) {
                struct ovs_key_ipv4 *ipv4_key;
 
@@ -1619,6 +1731,8 @@ static int __ovs_nla_put_key(const struct sw_flow_key *swkey,
        }
 
 unencap:
+       if (in_encap)
+               nla_nest_end(skb, in_encap);
        if (encap)
                nla_nest_end(skb, encap);
 
@@ -2283,7 +2397,7 @@ static int __ovs_nla_copy_actions(struct net *net, const struct nlattr *attr,
 
                case OVS_ACTION_ATTR_PUSH_VLAN:
                        vlan = nla_data(a);
-                       if (vlan->vlan_tpid != htons(ETH_P_8021Q))
+                       if (!eth_type_vlan(vlan->vlan_tpid))
                                return -EINVAL;
                        if (!(vlan->vlan_tci & htons(VLAN_TAG_PRESENT)))
                                return -EINVAL;
@@ -2388,7 +2502,7 @@ int ovs_nla_copy_actions(struct net *net, const struct nlattr *attr,
 
        (*sfa)->orig_len = nla_len(attr);
        err = __ovs_nla_copy_actions(net, attr, key, 0, sfa, key->eth.type,
-                                    key->eth.tci, log);
+                                    key->eth.vlan.tci, log);
        if (err)
                ovs_nla_free_flow_actions(*sfa);