From patchwork Wed Dec 9 18:52:30 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Mark Michelson X-Patchwork-Id: 1413620 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=openvswitch.org (client-ip=140.211.166.137; helo=fraxinus.osuosl.org; envelope-from=ovs-dev-bounces@openvswitch.org; receiver=) Authentication-Results: ozlabs.org; dmarc=fail (p=none dis=none) header.from=redhat.com Authentication-Results: ozlabs.org; dkim=fail reason="signature verification failed" (1024-bit key; unprotected) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=TqCumzuc; dkim-atps=neutral Received: from fraxinus.osuosl.org (smtp4.osuosl.org [140.211.166.137]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by ozlabs.org (Postfix) with ESMTPS id 4CrmQJ0CBFz9s0b for ; Thu, 10 Dec 2020 05:52:55 +1100 (AEDT) Received: from localhost (localhost [127.0.0.1]) by fraxinus.osuosl.org (Postfix) with ESMTP id 984C3876AD; Wed, 9 Dec 2020 18:52:53 +0000 (UTC) X-Virus-Scanned: amavisd-new at osuosl.org Received: from fraxinus.osuosl.org ([127.0.0.1]) by localhost (.osuosl.org [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id 0yiPrV2ASA-t; Wed, 9 Dec 2020 18:52:49 +0000 (UTC) Received: from lists.linuxfoundation.org (lf-lists.osuosl.org [140.211.9.56]) by fraxinus.osuosl.org (Postfix) with ESMTP id 5307D8769C; Wed, 9 Dec 2020 18:52:48 +0000 (UTC) Received: from lf-lists.osuosl.org (localhost [127.0.0.1]) by lists.linuxfoundation.org (Postfix) with ESMTP id 2BCA8C1DA2; Wed, 9 Dec 2020 18:52:48 +0000 (UTC) X-Original-To: dev@openvswitch.org Delivered-To: ovs-dev@lists.linuxfoundation.org Received: from whitealder.osuosl.org (smtp1.osuosl.org [140.211.166.138]) by lists.linuxfoundation.org (Postfix) with ESMTP id 32C45C013B for ; Wed, 9 Dec 2020 18:52:46 +0000 (UTC) Received: from localhost (localhost [127.0.0.1]) by whitealder.osuosl.org (Postfix) with ESMTP id 20F0286D7C for ; Wed, 9 Dec 2020 18:52:46 +0000 (UTC) X-Virus-Scanned: amavisd-new at osuosl.org Received: from whitealder.osuosl.org ([127.0.0.1]) by localhost (.osuosl.org [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id RRLoIhZiIzQ4 for ; Wed, 9 Dec 2020 18:52:45 +0000 (UTC) X-Greylist: domain auto-whitelisted by SQLgrey-1.7.6 Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [216.205.24.124]) by whitealder.osuosl.org (Postfix) with ESMTPS id BD57C86D7E for ; Wed, 9 Dec 2020 18:52:44 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1607539963; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=fA47nlFhU65wU8bqiwjNkLynH0omep/sofeqrT2RPoE=; b=TqCumzucAos3g9vDyoX995y/3uIp7Cn3mQDpCaJrkX5bRjH3pn9mBLi3E7l2WoT4MtDGq+ LLf3cJv6Z7WLMEJP8lHqS5FhOLB26bfLoBmyC91WdelLgGxVCv7UsTtVb8Pgk5TKhZim6u 7NPSmgLuC2ilYUeoXmnRn34SuzGF8Nk= Received: from mimecast-mx01.redhat.com (mimecast-mx01.redhat.com [209.132.183.4]) (Using TLS) by relay.mimecast.com with ESMTP id us-mta-405-LgZWk1U4Oz-ZIRZpVZMJIA-1; Wed, 09 Dec 2020 13:52:41 -0500 X-MC-Unique: LgZWk1U4Oz-ZIRZpVZMJIA-1 Received: from smtp.corp.redhat.com (int-mx02.intmail.prod.int.phx2.redhat.com [10.5.11.12]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mimecast-mx01.redhat.com (Postfix) with ESMTPS id 63162100F375 for ; Wed, 9 Dec 2020 18:52:34 +0000 (UTC) Received: from monae.redhat.com (ovpn-115-14.rdu2.redhat.com [10.10.115.14]) by smtp.corp.redhat.com (Postfix) with ESMTP id 634366198C for ; Wed, 9 Dec 2020 18:52:32 +0000 (UTC) From: Mark Michelson To: dev@openvswitch.org Date: Wed, 9 Dec 2020 13:52:30 -0500 Message-Id: <20201209185231.557227-1-mmichels@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 2.79 on 10.5.11.12 Authentication-Results: relay.mimecast.com; auth=pass smtp.auth=CUSA124A263 smtp.mailfrom=mmichels@redhat.com X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com Subject: [ovs-dev] [PATCH ovn 1/2] Implement SCTP-specific reject() action. X-BeenThere: ovs-dev@openvswitch.org X-Mailman-Version: 2.1.15 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: ovs-dev-bounces@openvswitch.org Sender: "dev" Currently in OVN, if an SCTP packet hits a reject() action, OVN responds with an ICMP packet. Instead, we should send an SCTP packet with an ABORT chunk. This will either end the current association or will prevent an association from being created, depending on which stage of the SCTP state machine we currently are in. This patch adds the desired behavior for SCTP. The reject() action will now send an SCTP ABORT if the incoming packet is SCTP. Signed-off-by: Mark Michelson --- controller/pinctrl.c | 113 ++++++++++++++++++++++++++++++++++++++++++ lib/ovn-util.h | 33 ++++++++++++ tests/ovn.at | 43 ++++++++++++++++ utilities/ovn-trace.c | 87 ++++++++++++++++++++++++++++++++ 4 files changed, 276 insertions(+) diff --git a/controller/pinctrl.c b/controller/pinctrl.c index 7e3abf0a4..d121a0f48 100644 --- a/controller/pinctrl.c +++ b/controller/pinctrl.c @@ -38,6 +38,7 @@ #include "openvswitch/ofp-util.h" #include "openvswitch/vlog.h" #include "lib/random.h" +#include "lib/crc32c.h" #include "lib/dhcp.h" #include "ovn-controller.h" @@ -1763,6 +1764,116 @@ pinctrl_handle_tcp_reset(struct rconn *swconn, const struct flow *ip_flow, dp_packet_uninit(&packet); } +static void dp_packet_put_sctp_abort(struct dp_packet *packet, + bool reflect_tag) +{ + struct sctp_chunk_header abort = { + .sctp_chunk_type = SCTP_CHUNK_TYPE_ABORT, + .sctp_chunk_flags = reflect_tag ? SCTP_ABORT_CHUNK_FLAG_T : 0, + .sctp_chunk_len = htons(SCTP_CHUNK_HEADER_LEN), + }; + + dp_packet_put(packet, &abort, sizeof abort); +} + +static void +pinctrl_handle_sctp_abort(struct rconn *swconn, const struct flow *ip_flow, + struct dp_packet *pkt_in, + const struct match *md, struct ofpbuf *userdata, + bool loopback) +{ + if (ip_flow->nw_proto != IPPROTO_SCTP) { + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); + VLOG_WARN_RL(&rl, "SCTP_ABORT action on non-SCTP packet"); + return; + } + + struct sctp_header *sh_in = dp_packet_l4(pkt_in); + if (!sh_in) { + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); + VLOG_WARN_RL(&rl, "SCTP_ABORT action on malformed SCTP packet"); + return; + } + + const struct sctp_chunk_header *sh_in_chunk = + dp_packet_get_sctp_payload(pkt_in); + if (!sh_in_chunk) { + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); + VLOG_WARN_RL(&rl, "SCTP_ABORT action on SCTP packet with no chunks"); + return; + } + + if (sh_in_chunk->sctp_chunk_type == SCTP_CHUNK_TYPE_ABORT) { + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); + VLOG_WARN_RL(&rl, "sctp_abort action on incoming SCTP ABORT."); + return; + } + + const struct sctp_init_chunk *sh_in_init = NULL; + if (sh_in_chunk->sctp_chunk_type == SCTP_CHUNK_TYPE_INIT) { + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); + sh_in_init = dp_packet_at(pkt_in, pkt_in->l4_ofs + + SCTP_HEADER_LEN + + SCTP_CHUNK_HEADER_LEN, + SCTP_INIT_CHUNK_LEN); + if (!sh_in_init) { + VLOG_WARN_RL(&rl, "Incomplete SCTP INIT chunk. Ignoring packet."); + return; + } + } + + uint64_t packet_stub[128 / 8]; + struct dp_packet packet; + + dp_packet_use_stub(&packet, packet_stub, sizeof packet_stub); + + struct eth_addr eth_src = loopback ? ip_flow->dl_dst : ip_flow->dl_src; + struct eth_addr eth_dst = loopback ? ip_flow->dl_src : ip_flow->dl_dst; + + if (get_dl_type(ip_flow) == htons(ETH_TYPE_IPV6)) { + const struct in6_addr *ip6_src = + loopback ? &ip_flow->ipv6_dst : &ip_flow->ipv6_src; + const struct in6_addr *ip6_dst = + loopback ? &ip_flow->ipv6_src : &ip_flow->ipv6_dst; + pinctrl_compose_ipv6(&packet, eth_src, eth_dst, + (struct in6_addr *) ip6_src, + (struct in6_addr *) ip6_dst, + IPPROTO_SCTP, 63, SCTP_HEADER_LEN + + SCTP_CHUNK_HEADER_LEN); + } else { + ovs_be32 nw_src = loopback ? ip_flow->nw_dst : ip_flow->nw_src; + ovs_be32 nw_dst = loopback ? ip_flow->nw_src : ip_flow->nw_dst; + pinctrl_compose_ipv4(&packet, eth_src, eth_dst, nw_src, nw_dst, + IPPROTO_SCTP, 63, SCTP_HEADER_LEN + + SCTP_CHUNK_HEADER_LEN); + } + + struct sctp_header *sh = dp_packet_put_zeros(&packet, sizeof *sh); + dp_packet_set_l4(&packet, sh); + sh->sctp_dst = ip_flow->tp_src; + sh->sctp_src = ip_flow->tp_dst; + put_16aligned_be32(&sh->sctp_csum, 0); + + bool tag_reflected; + if (get_16aligned_be32(&sh_in->sctp_vtag) == 0 && sh_in_init) { + /* See RFC 4960 Section 8.4, item 3. */ + put_16aligned_be32(&sh->sctp_vtag, sh_in_init->initiate_tag); + tag_reflected = false; + } else { + /* See RFC 4960 Section 8.4, item 8. */ + sh->sctp_vtag = sh_in->sctp_vtag; + tag_reflected = true; + } + + dp_packet_put_sctp_abort(&packet, tag_reflected); + + put_16aligned_be32(&sh->sctp_csum, crc32c((void *) sh, + dp_packet_l4_size(&packet))); + + set_actions_and_enqueue_msg(swconn, &packet, md, userdata); + dp_packet_uninit(&packet); +} + static void pinctrl_handle_reject(struct rconn *swconn, const struct flow *ip_flow, struct dp_packet *pkt_in, @@ -1770,6 +1881,8 @@ pinctrl_handle_reject(struct rconn *swconn, const struct flow *ip_flow, { if (ip_flow->nw_proto == IPPROTO_TCP) { pinctrl_handle_tcp_reset(swconn, ip_flow, pkt_in, md, userdata, true); + } else if (ip_flow->nw_proto == IPPROTO_SCTP) { + pinctrl_handle_sctp_abort(swconn, ip_flow, pkt_in, md, userdata, true); } else { pinctrl_handle_icmp(swconn, ip_flow, pkt_in, md, userdata, true, true); } diff --git a/lib/ovn-util.h b/lib/ovn-util.h index 1d2f7a9c5..9f6a2eb39 100644 --- a/lib/ovn-util.h +++ b/lib/ovn-util.h @@ -225,4 +225,37 @@ bool ip_address_and_port_from_lb_key(const char *key, char **ip_address, * value. */ char *ovn_get_internal_version(void); + +/* OVN Packet definitions. These may eventually find a home in OVS's + * packets.h file. For the time being, they live here because OVN uses them + * and OVS does not. + */ +#define SCTP_CHUNK_HEADER_LEN 4 +struct sctp_chunk_header { + uint8_t sctp_chunk_type; + uint8_t sctp_chunk_flags; + ovs_be16 sctp_chunk_len; +}; +BUILD_ASSERT_DECL(SCTP_CHUNK_HEADER_LEN == sizeof(struct sctp_chunk_header)); + +#define SCTP_INIT_CHUNK_LEN 16 +struct sctp_init_chunk { + ovs_be32 initiate_tag; + ovs_be32 a_rwnd; + ovs_be16 num_outbound_streams; + ovs_be16 num_inbound_streams; + ovs_be32 initial_tsn; +}; +BUILD_ASSERT_DECL(SCTP_INIT_CHUNK_LEN == sizeof(struct sctp_init_chunk)); + +/* These are the only SCTP chunk types that OVN cares about. + * There is no need to define the other chunk types until they are + * needed. + */ +#define SCTP_CHUNK_TYPE_INIT 1 +#define SCTP_CHUNK_TYPE_ABORT 6 + +/* See RFC 4960 Sections 3.3.7 and 8.5.1 for information on this flag. */ +#define SCTP_ABORT_CHUNK_FLAG_T (1 << 0) + #endif diff --git a/tests/ovn.at b/tests/ovn.at index f222fd8ac..6c3d4ffc4 100644 --- a/tests/ovn.at +++ b/tests/ovn.at @@ -12878,6 +12878,45 @@ test_tcp_syn_packet() { check as hv$hv ovs-appctl netdev-dummy/receive vif$inport $packet } +# test_sctp_init_packet INPORT HV ETH_SRC ETH_DST IPV4_SRC IPV4_DST IP_CHKSUM SCTP_SPORT SCTP_DPORT SCTP_INIT_TAG SCTP_CHKSUM EXP_IP_CHKSUM EXP_SCTP_ABORT_CHKSUM +# +# Causes a packet to be received on INPORT of the hypervisor HV. The packet is an SCTP INIT chunk with +# ETH_SRC, ETH_DST, IPV4_SRC, IPV4_DST, IP_CHKSUM, SCTP_SPORT, SCTP_DPORT, and SCTP_CHKSUM as specified. +# The INIT "initiate_tag" will be set to SCTP_INIT_TAG. +# EXP_IP_CHKSUM and EXP_SCTP_CHKSUM are the ip and sctp checksums of the SCTP ABORT chunk generated from the ACL rule hit +# +# INPORT is an lport number, e.g. 11 for vif11. +# HV is a hypervisor number. +# ETH_SRC and ETH_DST are each 12 hex digits. +# IPV4_SRC and IPV4_DST are each 8 hex digits. +# SCTP_SPORT and SCTP_DPORT are 4 hex digits. +# IP_CHKSUM and EXP_IP_CHKSUM are 4 hex digits. +# SCTP_CHKSUM and EXP_SCTP_CHKSUM are 8 hex digits. +test_sctp_init_packet() { + local inport=$1 hv=$2 eth_src=$3 eth_dst=$4 ipv4_src=$5 ipv4_dst=$6 ip_chksum=$7 + local sctp_sport=$8 sctp_dport=$9 sctp_init_tag=${10} sctp_chksum=${11} + local exp_ip_chksum=${12} exp_sctp_abort_chksum=${13} + + local ip_ttl=ff + local eth_hdr=${eth_dst}${eth_src}0800 + local ip_hdr=4500002500004000${ip_ttl}84${ip_chksum}${ipv4_src}${ipv4_dst} + local sctp_hdr=${sctp_sport}${sctp_dport}00000000${sctp_chksum} + local sctp_init=01000014${sctp_init_tag}0000000000010001${sctp_init_tag} + + local packet=${eth_hdr}${ip_hdr}${sctp_hdr}${sctp_init} + + local sctp_abort_ttl=3f + local reply_eth_hdr=${eth_src}${eth_dst}0800 + local reply_ip_hdr=4500002400004000${sctp_abort_ttl}84${exp_ip_chksum}${ipv4_dst}${ipv4_src} + local reply_sctp_hdr=${sctp_dport}${sctp_sport}${sctp_init_tag}${exp_sctp_abort_chksum} + local reply_sctp_abort=06000004 + + local reply=${reply_eth_hdr}${reply_ip_hdr}${reply_sctp_hdr}${reply_sctp_abort} + echo $reply >> vif$inport.expected + + check as hv$hv ovs-appctl netdev-dummy/receive vif$inport $packet +} + # Create hypervisors hv[123]. # Add vif1[123] to hv1, vif2[123] to hv2, vif3[123] to hv3. # Add all of the vifs to a single logical switch sw0. @@ -12931,6 +12970,10 @@ test_tcp_syn_packet 11 1 000000000011 000000000021 $(ip_to_hex 192 168 1 11) $(i test_tcp_syn_packet 21 2 000000000021 000000000011 $(ip_to_hex 192 168 1 21) $(ip_to_hex 192 168 1 11) 0000 8b40 3039 0000 b85f 70e4 test_tcp_syn_packet 31 3 000000000031 000000000012 $(ip_to_hex 192 168 1 31) $(ip_to_hex 192 168 1 12) 0000 8b40 3039 0000 b854 70d9 +test_sctp_init_packet 11 1 000000000011 000000000021 $(ip_to_hex 192 168 1 11) $(ip_to_hex 192 168 1 21) 0000 8b40 3039 00000001 82112601 b7e5 10fe95b6 +test_sctp_init_packet 21 2 000000000021 000000000011 $(ip_to_hex 192 168 1 21) $(ip_to_hex 192 168 1 11) 0000 8b40 3039 00000002 C0379D5A b7e5 39f23aaf +test_sctp_init_packet 31 3 000000000031 000000000012 $(ip_to_hex 192 168 1 31) $(ip_to_hex 192 168 1 12) 0000 8b40 3039 00000003 028E263C b7da 7124045b + for i in 1 2 3; do OVN_CHECK_PACKETS([hv$i/vif${i}1-tx.pcap], [vif${i}1.expected]) done diff --git a/utilities/ovn-trace.c b/utilities/ovn-trace.c index 6fad36512..08f7dfece 100644 --- a/utilities/ovn-trace.c +++ b/utilities/ovn-trace.c @@ -1802,6 +1802,91 @@ execute_tcp_reset(const struct ovnact_nest *on, execute_tcp6_reset(on, dp, uflow, table_id, loopback, pipeline, super); } } + +static void +execute_sctp4_abort(const struct ovnact_nest *on, + const struct ovntrace_datapath *dp, + const struct flow *uflow, uint8_t table_id, + bool loopback, enum ovnact_pipeline pipeline, + struct ovs_list *super) +{ + struct flow sctp_flow = *uflow; + + /* Update fields for TCP SCTP. */ + if (loopback) { + sctp_flow.dl_dst = uflow->dl_src; + sctp_flow.dl_src = uflow->dl_dst; + sctp_flow.nw_dst = uflow->nw_src; + sctp_flow.nw_src = uflow->nw_dst; + } else { + sctp_flow.dl_dst = uflow->dl_dst; + sctp_flow.dl_src = uflow->dl_src; + sctp_flow.nw_dst = uflow->nw_dst; + sctp_flow.nw_src = uflow->nw_src; + } + sctp_flow.nw_proto = IPPROTO_SCTP; + sctp_flow.nw_ttl = 255; + sctp_flow.tp_src = uflow->tp_src; + sctp_flow.tp_dst = uflow->tp_dst; + + struct ovntrace_node *node = ovntrace_node_append( + super, OVNTRACE_NODE_TRANSFORMATION, "sctp_abort"); + + trace_actions(on->nested, on->nested_len, dp, &sctp_flow, + table_id, pipeline, &node->subs); +} + +static void +execute_sctp6_abort(const struct ovnact_nest *on, + const struct ovntrace_datapath *dp, + const struct flow *uflow, uint8_t table_id, + bool loopback, enum ovnact_pipeline pipeline, + struct ovs_list *super) +{ + struct flow sctp_flow = *uflow; + + /* Update fields for SCTP. */ + if (loopback) { + sctp_flow.dl_dst = uflow->dl_src; + sctp_flow.dl_src = uflow->dl_dst; + sctp_flow.ipv6_dst = uflow->ipv6_src; + sctp_flow.ipv6_src = uflow->ipv6_dst; + } else { + sctp_flow.dl_dst = uflow->dl_dst; + sctp_flow.dl_src = uflow->dl_src; + sctp_flow.ipv6_dst = uflow->ipv6_dst; + sctp_flow.ipv6_src = uflow->ipv6_src; + } + sctp_flow.nw_proto = IPPROTO_TCP; + sctp_flow.nw_ttl = 255; + sctp_flow.tp_src = uflow->tp_src; + sctp_flow.tp_dst = uflow->tp_dst; + sctp_flow.tcp_flags = htons(TCP_RST); + + struct ovntrace_node *node = ovntrace_node_append( + super, OVNTRACE_NODE_TRANSFORMATION, "sctp_abort"); + + trace_actions(on->nested, on->nested_len, dp, &sctp_flow, + table_id, pipeline, &node->subs); +} + +static void +execute_sctp_abort(const struct ovnact_nest *on, + const struct ovntrace_datapath *dp, + const struct flow *uflow, uint8_t table_id, + bool loopback, enum ovnact_pipeline pipeline, + struct ovs_list *super) +{ + if (get_dl_type(uflow) == htons(ETH_TYPE_IP)) { + execute_sctp4_abort(on, dp, uflow, table_id, loopback, + pipeline, super); + } else { + execute_sctp6_abort(on, dp, uflow, table_id, loopback, + pipeline, super); + } +} + + static void execute_reject(const struct ovnact_nest *on, const struct ovntrace_datapath *dp, @@ -1810,6 +1895,8 @@ execute_reject(const struct ovnact_nest *on, { if (uflow->nw_proto == IPPROTO_TCP) { execute_tcp_reset(on, dp, uflow, table_id, true, pipeline, super); + } else if (uflow->nw_proto == IPPROTO_SCTP) { + execute_sctp_abort(on, dp, uflow, table_id, true, pipeline, super); } else { if (get_dl_type(uflow) == htons(ETH_TYPE_IP)) { execute_icmp4(on, dp, uflow, table_id, true, pipeline, super);