@@ -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"
@@ -1781,6 +1782,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,
@@ -1788,6 +1899,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);
}
@@ -227,4 +227,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
@@ -12895,6 +12895,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.
@@ -12948,6 +12987,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
@@ -1806,6 +1806,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,
@@ -1814,6 +1899,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);