On Thu, Jan 5, 2023 at 6:47 AM Sriram Yagnaraman <sriram.yagnaraman@xxxxxxxx> wrote: > > All the paths in an SCTP connection are kept alive either by actual > DATA/SACK running through the connection or by HEARTBEAT. This patch > proposes a simple state machine with only two states OPEN_WAIT and > ESTABLISHED (similar to UDP). The reason for this change is a full > stateful approach to SCTP is difficult when the association is > multihomed since the endpoints could use different paths in the network > during the lifetime of an association. > > Default timeouts are: > OPEN_WAIT: 3 seconds (rto_initial) > ESTABLISHED: 210 seconds (rto_max + hb_interval * path_max_retrans) > > Important changes/notes > - Timeout is used to clean up conntrack entries > - VTAG checks are kept as is (can be moved to a conntrack extension if > desired) > - SCTP chunks are parsed only once, and a map is populated with the > information on the chunks present in the packet > - ASSURED bit is NOT set in this version of the patch, need help > understanding when to set it I can still see a regression from the change: After the 1st connection shutdown successfully, the 2nd connection with the same 4-tuple will fail as the old conntrack entry stays in ESTABLISHED and waits to time out. This is actually acting differently from TCP and UDP NAT. Attached is a script to help reproduce it. Thanks. > > > Changes since v1: > - sctp_new() brought back for unconfirmed CT entries > - sctp_vtag_check() does all vtag related check, except for > HEARTBEAT/HEARTBEACK_ACK chunks > - Style check fixes > - Revert all UAPI changes > > Signed-off-by: Sriram Yagnaraman <sriram.yagnaraman@xxxxxxxx> > --- > .../uapi/linux/netfilter/nf_conntrack_sctp.h | 1 + > .../linux/netfilter/nfnetlink_cttimeout.h | 1 + > net/netfilter/nf_conntrack_proto_sctp.c | 624 +++++------------- > net/netfilter/nf_conntrack_standalone.c | 72 +- > 4 files changed, 179 insertions(+), 519 deletions(-) > > diff --git a/include/uapi/linux/netfilter/nf_conntrack_sctp.h b/include/uapi/linux/netfilter/nf_conntrack_sctp.h > index c742469afe21..703c2ae5adf4 100644 > --- a/include/uapi/linux/netfilter/nf_conntrack_sctp.h > +++ b/include/uapi/linux/netfilter/nf_conntrack_sctp.h > @@ -9,6 +9,7 @@ enum sctp_conntrack { > SCTP_CONNTRACK_NONE, > SCTP_CONNTRACK_CLOSED, > SCTP_CONNTRACK_COOKIE_WAIT, > + SCTP_CONNTRACK_OPEN_WAIT = SCTP_CONNTRACK_COOKIE_WAIT, > SCTP_CONNTRACK_COOKIE_ECHOED, > SCTP_CONNTRACK_ESTABLISHED, > SCTP_CONNTRACK_SHUTDOWN_SENT, > diff --git a/include/uapi/linux/netfilter/nfnetlink_cttimeout.h b/include/uapi/linux/netfilter/nfnetlink_cttimeout.h > index 94e74034706d..a76ea744d3e3 100644 > --- a/include/uapi/linux/netfilter/nfnetlink_cttimeout.h > +++ b/include/uapi/linux/netfilter/nfnetlink_cttimeout.h > @@ -88,6 +88,7 @@ enum ctattr_timeout_sctp { > CTA_TIMEOUT_SCTP_UNSPEC, > CTA_TIMEOUT_SCTP_CLOSED, > CTA_TIMEOUT_SCTP_COOKIE_WAIT, > + CTA_TIMEOUT_SCTP_OPEN_WAIT = CTA_TIMEOUT_SCTP_COOKIE_WAIT, > CTA_TIMEOUT_SCTP_COOKIE_ECHOED, > CTA_TIMEOUT_SCTP_ESTABLISHED, > CTA_TIMEOUT_SCTP_SHUTDOWN_SENT, > diff --git a/net/netfilter/nf_conntrack_proto_sctp.c b/net/netfilter/nf_conntrack_proto_sctp.c > index d88b92a8ffca..8c18528972b7 100644 > --- a/net/netfilter/nf_conntrack_proto_sctp.c > +++ b/net/netfilter/nf_conntrack_proto_sctp.c > @@ -5,12 +5,13 @@ > * Copyright (c) 2004 Kiran Kumar Immidi <immidi_kiran@xxxxxxxxx> > * Copyright (c) 2004-2012 Patrick McHardy <kaber@xxxxxxxxx> > * > - * SCTP is defined in RFC 2960. References to various sections in this code > + * SCTP is defined in RFC 4960. References to various sections in this code > * are to this RFC. > */ > > #include <linux/types.h> > #include <linux/timer.h> > +#include <linux/jiffies.h> > #include <linux/netfilter.h> > #include <linux/in.h> > #include <linux/ip.h> > @@ -27,127 +28,19 @@ > #include <net/netfilter/nf_conntrack_ecache.h> > #include <net/netfilter/nf_conntrack_timeout.h> > > -/* FIXME: Examine ipfilter's timeouts and conntrack transitions more > - closely. They're more complex. --RR > - > - And so for me for SCTP :D -Kiran */ > +#define SCTP_FLAG_HEARTBEAT_VTAG_FAILED 1 > > static const char *const sctp_conntrack_names[] = { > "NONE", > - "CLOSED", > - "COOKIE_WAIT", > - "COOKIE_ECHOED", > + "OPEN_WAIT", > "ESTABLISHED", > - "SHUTDOWN_SENT", > - "SHUTDOWN_RECD", > - "SHUTDOWN_ACK_SENT", > - "HEARTBEAT_SENT", > - "HEARTBEAT_ACKED", > }; > > #define SECS * HZ > -#define MINS * 60 SECS > -#define HOURS * 60 MINS > -#define DAYS * 24 HOURS > > static const unsigned int sctp_timeouts[SCTP_CONNTRACK_MAX] = { > - [SCTP_CONNTRACK_CLOSED] = 10 SECS, > - [SCTP_CONNTRACK_COOKIE_WAIT] = 3 SECS, > - [SCTP_CONNTRACK_COOKIE_ECHOED] = 3 SECS, > - [SCTP_CONNTRACK_ESTABLISHED] = 5 DAYS, > - [SCTP_CONNTRACK_SHUTDOWN_SENT] = 300 SECS / 1000, > - [SCTP_CONNTRACK_SHUTDOWN_RECD] = 300 SECS / 1000, > - [SCTP_CONNTRACK_SHUTDOWN_ACK_SENT] = 3 SECS, > - [SCTP_CONNTRACK_HEARTBEAT_SENT] = 30 SECS, > - [SCTP_CONNTRACK_HEARTBEAT_ACKED] = 210 SECS, > - [SCTP_CONNTRACK_DATA_SENT] = 30 SECS, > -}; > - > -#define SCTP_FLAG_HEARTBEAT_VTAG_FAILED 1 > - > -#define sNO SCTP_CONNTRACK_NONE > -#define sCL SCTP_CONNTRACK_CLOSED > -#define sCW SCTP_CONNTRACK_COOKIE_WAIT > -#define sCE SCTP_CONNTRACK_COOKIE_ECHOED > -#define sES SCTP_CONNTRACK_ESTABLISHED > -#define sSS SCTP_CONNTRACK_SHUTDOWN_SENT > -#define sSR SCTP_CONNTRACK_SHUTDOWN_RECD > -#define sSA SCTP_CONNTRACK_SHUTDOWN_ACK_SENT > -#define sHS SCTP_CONNTRACK_HEARTBEAT_SENT > -#define sHA SCTP_CONNTRACK_HEARTBEAT_ACKED > -#define sDS SCTP_CONNTRACK_DATA_SENT > -#define sIV SCTP_CONNTRACK_MAX > - > -/* > - These are the descriptions of the states: > - > -NOTE: These state names are tantalizingly similar to the states of an > -SCTP endpoint. But the interpretation of the states is a little different, > -considering that these are the states of the connection and not of an end > -point. Please note the subtleties. -Kiran > - > -NONE - Nothing so far. > -COOKIE WAIT - We have seen an INIT chunk in the original direction, or also > - an INIT_ACK chunk in the reply direction. > -COOKIE ECHOED - We have seen a COOKIE_ECHO chunk in the original direction. > -ESTABLISHED - We have seen a COOKIE_ACK in the reply direction. > -SHUTDOWN_SENT - We have seen a SHUTDOWN chunk in the original direction. > -SHUTDOWN_RECD - We have seen a SHUTDOWN chunk in the reply direction. > -SHUTDOWN_ACK_SENT - We have seen a SHUTDOWN_ACK chunk in the direction opposite > - to that of the SHUTDOWN chunk. > -CLOSED - We have seen a SHUTDOWN_COMPLETE chunk in the direction of > - the SHUTDOWN chunk. Connection is closed. > -HEARTBEAT_SENT - We have seen a HEARTBEAT in a new flow. > -HEARTBEAT_ACKED - We have seen a HEARTBEAT-ACK/DATA/SACK in the direction > - opposite to that of the HEARTBEAT/DATA chunk. Secondary connection > - is established. > -DATA_SENT - We have seen a DATA/SACK in a new flow. > -*/ > - > -/* TODO > - - I have assumed that the first INIT is in the original direction. > - This messes things when an INIT comes in the reply direction in CLOSED > - state. > - - Check the error type in the reply dir before transitioning from > -cookie echoed to closed. > - - Sec 5.2.4 of RFC 2960 > - - Full Multi Homing support. > -*/ > - > -/* SCTP conntrack state transitions */ > -static const u8 sctp_conntracks[2][12][SCTP_CONNTRACK_MAX] = { > - { > -/* ORIGINAL */ > -/* sNO, sCL, sCW, sCE, sES, sSS, sSR, sSA, sHS, sHA, sDS */ > -/* init */ {sCL, sCL, sCW, sCE, sES, sSS, sSR, sSA, sCW, sHA, sCW}, > -/* init_ack */ {sCL, sCL, sCW, sCE, sES, sSS, sSR, sSA, sCL, sHA, sCL}, > -/* abort */ {sCL, sCL, sCL, sCL, sCL, sCL, sCL, sCL, sCL, sCL, sCL}, > -/* shutdown */ {sCL, sCL, sCW, sCE, sSS, sSS, sSR, sSA, sCL, sSS, sCL}, > -/* shutdown_ack */ {sSA, sCL, sCW, sCE, sES, sSA, sSA, sSA, sSA, sHA, sSA}, > -/* error */ {sCL, sCL, sCW, sCE, sES, sSS, sSR, sSA, sCL, sHA, sCL},/* Can't have Stale cookie*/ > -/* cookie_echo */ {sCL, sCL, sCE, sCE, sES, sSS, sSR, sSA, sCL, sHA, sCL},/* 5.2.4 - Big TODO */ > -/* cookie_ack */ {sCL, sCL, sCW, sCE, sES, sSS, sSR, sSA, sCL, sHA, sCL},/* Can't come in orig dir */ > -/* shutdown_comp*/ {sCL, sCL, sCW, sCE, sES, sSS, sSR, sCL, sCL, sHA, sCL}, > -/* heartbeat */ {sHS, sCL, sCW, sCE, sES, sSS, sSR, sSA, sHS, sHA, sDS}, > -/* heartbeat_ack*/ {sCL, sCL, sCW, sCE, sES, sSS, sSR, sSA, sHS, sHA, sDS}, > -/* data/sack */ {sDS, sCL, sCW, sCE, sES, sSS, sSR, sSA, sHS, sHA, sDS} > - }, > - { > -/* REPLY */ > -/* sNO, sCL, sCW, sCE, sES, sSS, sSR, sSA, sHS, sHA, sDS */ > -/* init */ {sIV, sCL, sCW, sCE, sES, sSS, sSR, sSA, sIV, sHA, sIV},/* INIT in sCL Big TODO */ > -/* init_ack */ {sIV, sCW, sCW, sCE, sES, sSS, sSR, sSA, sIV, sHA, sIV}, > -/* abort */ {sIV, sCL, sCL, sCL, sCL, sCL, sCL, sCL, sIV, sCL, sIV}, > -/* shutdown */ {sIV, sCL, sCW, sCE, sSR, sSS, sSR, sSA, sIV, sSR, sIV}, > -/* shutdown_ack */ {sIV, sCL, sCW, sCE, sES, sSA, sSA, sSA, sIV, sHA, sIV}, > -/* error */ {sIV, sCL, sCW, sCL, sES, sSS, sSR, sSA, sIV, sHA, sIV}, > -/* cookie_echo */ {sIV, sCL, sCW, sCE, sES, sSS, sSR, sSA, sIV, sHA, sIV},/* Can't come in reply dir */ > -/* cookie_ack */ {sIV, sCL, sCW, sES, sES, sSS, sSR, sSA, sIV, sHA, sIV}, > -/* shutdown_comp*/ {sIV, sCL, sCW, sCE, sES, sSS, sSR, sCL, sIV, sHA, sIV}, > -/* heartbeat */ {sIV, sCL, sCW, sCE, sES, sSS, sSR, sSA, sHS, sHA, sHA}, > -/* heartbeat_ack*/ {sIV, sCL, sCW, sCE, sES, sSS, sSR, sSA, sHA, sHA, sHA}, > -/* data/sack */ {sIV, sCL, sCW, sCE, sES, sSS, sSR, sSA, sHA, sHA, sHA}, > - } > + [SCTP_CONNTRACK_OPEN_WAIT] = 3 SECS, > + [SCTP_CONNTRACK_ESTABLISHED] = 210 SECS, > }; > > #ifdef CONFIG_NF_CONNTRACK_PROCFS > @@ -158,184 +51,6 @@ static void sctp_print_conntrack(struct seq_file *s, struct nf_conn *ct) > } > #endif > > -#define for_each_sctp_chunk(skb, sch, _sch, offset, dataoff, count) \ > -for ((offset) = (dataoff) + sizeof(struct sctphdr), (count) = 0; \ > - (offset) < (skb)->len && \ > - ((sch) = skb_header_pointer((skb), (offset), sizeof(_sch), &(_sch))); \ > - (offset) += (ntohs((sch)->length) + 3) & ~3, (count)++) > - > -/* Some validity checks to make sure the chunks are fine */ > -static int do_basic_checks(struct nf_conn *ct, > - const struct sk_buff *skb, > - unsigned int dataoff, > - unsigned long *map) > -{ > - u_int32_t offset, count; > - struct sctp_chunkhdr _sch, *sch; > - int flag; > - > - flag = 0; > - > - for_each_sctp_chunk (skb, sch, _sch, offset, dataoff, count) { > - pr_debug("Chunk Num: %d Type: %d\n", count, sch->type); > - > - if (sch->type == SCTP_CID_INIT || > - sch->type == SCTP_CID_INIT_ACK || > - sch->type == SCTP_CID_SHUTDOWN_COMPLETE) > - flag = 1; > - > - /* > - * Cookie Ack/Echo chunks not the first OR > - * Init / Init Ack / Shutdown compl chunks not the only chunks > - * OR zero-length. > - */ > - if (((sch->type == SCTP_CID_COOKIE_ACK || > - sch->type == SCTP_CID_COOKIE_ECHO || > - flag) && > - count != 0) || !sch->length) { > - pr_debug("Basic checks failed\n"); > - return 1; > - } > - > - if (map) > - set_bit(sch->type, map); > - } > - > - pr_debug("Basic checks passed\n"); > - return count == 0; > -} > - > -static int sctp_new_state(enum ip_conntrack_dir dir, > - enum sctp_conntrack cur_state, > - int chunk_type) > -{ > - int i; > - > - pr_debug("Chunk type: %d\n", chunk_type); > - > - switch (chunk_type) { > - case SCTP_CID_INIT: > - pr_debug("SCTP_CID_INIT\n"); > - i = 0; > - break; > - case SCTP_CID_INIT_ACK: > - pr_debug("SCTP_CID_INIT_ACK\n"); > - i = 1; > - break; > - case SCTP_CID_ABORT: > - pr_debug("SCTP_CID_ABORT\n"); > - i = 2; > - break; > - case SCTP_CID_SHUTDOWN: > - pr_debug("SCTP_CID_SHUTDOWN\n"); > - i = 3; > - break; > - case SCTP_CID_SHUTDOWN_ACK: > - pr_debug("SCTP_CID_SHUTDOWN_ACK\n"); > - i = 4; > - break; > - case SCTP_CID_ERROR: > - pr_debug("SCTP_CID_ERROR\n"); > - i = 5; > - break; > - case SCTP_CID_COOKIE_ECHO: > - pr_debug("SCTP_CID_COOKIE_ECHO\n"); > - i = 6; > - break; > - case SCTP_CID_COOKIE_ACK: > - pr_debug("SCTP_CID_COOKIE_ACK\n"); > - i = 7; > - break; > - case SCTP_CID_SHUTDOWN_COMPLETE: > - pr_debug("SCTP_CID_SHUTDOWN_COMPLETE\n"); > - i = 8; > - break; > - case SCTP_CID_HEARTBEAT: > - pr_debug("SCTP_CID_HEARTBEAT"); > - i = 9; > - break; > - case SCTP_CID_HEARTBEAT_ACK: > - pr_debug("SCTP_CID_HEARTBEAT_ACK"); > - i = 10; > - break; > - case SCTP_CID_DATA: > - case SCTP_CID_SACK: > - pr_debug("SCTP_CID_DATA/SACK"); > - i = 11; > - break; > - default: > - /* Other chunks like DATA or SACK do not change the state */ > - pr_debug("Unknown chunk type, Will stay in %s\n", > - sctp_conntrack_names[cur_state]); > - return cur_state; > - } > - > - pr_debug("dir: %d cur_state: %s chunk_type: %d new_state: %s\n", > - dir, sctp_conntrack_names[cur_state], chunk_type, > - sctp_conntrack_names[sctp_conntracks[dir][i][cur_state]]); > - > - return sctp_conntracks[dir][i][cur_state]; > -} > - > -/* Don't need lock here: this conntrack not in circulation yet */ > -static noinline bool > -sctp_new(struct nf_conn *ct, const struct sk_buff *skb, > - const struct sctphdr *sh, unsigned int dataoff) > -{ > - enum sctp_conntrack new_state; > - const struct sctp_chunkhdr *sch; > - struct sctp_chunkhdr _sch; > - u32 offset, count; > - > - memset(&ct->proto.sctp, 0, sizeof(ct->proto.sctp)); > - new_state = SCTP_CONNTRACK_MAX; > - for_each_sctp_chunk(skb, sch, _sch, offset, dataoff, count) { > - new_state = sctp_new_state(IP_CT_DIR_ORIGINAL, > - SCTP_CONNTRACK_NONE, sch->type); > - > - /* Invalid: delete conntrack */ > - if (new_state == SCTP_CONNTRACK_NONE || > - new_state == SCTP_CONNTRACK_MAX) { > - pr_debug("nf_conntrack_sctp: invalid new deleting.\n"); > - return false; > - } > - > - /* Copy the vtag into the state info */ > - if (sch->type == SCTP_CID_INIT) { > - struct sctp_inithdr _inithdr, *ih; > - /* Sec 8.5.1 (A) */ > - if (sh->vtag) > - return false; > - > - ih = skb_header_pointer(skb, offset + sizeof(_sch), > - sizeof(_inithdr), &_inithdr); > - if (!ih) > - return false; > - > - pr_debug("Setting vtag %x for new conn\n", > - ih->init_tag); > - > - ct->proto.sctp.vtag[IP_CT_DIR_REPLY] = ih->init_tag; > - } else if (sch->type == SCTP_CID_HEARTBEAT || > - sch->type == SCTP_CID_DATA || > - sch->type == SCTP_CID_SACK) { > - pr_debug("Setting vtag %x for secondary conntrack\n", > - sh->vtag); > - ct->proto.sctp.vtag[IP_CT_DIR_ORIGINAL] = sh->vtag; > - } else { > - /* If it is a shutdown ack OOTB packet, we expect a return > - shutdown complete, otherwise an ABORT Sec 8.4 (5) and (8) */ > - pr_debug("Setting vtag %x for new conn OOTB\n", > - sh->vtag); > - ct->proto.sctp.vtag[IP_CT_DIR_REPLY] = sh->vtag; > - } > - > - ct->proto.sctp.state = SCTP_CONNTRACK_NONE; > - } > - > - return true; > -} > - > static bool sctp_error(struct sk_buff *skb, > unsigned int dataoff, > const struct nf_hook_state *state) > @@ -367,6 +82,90 @@ static bool sctp_error(struct sk_buff *skb, > return true; > } > > +static void sctp_new(struct nf_conn *ct, > + enum ip_conntrack_info ctinfo, > + u32 init_vtag, > + u32 vtag, > + unsigned long *map) > +{ > + enum ip_conntrack_dir dir = CTINFO2DIR(ctinfo); > + > + memset(&ct->proto.sctp, 0, sizeof(ct->proto.sctp)); > + ct->proto.sctp.state = SCTP_CONNTRACK_OPEN_WAIT; > + nf_conntrack_event_cache(IPCT_PROTOINFO, ct); > + > + if (test_bit(SCTP_CID_INIT, map)) > + ct->proto.sctp.vtag[!dir] = init_vtag; > + else if (test_bit(SCTP_CID_SHUTDOWN_ACK, map)) > + /* If it is a shutdown ack OOTB packet, we expect a return > + * shutdown complete, otherwise an ABORT Sec 8.4 (5) and (8) > + */ > + ct->proto.sctp.vtag[!dir] = vtag; > + else > + ct->proto.sctp.vtag[dir] = vtag; > +} > + > +static bool sctp_vtag_check(struct nf_conn *ct, > + enum ip_conntrack_info ctinfo, > + u32 vtag, > + unsigned long *map, > + unsigned long *tflags) > +{ > + enum ip_conntrack_dir dir = CTINFO2DIR(ctinfo); > + > + /* Check the verification tag (Sec 8.5) */ > + if (!test_bit(SCTP_CID_INIT, map) && > + !test_bit(SCTP_CID_SHUTDOWN_COMPLETE, map) && > + !test_bit(SCTP_CID_COOKIE_ECHO, map) && > + !test_bit(SCTP_CID_ABORT, map) && > + !test_bit(SCTP_CID_SHUTDOWN_ACK, map) && > + !test_bit(SCTP_CID_HEARTBEAT, map) && > + !test_bit(SCTP_CID_HEARTBEAT_ACK, map) && > + vtag != ct->proto.sctp.vtag[dir]) { > + return false; > + } > + > + /* Special cases of Verification tag check (Sec 8.5.1) */ > + if (test_bit(SCTP_CID_INIT, map)) { > + /* (A) vtag MUST be zero */ > + if (vtag != 0) > + return false; > + } > + if (test_bit(SCTP_CID_ABORT, map)) { > + /* (B) vtag MUST match own vtag if T flag is unset OR > + * MUST match peer's vtag if T flag is set > + */ > + if ((!test_bit(SCTP_CID_ABORT, tflags) && > + vtag != ct->proto.sctp.vtag[dir]) || > + (test_bit(SCTP_CID_ABORT, tflags) && > + vtag != ct->proto.sctp.vtag[!dir])) > + return false; > + } > + if (test_bit(SCTP_CID_SHUTDOWN_COMPLETE, map)) { > + /* (C) vtag MUST match own vtag if T flag is unset OR > + * MUST match peer's vtag if T flag is set > + */ > + if ((!test_bit(SCTP_CID_SHUTDOWN_COMPLETE, tflags) && > + vtag != ct->proto.sctp.vtag[dir]) || > + (test_bit(SCTP_CID_SHUTDOWN_COMPLETE, tflags) && > + vtag != ct->proto.sctp.vtag[!dir])) > + return false; > + } > + if (test_bit(SCTP_CID_COOKIE_ECHO, map)) { > + /* (D) vtag must be same as init_vtag as found in INIT_ACK */ > + if (vtag != ct->proto.sctp.vtag[dir]) > + return false; > + } > + > + return true; > +} > + > +#define for_each_sctp_chunk(skb, sch, _sch, offset, dataoff) \ > +for ((offset) = (dataoff) + sizeof(struct sctphdr); \ > + ((sch) = skb_header_pointer((skb), (offset), sizeof(_sch), &(_sch))) && \ > + (sch)->length; \ > + (offset) += (ntohs((sch)->length) + 3) & ~3) > + > /* Returns verdict for packet, or -NF_ACCEPT for invalid. */ > int nf_conntrack_sctp_packet(struct nf_conn *ct, > struct sk_buff *skb, > @@ -374,26 +173,39 @@ int nf_conntrack_sctp_packet(struct nf_conn *ct, > enum ip_conntrack_info ctinfo, > const struct nf_hook_state *state) > { > - enum sctp_conntrack new_state, old_state; > enum ip_conntrack_dir dir = CTINFO2DIR(ctinfo); > - const struct sctphdr *sh; > - struct sctphdr _sctph; > - const struct sctp_chunkhdr *sch; > - struct sctp_chunkhdr _sch; > - u_int32_t offset, count; > - unsigned int *timeouts; > unsigned long map[256 / sizeof(unsigned long)] = { 0 }; > - bool ignore = false; > + unsigned long tflags[256 / sizeof(unsigned long)] = { 0 }; > + unsigned int *timeouts; > + u32 init_vtag = 0; > + u32 offset, count; > + struct sctphdr _sctph, *sctph; > + struct sctp_chunkhdr _sch, *sch; > > if (sctp_error(skb, dataoff, state)) > return -NF_ACCEPT; > > - sh = skb_header_pointer(skb, dataoff, sizeof(_sctph), &_sctph); > - if (sh == NULL) > - goto out; > + sctph = skb_header_pointer(skb, dataoff, sizeof(_sctph), &_sctph); > + if (!sctph) > + return -NF_ACCEPT; > > - if (do_basic_checks(ct, skb, dataoff, map) != 0) > - goto out; > + for_each_sctp_chunk (skb, sch, _sch, offset, dataoff) { > + set_bit(sch->type, map); > + if (sch->flags & SCTP_CHUNK_FLAG_T) > + set_bit(sch->type, tflags); > + > + if (sch->type == SCTP_CID_INIT || > + sch->type == SCTP_CID_INIT_ACK) { > + struct sctp_inithdr _inith, *inith; > + > + inith = skb_header_pointer(skb, offset + sizeof(_sch), > + sizeof(_inith), &_inith); > + if (inith) > + init_vtag = inith->init_tag; > + else > + return -NF_ACCEPT; > + } > + } > > if (!nf_ct_is_confirmed(ct)) { > /* If an OOTB packet has any of these chunks discard (Sec 8.4) */ > @@ -402,167 +214,86 @@ int nf_conntrack_sctp_packet(struct nf_conn *ct, > test_bit(SCTP_CID_COOKIE_ACK, map)) > return -NF_ACCEPT; > > - if (!sctp_new(ct, skb, sh, dataoff)) > - return -NF_ACCEPT; > - } else { > - /* Check the verification tag (Sec 8.5) */ > - if (!test_bit(SCTP_CID_INIT, map) && > - !test_bit(SCTP_CID_SHUTDOWN_COMPLETE, map) && > - !test_bit(SCTP_CID_COOKIE_ECHO, map) && > - !test_bit(SCTP_CID_ABORT, map) && > - !test_bit(SCTP_CID_SHUTDOWN_ACK, map) && > - !test_bit(SCTP_CID_HEARTBEAT, map) && > - !test_bit(SCTP_CID_HEARTBEAT_ACK, map) && > - sh->vtag != ct->proto.sctp.vtag[dir]) { > - pr_debug("Verification tag check failed\n"); > - goto out; > - } > + sctp_new(ct, ctinfo, init_vtag, sctph->vtag, map); > + goto out; > } > > - old_state = new_state = SCTP_CONNTRACK_NONE; > + /* don't renew timeout on init retransmit so > + * port reuse by client or NAT middlebox cannot > + * keep entry alive indefinitely (incl. nat info). > + */ > + if (test_bit(SCTP_CID_INIT, map)) > + return NF_ACCEPT; > + > spin_lock_bh(&ct->lock); > - for_each_sctp_chunk (skb, sch, _sch, offset, dataoff, count) { > - /* Special cases of Verification tag check (Sec 8.5.1) */ > - if (sch->type == SCTP_CID_INIT) { > - /* Sec 8.5.1 (A) */ > - if (sh->vtag != 0) > - goto out_unlock; > - } else if (sch->type == SCTP_CID_ABORT) { > - /* Sec 8.5.1 (B) */ > - if (sh->vtag != ct->proto.sctp.vtag[dir] && > - sh->vtag != ct->proto.sctp.vtag[!dir]) > - goto out_unlock; > - } else if (sch->type == SCTP_CID_SHUTDOWN_COMPLETE) { > - /* Sec 8.5.1 (C) */ > - if (sh->vtag != ct->proto.sctp.vtag[dir] && > - sh->vtag != ct->proto.sctp.vtag[!dir] && > - sch->flags & SCTP_CHUNK_FLAG_T) > - goto out_unlock; > - } else if (sch->type == SCTP_CID_COOKIE_ECHO) { > - /* Sec 8.5.1 (D) */ > - if (sh->vtag != ct->proto.sctp.vtag[dir]) > - goto out_unlock; > - } else if (sch->type == SCTP_CID_HEARTBEAT) { > - if (ct->proto.sctp.vtag[dir] == 0) { > - pr_debug("Setting %d vtag %x for dir %d\n", sch->type, sh->vtag, dir); > - ct->proto.sctp.vtag[dir] = sh->vtag; > - } else if (sh->vtag != ct->proto.sctp.vtag[dir]) { > - if (test_bit(SCTP_CID_DATA, map) || ignore) > - goto out_unlock; > - > - ct->proto.sctp.flags |= SCTP_FLAG_HEARTBEAT_VTAG_FAILED; > - ct->proto.sctp.last_dir = dir; > - ignore = true; > - continue; > - } else if (ct->proto.sctp.flags & SCTP_FLAG_HEARTBEAT_VTAG_FAILED) { > - ct->proto.sctp.flags &= ~SCTP_FLAG_HEARTBEAT_VTAG_FAILED; > - } > - } else if (sch->type == SCTP_CID_HEARTBEAT_ACK) { > - if (ct->proto.sctp.vtag[dir] == 0) { > - pr_debug("Setting vtag %x for dir %d\n", > - sh->vtag, dir); > - ct->proto.sctp.vtag[dir] = sh->vtag; > - } else if (sh->vtag != ct->proto.sctp.vtag[dir]) { > - if (test_bit(SCTP_CID_DATA, map) || ignore) > - goto out_unlock; > - > - if ((ct->proto.sctp.flags & SCTP_FLAG_HEARTBEAT_VTAG_FAILED) == 0 || > - ct->proto.sctp.last_dir == dir) > - goto out_unlock; > - > - ct->proto.sctp.flags &= ~SCTP_FLAG_HEARTBEAT_VTAG_FAILED; > - ct->proto.sctp.vtag[dir] = sh->vtag; > - ct->proto.sctp.vtag[!dir] = 0; > - } else if (ct->proto.sctp.flags & SCTP_FLAG_HEARTBEAT_VTAG_FAILED) { > - ct->proto.sctp.flags &= ~SCTP_FLAG_HEARTBEAT_VTAG_FAILED; > - } > - } else if (sch->type == SCTP_CID_DATA || sch->type == SCTP_CID_SACK) { > - if (ct->proto.sctp.vtag[dir] == 0) { > - pr_debug("Setting vtag %x for dir %d\n", sh->vtag, dir); > - ct->proto.sctp.vtag[dir] = sh->vtag; > - } > - } > + if (!ct->proto.sctp.vtag[!dir] && > + test_bit(SCTP_CID_INIT_ACK, map)) > + ct->proto.sctp.vtag[!dir] = init_vtag; > > - old_state = ct->proto.sctp.state; > - new_state = sctp_new_state(dir, old_state, sch->type); > + if (!ct->proto.sctp.vtag[dir]) > + ct->proto.sctp.vtag[dir] = sctph->vtag; > > - /* Invalid */ > - if (new_state == SCTP_CONNTRACK_MAX) { > - pr_debug("nf_conntrack_sctp: Invalid dir=%i ctype=%u " > - "conntrack=%u\n", > - dir, sch->type, old_state); > - goto out_unlock; > - } > + /* we have seen traffic both ways, go to established */ > + if (dir == IP_CT_DIR_REPLY && > + ct->proto.sctp.state == SCTP_CONNTRACK_OPEN_WAIT) { > + ct->proto.sctp.state = SCTP_CONNTRACK_ESTABLISHED; > + nf_conntrack_event_cache(IPCT_PROTOINFO, ct); > > - /* If it is an INIT or an INIT ACK note down the vtag */ > - if (sch->type == SCTP_CID_INIT || > - sch->type == SCTP_CID_INIT_ACK) { > - struct sctp_inithdr _inithdr, *ih; > + if (!test_and_set_bit(IPS_ASSURED_BIT, &ct->status)) > + nf_conntrack_event_cache(IPCT_ASSURED, ct); > + } > > - ih = skb_header_pointer(skb, offset + sizeof(_sch), > - sizeof(_inithdr), &_inithdr); > - if (ih == NULL) > + if (test_bit(SCTP_CID_HEARTBEAT, map)) { > + if (sctph->vtag != ct->proto.sctp.vtag[dir]) { > + if (test_bit(SCTP_CID_DATA, map)) > goto out_unlock; > - pr_debug("Setting vtag %x for dir %d\n", > - ih->init_tag, !dir); > - ct->proto.sctp.vtag[!dir] = ih->init_tag; > - > - /* don't renew timeout on init retransmit so > - * port reuse by client or NAT middlebox cannot > - * keep entry alive indefinitely (incl. nat info). > - */ > - if (new_state == SCTP_CONNTRACK_CLOSED && > - old_state == SCTP_CONNTRACK_CLOSED && > - nf_ct_is_confirmed(ct)) > - ignore = true; > + > + ct->proto.sctp.flags |= SCTP_FLAG_HEARTBEAT_VTAG_FAILED; > + ct->proto.sctp.last_dir = dir; > + } else if (ct->proto.sctp.flags & SCTP_FLAG_HEARTBEAT_VTAG_FAILED) { > + ct->proto.sctp.flags &= ~SCTP_FLAG_HEARTBEAT_VTAG_FAILED; > } > + } > + if (test_bit(SCTP_CID_HEARTBEAT_ACK, map)) { > + if (sctph->vtag != ct->proto.sctp.vtag[dir]) { > + if (test_bit(SCTP_CID_DATA, map)) > + goto out_unlock; > + > + if ((ct->proto.sctp.flags & SCTP_FLAG_HEARTBEAT_VTAG_FAILED) == 0 || > + ct->proto.sctp.last_dir == dir) > + goto out_unlock; > > - ct->proto.sctp.state = new_state; > - if (old_state != new_state) > - nf_conntrack_event_cache(IPCT_PROTOINFO, ct); > + ct->proto.sctp.flags &= ~SCTP_FLAG_HEARTBEAT_VTAG_FAILED; > + ct->proto.sctp.vtag[dir] = sctph->vtag; > + ct->proto.sctp.vtag[!dir] = 0; > + } else if (ct->proto.sctp.flags & SCTP_FLAG_HEARTBEAT_VTAG_FAILED) { > + ct->proto.sctp.flags &= ~SCTP_FLAG_HEARTBEAT_VTAG_FAILED; > + } > } > spin_unlock_bh(&ct->lock); > > - /* allow but do not refresh timeout */ > - if (ignore) > - return NF_ACCEPT; > + if (!sctp_vtag_check(ct, ctinfo, sctph->vtag, map, tflags)) { > + nf_ct_l4proto_log_invalid(skb, ct, state, > + "verification tag check failed %x vs (%x: dir %d) and (%x: dir %d)", > + sctph->vtag, ct->proto.sctp.vtag[dir], dir, > + ct->proto.sctp.vtag[!dir], !dir); > + return -NF_ACCEPT; > + } > > +out: > timeouts = nf_ct_timeout_lookup(ct); > if (!timeouts) > timeouts = nf_sctp_pernet(nf_ct_net(ct))->timeouts; > > - nf_ct_refresh_acct(ct, ctinfo, skb, timeouts[new_state]); > - > - if (old_state == SCTP_CONNTRACK_COOKIE_ECHOED && > - dir == IP_CT_DIR_REPLY && > - new_state == SCTP_CONNTRACK_ESTABLISHED) { > - pr_debug("Setting assured bit\n"); > - set_bit(IPS_ASSURED_BIT, &ct->status); > - nf_conntrack_event_cache(IPCT_ASSURED, ct); > - } > + nf_ct_refresh_acct(ct, ctinfo, skb, timeouts[ct->proto.sctp.state]); > > return NF_ACCEPT; > > out_unlock: > spin_unlock_bh(&ct->lock); > -out: > return -NF_ACCEPT; > } > > -static bool sctp_can_early_drop(const struct nf_conn *ct) > -{ > - switch (ct->proto.sctp.state) { > - case SCTP_CONNTRACK_SHUTDOWN_SENT: > - case SCTP_CONNTRACK_SHUTDOWN_RECD: > - case SCTP_CONNTRACK_SHUTDOWN_ACK_SENT: > - return true; > - default: > - break; > - } > - > - return false; > -} > - > #if IS_ENABLED(CONFIG_NF_CT_NETLINK) > > #include <linux/netfilter/nfnetlink.h> > @@ -670,7 +401,7 @@ static int sctp_timeout_nlattr_to_obj(struct nlattr *tb[], > } > } > > - timeouts[CTA_TIMEOUT_SCTP_UNSPEC] = timeouts[CTA_TIMEOUT_SCTP_CLOSED]; > + timeouts[CTA_TIMEOUT_SCTP_UNSPEC] = timeouts[CTA_TIMEOUT_SCTP_OPEN_WAIT]; > return 0; > } > > @@ -692,16 +423,8 @@ sctp_timeout_obj_to_nlattr(struct sk_buff *skb, const void *data) > > static const struct nla_policy > sctp_timeout_nla_policy[CTA_TIMEOUT_SCTP_MAX+1] = { > - [CTA_TIMEOUT_SCTP_CLOSED] = { .type = NLA_U32 }, > - [CTA_TIMEOUT_SCTP_COOKIE_WAIT] = { .type = NLA_U32 }, > - [CTA_TIMEOUT_SCTP_COOKIE_ECHOED] = { .type = NLA_U32 }, > + [CTA_TIMEOUT_SCTP_OPEN_WAIT] = { .type = NLA_U32 }, > [CTA_TIMEOUT_SCTP_ESTABLISHED] = { .type = NLA_U32 }, > - [CTA_TIMEOUT_SCTP_SHUTDOWN_SENT] = { .type = NLA_U32 }, > - [CTA_TIMEOUT_SCTP_SHUTDOWN_RECD] = { .type = NLA_U32 }, > - [CTA_TIMEOUT_SCTP_SHUTDOWN_ACK_SENT] = { .type = NLA_U32 }, > - [CTA_TIMEOUT_SCTP_HEARTBEAT_SENT] = { .type = NLA_U32 }, > - [CTA_TIMEOUT_SCTP_HEARTBEAT_ACKED] = { .type = NLA_U32 }, > - [CTA_TIMEOUT_SCTP_DATA_SENT] = { .type = NLA_U32 }, > }; > #endif /* CONFIG_NF_CONNTRACK_TIMEOUT */ > > @@ -716,7 +439,7 @@ void nf_conntrack_sctp_init_net(struct net *net) > /* timeouts[0] is unused, init it so ->timeouts[0] contains > * 'new' timeout, like udp or icmp. > */ > - sn->timeouts[0] = sctp_timeouts[SCTP_CONNTRACK_CLOSED]; > + sn->timeouts[0] = sctp_timeouts[SCTP_CONNTRACK_OPEN_WAIT]; > } > > const struct nf_conntrack_l4proto nf_conntrack_l4proto_sctp = { > @@ -724,7 +447,6 @@ const struct nf_conntrack_l4proto nf_conntrack_l4proto_sctp = { > #ifdef CONFIG_NF_CONNTRACK_PROCFS > .print_conntrack = sctp_print_conntrack, > #endif > - .can_early_drop = sctp_can_early_drop, > #if IS_ENABLED(CONFIG_NF_CT_NETLINK) > .nlattr_size = SCTP_NLATTR_SIZE, > .to_nlattr = sctp_to_nlattr, > diff --git a/net/netfilter/nf_conntrack_standalone.c b/net/netfilter/nf_conntrack_standalone.c > index 0250725e38a4..07da9db31783 100644 > --- a/net/netfilter/nf_conntrack_standalone.c > +++ b/net/netfilter/nf_conntrack_standalone.c > @@ -593,16 +593,8 @@ enum nf_ct_sysctl_index { > NF_SYSCTL_CT_PROTO_TIMEOUT_ICMP, > NF_SYSCTL_CT_PROTO_TIMEOUT_ICMPV6, > #ifdef CONFIG_NF_CT_PROTO_SCTP > - NF_SYSCTL_CT_PROTO_TIMEOUT_SCTP_CLOSED, > - NF_SYSCTL_CT_PROTO_TIMEOUT_SCTP_COOKIE_WAIT, > - NF_SYSCTL_CT_PROTO_TIMEOUT_SCTP_COOKIE_ECHOED, > + NF_SYSCTL_CT_PROTO_TIMEOUT_SCTP_OPEN_WAIT, > NF_SYSCTL_CT_PROTO_TIMEOUT_SCTP_ESTABLISHED, > - NF_SYSCTL_CT_PROTO_TIMEOUT_SCTP_SHUTDOWN_SENT, > - NF_SYSCTL_CT_PROTO_TIMEOUT_SCTP_SHUTDOWN_RECD, > - NF_SYSCTL_CT_PROTO_TIMEOUT_SCTP_SHUTDOWN_ACK_SENT, > - NF_SYSCTL_CT_PROTO_TIMEOUT_SCTP_HEARTBEAT_SENT, > - NF_SYSCTL_CT_PROTO_TIMEOUT_SCTP_HEARTBEAT_ACKED, > - NF_SYSCTL_CT_PROTO_TIMEOUT_SCTP_DATA_SENT, > #endif > #ifdef CONFIG_NF_CT_PROTO_DCCP > NF_SYSCTL_CT_PROTO_TIMEOUT_DCCP_REQUEST, > @@ -839,20 +831,8 @@ static struct ctl_table nf_ct_sysctl_table[] = { > .proc_handler = proc_dointvec_jiffies, > }, > #ifdef CONFIG_NF_CT_PROTO_SCTP > - [NF_SYSCTL_CT_PROTO_TIMEOUT_SCTP_CLOSED] = { > - .procname = "nf_conntrack_sctp_timeout_closed", > - .maxlen = sizeof(unsigned int), > - .mode = 0644, > - .proc_handler = proc_dointvec_jiffies, > - }, > - [NF_SYSCTL_CT_PROTO_TIMEOUT_SCTP_COOKIE_WAIT] = { > - .procname = "nf_conntrack_sctp_timeout_cookie_wait", > - .maxlen = sizeof(unsigned int), > - .mode = 0644, > - .proc_handler = proc_dointvec_jiffies, > - }, > - [NF_SYSCTL_CT_PROTO_TIMEOUT_SCTP_COOKIE_ECHOED] = { > - .procname = "nf_conntrack_sctp_timeout_cookie_echoed", > + [NF_SYSCTL_CT_PROTO_TIMEOUT_SCTP_OPEN_WAIT] = { > + .procname = "nf_conntrack_sctp_timeout_open_wait", > .maxlen = sizeof(unsigned int), > .mode = 0644, > .proc_handler = proc_dointvec_jiffies, > @@ -863,42 +843,6 @@ static struct ctl_table nf_ct_sysctl_table[] = { > .mode = 0644, > .proc_handler = proc_dointvec_jiffies, > }, > - [NF_SYSCTL_CT_PROTO_TIMEOUT_SCTP_SHUTDOWN_SENT] = { > - .procname = "nf_conntrack_sctp_timeout_shutdown_sent", > - .maxlen = sizeof(unsigned int), > - .mode = 0644, > - .proc_handler = proc_dointvec_jiffies, > - }, > - [NF_SYSCTL_CT_PROTO_TIMEOUT_SCTP_SHUTDOWN_RECD] = { > - .procname = "nf_conntrack_sctp_timeout_shutdown_recd", > - .maxlen = sizeof(unsigned int), > - .mode = 0644, > - .proc_handler = proc_dointvec_jiffies, > - }, > - [NF_SYSCTL_CT_PROTO_TIMEOUT_SCTP_SHUTDOWN_ACK_SENT] = { > - .procname = "nf_conntrack_sctp_timeout_shutdown_ack_sent", > - .maxlen = sizeof(unsigned int), > - .mode = 0644, > - .proc_handler = proc_dointvec_jiffies, > - }, > - [NF_SYSCTL_CT_PROTO_TIMEOUT_SCTP_HEARTBEAT_SENT] = { > - .procname = "nf_conntrack_sctp_timeout_heartbeat_sent", > - .maxlen = sizeof(unsigned int), > - .mode = 0644, > - .proc_handler = proc_dointvec_jiffies, > - }, > - [NF_SYSCTL_CT_PROTO_TIMEOUT_SCTP_HEARTBEAT_ACKED] = { > - .procname = "nf_conntrack_sctp_timeout_heartbeat_acked", > - .maxlen = sizeof(unsigned int), > - .mode = 0644, > - .proc_handler = proc_dointvec_jiffies, > - }, > - [NF_SYSCTL_CT_PROTO_TIMEOUT_SCTP_DATA_SENT] = { > - .procname = "nf_conntrack_sctp_timeout_data_sent", > - .maxlen = sizeof(unsigned int), > - .mode = 0644, > - .proc_handler = proc_dointvec_jiffies, > - }, > #endif > #ifdef CONFIG_NF_CT_PROTO_DCCP > [NF_SYSCTL_CT_PROTO_TIMEOUT_DCCP_REQUEST] = { > @@ -1034,16 +978,8 @@ static void nf_conntrack_standalone_init_sctp_sysctl(struct net *net, > table[NF_SYSCTL_CT_PROTO_TIMEOUT_SCTP_ ## XNAME].data = \ > &(sn)->timeouts[SCTP_CONNTRACK_ ## XNAME] > > - XASSIGN(CLOSED, sn); > - XASSIGN(COOKIE_WAIT, sn); > - XASSIGN(COOKIE_ECHOED, sn); > + XASSIGN(OPEN_WAIT, sn); > XASSIGN(ESTABLISHED, sn); > - XASSIGN(SHUTDOWN_SENT, sn); > - XASSIGN(SHUTDOWN_RECD, sn); > - XASSIGN(SHUTDOWN_ACK_SENT, sn); > - XASSIGN(HEARTBEAT_SENT, sn); > - XASSIGN(HEARTBEAT_ACKED, sn); > - XASSIGN(DATA_SENT, sn); > #undef XASSIGN > #endif > } > -- > 2.34.1 >
#!/bin/bash # SPDX-License-Identifier: GPL-2.0 # # Testing For TCP/UDP/SCTP(4/6) NAT. # TOPO: CLIENT_NS (link0)<--->(link1) HOST/ROUTER (link2)<--->(link3) SERVER_NS CLIENT_NS="client-ns" CLIENT_IP4="198.51.100.1" CLIENT_GW4="198.51.100.2" CLIENT_IP6="2001:db8:1::1" CLIENT_GW6="2001:db8:1::2" SERVER_NS="server-ns" SERVER_IP4="203.0.113.1" SERVER_GW4="203.0.113.2" SERVER_IP6="2001:db8:2::1" SERVER_GW6="2001:db8:2::2" setup() { ip netns add $CLIENT_NS ip netns add $SERVER_NS ip link add link1 type veth peer name link0 netns $CLIENT_NS ip link add link2 type veth peer name link3 netns $SERVER_NS ip net exec $CLIENT_NS ip link set link0 up ip net exec $CLIENT_NS ip addr add $CLIENT_IP4/24 dev link0 ip net exec $CLIENT_NS ip addr add $CLIENT_IP6/64 dev link0 nodad ip net exec $CLIENT_NS ip route add $SERVER_IP4 dev link0 via $CLIENT_GW4 ip net exec $CLIENT_NS ip route add $SERVER_IP6 dev link0 via $CLIENT_GW6 ip link set link1 up ip link set link2 up ip addr add $CLIENT_GW4/24 dev link1 ip addr add $CLIENT_GW6/64 dev link1 nodad ip addr add $SERVER_GW4/24 dev link2 ip addr add $SERVER_GW6/64 dev link2 nodad iptables -t nat -A POSTROUTING -o link2 -j MASQUERADE ip6tables -t nat -A POSTROUTING -o link2 -j MASQUERADE ip net exec $SERVER_NS ip link set link3 up ip net exec $SERVER_NS ip addr add $SERVER_IP4/24 dev link3 ip net exec $SERVER_NS ip addr add $SERVER_IP6/64 dev link3 nodad IP4_FWD=`cat /proc/sys/net/ipv4/ip_forward` IP6_FWD=`cat /proc/sys/net/ipv6/conf/all/forwarding` sysctl -w net.ipv4.ip_forward=1 2>&1 >/dev/null sysctl -w net.ipv6.conf.all.forwarding=1 2>&1 >/dev/null modprobe sctp } cleanup() { sysctl -w net.ipv4.ip_forward=$IP4_FWD 2>&1 >/dev/null sysctl -w net.ipv6.conf.all.forwarding=$IP6_FWD 2>&1 >/dev/null ip link del link1 ip link del link2 ip netns del "$CLIENT_NS" ip netns del "$SERVER_NS" } testup() { local ipaddr="$1" local proto="$2" ip net exec $SERVER_NS nc -l $proto -p 1234 2>&1 >/dev/null & disown echo -n "msg1" | ip net exec $CLIENT_NS nc $ipaddr 1234 -p 4321 $proto ip net exec $SERVER_NS nc -l $proto -p 1234 2>&1 >/dev/null & disown echo -n "msg2" | ip net exec $CLIENT_NS nc $ipaddr 1234 -p 4321 $proto RET=$? ip net exec $SERVER_NS pkill nc return $RET } if ! nc --version &> /dev/null; then echo "SKIP: Could not run test without nc tool" exit 4 fi trap cleanup EXIT setup && testup $SERVER_IP4 && echo "TCP4 NAT: PASS" && testup $SERVER_IP6 && echo "TCP6 NAT: PASS" && testup $SERVER_IP4 "--udp" && echo "UDP4 NAT: PASS" && testup $SERVER_IP6 "--udp" && echo "UDP6 NAT: PASS" && testup $SERVER_IP4 "--sctp" && echo "SCTP4 NAT: PASS" && testup $SERVER_IP6 "--sctp" && echo "SCTP6 NAT: PASS" exit $?