diff options
Diffstat (limited to 'net/openvswitch')
| -rw-r--r-- | net/openvswitch/Kconfig | 1 | ||||
| -rw-r--r-- | net/openvswitch/actions.c | 116 | ||||
| -rw-r--r-- | net/openvswitch/flow.c | 51 | ||||
| -rw-r--r-- | net/openvswitch/flow.h | 7 | ||||
| -rw-r--r-- | net/openvswitch/flow_netlink.c | 343 | ||||
| -rw-r--r-- | net/openvswitch/flow_netlink.h | 5 |
6 files changed, 521 insertions, 2 deletions
diff --git a/net/openvswitch/Kconfig b/net/openvswitch/Kconfig index ce947292ae77..2650205cdaf9 100644 --- a/net/openvswitch/Kconfig +++ b/net/openvswitch/Kconfig | |||
| @@ -14,6 +14,7 @@ config OPENVSWITCH | |||
| 14 | select MPLS | 14 | select MPLS |
| 15 | select NET_MPLS_GSO | 15 | select NET_MPLS_GSO |
| 16 | select DST_CACHE | 16 | select DST_CACHE |
| 17 | select NET_NSH | ||
| 17 | ---help--- | 18 | ---help--- |
| 18 | Open vSwitch is a multilayer Ethernet switch targeted at virtualized | 19 | Open vSwitch is a multilayer Ethernet switch targeted at virtualized |
| 19 | environments. In addition to supporting a variety of features | 20 | environments. In addition to supporting a variety of features |
diff --git a/net/openvswitch/actions.c b/net/openvswitch/actions.c index a551232daf61..9a6a6d51e421 100644 --- a/net/openvswitch/actions.c +++ b/net/openvswitch/actions.c | |||
| @@ -43,6 +43,7 @@ | |||
| 43 | #include "flow.h" | 43 | #include "flow.h" |
| 44 | #include "conntrack.h" | 44 | #include "conntrack.h" |
| 45 | #include "vport.h" | 45 | #include "vport.h" |
| 46 | #include "flow_netlink.h" | ||
| 46 | 47 | ||
| 47 | struct deferred_action { | 48 | struct deferred_action { |
| 48 | struct sk_buff *skb; | 49 | struct sk_buff *skb; |
| @@ -380,6 +381,38 @@ static int push_eth(struct sk_buff *skb, struct sw_flow_key *key, | |||
| 380 | return 0; | 381 | return 0; |
| 381 | } | 382 | } |
| 382 | 383 | ||
| 384 | static int push_nsh(struct sk_buff *skb, struct sw_flow_key *key, | ||
| 385 | const struct nshhdr *nh) | ||
| 386 | { | ||
| 387 | int err; | ||
| 388 | |||
| 389 | err = nsh_push(skb, nh); | ||
| 390 | if (err) | ||
| 391 | return err; | ||
| 392 | |||
| 393 | /* safe right before invalidate_flow_key */ | ||
| 394 | key->mac_proto = MAC_PROTO_NONE; | ||
| 395 | invalidate_flow_key(key); | ||
| 396 | return 0; | ||
| 397 | } | ||
| 398 | |||
| 399 | static int pop_nsh(struct sk_buff *skb, struct sw_flow_key *key) | ||
| 400 | { | ||
| 401 | int err; | ||
| 402 | |||
| 403 | err = nsh_pop(skb); | ||
| 404 | if (err) | ||
| 405 | return err; | ||
| 406 | |||
| 407 | /* safe right before invalidate_flow_key */ | ||
| 408 | if (skb->protocol == htons(ETH_P_TEB)) | ||
| 409 | key->mac_proto = MAC_PROTO_ETHERNET; | ||
| 410 | else | ||
| 411 | key->mac_proto = MAC_PROTO_NONE; | ||
| 412 | invalidate_flow_key(key); | ||
| 413 | return 0; | ||
| 414 | } | ||
| 415 | |||
| 383 | static void update_ip_l4_checksum(struct sk_buff *skb, struct iphdr *nh, | 416 | static void update_ip_l4_checksum(struct sk_buff *skb, struct iphdr *nh, |
| 384 | __be32 addr, __be32 new_addr) | 417 | __be32 addr, __be32 new_addr) |
| 385 | { | 418 | { |
| @@ -602,6 +635,69 @@ static int set_ipv6(struct sk_buff *skb, struct sw_flow_key *flow_key, | |||
| 602 | return 0; | 635 | return 0; |
| 603 | } | 636 | } |
| 604 | 637 | ||
| 638 | static int set_nsh(struct sk_buff *skb, struct sw_flow_key *flow_key, | ||
| 639 | const struct nlattr *a) | ||
| 640 | { | ||
| 641 | struct nshhdr *nh; | ||
| 642 | size_t length; | ||
| 643 | int err; | ||
| 644 | u8 flags; | ||
| 645 | u8 ttl; | ||
| 646 | int i; | ||
| 647 | |||
| 648 | struct ovs_key_nsh key; | ||
| 649 | struct ovs_key_nsh mask; | ||
| 650 | |||
| 651 | err = nsh_key_from_nlattr(a, &key, &mask); | ||
| 652 | if (err) | ||
| 653 | return err; | ||
| 654 | |||
| 655 | /* Make sure the NSH base header is there */ | ||
| 656 | if (!pskb_may_pull(skb, skb_network_offset(skb) + NSH_BASE_HDR_LEN)) | ||
| 657 | return -ENOMEM; | ||
| 658 | |||
| 659 | nh = nsh_hdr(skb); | ||
| 660 | length = nsh_hdr_len(nh); | ||
| 661 | |||
| 662 | /* Make sure the whole NSH header is there */ | ||
| 663 | err = skb_ensure_writable(skb, skb_network_offset(skb) + | ||
| 664 | length); | ||
| 665 | if (unlikely(err)) | ||
| 666 | return err; | ||
| 667 | |||
| 668 | nh = nsh_hdr(skb); | ||
| 669 | skb_postpull_rcsum(skb, nh, length); | ||
| 670 | flags = nsh_get_flags(nh); | ||
| 671 | flags = OVS_MASKED(flags, key.base.flags, mask.base.flags); | ||
| 672 | flow_key->nsh.base.flags = flags; | ||
| 673 | ttl = nsh_get_ttl(nh); | ||
| 674 | ttl = OVS_MASKED(ttl, key.base.ttl, mask.base.ttl); | ||
| 675 | flow_key->nsh.base.ttl = ttl; | ||
| 676 | nsh_set_flags_and_ttl(nh, flags, ttl); | ||
| 677 | nh->path_hdr = OVS_MASKED(nh->path_hdr, key.base.path_hdr, | ||
| 678 | mask.base.path_hdr); | ||
| 679 | flow_key->nsh.base.path_hdr = nh->path_hdr; | ||
| 680 | switch (nh->mdtype) { | ||
| 681 | case NSH_M_TYPE1: | ||
| 682 | for (i = 0; i < NSH_MD1_CONTEXT_SIZE; i++) { | ||
| 683 | nh->md1.context[i] = | ||
| 684 | OVS_MASKED(nh->md1.context[i], key.context[i], | ||
| 685 | mask.context[i]); | ||
| 686 | } | ||
| 687 | memcpy(flow_key->nsh.context, nh->md1.context, | ||
| 688 | sizeof(nh->md1.context)); | ||
| 689 | break; | ||
| 690 | case NSH_M_TYPE2: | ||
| 691 | memset(flow_key->nsh.context, 0, | ||
| 692 | sizeof(flow_key->nsh.context)); | ||
| 693 | break; | ||
| 694 | default: | ||
| 695 | return -EINVAL; | ||
| 696 | } | ||
| 697 | skb_postpush_rcsum(skb, nh, length); | ||
| 698 | return 0; | ||
| 699 | } | ||
| 700 | |||
| 605 | /* Must follow skb_ensure_writable() since that can move the skb data. */ | 701 | /* Must follow skb_ensure_writable() since that can move the skb data. */ |
| 606 | static void set_tp_port(struct sk_buff *skb, __be16 *port, | 702 | static void set_tp_port(struct sk_buff *skb, __be16 *port, |
| 607 | __be16 new_port, __sum16 *check) | 703 | __be16 new_port, __sum16 *check) |
| @@ -1024,6 +1120,10 @@ static int execute_masked_set_action(struct sk_buff *skb, | |||
| 1024 | get_mask(a, struct ovs_key_ethernet *)); | 1120 | get_mask(a, struct ovs_key_ethernet *)); |
| 1025 | break; | 1121 | break; |
| 1026 | 1122 | ||
| 1123 | case OVS_KEY_ATTR_NSH: | ||
| 1124 | err = set_nsh(skb, flow_key, a); | ||
| 1125 | break; | ||
| 1126 | |||
| 1027 | case OVS_KEY_ATTR_IPV4: | 1127 | case OVS_KEY_ATTR_IPV4: |
| 1028 | err = set_ipv4(skb, flow_key, nla_data(a), | 1128 | err = set_ipv4(skb, flow_key, nla_data(a), |
| 1029 | get_mask(a, struct ovs_key_ipv4 *)); | 1129 | get_mask(a, struct ovs_key_ipv4 *)); |
| @@ -1214,6 +1314,22 @@ static int do_execute_actions(struct datapath *dp, struct sk_buff *skb, | |||
| 1214 | case OVS_ACTION_ATTR_POP_ETH: | 1314 | case OVS_ACTION_ATTR_POP_ETH: |
| 1215 | err = pop_eth(skb, key); | 1315 | err = pop_eth(skb, key); |
| 1216 | break; | 1316 | break; |
| 1317 | |||
| 1318 | case OVS_ACTION_ATTR_PUSH_NSH: { | ||
| 1319 | u8 buffer[NSH_HDR_MAX_LEN]; | ||
| 1320 | struct nshhdr *nh = (struct nshhdr *)buffer; | ||
| 1321 | |||
| 1322 | err = nsh_hdr_from_nlattr(nla_data(a), nh, | ||
| 1323 | NSH_HDR_MAX_LEN); | ||
| 1324 | if (unlikely(err)) | ||
| 1325 | break; | ||
| 1326 | err = push_nsh(skb, key, nh); | ||
| 1327 | break; | ||
| 1328 | } | ||
| 1329 | |||
| 1330 | case OVS_ACTION_ATTR_POP_NSH: | ||
| 1331 | err = pop_nsh(skb, key); | ||
| 1332 | break; | ||
| 1217 | } | 1333 | } |
| 1218 | 1334 | ||
| 1219 | if (unlikely(err)) { | 1335 | if (unlikely(err)) { |
diff --git a/net/openvswitch/flow.c b/net/openvswitch/flow.c index 8c94cef25a72..864ddb1e3642 100644 --- a/net/openvswitch/flow.c +++ b/net/openvswitch/flow.c | |||
| @@ -46,6 +46,7 @@ | |||
| 46 | #include <net/ipv6.h> | 46 | #include <net/ipv6.h> |
| 47 | #include <net/mpls.h> | 47 | #include <net/mpls.h> |
| 48 | #include <net/ndisc.h> | 48 | #include <net/ndisc.h> |
| 49 | #include <net/nsh.h> | ||
| 49 | 50 | ||
| 50 | #include "conntrack.h" | 51 | #include "conntrack.h" |
| 51 | #include "datapath.h" | 52 | #include "datapath.h" |
| @@ -490,6 +491,52 @@ invalid: | |||
| 490 | return 0; | 491 | return 0; |
| 491 | } | 492 | } |
| 492 | 493 | ||
| 494 | static int parse_nsh(struct sk_buff *skb, struct sw_flow_key *key) | ||
| 495 | { | ||
| 496 | struct nshhdr *nh; | ||
| 497 | unsigned int nh_ofs = skb_network_offset(skb); | ||
| 498 | u8 version, length; | ||
| 499 | int err; | ||
| 500 | |||
| 501 | err = check_header(skb, nh_ofs + NSH_BASE_HDR_LEN); | ||
| 502 | if (unlikely(err)) | ||
| 503 | return err; | ||
| 504 | |||
| 505 | nh = nsh_hdr(skb); | ||
| 506 | version = nsh_get_ver(nh); | ||
| 507 | length = nsh_hdr_len(nh); | ||
| 508 | |||
| 509 | if (version != 0) | ||
| 510 | return -EINVAL; | ||
| 511 | |||
| 512 | err = check_header(skb, nh_ofs + length); | ||
| 513 | if (unlikely(err)) | ||
| 514 | return err; | ||
| 515 | |||
| 516 | nh = nsh_hdr(skb); | ||
| 517 | key->nsh.base.flags = nsh_get_flags(nh); | ||
| 518 | key->nsh.base.ttl = nsh_get_ttl(nh); | ||
| 519 | key->nsh.base.mdtype = nh->mdtype; | ||
| 520 | key->nsh.base.np = nh->np; | ||
| 521 | key->nsh.base.path_hdr = nh->path_hdr; | ||
| 522 | switch (key->nsh.base.mdtype) { | ||
| 523 | case NSH_M_TYPE1: | ||
| 524 | if (length != NSH_M_TYPE1_LEN) | ||
| 525 | return -EINVAL; | ||
| 526 | memcpy(key->nsh.context, nh->md1.context, | ||
| 527 | sizeof(nh->md1)); | ||
| 528 | break; | ||
| 529 | case NSH_M_TYPE2: | ||
| 530 | memset(key->nsh.context, 0, | ||
| 531 | sizeof(nh->md1)); | ||
| 532 | break; | ||
| 533 | default: | ||
| 534 | return -EINVAL; | ||
| 535 | } | ||
| 536 | |||
| 537 | return 0; | ||
| 538 | } | ||
| 539 | |||
| 493 | /** | 540 | /** |
| 494 | * key_extract - extracts a flow key from an Ethernet frame. | 541 | * key_extract - extracts a flow key from an Ethernet frame. |
| 495 | * @skb: sk_buff that contains the frame, with skb->data pointing to the | 542 | * @skb: sk_buff that contains the frame, with skb->data pointing to the |
| @@ -735,6 +782,10 @@ static int key_extract(struct sk_buff *skb, struct sw_flow_key *key) | |||
| 735 | memset(&key->tp, 0, sizeof(key->tp)); | 782 | memset(&key->tp, 0, sizeof(key->tp)); |
| 736 | } | 783 | } |
| 737 | } | 784 | } |
| 785 | } else if (key->eth.type == htons(ETH_P_NSH)) { | ||
| 786 | error = parse_nsh(skb, key); | ||
| 787 | if (error) | ||
| 788 | return error; | ||
| 738 | } | 789 | } |
| 739 | return 0; | 790 | return 0; |
| 740 | } | 791 | } |
diff --git a/net/openvswitch/flow.h b/net/openvswitch/flow.h index 1875bba4f865..c670dd24b8b7 100644 --- a/net/openvswitch/flow.h +++ b/net/openvswitch/flow.h | |||
| @@ -35,6 +35,7 @@ | |||
| 35 | #include <net/inet_ecn.h> | 35 | #include <net/inet_ecn.h> |
| 36 | #include <net/ip_tunnels.h> | 36 | #include <net/ip_tunnels.h> |
| 37 | #include <net/dst_metadata.h> | 37 | #include <net/dst_metadata.h> |
| 38 | #include <net/nsh.h> | ||
| 38 | 39 | ||
| 39 | struct sk_buff; | 40 | struct sk_buff; |
| 40 | 41 | ||
| @@ -66,6 +67,11 @@ struct vlan_head { | |||
| 66 | (offsetof(struct sw_flow_key, recirc_id) + \ | 67 | (offsetof(struct sw_flow_key, recirc_id) + \ |
| 67 | FIELD_SIZEOF(struct sw_flow_key, recirc_id)) | 68 | FIELD_SIZEOF(struct sw_flow_key, recirc_id)) |
| 68 | 69 | ||
| 70 | struct ovs_key_nsh { | ||
| 71 | struct ovs_nsh_key_base base; | ||
| 72 | __be32 context[NSH_MD1_CONTEXT_SIZE]; | ||
| 73 | }; | ||
| 74 | |||
| 69 | struct sw_flow_key { | 75 | struct sw_flow_key { |
| 70 | u8 tun_opts[IP_TUNNEL_OPTS_MAX]; | 76 | u8 tun_opts[IP_TUNNEL_OPTS_MAX]; |
| 71 | u8 tun_opts_len; | 77 | u8 tun_opts_len; |
| @@ -143,6 +149,7 @@ struct sw_flow_key { | |||
| 143 | } nd; | 149 | } nd; |
| 144 | }; | 150 | }; |
| 145 | } ipv6; | 151 | } ipv6; |
| 152 | struct ovs_key_nsh nsh; /* network service header */ | ||
| 146 | }; | 153 | }; |
| 147 | struct { | 154 | struct { |
| 148 | /* Connection tracking fields not packed above. */ | 155 | /* Connection tracking fields not packed above. */ |
diff --git a/net/openvswitch/flow_netlink.c b/net/openvswitch/flow_netlink.c index dc0d79092e74..4201f9293af3 100644 --- a/net/openvswitch/flow_netlink.c +++ b/net/openvswitch/flow_netlink.c | |||
| @@ -48,6 +48,7 @@ | |||
| 48 | #include <net/ndisc.h> | 48 | #include <net/ndisc.h> |
| 49 | #include <net/mpls.h> | 49 | #include <net/mpls.h> |
| 50 | #include <net/vxlan.h> | 50 | #include <net/vxlan.h> |
| 51 | #include <net/tun_proto.h> | ||
| 51 | #include <net/erspan.h> | 52 | #include <net/erspan.h> |
| 52 | 53 | ||
| 53 | #include "flow_netlink.h" | 54 | #include "flow_netlink.h" |
| @@ -80,9 +81,11 @@ static bool actions_may_change_flow(const struct nlattr *actions) | |||
| 80 | case OVS_ACTION_ATTR_HASH: | 81 | case OVS_ACTION_ATTR_HASH: |
| 81 | case OVS_ACTION_ATTR_POP_ETH: | 82 | case OVS_ACTION_ATTR_POP_ETH: |
| 82 | case OVS_ACTION_ATTR_POP_MPLS: | 83 | case OVS_ACTION_ATTR_POP_MPLS: |
| 84 | case OVS_ACTION_ATTR_POP_NSH: | ||
| 83 | case OVS_ACTION_ATTR_POP_VLAN: | 85 | case OVS_ACTION_ATTR_POP_VLAN: |
| 84 | case OVS_ACTION_ATTR_PUSH_ETH: | 86 | case OVS_ACTION_ATTR_PUSH_ETH: |
| 85 | case OVS_ACTION_ATTR_PUSH_MPLS: | 87 | case OVS_ACTION_ATTR_PUSH_MPLS: |
| 88 | case OVS_ACTION_ATTR_PUSH_NSH: | ||
| 86 | case OVS_ACTION_ATTR_PUSH_VLAN: | 89 | case OVS_ACTION_ATTR_PUSH_VLAN: |
| 87 | case OVS_ACTION_ATTR_SAMPLE: | 90 | case OVS_ACTION_ATTR_SAMPLE: |
| 88 | case OVS_ACTION_ATTR_SET: | 91 | case OVS_ACTION_ATTR_SET: |
| @@ -175,7 +178,8 @@ static bool match_validate(const struct sw_flow_match *match, | |||
| 175 | | (1 << OVS_KEY_ATTR_ICMPV6) | 178 | | (1 << OVS_KEY_ATTR_ICMPV6) |
| 176 | | (1 << OVS_KEY_ATTR_ARP) | 179 | | (1 << OVS_KEY_ATTR_ARP) |
| 177 | | (1 << OVS_KEY_ATTR_ND) | 180 | | (1 << OVS_KEY_ATTR_ND) |
| 178 | | (1 << OVS_KEY_ATTR_MPLS)); | 181 | | (1 << OVS_KEY_ATTR_MPLS) |
| 182 | | (1 << OVS_KEY_ATTR_NSH)); | ||
| 179 | 183 | ||
| 180 | /* Always allowed mask fields. */ | 184 | /* Always allowed mask fields. */ |
| 181 | mask_allowed |= ((1 << OVS_KEY_ATTR_TUNNEL) | 185 | mask_allowed |= ((1 << OVS_KEY_ATTR_TUNNEL) |
| @@ -284,6 +288,14 @@ static bool match_validate(const struct sw_flow_match *match, | |||
| 284 | } | 288 | } |
| 285 | } | 289 | } |
| 286 | 290 | ||
| 291 | if (match->key->eth.type == htons(ETH_P_NSH)) { | ||
| 292 | key_expected |= 1 << OVS_KEY_ATTR_NSH; | ||
| 293 | if (match->mask && | ||
| 294 | match->mask->key.eth.type == htons(0xffff)) { | ||
| 295 | mask_allowed |= 1 << OVS_KEY_ATTR_NSH; | ||
| 296 | } | ||
| 297 | } | ||
| 298 | |||
| 287 | if ((key_attrs & key_expected) != key_expected) { | 299 | if ((key_attrs & key_expected) != key_expected) { |
| 288 | /* Key attributes check failed. */ | 300 | /* Key attributes check failed. */ |
| 289 | OVS_NLERR(log, "Missing key (keys=%llx, expected=%llx)", | 301 | OVS_NLERR(log, "Missing key (keys=%llx, expected=%llx)", |
| @@ -325,12 +337,25 @@ size_t ovs_tun_key_attr_size(void) | |||
| 325 | + nla_total_size(4); /* OVS_TUNNEL_KEY_ATTR_ERSPAN_OPTS */ | 337 | + nla_total_size(4); /* OVS_TUNNEL_KEY_ATTR_ERSPAN_OPTS */ |
| 326 | } | 338 | } |
| 327 | 339 | ||
| 340 | size_t ovs_nsh_key_attr_size(void) | ||
| 341 | { | ||
| 342 | /* Whenever adding new OVS_NSH_KEY_ FIELDS, we should consider | ||
| 343 | * updating this function. | ||
| 344 | */ | ||
| 345 | return nla_total_size(NSH_BASE_HDR_LEN) /* OVS_NSH_KEY_ATTR_BASE */ | ||
| 346 | /* OVS_NSH_KEY_ATTR_MD1 and OVS_NSH_KEY_ATTR_MD2 are | ||
| 347 | * mutually exclusive, so the bigger one can cover | ||
| 348 | * the small one. | ||
| 349 | */ | ||
| 350 | + nla_total_size(NSH_CTX_HDRS_MAX_LEN); | ||
| 351 | } | ||
| 352 | |||
| 328 | size_t ovs_key_attr_size(void) | 353 | size_t ovs_key_attr_size(void) |
| 329 | { | 354 | { |
| 330 | /* Whenever adding new OVS_KEY_ FIELDS, we should consider | 355 | /* Whenever adding new OVS_KEY_ FIELDS, we should consider |
| 331 | * updating this function. | 356 | * updating this function. |
| 332 | */ | 357 | */ |
| 333 | BUILD_BUG_ON(OVS_KEY_ATTR_TUNNEL_INFO != 28); | 358 | BUILD_BUG_ON(OVS_KEY_ATTR_TUNNEL_INFO != 29); |
| 334 | 359 | ||
| 335 | return nla_total_size(4) /* OVS_KEY_ATTR_PRIORITY */ | 360 | return nla_total_size(4) /* OVS_KEY_ATTR_PRIORITY */ |
| 336 | + nla_total_size(0) /* OVS_KEY_ATTR_TUNNEL */ | 361 | + nla_total_size(0) /* OVS_KEY_ATTR_TUNNEL */ |
| @@ -344,6 +369,8 @@ size_t ovs_key_attr_size(void) | |||
| 344 | + nla_total_size(4) /* OVS_KEY_ATTR_CT_MARK */ | 369 | + nla_total_size(4) /* OVS_KEY_ATTR_CT_MARK */ |
| 345 | + nla_total_size(16) /* OVS_KEY_ATTR_CT_LABELS */ | 370 | + nla_total_size(16) /* OVS_KEY_ATTR_CT_LABELS */ |
| 346 | + nla_total_size(40) /* OVS_KEY_ATTR_CT_ORIG_TUPLE_IPV6 */ | 371 | + nla_total_size(40) /* OVS_KEY_ATTR_CT_ORIG_TUPLE_IPV6 */ |
| 372 | + nla_total_size(0) /* OVS_KEY_ATTR_NSH */ | ||
| 373 | + ovs_nsh_key_attr_size() | ||
| 347 | + nla_total_size(12) /* OVS_KEY_ATTR_ETHERNET */ | 374 | + nla_total_size(12) /* OVS_KEY_ATTR_ETHERNET */ |
| 348 | + nla_total_size(2) /* OVS_KEY_ATTR_ETHERTYPE */ | 375 | + nla_total_size(2) /* OVS_KEY_ATTR_ETHERTYPE */ |
| 349 | + nla_total_size(4) /* OVS_KEY_ATTR_VLAN */ | 376 | + nla_total_size(4) /* OVS_KEY_ATTR_VLAN */ |
| @@ -377,6 +404,13 @@ static const struct ovs_len_tbl ovs_tunnel_key_lens[OVS_TUNNEL_KEY_ATTR_MAX + 1] | |||
| 377 | [OVS_TUNNEL_KEY_ATTR_ERSPAN_OPTS] = { .len = sizeof(u32) }, | 404 | [OVS_TUNNEL_KEY_ATTR_ERSPAN_OPTS] = { .len = sizeof(u32) }, |
| 378 | }; | 405 | }; |
| 379 | 406 | ||
| 407 | static const struct ovs_len_tbl | ||
| 408 | ovs_nsh_key_attr_lens[OVS_NSH_KEY_ATTR_MAX + 1] = { | ||
| 409 | [OVS_NSH_KEY_ATTR_BASE] = { .len = sizeof(struct ovs_nsh_key_base) }, | ||
| 410 | [OVS_NSH_KEY_ATTR_MD1] = { .len = sizeof(struct ovs_nsh_key_md1) }, | ||
| 411 | [OVS_NSH_KEY_ATTR_MD2] = { .len = OVS_ATTR_VARIABLE }, | ||
| 412 | }; | ||
| 413 | |||
| 380 | /* The size of the argument for each %OVS_KEY_ATTR_* Netlink attribute. */ | 414 | /* The size of the argument for each %OVS_KEY_ATTR_* Netlink attribute. */ |
| 381 | static const struct ovs_len_tbl ovs_key_lens[OVS_KEY_ATTR_MAX + 1] = { | 415 | static const struct ovs_len_tbl ovs_key_lens[OVS_KEY_ATTR_MAX + 1] = { |
| 382 | [OVS_KEY_ATTR_ENCAP] = { .len = OVS_ATTR_NESTED }, | 416 | [OVS_KEY_ATTR_ENCAP] = { .len = OVS_ATTR_NESTED }, |
| @@ -409,6 +443,8 @@ static const struct ovs_len_tbl ovs_key_lens[OVS_KEY_ATTR_MAX + 1] = { | |||
| 409 | .len = sizeof(struct ovs_key_ct_tuple_ipv4) }, | 443 | .len = sizeof(struct ovs_key_ct_tuple_ipv4) }, |
| 410 | [OVS_KEY_ATTR_CT_ORIG_TUPLE_IPV6] = { | 444 | [OVS_KEY_ATTR_CT_ORIG_TUPLE_IPV6] = { |
| 411 | .len = sizeof(struct ovs_key_ct_tuple_ipv6) }, | 445 | .len = sizeof(struct ovs_key_ct_tuple_ipv6) }, |
| 446 | [OVS_KEY_ATTR_NSH] = { .len = OVS_ATTR_NESTED, | ||
| 447 | .next = ovs_nsh_key_attr_lens, }, | ||
| 412 | }; | 448 | }; |
| 413 | 449 | ||
| 414 | static bool check_attr_len(unsigned int attr_len, unsigned int expected_len) | 450 | static bool check_attr_len(unsigned int attr_len, unsigned int expected_len) |
| @@ -1227,6 +1263,221 @@ static int metadata_from_nlattrs(struct net *net, struct sw_flow_match *match, | |||
| 1227 | return 0; | 1263 | return 0; |
| 1228 | } | 1264 | } |
| 1229 | 1265 | ||
| 1266 | int nsh_hdr_from_nlattr(const struct nlattr *attr, | ||
| 1267 | struct nshhdr *nh, size_t size) | ||
| 1268 | { | ||
| 1269 | struct nlattr *a; | ||
| 1270 | int rem; | ||
| 1271 | u8 flags = 0; | ||
| 1272 | u8 ttl = 0; | ||
| 1273 | int mdlen = 0; | ||
| 1274 | |||
| 1275 | /* validate_nsh has check this, so we needn't do duplicate check here | ||
| 1276 | */ | ||
| 1277 | if (size < NSH_BASE_HDR_LEN) | ||
| 1278 | return -ENOBUFS; | ||
| 1279 | |||
| 1280 | nla_for_each_nested(a, attr, rem) { | ||
| 1281 | int type = nla_type(a); | ||
| 1282 | |||
| 1283 | switch (type) { | ||
| 1284 | case OVS_NSH_KEY_ATTR_BASE: { | ||
| 1285 | const struct ovs_nsh_key_base *base = nla_data(a); | ||
| 1286 | |||
| 1287 | flags = base->flags; | ||
| 1288 | ttl = base->ttl; | ||
| 1289 | nh->np = base->np; | ||
| 1290 | nh->mdtype = base->mdtype; | ||
| 1291 | nh->path_hdr = base->path_hdr; | ||
| 1292 | break; | ||
| 1293 | } | ||
| 1294 | case OVS_NSH_KEY_ATTR_MD1: | ||
| 1295 | mdlen = nla_len(a); | ||
| 1296 | if (mdlen > size - NSH_BASE_HDR_LEN) | ||
| 1297 | return -ENOBUFS; | ||
| 1298 | memcpy(&nh->md1, nla_data(a), mdlen); | ||
| 1299 | break; | ||
| 1300 | |||
| 1301 | case OVS_NSH_KEY_ATTR_MD2: | ||
| 1302 | mdlen = nla_len(a); | ||
| 1303 | if (mdlen > size - NSH_BASE_HDR_LEN) | ||
| 1304 | return -ENOBUFS; | ||
| 1305 | memcpy(&nh->md2, nla_data(a), mdlen); | ||
| 1306 | break; | ||
| 1307 | |||
| 1308 | default: | ||
| 1309 | return -EINVAL; | ||
| 1310 | } | ||
| 1311 | } | ||
| 1312 | |||
| 1313 | /* nsh header length = NSH_BASE_HDR_LEN + mdlen */ | ||
| 1314 | nh->ver_flags_ttl_len = 0; | ||
| 1315 | nsh_set_flags_ttl_len(nh, flags, ttl, NSH_BASE_HDR_LEN + mdlen); | ||
| 1316 | |||
| 1317 | return 0; | ||
| 1318 | } | ||
| 1319 | |||
| 1320 | int nsh_key_from_nlattr(const struct nlattr *attr, | ||
| 1321 | struct ovs_key_nsh *nsh, struct ovs_key_nsh *nsh_mask) | ||
| 1322 | { | ||
| 1323 | struct nlattr *a; | ||
| 1324 | int rem; | ||
| 1325 | |||
| 1326 | /* validate_nsh has check this, so we needn't do duplicate check here | ||
| 1327 | */ | ||
| 1328 | nla_for_each_nested(a, attr, rem) { | ||
| 1329 | int type = nla_type(a); | ||
| 1330 | |||
| 1331 | switch (type) { | ||
| 1332 | case OVS_NSH_KEY_ATTR_BASE: { | ||
| 1333 | const struct ovs_nsh_key_base *base = nla_data(a); | ||
| 1334 | const struct ovs_nsh_key_base *base_mask = base + 1; | ||
| 1335 | |||
| 1336 | nsh->base = *base; | ||
| 1337 | nsh_mask->base = *base_mask; | ||
| 1338 | break; | ||
| 1339 | } | ||
| 1340 | case OVS_NSH_KEY_ATTR_MD1: { | ||
| 1341 | const struct ovs_nsh_key_md1 *md1 = nla_data(a); | ||
| 1342 | const struct ovs_nsh_key_md1 *md1_mask = md1 + 1; | ||
| 1343 | |||
| 1344 | memcpy(nsh->context, md1->context, sizeof(*md1)); | ||
| 1345 | memcpy(nsh_mask->context, md1_mask->context, | ||
| 1346 | sizeof(*md1_mask)); | ||
| 1347 | break; | ||
| 1348 | } | ||
| 1349 | case OVS_NSH_KEY_ATTR_MD2: | ||
| 1350 | /* Not supported yet */ | ||
| 1351 | return -ENOTSUPP; | ||
| 1352 | default: | ||
| 1353 | return -EINVAL; | ||
| 1354 | } | ||
| 1355 | } | ||
| 1356 | |||
| 1357 | return 0; | ||
| 1358 | } | ||
| 1359 | |||
| 1360 | static int nsh_key_put_from_nlattr(const struct nlattr *attr, | ||
| 1361 | struct sw_flow_match *match, bool is_mask, | ||
| 1362 | bool is_push_nsh, bool log) | ||
| 1363 | { | ||
| 1364 | struct nlattr *a; | ||
| 1365 | int rem; | ||
| 1366 | bool has_base = false; | ||
| 1367 | bool has_md1 = false; | ||
| 1368 | bool has_md2 = false; | ||
| 1369 | u8 mdtype = 0; | ||
| 1370 | int mdlen = 0; | ||
| 1371 | |||
| 1372 | if (WARN_ON(is_push_nsh && is_mask)) | ||
| 1373 | return -EINVAL; | ||
| 1374 | |||
| 1375 | nla_for_each_nested(a, attr, rem) { | ||
| 1376 | int type = nla_type(a); | ||
| 1377 | int i; | ||
| 1378 | |||
| 1379 | if (type > OVS_NSH_KEY_ATTR_MAX) { | ||
| 1380 | OVS_NLERR(log, "nsh attr %d is out of range max %d", | ||
| 1381 | type, OVS_NSH_KEY_ATTR_MAX); | ||
| 1382 | return -EINVAL; | ||
| 1383 | } | ||
| 1384 | |||
| 1385 | if (!check_attr_len(nla_len(a), | ||
| 1386 | ovs_nsh_key_attr_lens[type].len)) { | ||
| 1387 | OVS_NLERR( | ||
| 1388 | log, | ||
| 1389 | "nsh attr %d has unexpected len %d expected %d", | ||
| 1390 | type, | ||
| 1391 | nla_len(a), | ||
| 1392 | ovs_nsh_key_attr_lens[type].len | ||
| 1393 | ); | ||
| 1394 | return -EINVAL; | ||
| 1395 | } | ||
| 1396 | |||
| 1397 | switch (type) { | ||
| 1398 | case OVS_NSH_KEY_ATTR_BASE: { | ||
| 1399 | const struct ovs_nsh_key_base *base = nla_data(a); | ||
| 1400 | |||
| 1401 | has_base = true; | ||
| 1402 | mdtype = base->mdtype; | ||
| 1403 | SW_FLOW_KEY_PUT(match, nsh.base.flags, | ||
| 1404 | base->flags, is_mask); | ||
| 1405 | SW_FLOW_KEY_PUT(match, nsh.base.ttl, | ||
| 1406 | base->ttl, is_mask); | ||
| 1407 | SW_FLOW_KEY_PUT(match, nsh.base.mdtype, | ||
| 1408 | base->mdtype, is_mask); | ||
| 1409 | SW_FLOW_KEY_PUT(match, nsh.base.np, | ||
| 1410 | base->np, is_mask); | ||
| 1411 | SW_FLOW_KEY_PUT(match, nsh.base.path_hdr, | ||
| 1412 | base->path_hdr, is_mask); | ||
| 1413 | break; | ||
| 1414 | } | ||
| 1415 | case OVS_NSH_KEY_ATTR_MD1: { | ||
| 1416 | const struct ovs_nsh_key_md1 *md1 = nla_data(a); | ||
| 1417 | |||
| 1418 | has_md1 = true; | ||
| 1419 | for (i = 0; i < NSH_MD1_CONTEXT_SIZE; i++) | ||
| 1420 | SW_FLOW_KEY_PUT(match, nsh.context[i], | ||
| 1421 | md1->context[i], is_mask); | ||
| 1422 | break; | ||
| 1423 | } | ||
| 1424 | case OVS_NSH_KEY_ATTR_MD2: | ||
| 1425 | if (!is_push_nsh) /* Not supported MD type 2 yet */ | ||
| 1426 | return -ENOTSUPP; | ||
| 1427 | |||
| 1428 | has_md2 = true; | ||
| 1429 | mdlen = nla_len(a); | ||
| 1430 | if (mdlen > NSH_CTX_HDRS_MAX_LEN || mdlen <= 0) { | ||
| 1431 | OVS_NLERR( | ||
| 1432 | log, | ||
| 1433 | "Invalid MD length %d for MD type %d", | ||
| 1434 | mdlen, | ||
| 1435 | mdtype | ||
| 1436 | ); | ||
| 1437 | return -EINVAL; | ||
| 1438 | } | ||
| 1439 | break; | ||
| 1440 | default: | ||
| 1441 | OVS_NLERR(log, "Unknown nsh attribute %d", | ||
| 1442 | type); | ||
| 1443 | return -EINVAL; | ||
| 1444 | } | ||
| 1445 | } | ||
| 1446 | |||
| 1447 | if (rem > 0) { | ||
| 1448 | OVS_NLERR(log, "nsh attribute has %d unknown bytes.", rem); | ||
| 1449 | return -EINVAL; | ||
| 1450 | } | ||
| 1451 | |||
| 1452 | if (has_md1 && has_md2) { | ||
| 1453 | OVS_NLERR( | ||
| 1454 | 1, | ||
| 1455 | "invalid nsh attribute: md1 and md2 are exclusive." | ||
| 1456 | ); | ||
| 1457 | return -EINVAL; | ||
| 1458 | } | ||
| 1459 | |||
| 1460 | if (!is_mask) { | ||
| 1461 | if ((has_md1 && mdtype != NSH_M_TYPE1) || | ||
| 1462 | (has_md2 && mdtype != NSH_M_TYPE2)) { | ||
| 1463 | OVS_NLERR(1, "nsh attribute has unmatched MD type %d.", | ||
| 1464 | mdtype); | ||
| 1465 | return -EINVAL; | ||
| 1466 | } | ||
| 1467 | |||
| 1468 | if (is_push_nsh && | ||
| 1469 | (!has_base || (!has_md1 && !has_md2))) { | ||
| 1470 | OVS_NLERR( | ||
| 1471 | 1, | ||
| 1472 | "push_nsh: missing base or metadata attributes" | ||
| 1473 | ); | ||
| 1474 | return -EINVAL; | ||
| 1475 | } | ||
| 1476 | } | ||
| 1477 | |||
| 1478 | return 0; | ||
| 1479 | } | ||
| 1480 | |||
| 1230 | static int ovs_key_from_nlattrs(struct net *net, struct sw_flow_match *match, | 1481 | static int ovs_key_from_nlattrs(struct net *net, struct sw_flow_match *match, |
| 1231 | u64 attrs, const struct nlattr **a, | 1482 | u64 attrs, const struct nlattr **a, |
| 1232 | bool is_mask, bool log) | 1483 | bool is_mask, bool log) |
| @@ -1354,6 +1605,13 @@ static int ovs_key_from_nlattrs(struct net *net, struct sw_flow_match *match, | |||
| 1354 | attrs &= ~(1 << OVS_KEY_ATTR_ARP); | 1605 | attrs &= ~(1 << OVS_KEY_ATTR_ARP); |
| 1355 | } | 1606 | } |
| 1356 | 1607 | ||
| 1608 | if (attrs & (1 << OVS_KEY_ATTR_NSH)) { | ||
| 1609 | if (nsh_key_put_from_nlattr(a[OVS_KEY_ATTR_NSH], match, | ||
| 1610 | is_mask, false, log) < 0) | ||
| 1611 | return -EINVAL; | ||
| 1612 | attrs &= ~(1 << OVS_KEY_ATTR_NSH); | ||
| 1613 | } | ||
| 1614 | |||
| 1357 | if (attrs & (1 << OVS_KEY_ATTR_MPLS)) { | 1615 | if (attrs & (1 << OVS_KEY_ATTR_MPLS)) { |
| 1358 | const struct ovs_key_mpls *mpls_key; | 1616 | const struct ovs_key_mpls *mpls_key; |
| 1359 | 1617 | ||
| @@ -1670,6 +1928,34 @@ static int ovs_nla_put_vlan(struct sk_buff *skb, const struct vlan_head *vh, | |||
| 1670 | return 0; | 1928 | return 0; |
| 1671 | } | 1929 | } |
| 1672 | 1930 | ||
| 1931 | static int nsh_key_to_nlattr(const struct ovs_key_nsh *nsh, bool is_mask, | ||
| 1932 | struct sk_buff *skb) | ||
| 1933 | { | ||
| 1934 | struct nlattr *start; | ||
| 1935 | |||
| 1936 | start = nla_nest_start(skb, OVS_KEY_ATTR_NSH); | ||
| 1937 | if (!start) | ||
| 1938 | return -EMSGSIZE; | ||
| 1939 | |||
| 1940 | if (nla_put(skb, OVS_NSH_KEY_ATTR_BASE, sizeof(nsh->base), &nsh->base)) | ||
| 1941 | goto nla_put_failure; | ||
| 1942 | |||
| 1943 | if (is_mask || nsh->base.mdtype == NSH_M_TYPE1) { | ||
| 1944 | if (nla_put(skb, OVS_NSH_KEY_ATTR_MD1, | ||
| 1945 | sizeof(nsh->context), nsh->context)) | ||
| 1946 | goto nla_put_failure; | ||
| 1947 | } | ||
| 1948 | |||
| 1949 | /* Don't support MD type 2 yet */ | ||
| 1950 | |||
| 1951 | nla_nest_end(skb, start); | ||
| 1952 | |||
| 1953 | return 0; | ||
| 1954 | |||
| 1955 | nla_put_failure: | ||
| 1956 | return -EMSGSIZE; | ||
| 1957 | } | ||
| 1958 | |||
| 1673 | static int __ovs_nla_put_key(const struct sw_flow_key *swkey, | 1959 | static int __ovs_nla_put_key(const struct sw_flow_key *swkey, |
| 1674 | const struct sw_flow_key *output, bool is_mask, | 1960 | const struct sw_flow_key *output, bool is_mask, |
| 1675 | struct sk_buff *skb) | 1961 | struct sk_buff *skb) |
| @@ -1798,6 +2084,9 @@ static int __ovs_nla_put_key(const struct sw_flow_key *swkey, | |||
| 1798 | ipv6_key->ipv6_tclass = output->ip.tos; | 2084 | ipv6_key->ipv6_tclass = output->ip.tos; |
| 1799 | ipv6_key->ipv6_hlimit = output->ip.ttl; | 2085 | ipv6_key->ipv6_hlimit = output->ip.ttl; |
| 1800 | ipv6_key->ipv6_frag = output->ip.frag; | 2086 | ipv6_key->ipv6_frag = output->ip.frag; |
| 2087 | } else if (swkey->eth.type == htons(ETH_P_NSH)) { | ||
| 2088 | if (nsh_key_to_nlattr(&output->nsh, is_mask, skb)) | ||
| 2089 | goto nla_put_failure; | ||
| 1801 | } else if (swkey->eth.type == htons(ETH_P_ARP) || | 2090 | } else if (swkey->eth.type == htons(ETH_P_ARP) || |
| 1802 | swkey->eth.type == htons(ETH_P_RARP)) { | 2091 | swkey->eth.type == htons(ETH_P_RARP)) { |
| 1803 | struct ovs_key_arp *arp_key; | 2092 | struct ovs_key_arp *arp_key; |
| @@ -2292,6 +2581,19 @@ static int validate_and_copy_set_tun(const struct nlattr *attr, | |||
| 2292 | return err; | 2581 | return err; |
| 2293 | } | 2582 | } |
| 2294 | 2583 | ||
| 2584 | static bool validate_nsh(const struct nlattr *attr, bool is_mask, | ||
| 2585 | bool is_push_nsh, bool log) | ||
| 2586 | { | ||
| 2587 | struct sw_flow_match match; | ||
| 2588 | struct sw_flow_key key; | ||
| 2589 | int ret = 0; | ||
| 2590 | |||
| 2591 | ovs_match_init(&match, &key, true, NULL); | ||
| 2592 | ret = nsh_key_put_from_nlattr(attr, &match, is_mask, | ||
| 2593 | is_push_nsh, log); | ||
| 2594 | return !ret; | ||
| 2595 | } | ||
| 2596 | |||
| 2295 | /* Return false if there are any non-masked bits set. | 2597 | /* Return false if there are any non-masked bits set. |
| 2296 | * Mask follows data immediately, before any netlink padding. | 2598 | * Mask follows data immediately, before any netlink padding. |
| 2297 | */ | 2599 | */ |
| @@ -2434,6 +2736,13 @@ static int validate_set(const struct nlattr *a, | |||
| 2434 | 2736 | ||
| 2435 | break; | 2737 | break; |
| 2436 | 2738 | ||
| 2739 | case OVS_KEY_ATTR_NSH: | ||
| 2740 | if (eth_type != htons(ETH_P_NSH)) | ||
| 2741 | return -EINVAL; | ||
| 2742 | if (!validate_nsh(nla_data(a), masked, false, log)) | ||
| 2743 | return -EINVAL; | ||
| 2744 | break; | ||
| 2745 | |||
| 2437 | default: | 2746 | default: |
| 2438 | return -EINVAL; | 2747 | return -EINVAL; |
| 2439 | } | 2748 | } |
| @@ -2533,6 +2842,8 @@ static int __ovs_nla_copy_actions(struct net *net, const struct nlattr *attr, | |||
| 2533 | [OVS_ACTION_ATTR_TRUNC] = sizeof(struct ovs_action_trunc), | 2842 | [OVS_ACTION_ATTR_TRUNC] = sizeof(struct ovs_action_trunc), |
| 2534 | [OVS_ACTION_ATTR_PUSH_ETH] = sizeof(struct ovs_action_push_eth), | 2843 | [OVS_ACTION_ATTR_PUSH_ETH] = sizeof(struct ovs_action_push_eth), |
| 2535 | [OVS_ACTION_ATTR_POP_ETH] = 0, | 2844 | [OVS_ACTION_ATTR_POP_ETH] = 0, |
| 2845 | [OVS_ACTION_ATTR_PUSH_NSH] = (u32)-1, | ||
| 2846 | [OVS_ACTION_ATTR_POP_NSH] = 0, | ||
| 2536 | }; | 2847 | }; |
| 2537 | const struct ovs_action_push_vlan *vlan; | 2848 | const struct ovs_action_push_vlan *vlan; |
| 2538 | int type = nla_type(a); | 2849 | int type = nla_type(a); |
| @@ -2690,6 +3001,34 @@ static int __ovs_nla_copy_actions(struct net *net, const struct nlattr *attr, | |||
| 2690 | mac_proto = MAC_PROTO_ETHERNET; | 3001 | mac_proto = MAC_PROTO_ETHERNET; |
| 2691 | break; | 3002 | break; |
| 2692 | 3003 | ||
| 3004 | case OVS_ACTION_ATTR_PUSH_NSH: | ||
| 3005 | if (mac_proto != MAC_PROTO_ETHERNET) { | ||
| 3006 | u8 next_proto; | ||
| 3007 | |||
| 3008 | next_proto = tun_p_from_eth_p(eth_type); | ||
| 3009 | if (!next_proto) | ||
| 3010 | return -EINVAL; | ||
| 3011 | } | ||
| 3012 | mac_proto = MAC_PROTO_NONE; | ||
| 3013 | if (!validate_nsh(nla_data(a), false, true, true)) | ||
| 3014 | return -EINVAL; | ||
| 3015 | break; | ||
| 3016 | |||
| 3017 | case OVS_ACTION_ATTR_POP_NSH: { | ||
| 3018 | __be16 inner_proto; | ||
| 3019 | |||
| 3020 | if (eth_type != htons(ETH_P_NSH)) | ||
| 3021 | return -EINVAL; | ||
| 3022 | inner_proto = tun_p_to_eth_p(key->nsh.base.np); | ||
| 3023 | if (!inner_proto) | ||
| 3024 | return -EINVAL; | ||
| 3025 | if (key->nsh.base.np == TUN_P_ETHERNET) | ||
| 3026 | mac_proto = MAC_PROTO_ETHERNET; | ||
| 3027 | else | ||
| 3028 | mac_proto = MAC_PROTO_NONE; | ||
| 3029 | break; | ||
| 3030 | } | ||
| 3031 | |||
| 2693 | default: | 3032 | default: |
| 2694 | OVS_NLERR(log, "Unknown Action type %d", type); | 3033 | OVS_NLERR(log, "Unknown Action type %d", type); |
| 2695 | return -EINVAL; | 3034 | return -EINVAL; |
diff --git a/net/openvswitch/flow_netlink.h b/net/openvswitch/flow_netlink.h index 929c665ac3aa..6657606b2b47 100644 --- a/net/openvswitch/flow_netlink.h +++ b/net/openvswitch/flow_netlink.h | |||
| @@ -79,4 +79,9 @@ int ovs_nla_put_actions(const struct nlattr *attr, | |||
| 79 | void ovs_nla_free_flow_actions(struct sw_flow_actions *); | 79 | void ovs_nla_free_flow_actions(struct sw_flow_actions *); |
| 80 | void ovs_nla_free_flow_actions_rcu(struct sw_flow_actions *); | 80 | void ovs_nla_free_flow_actions_rcu(struct sw_flow_actions *); |
| 81 | 81 | ||
| 82 | int nsh_key_from_nlattr(const struct nlattr *attr, struct ovs_key_nsh *nsh, | ||
| 83 | struct ovs_key_nsh *nsh_mask); | ||
| 84 | int nsh_hdr_from_nlattr(const struct nlattr *attr, struct nshhdr *nh, | ||
| 85 | size_t size); | ||
| 86 | |||
| 82 | #endif /* flow_netlink.h */ | 87 | #endif /* flow_netlink.h */ |
