From patchwork Fri Oct 2 21:16:14 2015 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Joe Stringer X-Patchwork-Id: 525807 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from archives.nicira.com (li376-54.members.linode.com [96.126.127.54]) by ozlabs.org (Postfix) with ESMTP id 4685B1402C4 for ; Sat, 3 Oct 2015 07:17:38 +1000 (AEST) Received: from archives.nicira.com (localhost [127.0.0.1]) by archives.nicira.com (Postfix) with ESMTP id EDDCE22C4BC; Fri, 2 Oct 2015 14:16:39 -0700 (PDT) X-Original-To: dev@openvswitch.org Delivered-To: dev@openvswitch.org Received: from mx1e3.cudamail.com (mx1.cudamail.com [69.90.118.67]) by archives.nicira.com (Postfix) with ESMTPS id B7CEE22C4AC for ; Fri, 2 Oct 2015 14:16:37 -0700 (PDT) Received: from bar2.cudamail.com (localhost [127.0.0.1]) by mx1e3.cudamail.com (Postfix) with ESMTPS id 3469C4201F6 for ; Fri, 2 Oct 2015 15:16:37 -0600 (MDT) X-ASG-Debug-ID: 1443820596-03dc537fe1ab1fa0001-byXFYA Received: from mx1-pf2.cudamail.com ([192.168.24.2]) by bar2.cudamail.com with ESMTP id FW5wemKBELT5Mz1N (version=TLSv1 cipher=DHE-RSA-AES256-SHA bits=256 verify=NO) for ; Fri, 02 Oct 2015 15:16:36 -0600 (MDT) X-Barracuda-Envelope-From: joestringer@nicira.com X-Barracuda-RBL-Trusted-Forwarder: 192.168.24.2 Received: from unknown (HELO mail-pa0-f43.google.com) (209.85.220.43) by mx1-pf2.cudamail.com with ESMTPS (RC4-SHA encrypted); 2 Oct 2015 21:16:36 -0000 Received-SPF: unknown (mx1-pf2.cudamail.com: Multiple SPF records returned) X-Barracuda-RBL-Trusted-Forwarder: 209.85.220.43 Received: by pacfv12 with SMTP id fv12so118672837pac.2 for ; Fri, 02 Oct 2015 14:16:35 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20130820; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references; bh=AOZOkfh7anjcWoo+283cjnDEFlxqjIoSYHR0NS/H18k=; b=E+677I806LTrmY2E0TJyuvVJnDzj2zib9q0KczOKd6BIPf7AgYsz9eGwKwWro6Pwm4 YmY016ptwfa0zF3mj6IdAJ3mAo6iOQPZypkd+XvrQv+w+azEvT0myUnRArNK2sfT0x9O NxmvVuHT/5cwrxJnvGgyIfpcSSVN1YpwOsiYUkC3y8Yl1SF7zV+uM0kPNnSiU5Xv0Ysx n1pYYo25w3SCsl4i3qH86pbOdIMlLlt6i2qH5Jln5yRFRnDP7Rp97qH9En2DvZcGV8Vf 0iK3FCK3J65007iFz8CEhQVPgAziPGRY5Ms1wot42XYYiNIZJmyo3bE+VCHQeEYcSoF6 28/g== X-Gm-Message-State: ALoCoQmJsfd3uNxXTJx/GJ80UkO/TCHWwhv+zx/xh6JwxIU4owfiZVHoVHtxvJYw4RG4UQCrgdMP X-Received: by 10.66.66.196 with SMTP id h4mr7857681pat.38.1443820595431; Fri, 02 Oct 2015 14:16:35 -0700 (PDT) Received: from localhost.localdomain ([208.91.2.4]) by smtp.gmail.com with ESMTPSA id z6sm13633900pbt.51.2015.10.02.14.16.34 (version=TLSv1.2 cipher=ECDHE-RSA-AES128-SHA bits=128/128); Fri, 02 Oct 2015 14:16:34 -0700 (PDT) X-CudaMail-Envelope-Sender: joestringer@nicira.com X-Barracuda-Apparent-Source-IP: 208.91.2.4 From: Joe Stringer To: dev@openvswitch.org X-CudaMail-Whitelist-To: dev@openvswitch.org X-CudaMail-MID: CM-E2-1001085208 X-CudaMail-DTE: 100215 X-CudaMail-Originating-IP: 209.85.220.43 Date: Fri, 2 Oct 2015 14:16:14 -0700 X-ASG-Orig-Subj: [##CM-E2-1001085208##][PATCHv4 07/11] Add connection tracking mark support. Message-Id: <1443820578-9287-8-git-send-email-joestringer@nicira.com> X-Mailer: git-send-email 2.1.4 In-Reply-To: <1443820578-9287-1-git-send-email-joestringer@nicira.com> References: <1443820578-9287-1-git-send-email-joestringer@nicira.com> X-Barracuda-Connect: UNKNOWN[192.168.24.2] X-Barracuda-Start-Time: 1443820596 X-Barracuda-Encrypted: DHE-RSA-AES256-SHA X-Barracuda-URL: https://web.cudamail.com:443/cgi-mod/mark.cgi X-ASG-Whitelist: Header =?UTF-8?B?eFwtY3VkYW1haWxcLXdoaXRlbGlzdFwtdG8=?= X-Barracuda-BRTS-Status: 1 X-Virus-Scanned: by bsmtpd at cudamail.com Subject: [ovs-dev] [PATCHv4 07/11] Add connection tracking mark support. X-BeenThere: dev@openvswitch.org X-Mailman-Version: 2.1.16 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , MIME-Version: 1.0 Errors-To: dev-bounces@openvswitch.org Sender: "dev" This patch adds a new 32-bit metadata field to the connection tracking interface. When a mark is specified as part of the ct action and the connection is committed, the value is saved with the current connection. Subsequent ct lookups with the table specified will expose this metadata as the "ct_mark" field in the flow. For example, to allow new TCP connections from port 1->2 and only allow established connections from port 2->1, and to associate a mark with those connections: table=0,priority=1,action=drop table=0,arp,action=normal table=0,in_port=1,tcp,action=ct(commit,exec(set_field:1->ct_mark)),2 table=0,in_port=2,ct_state=-trk,tcp,action=ct(table=1) table=1,in_port=2,ct_state=+trk,ct_mark=1,tcp,action=1 Signed-off-by: Joe Stringer Acked-by: Jarno Rajahalme Acked-by: Ben Pfaff --- v4: - Acked - Rebase v3: - Remove extraneous NXM mask checks - Simplify ODP action formatting - Check prerequisites for nested CT actions - Pass ofp_version to nested CT actions decode - Fix decode_CT error case where buffer was not pushed - Always call ofpacts_verify_nested() - Improve ct nested actions, ct_mark documentation - Add dpctl, ofctl tests - Add openflow wire format tests v2: Only make ct_mark visible after recirculation - Require COMMIT flag for nested actions - Address feedback from v1 --- NEWS | 4 +- datapath/flow_netlink.c | 2 +- datapath/linux/compat/include/linux/openvswitch.h | 5 + lib/dpif-netdev.c | 2 +- lib/flow.c | 13 ++ lib/flow.h | 9 +- lib/match.c | 18 +++ lib/match.h | 2 + lib/meta-flow.c | 24 ++++ lib/meta-flow.h | 19 +++ lib/nx-match.c | 4 +- lib/odp-execute.c | 2 + lib/odp-util.c | 56 ++++++++- lib/odp-util.h | 6 +- lib/ofp-actions.c | 138 +++++++++++++++++++--- lib/ofp-actions.h | 34 +++++- lib/packets.h | 1 + ofproto/ofproto-dpif-sflow.c | 1 + ofproto/ofproto-dpif-xlate.c | 28 +++++ ofproto/ofproto-dpif.c | 9 +- ofproto/ofproto-unixctl.man | 2 + tests/dpif-netdev.at | 2 +- tests/odp.at | 9 +- tests/ofp-actions.at | 23 ++++ tests/ofproto-dpif.at | 4 +- tests/ofproto.at | 3 +- tests/ovs-ofctl.at | 13 +- tests/system-traffic.at | 132 +++++++++++++++++++++ tests/test-odp.c | 1 + utilities/ovs-ofctl.8.in | 29 ++++- 30 files changed, 552 insertions(+), 43 deletions(-) diff --git a/NEWS b/NEWS index 9881458..fb3a20c 100644 --- a/NEWS +++ b/NEWS @@ -25,8 +25,8 @@ Post-v2.4.0 the next OVS release. - Added --user option to all daemons - Add support for connection tracking through the new "ct" action - and "ct_state"/"ct_zone" match fields. Only available on Linux kernels - with the connection tracking module loaded. + and "ct_state"/"ct_zone"/"ct_mark" match fields. Only available on + Linux kernels with the connection tracking module loaded. v2.4.0 - 20 Aug 2015 diff --git a/datapath/flow_netlink.c b/datapath/flow_netlink.c index 5d14f35..f50f023 100644 --- a/datapath/flow_netlink.c +++ b/datapath/flow_netlink.c @@ -282,7 +282,7 @@ size_t ovs_key_attr_size(void) /* Whenever adding new OVS_KEY_ FIELDS, we should consider * updating this function. */ - BUILD_BUG_ON(OVS_KEY_ATTR_TUNNEL_INFO != 24); + BUILD_BUG_ON(OVS_KEY_ATTR_TUNNEL_INFO != 25); return nla_total_size(4) /* OVS_KEY_ATTR_PRIORITY */ + nla_total_size(0) /* OVS_KEY_ATTR_TUNNEL */ diff --git a/datapath/linux/compat/include/linux/openvswitch.h b/datapath/linux/compat/include/linux/openvswitch.h index ae86d1a..fbe2467 100644 --- a/datapath/linux/compat/include/linux/openvswitch.h +++ b/datapath/linux/compat/include/linux/openvswitch.h @@ -345,6 +345,7 @@ enum ovs_key_attr { * the accepted length of the array. */ OVS_KEY_ATTR_CT_STATE, /* u32 bitmask of OVS_CS_F_* */ OVS_KEY_ATTR_CT_ZONE, /* u16 connection tracking zone. */ + OVS_KEY_ATTR_CT_MARK, /* u32 connection tracking mark */ #ifdef __KERNEL__ /* Only used within kernel data path. */ @@ -658,11 +659,15 @@ struct ovs_action_push_tnl { * table. This allows future packets for the same connection to be identified * as 'established' or 'related'. * @OVS_CT_ATTR_ZONE: u16 connection tracking zone. + * @OVS_CT_ATTR_MARK: u32 value followed by u32 mask. For each bit set in the + * mask, the corresponding bit in the value is copied to the connection + * tracking mark field in the connection. */ enum ovs_ct_attr { OVS_CT_ATTR_UNSPEC, OVS_CT_ATTR_COMMIT, /* No argument, commits connection. */ OVS_CT_ATTR_ZONE, /* u16 zone id. */ + OVS_CT_ATTR_MARK, /* mark to associate with this connection. */ __OVS_CT_ATTR_MAX }; diff --git a/lib/dpif-netdev.c b/lib/dpif-netdev.c index 3dc0a62..8769842 100644 --- a/lib/dpif-netdev.c +++ b/lib/dpif-netdev.c @@ -1921,7 +1921,7 @@ dpif_netdev_flow_from_nlattrs(const struct nlattr *key, uint32_t key_len, } /* Userspace datapath doesn't support conntrack. */ - if (flow->ct_state || flow->ct_zone) { + if (flow->ct_state || flow->ct_zone || flow->ct_mark) { return EINVAL; } diff --git a/lib/flow.c b/lib/flow.c index 918caa2..1e2d2ab 100644 --- a/lib/flow.c +++ b/lib/flow.c @@ -483,6 +483,11 @@ miniflow_extract(struct dp_packet *packet, struct miniflow *dst) miniflow_push_uint16(mf, ct_zone, md->ct_zone); } + if (md->ct_state) { + miniflow_push_uint32(mf, ct_mark, md->ct_mark); + miniflow_pad_to_64(mf, pad1); + } + /* Initialize packet's layer pointer and offsets. */ l2 = data; dp_packet_reset_offsets(packet); @@ -839,6 +844,9 @@ flow_get_metadata(const struct flow *flow, struct match *flow_metadata) if (flow->ct_zone != 0) { match_set_ct_zone(flow_metadata, flow->ct_zone); } + if (flow->ct_mark != 0) { + match_set_ct_mark(flow_metadata, flow->ct_mark); + } } const char *ct_state_to_string(uint32_t state) @@ -1140,6 +1148,9 @@ flow_format(struct ds *ds, const struct flow *flow) if (!flow->ct_zone) { WC_UNMASK_FIELD(wc, ct_zone); } + if (!flow->ct_mark) { + WC_UNMASK_FIELD(wc, ct_mark); + } for (int i = 0; i < FLOW_N_REGS; i++) { if (!flow->regs[i]) { WC_UNMASK_FIELD(wc, regs[i]); @@ -1216,6 +1227,7 @@ void flow_wildcards_init_for_packet(struct flow_wildcards *wc, WC_MASK_FIELD(wc, pkt_mark); WC_MASK_FIELD(wc, ct_state); WC_MASK_FIELD(wc, ct_zone); + WC_MASK_FIELD(wc, ct_mark); WC_MASK_FIELD(wc, recirc_id); WC_MASK_FIELD(wc, dp_hash); WC_MASK_FIELD(wc, in_port); @@ -1321,6 +1333,7 @@ flow_wc_map(const struct flow *flow, struct flowmap *map) FLOWMAP_SET(map, vlan_tci); FLOWMAP_SET(map, ct_state); FLOWMAP_SET(map, ct_zone); + FLOWMAP_SET(map, ct_mark); /* Ethertype-dependent fields. */ if (OVS_LIKELY(flow->dl_type == htons(ETH_TYPE_IP))) { diff --git a/lib/flow.h b/lib/flow.h index ce297e3..1ca0a1d 100644 --- a/lib/flow.h +++ b/lib/flow.h @@ -104,9 +104,11 @@ struct flow { uint32_t recirc_id; /* Must be exact match. */ uint16_t ct_state; /* Connection tracking state. */ uint16_t ct_zone; /* Connection tracking zone. */ + uint32_t ct_mark; /* Connection mark.*/ + uint8_t pad1[4]; /* Pad to 64 bits. */ uint32_t conj_id; /* Conjunction ID. */ ofp_port_t actset_output; /* Output port in action set. */ - uint8_t pad1[2]; /* Pad to 64 bits. */ + uint8_t pad2[2]; /* Pad to 64 bits. */ /* L2, Order the same as in the Ethernet header! (64-bit aligned) */ struct eth_addr dl_dst; /* Ethernet destination address. */ @@ -129,7 +131,7 @@ struct flow { struct eth_addr arp_sha; /* ARP/ND source hardware address. */ struct eth_addr arp_tha; /* ARP/ND target hardware address. */ ovs_be16 tcp_flags; /* TCP flags. With L3 to avoid matching L4. */ - ovs_be16 pad2; /* Pad to 64 bits. */ + ovs_be16 pad3; /* Pad to 64 bits. */ /* L4 (64-bit aligned) */ ovs_be16 tp_src; /* TCP/UDP/SCTP source port. */ @@ -155,7 +157,7 @@ BUILD_ASSERT_DECL(sizeof(struct flow_tnl) % sizeof(uint64_t) == 0); /* Remember to update FLOW_WC_SEQ when changing 'struct flow'. */ BUILD_ASSERT_DECL(offsetof(struct flow, igmp_group_ip4) + sizeof(uint32_t) - == sizeof(struct flow_tnl) + 192 + == sizeof(struct flow_tnl) + 200 && FLOW_WC_SEQ == 34); /* Incremental points at which flow classification may be performed in @@ -985,6 +987,7 @@ pkt_metadata_from_flow(struct pkt_metadata *md, const struct flow *flow) md->in_port = flow->in_port; md->ct_state = flow->ct_state; md->ct_zone = flow->ct_zone; + md->ct_mark = flow->ct_mark; } static inline bool is_ip_any(const struct flow *flow) diff --git a/lib/match.c b/lib/match.c index d527d04..dc47168 100644 --- a/lib/match.c +++ b/lib/match.c @@ -305,6 +305,20 @@ match_set_ct_zone(struct match *match, uint16_t ct_zone) } void +match_set_ct_mark(struct match *match, uint32_t ct_mark) +{ + match_set_ct_mark_masked(match, ct_mark, UINT32_MAX); +} + +void +match_set_ct_mark_masked(struct match *match, uint32_t ct_mark, + uint32_t mask) +{ + match->flow.ct_mark = ct_mark & mask; + match->wc.masks.ct_mark = mask; +} + +void match_set_dl_type(struct match *match, ovs_be16 dl_type) { match->wc.masks.dl_type = OVS_BE16_MAX; @@ -1007,6 +1021,10 @@ match_format(const struct match *match, struct ds *s, int priority) format_uint16_masked(s, "ct_zone", f->ct_zone, wc->masks.ct_zone); } + if (wc->masks.ct_mark) { + format_uint32_masked(s, "ct_mark", f->ct_mark, wc->masks.ct_mark); + } + if (wc->masks.dl_type) { skip_type = true; if (f->dl_type == htons(ETH_TYPE_IP)) { diff --git a/lib/match.h b/lib/match.h index 03991ac..17c0879 100644 --- a/lib/match.h +++ b/lib/match.h @@ -86,6 +86,8 @@ void match_set_pkt_mark_masked(struct match *, uint32_t pkt_mark, uint32_t mask) void match_set_ct_state(struct match *, uint32_t ct_state); void match_set_ct_state_masked(struct match *, uint32_t ct_state, uint32_t mask); void match_set_ct_zone(struct match *, uint16_t ct_zone); +void match_set_ct_mark(struct match *, uint32_t ct_mark); +void match_set_ct_mark_masked(struct match *, uint32_t ct_mark, uint32_t mask); void match_set_skb_priority(struct match *, uint32_t skb_priority); void match_set_dl_type(struct match *, ovs_be16); void match_set_dl_src(struct match *, const struct eth_addr ); diff --git a/lib/meta-flow.c b/lib/meta-flow.c index 02364fc..084a572 100644 --- a/lib/meta-flow.c +++ b/lib/meta-flow.c @@ -218,6 +218,8 @@ mf_is_all_wild(const struct mf_field *mf, const struct flow_wildcards *wc) return !wc->masks.ct_state; case MFF_CT_ZONE: return !wc->masks.ct_zone; + case MFF_CT_MARK: + return !wc->masks.ct_mark; CASE_MFF_REGS: return !wc->masks.regs[mf->id - MFF_REG0]; CASE_MFF_XREGS: @@ -502,6 +504,7 @@ mf_is_value_valid(const struct mf_field *mf, const union mf_value *value) case MFF_SKB_PRIORITY: case MFF_PKT_MARK: case MFF_CT_ZONE: + case MFF_CT_MARK: CASE_MFF_REGS: CASE_MFF_XREGS: case MFF_ETH_SRC: @@ -660,6 +663,10 @@ mf_get_value(const struct mf_field *mf, const struct flow *flow, value->be16 = htons(flow->ct_zone); break; + case MFF_CT_MARK: + value->be32 = htonl(flow->ct_mark); + break; + CASE_MFF_REGS: value->be32 = htonl(flow->regs[mf->id - MFF_REG0]); break; @@ -900,6 +907,10 @@ mf_set_value(const struct mf_field *mf, match_set_ct_zone(match, ntohs(value->be16)); break; + case MFF_CT_MARK: + match_set_ct_mark(match, ntohl(value->be32)); + break; + CASE_MFF_REGS: match_set_reg(match, mf->id - MFF_REG0, ntohl(value->be32)); break; @@ -1192,6 +1203,10 @@ mf_set_flow_value(const struct mf_field *mf, flow->ct_zone = ntohs(value->be16); break; + case MFF_CT_MARK: + flow->ct_mark = ntohl(value->be32); + break; + CASE_MFF_REGS: flow->regs[mf->id - MFF_REG0] = ntohl(value->be32); break; @@ -1491,6 +1506,11 @@ mf_set_wild(const struct mf_field *mf, struct match *match, char **err_str) match->wc.masks.ct_zone = 0; break; + case MFF_CT_MARK: + match->flow.ct_mark = 0; + match->wc.masks.ct_mark = 0; + break; + CASE_MFF_REGS: match_set_reg_masked(match, mf->id - MFF_REG0, 0, 0); break; @@ -1758,6 +1778,10 @@ mf_set(const struct mf_field *mf, match_set_ct_state_masked(match, ntohl(value->be32), ntohl(mask->be32)); break; + case MFF_CT_MARK: + match_set_ct_mark_masked(match, ntohl(value->be32), ntohl(mask->be32)); + break; + case MFF_ETH_DST: match_set_dl_dst_masked(match, value->mac, mask->mac); break; diff --git a/lib/meta-flow.h b/lib/meta-flow.h index 58c34c6..65c6eeb 100644 --- a/lib/meta-flow.h +++ b/lib/meta-flow.h @@ -768,6 +768,25 @@ enum OVS_PACKED_ENUM mf_field_id { */ MFF_CT_ZONE, + /* "ct_mark". + * + * Connection tracking mark. The mark is carried with the + * connection tracking state. On Linux this corresponds to the + * nf_conn's "mark" member but the exact implementation is + * platform-dependent. + * + * Writable only from nested actions within the NXAST_CT action. + * + * Type: be32. + * Maskable: bitwise. + * Formatting: hexadecimal. + * Prerequisites: none. + * Access: read/write. + * NXM: NXM_NX_CT_MARK(107) since v2.5. + * OXM: none. + */ + MFF_CT_MARK, + #if FLOW_N_REGS == 8 /* "reg". * diff --git a/lib/nx-match.c b/lib/nx-match.c index 5eaa62e..094a975 100644 --- a/lib/nx-match.c +++ b/lib/nx-match.c @@ -1032,7 +1032,7 @@ nx_put_raw(struct ofpbuf *b, enum ofp_version oxm, const struct match *match, } } - /* Mark. */ + /* Packet mark. */ nxm_put_32m(b, MFF_PKT_MARK, oxm, htonl(flow->pkt_mark), htonl(match->wc.masks.pkt_mark)); @@ -1041,6 +1041,8 @@ nx_put_raw(struct ofpbuf *b, enum ofp_version oxm, const struct match *match, htonl(match->wc.masks.ct_state)); nxm_put_16m(b, MFF_CT_ZONE, oxm, htons(flow->ct_zone), htons(match->wc.masks.ct_zone)); + nxm_put_32m(b, MFF_CT_MARK, oxm, htonl(flow->ct_mark), + htonl(match->wc.masks.ct_mark)); /* OpenFlow 1.1+ Metadata. */ nxm_put_64m(b, MFF_METADATA, oxm, diff --git a/lib/odp-execute.c b/lib/odp-execute.c index 575631c..4d6384e 100644 --- a/lib/odp-execute.c +++ b/lib/odp-execute.c @@ -328,6 +328,7 @@ odp_execute_set_action(struct dp_packet *packet, const struct nlattr *a) case OVS_KEY_ATTR_TCP_FLAGS: case OVS_KEY_ATTR_CT_STATE: case OVS_KEY_ATTR_CT_ZONE: + case OVS_KEY_ATTR_CT_MARK: case __OVS_KEY_ATTR_MAX: default: OVS_NOT_REACHED(); @@ -418,6 +419,7 @@ odp_execute_masked_set_action(struct dp_packet *packet, case OVS_KEY_ATTR_UNSPEC: case OVS_KEY_ATTR_CT_STATE: case OVS_KEY_ATTR_CT_ZONE: + case OVS_KEY_ATTR_CT_MARK: case OVS_KEY_ATTR_ENCAP: case OVS_KEY_ATTR_ETHERTYPE: case OVS_KEY_ATTR_IN_PORT: diff --git a/lib/odp-util.c b/lib/odp-util.c index a570afa..fa2fe73 100644 --- a/lib/odp-util.c +++ b/lib/odp-util.c @@ -137,6 +137,7 @@ ovs_key_attr_to_string(enum ovs_key_attr attr, char *namebuf, size_t bufsize) case OVS_KEY_ATTR_SKB_MARK: return "skb_mark"; case OVS_KEY_ATTR_CT_STATE: return "ct_state"; case OVS_KEY_ATTR_CT_ZONE: return "ct_zone"; + case OVS_KEY_ATTR_CT_MARK: return "ct_mark"; case OVS_KEY_ATTR_TUNNEL: return "tunnel"; case OVS_KEY_ATTR_IN_PORT: return "in_port"; case OVS_KEY_ATTR_ETHERNET: return "eth"; @@ -538,14 +539,17 @@ format_odp_tnl_push_action(struct ds *ds, const struct nlattr *attr) static const struct nl_policy ovs_conntrack_policy[] = { [OVS_CT_ATTR_COMMIT] = { .type = NL_A_FLAG, .optional = true, }, [OVS_CT_ATTR_ZONE] = { .type = NL_A_U16, .optional = true, }, + [OVS_CT_ATTR_MARK] = { .type = NL_A_UNSPEC, .optional = true, + .min_len = sizeof(uint32_t) * 2 }, }; static void format_odp_conntrack_action(struct ds *ds, const struct nlattr *attr) { struct nlattr *a[ARRAY_SIZE(ovs_conntrack_policy)]; - bool commit; + const uint32_t *mark; uint16_t zone; + bool commit; if (!nl_parse_nested(attr, ovs_conntrack_policy, a, ARRAY_SIZE(a))) { ds_put_cstr(ds, "ct(error)"); @@ -554,9 +558,10 @@ format_odp_conntrack_action(struct ds *ds, const struct nlattr *attr) commit = a[OVS_CT_ATTR_COMMIT] ? true : false; zone = a[OVS_CT_ATTR_ZONE] ? nl_attr_get_u16(a[OVS_CT_ATTR_ZONE]) : 0; + mark = a[OVS_CT_ATTR_MARK] ? nl_attr_get(a[OVS_CT_ATTR_MARK]) : NULL; ds_put_format(ds, "ct"); - if (commit || zone) { + if (commit || zone || mark) { ds_put_cstr(ds, "("); if (commit) { ds_put_format(ds, "commit,"); @@ -564,6 +569,10 @@ format_odp_conntrack_action(struct ds *ds, const struct nlattr *attr) if (zone) { ds_put_format(ds, "zone=%"PRIu16",", zone); } + if (mark) { + ds_put_format(ds, "mark=%#"PRIx32"/%#"PRIx32",", *mark, + *(mark + 1)); + } ds_chomp(ds, ','); ds_put_cstr(ds, ")"); } @@ -1007,6 +1016,10 @@ parse_conntrack_action(const char *s_, struct ofpbuf *actions) if (ovs_scan(s, "ct")) { bool commit = false; uint16_t zone = 0; + struct { + uint32_t value; + uint32_t mask; + } ct_mark = { 0, 0 }; size_t start; char *end; @@ -1031,6 +1044,16 @@ parse_conntrack_action(const char *s_, struct ofpbuf *actions) s += n; continue; } + if (ovs_scan(s, "mark=%"SCNx32"%n", &ct_mark.value, &n)) { + s += n; + n = -1; + if (ovs_scan(s, "/%"SCNx32"%n", &ct_mark.mask, &n)) { + s += n; + } else { + ct_mark.mask = UINT32_MAX; + } + continue; + } return -EINVAL; } @@ -1044,6 +1067,10 @@ parse_conntrack_action(const char *s_, struct ofpbuf *actions) if (zone) { nl_msg_put_u16(actions, OVS_CT_ATTR_ZONE, zone); } + if (ct_mark.mask) { + nl_msg_put_unspec(actions, OVS_CT_ATTR_MARK, &ct_mark, + sizeof(ct_mark)); + } nl_msg_end_nested(actions, start); } @@ -1313,6 +1340,7 @@ static const struct attr_len_tbl ovs_flow_key_attr_lens[OVS_KEY_ATTR_MAX + 1] = [OVS_KEY_ATTR_ND] = { .len = sizeof(struct ovs_key_nd) }, [OVS_KEY_ATTR_CT_STATE] = { .len = 4 }, [OVS_KEY_ATTR_CT_ZONE] = { .len = 2 }, + [OVS_KEY_ATTR_CT_MARK] = { .len = 4 }, }; /* Returns the correct length of the payload for a flow key attribute of the @@ -2195,6 +2223,15 @@ format_odp_key_attr(const struct nlattr *a, const struct nlattr *ma, } break; + case OVS_KEY_ATTR_CT_MARK: + if (verbose || !mask_empty(ma)) { + ds_put_format(ds, "%#"PRIx32, nl_attr_get_u32(a)); + if (!is_exact) { + ds_put_format(ds, "/%#"PRIx32, nl_attr_get_u32(ma)); + } + } + break; + case OVS_KEY_ATTR_CT_STATE: if (verbose) { ds_put_format(ds, "%#"PRIx32, nl_attr_get_u32(a)); @@ -3333,6 +3370,7 @@ parse_odp_key_mask_attr(const char *s, const struct simap *port_names, SCAN_SINGLE("ct_state(", uint32_t, ct_state, OVS_KEY_ATTR_CT_STATE); SCAN_SINGLE("ct_zone(", uint16_t, u16, OVS_KEY_ATTR_CT_ZONE); + SCAN_SINGLE("ct_mark(", uint32_t, u32, OVS_KEY_ATTR_CT_MARK); SCAN_BEGIN_NESTED("tunnel(", OVS_KEY_ATTR_TUNNEL) { SCAN_FIELD_NESTED("tun_id=", ovs_be64, be64, OVS_TUNNEL_KEY_ATTR_ID); @@ -3636,6 +3674,9 @@ odp_flow_key_from_flow__(const struct odp_flow_key_parms *parms, if (parms->support.ct_zone) { nl_msg_put_u16(buf, OVS_KEY_ATTR_CT_ZONE, data->ct_zone); } + if (parms->support.ct_mark) { + nl_msg_put_u32(buf, OVS_KEY_ATTR_CT_MARK, data->ct_mark); + } if (parms->support.recirc) { nl_msg_put_u32(buf, OVS_KEY_ATTR_RECIRC_ID, data->recirc_id); nl_msg_put_u32(buf, OVS_KEY_ATTR_DP_HASH, data->dp_hash); @@ -3824,6 +3865,9 @@ odp_key_from_pkt_metadata(struct ofpbuf *buf, const struct pkt_metadata *md) if (md->ct_zone) { nl_msg_put_u16(buf, OVS_KEY_ATTR_CT_ZONE, md->ct_zone); } + if (md->ct_mark) { + nl_msg_put_u32(buf, OVS_KEY_ATTR_CT_MARK, md->ct_mark); + } } /* Add an ingress port attribute if 'odp_in_port' is not the magical @@ -3881,6 +3925,10 @@ odp_key_to_pkt_metadata(const struct nlattr *key, size_t key_len, md->ct_zone = nl_attr_get_u16(nla); wanted_attrs &= ~(1u << OVS_KEY_ATTR_CT_ZONE); break; + case OVS_KEY_ATTR_CT_MARK: + md->ct_mark = nl_attr_get_u32(nla); + wanted_attrs &= ~(1u << OVS_KEY_ATTR_CT_MARK); + break; case OVS_KEY_ATTR_TUNNEL: { enum odp_key_fitness res; @@ -4445,6 +4493,10 @@ odp_flow_key_to_flow__(const struct nlattr *key, size_t key_len, flow->ct_zone = nl_attr_get_u16(attrs[OVS_KEY_ATTR_CT_ZONE]); expected_attrs |= UINT64_C(1) << OVS_KEY_ATTR_CT_ZONE; } + if (present_attrs & (UINT64_C(1) << OVS_KEY_ATTR_CT_MARK)) { + flow->ct_mark = nl_attr_get_u32(attrs[OVS_KEY_ATTR_CT_MARK]); + expected_attrs |= UINT64_C(1) << OVS_KEY_ATTR_CT_MARK; + } if (present_attrs & (UINT64_C(1) << OVS_KEY_ATTR_TUNNEL)) { enum odp_key_fitness res; diff --git a/lib/odp-util.h b/lib/odp-util.h index 7e8cd38..1bb1484 100644 --- a/lib/odp-util.h +++ b/lib/odp-util.h @@ -122,6 +122,7 @@ void odp_portno_names_destroy(struct hmap *portno_names); * OVS_KEY_ATTR_RECIRC_ID 4 -- 4 8 * OVS_KEY_ATTR_CT_STATE 4 -- 4 8 * OVS_KEY_ATTR_CT_ZONE 2 2 4 8 + * OVS_KEY_ATTR_CT_MARK 4 -- 4 8 * OVS_KEY_ATTR_ETHERNET 12 -- 4 16 * OVS_KEY_ATTR_ETHERTYPE 2 2 4 8 (outer VLAN ethertype) * OVS_KEY_ATTR_VLAN 2 2 4 8 @@ -131,12 +132,12 @@ void odp_portno_names_destroy(struct hmap *portno_names); * OVS_KEY_ATTR_ICMPV6 2 2 4 8 * OVS_KEY_ATTR_ND 28 -- 4 32 * ---------------------------------------------------------- - * total 504 + * total 512 * * We include some slack space in case the calculation isn't quite right or we * add another field and forget to adjust this value. */ -#define ODPUTIL_FLOW_KEY_BYTES 512 +#define ODPUTIL_FLOW_KEY_BYTES 576 BUILD_ASSERT_DECL(FLOW_WC_SEQ == 34); /* A buffer with sufficient size and alignment to hold an nlattr-formatted flow @@ -172,6 +173,7 @@ struct odp_support { /* If true, serialise the corresponding OVS_KEY_ATTR_CONN_* field. */ bool ct_state; bool ct_zone; + bool ct_mark; }; struct odp_flow_key_parms { diff --git a/lib/ofp-actions.c b/lib/ofp-actions.c index d70b3eb..5b3c392 100644 --- a/lib/ofp-actions.c +++ b/lib/ofp-actions.c @@ -353,6 +353,10 @@ static enum ofperr ofpacts_pull_openflow_actions__( struct ofpbuf *openflow, unsigned int actions_len, enum ofp_version version, uint32_t allowed_ovsinsts, struct ofpbuf *ofpacts, enum ofpact_type outer_action); +static char * OVS_WARN_UNUSED_RESULT ofpacts_parse_copy( + const char *s_, struct ofpbuf *ofpacts, + enum ofputil_protocol *usable_protocols, + bool allow_instructions, enum ofpact_type outer_action); /* Pull off existing actions or instructions. Used by nesting actions to keep * ofpacts_parse() oblivious of actions nesting. @@ -4657,6 +4661,10 @@ format_DEBUG_RECIRC(const struct ofpact_null *a OVS_UNUSED, struct ds *s) * * It is strongly recommended that this table is later than the current * table, to prevent loops. + * + * Zero or more actions may immediately follow this action. These actions will + * be executed within the context of the connection tracker, and they require + * the NX_CT_F_COMMIT flag to be set. */ struct nx_action_conntrack { ovs_be16 type; /* OFPAT_VENDOR. */ @@ -4673,6 +4681,8 @@ struct nx_action_conntrack { uint8_t recirc_table; /* Recirculate to a specific table, or NX_CT_RECIRC_NONE for no recirculation. */ uint8_t pad[5]; /* Zeroes */ + /* Followed by a sequence of ofpact elements, until the end of the action + * is reached. */ }; OFP_ASSERT(sizeof(struct nx_action_conntrack) == 24); @@ -4706,10 +4716,11 @@ decode_ct_zone(const struct nx_action_conntrack *nac, static enum ofperr decode_NXAST_RAW_CT(const struct nx_action_conntrack *nac, - enum ofp_version ofp_version OVS_UNUSED, - struct ofpbuf *out) + enum ofp_version ofp_version, struct ofpbuf *out) { + const size_t ct_offset = ofpacts_pull(out); struct ofpact_conntrack *conntrack; + struct ofpbuf openflow; int error = 0; conntrack = ofpact_put_CT(out); @@ -4720,15 +4731,40 @@ decode_NXAST_RAW_CT(const struct nx_action_conntrack *nac, } conntrack->recirc_table = nac->recirc_table; + ofpbuf_pull(out, sizeof(*conntrack)); + + ofpbuf_use_const(&openflow, nac + 1, ntohs(nac->len) - sizeof(*nac)); + error = ofpacts_pull_openflow_actions__(&openflow, openflow.size, + ofp_version, + 1u << OVSINST_OFPIT11_APPLY_ACTIONS, + out, OFPACT_CT); + if (error) { + goto out; + } + + conntrack = ofpbuf_push_uninit(out, sizeof(*conntrack)); + out->header = &conntrack->ofpact; + ofpact_update_len(out, &conntrack->ofpact); + + if (conntrack->ofpact.len > sizeof(*conntrack) + && !(conntrack->flags & NX_CT_F_COMMIT)) { + VLOG_WARN_RL(&rl, "CT action requires commit flag if actions are " + "specified."); + error = OFPERR_OFPBAC_BAD_ARGUMENT; + } + out: + ofpbuf_push_uninit(out, ct_offset); return error; } static void encode_CT(const struct ofpact_conntrack *conntrack, - enum ofp_version ofp_version OVS_UNUSED, struct ofpbuf *out) + enum ofp_version ofp_version, struct ofpbuf *out) { struct nx_action_conntrack *nac; + const size_t ofs = out->size; + size_t len; nac = put_NXAST_CT(out); nac->flags = htons(conntrack->flags); @@ -4741,6 +4777,13 @@ encode_CT(const struct ofpact_conntrack *conntrack, nac->zone_imm = htons(conntrack->zone_imm); } nac->recirc_table = conntrack->recirc_table; + + len = ofpacts_put_openflow_actions(conntrack->actions, + ofpact_ct_get_action_len(conntrack), + out, ofp_version); + len += sizeof(*nac); + nac = ofpbuf_at(out, ofs, sizeof(*nac)); + nac->len = htons(len); } /* Parses 'arg' as the argument to a "ct" action, and appends such an @@ -4750,8 +4793,9 @@ encode_CT(const struct ofpact_conntrack *conntrack, * error. The caller is responsible for freeing the returned string. */ static char * OVS_WARN_UNUSED_RESULT parse_CT(char *arg, struct ofpbuf *ofpacts, - enum ofputil_protocol *usable_protocols OVS_UNUSED) + enum ofputil_protocol *usable_protocols) { + const size_t ct_offset = ofpacts_pull(ofpacts); struct ofpact_conntrack *oc; char *error = NULL; char *key, *value; @@ -4777,6 +4821,15 @@ parse_CT(char *arg, struct ofpbuf *ofpacts, return error; } } + } else if (!strcmp(key, "exec")) { + /* Hide existing actions from ofpacts_parse_copy(), so the + * nesting can be handled transparently. */ + ofpbuf_pull(ofpacts, sizeof(*oc)); + error = ofpacts_parse_copy(value, ofpacts, usable_protocols, false, + OFPACT_CT); + ofpact_pad(ofpacts); + ofpacts->header = ofpbuf_push_uninit(ofpacts, sizeof(*oc)); + oc = ofpacts->header; } else { error = xasprintf("invalid argument to \"ct\" action: `%s'", key); } @@ -4785,6 +4838,8 @@ parse_CT(char *arg, struct ofpbuf *ofpacts, } } + ofpact_update_len(ofpacts, &oc->ofpact); + ofpbuf_push_uninit(ofpacts, ct_offset); return error; } @@ -4805,6 +4860,11 @@ format_CT(const struct ofpact_conntrack *a, struct ds *s) } else if (a->zone_imm) { ds_put_format(s, "zone=%"PRIu16",", a->zone_imm); } + if (ofpact_ct_get_action_len(a)) { + ds_put_cstr(s, "exec("); + ofpacts_format(a->actions, ofpact_ct_get_action_len(a), s); + ds_put_format(s, "),"); + } ds_chomp(s, ','); ds_put_char(s, ')'); } @@ -6052,6 +6112,7 @@ ofpact_check__(enum ofputil_protocol *usable_protocols, struct ofpact *a, case OFPACT_CT: { struct ofpact_conntrack *oc = ofpact_get_CT(a); + enum ofputil_protocol p = *usable_protocols; if (!dl_type_is_ip_any(flow->dl_type) || (flow->ct_state & CS_INVALID && oc->flags & NX_CT_F_COMMIT)) { @@ -6061,7 +6122,9 @@ ofpact_check__(enum ofputil_protocol *usable_protocols, struct ofpact *a, if (oc->zone_src.field) { return mf_check_src(&oc->zone_src, flow); } - return 0; + + return ofpacts_check(oc->actions, ofpact_ct_get_action_len(oc), + flow, max_ports, table_id, n_tables, &p); } case OFPACT_CLEAR_ACTIONS: @@ -6168,13 +6231,60 @@ ofpacts_check_consistency(struct ofpact ofpacts[], size_t ofpacts_len, : 0); } +static const struct mf_field * +ofpact_get_mf_field(enum ofpact_type type, const void *ofpact) +{ + if (type == OFPACT_SET_FIELD) { + const struct ofpact_set_field *orl = ofpact; + + return orl->field; + } else if (type == OFPACT_REG_MOVE) { + const struct ofpact_reg_move *orm = ofpact; + + return orm->dst.field; + } + + return NULL; +} + +static enum ofperr +unsupported_nesting(enum ofpact_type action, enum ofpact_type outer_action) +{ + VLOG_WARN("%s action doesn't support nested action %s", + ofpact_name(outer_action), ofpact_name(action)); + return OFPERR_OFPBAC_BAD_ARGUMENT; +} + +static bool +field_requires_ct(enum mf_field_id field) +{ + return field == MFF_CT_MARK; +} + +/* Apply nesting constraints for actions */ static enum ofperr ofpacts_verify_nested(const struct ofpact *a, enum ofpact_type outer_action) { - if (outer_action != OFPACT_WRITE_ACTIONS) { - VLOG_WARN("\"%s\" action doesn't support nested action \"%s\"", - ofpact_name(outer_action), ofpact_name(a->type)); - return OFPERR_OFPBAC_BAD_ARGUMENT; + const struct mf_field *field = ofpact_get_mf_field(a->type, a); + + if (field && field_requires_ct(field->id) && outer_action != OFPACT_CT) { + VLOG_WARN("cannot set CT fields outside of ct action"); + return OFPERR_OFPBAC_BAD_SET_ARGUMENT; + } + + if (outer_action) { + ovs_assert(outer_action == OFPACT_WRITE_ACTIONS + || outer_action == OFPACT_CT); + + if (outer_action == OFPACT_CT) { + if (!field) { + return unsupported_nesting(a->type, outer_action); + } else if (!field_requires_ct(field->id)) { + VLOG_WARN("%s action doesn't support nested modification " + "of %s", ofpact_name(outer_action), field->name); + return OFPERR_OFPBAC_BAD_ARGUMENT; + } + } } return 0; @@ -6199,6 +6309,7 @@ ofpacts_verify(const struct ofpact ofpacts[], size_t ofpacts_len, inst = OVSINST_OFPIT13_METER; OFPACT_FOR_EACH (a, ofpacts, ofpacts_len) { enum ovs_instruction_type next; + enum ofperr error; if (a->type == OFPACT_CONJUNCTION) { OFPACT_FOR_EACH (a, ofpacts, ofpacts_len) { @@ -6213,12 +6324,9 @@ ofpacts_verify(const struct ofpact ofpacts[], size_t ofpacts_len, return 0; } - if (outer_action) { - enum ofperr error = ofpacts_verify_nested(a, outer_action); - - if (error) { - return error; - } + error = ofpacts_verify_nested(a, outer_action); + if (error) { + return error; } next = ovs_instruction_type_from_ofpact_type(a->type); diff --git a/lib/ofp-actions.h b/lib/ofp-actions.h index abe2469..a0e270c 100644 --- a/lib/ofp-actions.h +++ b/lib/ofp-actions.h @@ -485,16 +485,40 @@ enum nx_conntrack_flags { * used in conjunction with the NX_CT_F_COMMIT flag above. */ #define NX_CT_RECIRC_NONE OFPTT_ALL +/* We want to determine the size of these elements at compile time to ensure + * actions alignment, but we also want to allow ofpact_conntrack to have + * basic _put(), _get(), etc accessors defined below which access these + * members directly from ofpact_conntrack. An anonymous struct will serve + * both of these purposes. */ +#define CT_MEMBERS \ +struct { \ + struct ofpact ofpact; \ + uint16_t flags; \ + uint16_t zone_imm; \ + struct mf_subfield zone_src; \ + uint8_t recirc_table; \ +} + /* OFPACT_CT. * * Used for NXAST_CT. */ struct ofpact_conntrack { - struct ofpact ofpact; - uint16_t flags; - uint16_t zone_imm; - struct mf_subfield zone_src; - uint8_t recirc_table; + union { + CT_MEMBERS; + uint8_t pad[OFPACT_ALIGN(sizeof(CT_MEMBERS))]; + }; + struct ofpact actions[]; }; +BUILD_ASSERT_DECL(offsetof(struct ofpact_conntrack, actions) + % OFPACT_ALIGNTO == 0); +BUILD_ASSERT_DECL(offsetof(struct ofpact_conntrack, actions) + == sizeof(struct ofpact_conntrack)); + +static inline size_t +ofpact_ct_get_action_len(const struct ofpact_conntrack *oc) +{ + return oc->ofpact.len - offsetof(struct ofpact_conntrack, actions); +} static inline size_t ofpact_nest_get_action_len(const struct ofpact_nest *on) diff --git a/lib/packets.h b/lib/packets.h index 9b91560..91eb290 100644 --- a/lib/packets.h +++ b/lib/packets.h @@ -128,6 +128,7 @@ struct pkt_metadata { uint32_t pkt_mark; /* Packet mark. */ uint16_t ct_state; /* Connection state. */ uint16_t ct_zone; /* Connection zone. */ + uint32_t ct_mark; /* Connection mark. */ union flow_in_port in_port; /* Input port. */ struct flow_tnl tunnel; /* Encapsulating tunnel parameters. Note that * if 'ip_dst' == 0, the rest of the fields may diff --git a/ofproto/ofproto-dpif-sflow.c b/ofproto/ofproto-dpif-sflow.c index b57c84b..310bbc5 100644 --- a/ofproto/ofproto-dpif-sflow.c +++ b/ofproto/ofproto-dpif-sflow.c @@ -1031,6 +1031,7 @@ sflow_read_set_action(const struct nlattr *attr, case OVS_KEY_ATTR_ND: case OVS_KEY_ATTR_CT_STATE: case OVS_KEY_ATTR_CT_ZONE: + case OVS_KEY_ATTR_CT_MARK: case OVS_KEY_ATTR_UNSPEC: case __OVS_KEY_ATTR_MAX: default: diff --git a/ofproto/ofproto-dpif-xlate.c b/ofproto/ofproto-dpif-xlate.c index f0c2a6e..3955b3a 100644 --- a/ofproto/ofproto-dpif-xlate.c +++ b/ofproto/ofproto-dpif-xlate.c @@ -2807,6 +2807,7 @@ clear_conntrack(struct flow *flow) { flow->ct_state = 0; flow->ct_zone = 0; + flow->ct_mark = 0; } static void @@ -4146,8 +4147,27 @@ recirc_unroll_actions(const struct ofpact *ofpacts, size_t ofpacts_len, } static void +put_ct_mark(const struct flow *flow, struct flow *base_flow, + struct ofpbuf *odp_actions, struct flow_wildcards *wc) +{ + struct { + uint32_t key; + uint32_t mask; + } odp_attr; + + odp_attr.key = flow->ct_mark; + odp_attr.mask = wc->masks.ct_mark; + + if (odp_attr.mask && odp_attr.key != base_flow->ct_mark) { + nl_msg_put_unspec(odp_actions, OVS_CT_ATTR_MARK, &odp_attr, + sizeof(odp_attr)); + } +} + +static void compose_conntrack_action(struct xlate_ctx *ctx, struct ofpact_conntrack *ofc) { + uint32_t old_ct_mark = ctx->base_flow.ct_mark; size_t ct_offset; uint16_t zone; @@ -4155,6 +4175,9 @@ compose_conntrack_action(struct xlate_ctx *ctx, struct ofpact_conntrack *ofc) * conntrack action. */ xlate_commit_actions(ctx); + /* Process nested actions first, to populate the key. */ + do_xlate_actions(ofc->actions, ofpact_ct_get_action_len(ofc), ctx); + if (ofc->zone_src.field) { zone = mf_get_subfield(&ofc->zone_src, &ctx->xin->flow); } else { @@ -4166,8 +4189,13 @@ compose_conntrack_action(struct xlate_ctx *ctx, struct ofpact_conntrack *ofc) nl_msg_put_flag(ctx->odp_actions, OVS_CT_ATTR_COMMIT); } nl_msg_put_u16(ctx->odp_actions, OVS_CT_ATTR_ZONE, zone); + put_ct_mark(&ctx->xin->flow, &ctx->base_flow, ctx->odp_actions, ctx->wc); nl_msg_end_nested(ctx->odp_actions, ct_offset); + /* Restore the original ct fields in the key. These should only be exposed + * after recirculation to another table. */ + ctx->base_flow.ct_mark = old_ct_mark; + if (ofc->recirc_table == NX_CT_RECIRC_NONE) { /* If we do not recirculate as part of this action, hide the results of * connection tracking from subsequent recirculations. */ diff --git a/ofproto/ofproto-dpif.c b/ofproto/ofproto-dpif.c index ae6d460..6ad9e93 100644 --- a/ofproto/ofproto-dpif.c +++ b/ofproto/ofproto-dpif.c @@ -1269,6 +1269,7 @@ check_##NAME(struct dpif_backer *backer) \ CHECK_FEATURE(ct_state) CHECK_FEATURE(ct_zone) +CHECK_FEATURE(ct_mark) #undef CHECK_FEATURE #undef CHECK_FEATURE__ @@ -1287,6 +1288,7 @@ check_support(struct dpif_backer *backer) backer->support.odp.ct_state = check_ct_state(backer); backer->support.odp.ct_zone = check_ct_zone(backer); + backer->support.odp.ct_mark = check_ct_mark(backer); } static int @@ -4000,16 +4002,19 @@ static enum ofperr rule_check(struct rule *rule) { uint16_t ct_state, ct_zone; + uint32_t ct_mark; ct_state = MINIFLOW_GET_U16(rule->cr.match.flow, ct_state); ct_zone = MINIFLOW_GET_U16(rule->cr.match.flow, ct_zone); + ct_mark = MINIFLOW_GET_U32(rule->cr.match.flow, ct_mark); - if (ct_state || ct_zone) { + if (ct_state || ct_zone || ct_mark) { struct ofproto_dpif *ofproto = ofproto_dpif_cast(rule->ofproto); const struct odp_support *support = &ofproto_dpif_get_support(ofproto)->odp; if ((ct_state && !support->ct_state) - || (ct_zone && !support->ct_zone)) { + || (ct_zone && !support->ct_zone) + || (ct_mark && !support->ct_mark)) { return OFPERR_OFPBMC_BAD_FIELD; } if (ct_state & CS_UNSUPPORTED_MASK) { diff --git a/ofproto/ofproto-unixctl.man b/ofproto/ofproto-unixctl.man index 892f6fc..87ef80d 100644 --- a/ofproto/ofproto-unixctl.man +++ b/ofproto/ofproto-unixctl.man @@ -107,6 +107,8 @@ Mark of the packet. Connection state of the packet. .IP \fIct_zone\fR Connection tracking zone for packet. +.IP \fIct_mark\fR +Connection mark of the packet. .IP \fItun_id\fR The tunnel ID on which the packet arrived. .IP \fIin_port\fR diff --git a/tests/dpif-netdev.at b/tests/dpif-netdev.at index 43108e7..502416f 100644 --- a/tests/dpif-netdev.at +++ b/tests/dpif-netdev.at @@ -82,7 +82,7 @@ AT_CHECK([cat ovs-vswitchd.log | grep -A 1 'miss upcall' | tail -n 1], [0], [dnl skb_priority(0),skb_mark(0),recirc_id(0),dp_hash(0),in_port(1),eth(src=50:54:00:00:00:09,dst=50:54:00:00:00:0a),eth_type(0x0800),ipv4(src=10.0.0.2,dst=10.0.0.1,proto=1,tos=0,ttl=64,frag=no),icmp(type=8,code=0) ]) AT_CHECK([cat ovs-vswitchd.log | FILTER_FLOW_INSTALL | STRIP_XOUT], [0], [dnl -pkt_mark=0,recirc_id=0,dp_hash=0,skb_priority=0,ct_state=0,ct_zone=0,icmp,in_port=1,vlan_tci=0x0000,dl_src=50:54:00:00:00:09,dl_dst=50:54:00:00:00:0a,nw_src=10.0.0.2,nw_dst=10.0.0.1,nw_tos=0,nw_ecn=0,nw_ttl=64,icmp_type=8,icmp_code=0, actions: +pkt_mark=0,recirc_id=0,dp_hash=0,skb_priority=0,ct_state=0,ct_zone=0,ct_mark=0,icmp,in_port=1,vlan_tci=0x0000,dl_src=50:54:00:00:00:09,dl_dst=50:54:00:00:00:0a,nw_src=10.0.0.2,nw_dst=10.0.0.1,nw_tos=0,nw_ecn=0,nw_ttl=64,icmp_type=8,icmp_code=0, actions: recirc_id=0,ip,in_port=1,vlan_tci=0x0000/0x1fff,dl_src=50:54:00:00:00:09,dl_dst=50:54:00:00:00:0a,nw_frag=no, actions: ]) diff --git a/tests/odp.at b/tests/odp.at index 16d7411..6e51faa 100644 --- a/tests/odp.at +++ b/tests/odp.at @@ -70,6 +70,10 @@ s/\(eth([[^)]]*)\),*/\1,eth_type(0x8100),vlan(vid=99,pcp=7),encap(/ s/$/)/' odp-base.txt echo + echo '# Valid forms with conntrack fields.' + sed 's/^/skb_priority(0),skb_mark(0),ct_mark(0x12345678),recirc_id(0),dp_hash(0),/' odp-base.txt + + echo echo '# Valid forms with IP first fragment.' sed 's/^/skb_priority(0),skb_mark(0),recirc_id(0),dp_hash(0),/' odp-base.txt | sed -n 's/,frag=no),/,frag=first),/p' @@ -89,7 +93,7 @@ s/^/ODP_FIT_TOO_LITTLE: / dnl Some fields are always printed for this test, because wildcards aren't dnl specified. We can skip these. sed -i 's/\(skb_mark(0)\),\(ct\)/\1,ct_state(0),ct_zone(0),\2/' odp-out.txt -sed -i 's/\(skb_mark([[^)]]*)\),\(recirc\)/\1,ct_state(0),ct_zone(0),\2/' odp-out.txt +sed -i 's/\(skb_mark([[^)]]*)\),\(recirc\)/\1,ct_state(0),ct_zone(0),ct_mark(0),\2/' odp-out.txt AT_CHECK_UNQUOTED([ovstest test-odp parse-keys < odp-in.txt], [0], [`cat odp-out.txt` ]) @@ -159,7 +163,7 @@ s/$/)/' odp-base.txt echo echo '# Valid forms with conntrack fields.' - sed 's/\(eth([[^)]]*)\),/\1,ct_state(+trk),ct_zone(5/0xff),/' odp-base.txt + sed 's/\(eth([[^)]]*)\),/\1,ct_state(+trk),ct_zone(5/0xff),ct_mark(0x12345678\/0xFOFOFOFO),/' odp-base.txt echo echo '# Valid forms with IP first fragment.' @@ -305,6 +309,7 @@ tnl_push(tnl_port(6),header(size=50,type=5,eth(dst=f8:bc:12:44:34:b6,src=f8:bc:1 ct ct(commit) ct(commit,zone=5) +ct(commit,mark=0xa0a0a0a0/0xfefefefe) ]) AT_CHECK_UNQUOTED([ovstest test-odp parse-actions < actions.txt], [0], [`cat actions.txt` diff --git a/tests/ofp-actions.at b/tests/ofp-actions.at index c246981..fd48a8b 100644 --- a/tests/ofp-actions.at +++ b/tests/ofp-actions.at @@ -161,6 +161,29 @@ dnl Can't read 32 bits from register into 16-bit zone. # bad OpenFlow10 actions: OFPBAC_BAD_SET_LEN ffff 0018 00002320 0023 0000 00010004 001F FF 000000 0000 +# actions=ct(commit,exec(load:0xf009->NXM_NX_CT_MARK[])) +ffff 0030 00002320 0023 0001 00000000 0000 FF 000000 0000 dnl +ffff 0018 00002320 0007 001f 0001d604 000000000000f009 + +# bad OpenFlow10 actions: OFPBAC_BAD_SET_ARGUMENT +& ofp_actions|WARN|cannot set CT fields outside of ct action +ffff 0018 00002320 0007 001f 0001d604 000000000000f009 + +# bad OpenFlow10 actions: OFPBAC_BAD_SET_ARGUMENT +& meta_flow|WARN|destination field ct_zone is not writable +ffff 0030 00002320 0023 0001 00000000 0000 FF 000000 0000 dnl +ffff 0018 00002320 0007 000f 0001d504 000000000000f009 + +# bad OpenFlow10 actions: OFPBAC_BAD_ARGUMENT +& ofp_actions|WARN|ct action doesn't support nested action ct +ffff 0030 00002320 0023 0001 00000000 0000 FF 000000 0000 dnl +ffff 0018 00002320 0023 0000 00000000 0000 FF 000000 0000 + +# bad OpenFlow10 actions: OFPBAC_BAD_ARGUMENT +& ofp_actions|WARN|ct action doesn't support nested modification of reg0 +ffff 0030 00002320 0023 0001 00000000 0000 FF 000000 0000 dnl +ffff 0018 00002320 0007 001f 00010004 000000000000f009 + ]) sed '/^[[#&]]/d' < test-data > input.txt sed -n 's/^# //p; /^$/p' < test-data > expout diff --git a/tests/ofproto-dpif.at b/tests/ofproto-dpif.at index ff278b7..55eab7f 100644 --- a/tests/ofproto-dpif.at +++ b/tests/ofproto-dpif.at @@ -6523,8 +6523,8 @@ for i in 1 2 3 4; do done sleep 1 AT_CHECK([cat ovs-vswitchd.log | STRIP_UFID | FILTER_FLOW_INSTALL | STRIP_USED], [0], [dnl -pkt_mark=0,recirc_id=0,dp_hash=0,skb_priority=0,ct_state=0,ct_zone=0,icmp,in_port=1,vlan_tci=0x0000,dl_src=50:54:00:00:00:09,dl_dst=50:54:00:00:00:0a,nw_src=10.0.0.2,nw_dst=10.0.0.1,nw_tos=0,nw_ecn=0,nw_ttl=64,icmp_type=8,icmp_code=0, actions:2 -pkt_mark=0,recirc_id=0,dp_hash=0,skb_priority=0,ct_state=0,ct_zone=0,icmp,in_port=1,vlan_tci=0x0000,dl_src=50:54:00:00:00:0b,dl_dst=50:54:00:00:00:0c,nw_src=10.0.0.4,nw_dst=10.0.0.3,nw_tos=0,nw_ecn=0,nw_ttl=64,icmp_type=8,icmp_code=0, actions:drop +pkt_mark=0,recirc_id=0,dp_hash=0,skb_priority=0,ct_state=0,ct_zone=0,ct_mark=0,icmp,in_port=1,vlan_tci=0x0000,dl_src=50:54:00:00:00:09,dl_dst=50:54:00:00:00:0a,nw_src=10.0.0.2,nw_dst=10.0.0.1,nw_tos=0,nw_ecn=0,nw_ttl=64,icmp_type=8,icmp_code=0, actions:2 +pkt_mark=0,recirc_id=0,dp_hash=0,skb_priority=0,ct_state=0,ct_zone=0,ct_mark=0,icmp,in_port=1,vlan_tci=0x0000,dl_src=50:54:00:00:00:0b,dl_dst=50:54:00:00:00:0c,nw_src=10.0.0.4,nw_dst=10.0.0.3,nw_tos=0,nw_ecn=0,nw_ttl=64,icmp_type=8,icmp_code=0, actions:drop ]) AT_CHECK([cat ovs-vswitchd.log | STRIP_UFID | FILTER_FLOW_DUMP | grep 'packets:3'], [0], [dnl skb_priority(0),skb_mark(0),recirc_id(0),dp_hash(0),in_port(1),eth(src=50:54:00:00:00:09,dst=50:54:00:00:00:0a),eth_type(0x0800),ipv4(src=10.0.0.2,dst=10.0.0.1,proto=1,tos=0,ttl=64,frag=no),icmp(type=8,code=0), packets:3, bytes:180, used:0.0s, actions:2 diff --git a/tests/ofproto.at b/tests/ofproto.at index 58a6084..d218256 100644 --- a/tests/ofproto.at +++ b/tests/ofproto.at @@ -1531,7 +1531,7 @@ head_table () { actions: output group set_field strip_vlan push_vlan mod_nw_ttl dec_ttl set_mpls_ttl dec_mpls_ttl push_mpls pop_mpls set_queue supported on Set-Field: tun_id tun_src tun_dst tun_flags tun_gbp_id tun_gbp_flags tun_metadata0 dnl tun_metadata1 tun_metadata2 tun_metadata3 tun_metadata4 tun_metadata5 tun_metadata6 tun_metadata7 tun_metadata8 tun_metadata9 tun_metadata10 tun_metadata11 tun_metadata12 tun_metadata13 tun_metadata14 tun_metadata15 tun_metadata16 tun_metadata17 tun_metadata18 tun_metadata19 tun_metadata20 tun_metadata21 tun_metadata22 tun_metadata23 tun_metadata24 tun_metadata25 tun_metadata26 tun_metadata27 tun_metadata28 tun_metadata29 tun_metadata30 tun_metadata31 tun_metadata32 tun_metadata33 tun_metadata34 tun_metadata35 tun_metadata36 tun_metadata37 tun_metadata38 tun_metadata39 tun_metadata40 tun_metadata41 tun_metadata42 tun_metadata43 tun_metadata44 tun_metadata45 tun_metadata46 tun_metadata47 tun_metadata48 tun_metadata49 tun_metadata50 tun_metadata51 tun_metadata52 tun_metadata53 tun_metadata54 tun_metadata55 tun_metadata56 tun_metadata57 tun_metadata58 tun_metadata59 tun_metadata60 tun_metadata61 tun_metadata62 tun_metadata63 dnl -metadata in_port in_port_oxm pkt_mark reg0 reg1 reg2 reg3 reg4 reg5 reg6 reg7 xreg0 xreg1 xreg2 xreg3 eth_src eth_dst vlan_tci vlan_vid vlan_pcp mpls_label mpls_tc ip_src ip_dst ipv6_src ipv6_dst ipv6_label nw_tos ip_dscp nw_ecn nw_ttl arp_op arp_spa arp_tpa arp_sha arp_tha tcp_src tcp_dst udp_src udp_dst sctp_src sctp_dst nd_target nd_sll nd_tll +metadata in_port in_port_oxm pkt_mark ct_mark reg0 reg1 reg2 reg3 reg4 reg5 reg6 reg7 xreg0 xreg1 xreg2 xreg3 eth_src eth_dst vlan_tci vlan_vid vlan_pcp mpls_label mpls_tc ip_src ip_dst ipv6_src ipv6_dst ipv6_label nw_tos ip_dscp nw_ecn nw_ttl arp_op arp_spa arp_tpa arp_sha arp_tha tcp_src tcp_dst udp_src udp_dst sctp_src sctp_dst nd_target nd_sll nd_tll matching: dp_hash: arbitrary mask recirc_id: exact match or wildcard @@ -1613,6 +1613,7 @@ metadata in_port in_port_oxm pkt_mark reg0 reg1 reg2 reg3 reg4 reg5 reg6 reg7 xr pkt_mark: arbitrary mask ct_state: arbitrary mask ct_zone: exact match or wildcard + ct_mark: arbitrary mask reg0: arbitrary mask reg1: arbitrary mask reg2: arbitrary mask diff --git a/tests/ovs-ofctl.at b/tests/ovs-ofctl.at index 6b5fc55..1ffc230 100644 --- a/tests/ovs-ofctl.at +++ b/tests/ovs-ofctl.at @@ -123,7 +123,8 @@ for test_case in \ 'icmp6,icmpv6_type=1 NXM,OXM' \ 'icmp6,icmpv6_code=2 NXM,OXM' \ 'ct_state=+trk NXM,OXM' \ - 'ct_zone=0 NXM,OXM' + 'ct_zone=0 NXM,OXM' \ + 'ct_mark=0 NXM,OXM' do set $test_case echo @@ -351,6 +352,7 @@ tcp,actions=fin_timeout(idle_timeout=5,hard_timeout=15) actions=controller(max_len=123,reason=invalid_ttl,id=555) actions=sample(probability=12345,collector_set_id=23456,obs_domain_id=34567,obs_point_id=45678) ip,actions=ct(commit,zone=5) +ip,actions=ct(commit,exec(load(1->NXM_NX_CT_MARK[]))) ]]) AT_CHECK([ovs-ofctl parse-flows flows.txt @@ -388,6 +390,7 @@ NXT_FLOW_MOD: ADD table:255 tcp actions=fin_timeout(idle_timeout=5,hard_timeout= NXT_FLOW_MOD: ADD table:255 actions=controller(reason=invalid_ttl,max_len=123,id=555) NXT_FLOW_MOD: ADD table:255 actions=sample(probability=12345,collector_set_id=23456,obs_domain_id=34567,obs_point_id=45678) NXT_FLOW_MOD: ADD table:255 ip actions=ct(commit,zone=5) +NXT_FLOW_MOD: ADD table:255 ip actions=ct(commit,exec(load:0x1->NXM_NX_CT_MARK[])) ]]) AT_CLEANUP @@ -423,6 +426,7 @@ dl_dst=aa:bb:cc:dd:ee:ff/fe:ff:ff:ff:ff:ff,actions=drop dl_dst=aa:bb:cc:dd:ee:ff/00:00:00:00:00:00,actions=drop actions=sample(probability=12345,collector_set_id=23456,obs_domain_id=34567,obs_point_id=45678) ip,actions=ct(commit,zone=5) +ip,actions=ct(commit,exec(load(1->NXM_NX_CT_MARK[[]]))) ]) AT_CHECK([ovs-ofctl -F nxm parse-flows flows.txt], [0], [stdout]) AT_CHECK([[sed 's/ (xid=0x[0-9a-fA-F]*)//' stdout]], [0], [dnl @@ -457,6 +461,7 @@ NXT_FLOW_MOD: ADD dl_dst=aa:bb:cc:dd:ee:ff/fe:ff:ff:ff:ff:ff actions=drop NXT_FLOW_MOD: ADD actions=drop NXT_FLOW_MOD: ADD actions=sample(probability=12345,collector_set_id=23456,obs_domain_id=34567,obs_point_id=45678) NXT_FLOW_MOD: ADD ip actions=ct(commit,zone=5) +NXT_FLOW_MOD: ADD ip actions=ct(commit,exec(load:0x1->NXM_NX_CT_MARK[[]])) ]) AT_CLEANUP @@ -491,6 +496,7 @@ actions=push:NXM_NX_REG0[0..31],pop:NXM_NX_REG0[] vlan_tci=0x1123/0x1fff,actions=drop actions=sample(probability=12345,collector_set_id=23456,obs_domain_id=34567,obs_point_id=45678) ip,actions=ct(commit,zone=5) +ip,actions=ct(commit,exec(load(1->NXM_NX_CT_MARK[]))) ]]) AT_CHECK([ovs-ofctl -F nxm -mmm parse-flows flows.txt], [0], [stdout], [stderr]) AT_CHECK([[sed 's/ (xid=0x[0-9a-fA-F]*)//' stdout]], [0], @@ -524,6 +530,7 @@ NXT_FLOW_MOD: ADD actions=push:NXM_NX_REG0[],pop:NXM_NX_REG0[] NXT_FLOW_MOD: ADD NXM_OF_VLAN_TCI_W(1123/1fff) actions=drop NXT_FLOW_MOD: ADD actions=sample(probability=12345,collector_set_id=23456,obs_domain_id=34567,obs_point_id=45678) NXT_FLOW_MOD: ADD NXM_OF_ETH_TYPE(0800) actions=ct(commit,zone=5) +NXT_FLOW_MOD: ADD NXM_OF_ETH_TYPE(0800) actions=ct(commit,exec(load:0x1->NXM_NX_CT_MARK[])) ]]) AT_CLEANUP @@ -806,6 +813,8 @@ NXM_OF_ETH_TYPE(0800) NXM_NX_CT_STATE(00001080) NXM_OF_ETH_TYPE(0800) NXM_NX_CT_STATE_W(00000080/00000080) NXM_OF_ETH_TYPE(0800) NXM_NX_CT_STATE_W(00000080/000000F0) NXM_OF_ETH_TYPE(0800) NXM_NX_CT_ZONE(5a5a) +NXM_OF_ETH_TYPE(0800) NXM_NX_CT_MARK(5a5a5a5a) +NXM_OF_ETH_TYPE(0800) NXM_NX_CT_MARK_W(5a5a5a5a/fefefefe) # dp_hash (testing experimenter OXM). NXM_NX_DP_HASH(01234567) @@ -1107,6 +1116,8 @@ nx_pull_match() returned error OFPBMC_BAD_VALUE NXM_OF_ETH_TYPE(0800), NXM_NX_CT_STATE_W(00000080/00000080) NXM_OF_ETH_TYPE(0800), NXM_NX_CT_STATE_W(00000080/000000f0) NXM_OF_ETH_TYPE(0800), NXM_NX_CT_ZONE(5a5a) +NXM_OF_ETH_TYPE(0800), NXM_NX_CT_MARK(5a5a5a5a) +NXM_OF_ETH_TYPE(0800), NXM_NX_CT_MARK_W(5a5a5a5a/fefefefe) # dp_hash (testing experimenter OXM). NXM_NX_DP_HASH(01234567) diff --git a/tests/system-traffic.at b/tests/system-traffic.at index f882dd7..0b6adf7 100644 --- a/tests/system-traffic.at +++ b/tests/system-traffic.at @@ -566,6 +566,138 @@ TIME_WAIT src=10.1.1.1 dst=10.1.1.2 sport= dport= src=10.1.1.2 OVS_TRAFFIC_VSWITCHD_STOP AT_CLEANUP +AT_SETUP([conntrack - ct_mark]) +CHECK_CONNTRACK() +OVS_TRAFFIC_VSWITCHD_START( + [set-fail-mode br0 standalone -- ]) + +ADD_NAMESPACES(at_ns0, at_ns1, at_ns2, at_ns3) + +ADD_VETH(p0, at_ns0, br0, "10.1.1.1/24") +ADD_VETH(p1, at_ns1, br0, "10.1.1.2/24") +ADD_VETH(p2, at_ns2, br0, "10.1.1.3/24") +ADD_VETH(p3, at_ns3, br0, "10.1.1.4/24") + +dnl Allow traffic between ns0<->ns1 using the ct_mark. +dnl Check that different marks do not match for traffic between ns2<->ns3. +AT_DATA([flows.txt], [dnl +priority=1,action=drop +priority=10,arp,action=normal +priority=10,icmp,action=normal +priority=100,in_port=1,tcp,action=ct(commit,exec(set_field:1->ct_mark)),2 +priority=100,in_port=2,ct_state=-trk,tcp,action=ct(table=0) +priority=100,in_port=2,ct_state=+trk,ct_mark=1,tcp,action=1 +priority=100,in_port=3,tcp,action=ct(commit,exec(set_field:2->ct_mark)),4 +priority=100,in_port=4,ct_state=-trk,tcp,action=ct(table=0) +priority=100,in_port=4,ct_state=+trk,ct_mark=1,tcp,action=3 +]) + +AT_CHECK([ovs-ofctl add-flows br0 flows.txt]) + +dnl HTTP requests from p0->p1 should work fine. +NETNS_DAEMONIZE([at_ns1], [[$PYTHON $srcdir/test-l7.py]], [http0.pid]) +NS_CHECK_EXEC([at_ns0], [wget 10.1.1.2 -t 3 -T 1 --retry-connrefused -v -o wget0.log]) + +AT_CHECK([conntrack -L 2>&1 | FORMAT_CT(10.1.1.2) | grep TIME], [0], [dnl +TIME_WAIT src=10.1.1.1 dst=10.1.1.2 sport= dport= src=10.1.1.2 dst=10.1.1.1 sport= dport= [[ASSURED]] mark=1 use=1 +]) + +dnl HTTP requests from p2->p3 should fail due to network failure. +dnl Try 3 times, in 1 second intervals. +NETNS_DAEMONIZE([at_ns3], [[$PYTHON $srcdir/test-l7.py]], [http1.pid]) +NS_CHECK_EXEC([at_ns2], [wget 10.1.1.4 -t 3 -T 1 -v -o wget1.log], [4]) + +AT_CHECK([conntrack -L 2>&1 | FORMAT_CT(10.1.1.4)], [0], [dnl +SYN_RECV src=10.1.1.3 dst=10.1.1.4 sport= dport= src=10.1.1.4 dst=10.1.1.3 sport= dport= mark=2 use=1 +]) + +OVS_TRAFFIC_VSWITCHD_STOP +AT_CLEANUP + +AT_SETUP([conntrack - ct_mark from register]) +CHECK_CONNTRACK() +OVS_TRAFFIC_VSWITCHD_START( + [set-fail-mode br0 standalone -- ]) + +ADD_NAMESPACES(at_ns0, at_ns1, at_ns2, at_ns3) + +ADD_VETH(p0, at_ns0, br0, "10.1.1.1/24") +ADD_VETH(p1, at_ns1, br0, "10.1.1.2/24") +ADD_VETH(p2, at_ns2, br0, "10.1.1.3/24") +ADD_VETH(p3, at_ns3, br0, "10.1.1.4/24") + +dnl Allow any traffic from ns0->ns1. Only allow nd, return traffic from ns1->ns0. +AT_DATA([flows.txt], [dnl +priority=1,action=drop +priority=10,arp,action=normal +priority=10,icmp,action=normal +priority=100,in_port=1,tcp,action=load:1->NXM_NX_REG0[[0..31]],ct(commit,exec(move:NXM_NX_REG0[[0..31]]->NXM_NX_CT_MARK[[]])),2 +priority=100,in_port=2,ct_state=-trk,tcp,action=ct(table=0) +priority=100,in_port=2,ct_state=+trk,ct_mark=1,tcp,action=1 +priority=100,in_port=3,tcp,action=load:2->NXM_NX_REG0[[0..31]],ct(commit,exec(move:NXM_NX_REG0[[0..31]]->NXM_NX_CT_MARK[[]])),4 +priority=100,in_port=4,ct_state=-trk,tcp,action=ct(table=0) +priority=100,in_port=4,ct_state=+trk,ct_mark=1,tcp,action=3 +]) + +AT_CHECK([ovs-ofctl add-flows br0 flows.txt]) + +dnl HTTP requests from p0->p1 should work fine. +NETNS_DAEMONIZE([at_ns1], [[$PYTHON $srcdir/test-l7.py]], [http0.pid]) +NS_CHECK_EXEC([at_ns0], [wget 10.1.1.2 -t 3 -T 1 --retry-connrefused -v -o wget0.log]) + +AT_CHECK([conntrack -L 2>&1 | FORMAT_CT(10.1.1.2) | grep TIME], [0], [dnl +TIME_WAIT src=10.1.1.1 dst=10.1.1.2 sport= dport= src=10.1.1.2 dst=10.1.1.1 sport= dport= [[ASSURED]] mark=1 use=1 +]) + +dnl HTTP requests from p2->p3 should fail due to network failure. +dnl Try 3 times, in 1 second intervals. +NETNS_DAEMONIZE([at_ns3], [[$PYTHON $srcdir/test-l7.py]], [http1.pid]) +NS_CHECK_EXEC([at_ns2], [wget 10.1.1.4 -t 3 -T 1 -v -o wget1.log], [4]) + +AT_CHECK([conntrack -L 2>&1 | FORMAT_CT(10.1.1.4)], [0], [dnl +SYN_RECV src=10.1.1.3 dst=10.1.1.4 sport= dport= src=10.1.1.4 dst=10.1.1.3 sport= dport= mark=2 use=1 +]) + +OVS_TRAFFIC_VSWITCHD_STOP +AT_CLEANUP + +AT_SETUP([conntrack - ICMP related]) +CHECK_CONNTRACK() +OVS_TRAFFIC_VSWITCHD_START( + [set-fail-mode br0 secure -- ]) + +ADD_NAMESPACES(at_ns0, at_ns1) + +ADD_VETH(p0, at_ns0, br0, "10.1.1.1/24") +ADD_VETH(p1, at_ns1, br0, "10.1.1.2/24") + +dnl Allow UDP traffic from ns0->ns1. Only allow related ICMP responses back. +AT_DATA([flows.txt], [dnl +priority=1,action=drop +priority=10,arp,action=normal +priority=100,in_port=1,udp,action=ct(commit,exec(set_field:1->ct_mark)),2 +priority=100,in_port=2,icmp,ct_state=-trk,action=ct(table=0) +priority=100,in_port=2,icmp,ct_state=+trk+rel,ct_mark=1,action=1 +]) + +AT_CHECK([ovs-ofctl add-flows br0 flows.txt]) + +dnl UDP packets from ns0->ns1 should solicit "destination unreachable" response. +dnl We pass "-q 1" here to handle openbsd-style nc that can't quit immediately. +NS_CHECK_EXEC([at_ns0], [bash -c "echo a | nc -q 1 -u 10.1.1.2 10000"]) + +AT_CHECK([ovs-appctl revalidator/purge], [0]) +AT_CHECK([ovs-ofctl dump-flows br0 | ofctl_strip | sort | grep -v drop], [0], [dnl + n_packets=1, n_bytes=44, priority=100,udp,in_port=1 actions=ct(commit,exec(load:0x1->NXM_NX_CT_MARK[[]])),output:2 + n_packets=1, n_bytes=72, priority=100,ct_state=+rel+trk,ct_mark=0x1,icmp,in_port=2 actions=output:1 + n_packets=1, n_bytes=72, priority=100,ct_state=-trk,icmp,in_port=2 actions=ct(table=0) + n_packets=2, n_bytes=84, priority=10,arp actions=NORMAL +NXST_FLOW reply: +]) + +OVS_TRAFFIC_VSWITCHD_STOP +AT_CLEANUP + AT_SETUP([conntrack - ICMP related 2]) CHECK_CONNTRACK() OVS_TRAFFIC_VSWITCHD_START( diff --git a/tests/test-odp.c b/tests/test-odp.c index 0e65ced..245e1f9 100644 --- a/tests/test-odp.c +++ b/tests/test-odp.c @@ -60,6 +60,7 @@ parse_keys(bool wc_keys) .recirc = true, .ct_state = true, .ct_zone = true, + .ct_mark = true, }, }; diff --git a/utilities/ovs-ofctl.8.in b/utilities/ovs-ofctl.8.in index 5f8f349..f4436b8 100644 --- a/utilities/ovs-ofctl.8.in +++ b/utilities/ovs-ofctl.8.in @@ -1391,6 +1391,11 @@ zone is an independent connection tracking context, so if you wish to track the same packet in multiple contexts then you must use the \fBct\fR action multiple times. Introduced in Open vSwitch 2.5. . +.IP \fBct_mark=\fIvalue\fR[\fB/\fImask\fR] +Matches the given 32-bit connection mark \fIvalue\fR either exactly or with +optional \fImask\fR. This represents metadata associated with the connection +that the current packet is part of. Introduced in Open vSwitch 2.5. +. .PP Defining IPv6 flows (those with \fBdl_type\fR equal to 0x86dd) requires support for NXM. The following shorthand notations are available for @@ -1657,8 +1662,26 @@ A 16-bit context id that can be used to isolate connections into separate domains, allowing overlapping network addresses in different zones. If a zone is not provided, then the default is to use zone zero. The \fBzone\fR may be specified either as an immediate 16-bit \fIvalue\fR, or may be provided from an -OpenFlow field \fIsrc\fR. The \fIstart\fR and \fIend\fR pair are inclusive, and -must specify a 16-bit range within the field. +NXM field \fIsrc\fR. The \fIstart\fR and \fIend\fR pair are inclusive, and must +specify a 16-bit range within the field. +.IP \fBexec\fB(\fR[\fIaction\fR][\fB,\fIaction\fR...]\fB)\fR +Perform actions within the context of connection tracking. These actions +are in the same format as the actions accepted as part of a flow, however +there are additional restrictions applied. For instance, only actions which +modify the ct fields are accepted within the \fBexec\fR action. Furthermore, +some actions may only be performed in this context, for instance modifying the +ct_mark field: +. +.RS +.IP \fBset_field:\fIvalue\fR->ct_mark\fR +Store a 32-bit metadata value with the connection. If the connection is +committed, then subsequent lookups for packets in this connection will +populate the \fBct_mark\fR flow field when the packet is sent to the +connection tracker with the \fBtable\fR specified. +.RE +.IP +The \fBcommit\fR parameter must be specified to use \fBexec(...)\fR. +. .RE .IP The \fBct\fR action may be used as a primitive to construct stateful firewalls @@ -1690,6 +1713,8 @@ same IP message. Currently, connection tracking is only available on Linux kernels with the nf_conntrack module loaded. . +.RE +. .IP \fBdec_ttl\fR .IQ \fBdec_ttl\fB[\fR(\fIid1,id2\fI)\fR]\fR Decrement TTL of IPv4 packet or hop limit of IPv6 packet. If the