Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

netfilter: nf_ct_sctp: minimal multihoming support

Currently nf_conntrack_proto_sctp module handles only packets between
primary addresses used to establish the connection. Any packets between
secondary addresses are classified as invalid so that usual firewall
configurations drop them. Allowing HEARTBEAT and HEARTBEAT-ACK chunks to
establish a new conntrack would allow traffic between secondary
addresses to pass through. A more sophisticated solution based on the
addresses advertised in the initial handshake (and possibly also later
dynamic address addition and removal) would be much harder to implement.
Moreover, in general we cannot assume to always see the initial
handshake as it can be routed through a different path.

The patch adds two new conntrack states:

SCTP_CONNTRACK_HEARTBEAT_SENT - a HEARTBEAT chunk seen but not acked
SCTP_CONNTRACK_HEARTBEAT_ACKED - a HEARTBEAT acked by HEARTBEAT-ACK

State transition rules:

- HEARTBEAT_SENT responds to usual chunks the same way as NONE (so that
the behaviour changes as little as possible)
- HEARTBEAT_ACKED responds to usual chunks the same way as ESTABLISHED
does, except the resulting state is HEARTBEAT_ACKED rather than
ESTABLISHED
- previously existing states except NONE are preserved when HEARTBEAT or
HEARTBEAT-ACK is seen
- NONE (in the initial direction) changes to HEARTBEAT_SENT on HEARTBEAT
and to CLOSED on HEARTBEAT-ACK
- HEARTBEAT_SENT changes to HEARTBEAT_ACKED on HEARTBEAT-ACK in the
reply direction
- HEARTBEAT_SENT and HEARTBEAT_ACKED are preserved on HEARTBEAT and
HEARTBEAT-ACK otherwise

Normally, vtag is set from the INIT chunk for the reply direction and
from the INIT-ACK chunk for the originating direction (i.e. each of
these defines vtag value for the opposite direction). For secondary
conntracks, we can't rely on seeing INIT/INIT-ACK and even if we have
seen them, we would need to connect two different conntracks. Therefore
simplified logic is applied: vtag of first packet in each direction
(HEARTBEAT in the originating and HEARTBEAT-ACK in reply direction) is
saved and all following packets in that direction are compared with this
saved value. While INIT and INIT-ACK define vtag for the opposite
direction, vtags extracted from HEARTBEAT and HEARTBEAT-ACK are always
for their direction.

Default timeout values for new states are

HEARTBEAT_SENT: 30 seconds (default hb_interval)
HEARTBEAT_ACKED: 210 seconds (hb_interval * path_max_retry + max_rto)

(We cannot expect to see the shutdown sequence so that, unlike
ESTABLISHED, the HEARTBEAT_ACKED timeout shouldn't be too long.)

Signed-off-by: Michal Kubecek <mkubecek@suse.cz>
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>

authored by

Michal Kubeček and committed by
Pablo Neira Ayuso
d7ee3519 3bbd14e0

+81 -24
+2
include/uapi/linux/netfilter/nf_conntrack_sctp.h
··· 13 13 SCTP_CONNTRACK_SHUTDOWN_SENT, 14 14 SCTP_CONNTRACK_SHUTDOWN_RECD, 15 15 SCTP_CONNTRACK_SHUTDOWN_ACK_SENT, 16 + SCTP_CONNTRACK_HEARTBEAT_SENT, 17 + SCTP_CONNTRACK_HEARTBEAT_ACKED, 16 18 SCTP_CONNTRACK_MAX 17 19 }; 18 20
+77 -24
net/netfilter/nf_conntrack_proto_sctp.c
··· 42 42 "SHUTDOWN_SENT", 43 43 "SHUTDOWN_RECD", 44 44 "SHUTDOWN_ACK_SENT", 45 + "HEARTBEAT_SENT", 46 + "HEARTBEAT_ACKED", 45 47 }; 46 48 47 49 #define SECS * HZ ··· 59 57 [SCTP_CONNTRACK_SHUTDOWN_SENT] = 300 SECS / 1000, 60 58 [SCTP_CONNTRACK_SHUTDOWN_RECD] = 300 SECS / 1000, 61 59 [SCTP_CONNTRACK_SHUTDOWN_ACK_SENT] = 3 SECS, 60 + [SCTP_CONNTRACK_HEARTBEAT_SENT] = 30 SECS, 61 + [SCTP_CONNTRACK_HEARTBEAT_ACKED] = 210 SECS, 62 62 }; 63 63 64 64 #define sNO SCTP_CONNTRACK_NONE ··· 71 67 #define sSS SCTP_CONNTRACK_SHUTDOWN_SENT 72 68 #define sSR SCTP_CONNTRACK_SHUTDOWN_RECD 73 69 #define sSA SCTP_CONNTRACK_SHUTDOWN_ACK_SENT 70 + #define sHS SCTP_CONNTRACK_HEARTBEAT_SENT 71 + #define sHA SCTP_CONNTRACK_HEARTBEAT_ACKED 74 72 #define sIV SCTP_CONNTRACK_MAX 75 73 76 74 /* ··· 94 88 to that of the SHUTDOWN chunk. 95 89 CLOSED - We have seen a SHUTDOWN_COMPLETE chunk in the direction of 96 90 the SHUTDOWN chunk. Connection is closed. 91 + HEARTBEAT_SENT - We have seen a HEARTBEAT in a new flow. 92 + HEARTBEAT_ACKED - We have seen a HEARTBEAT-ACK in the direction opposite to 93 + that of the HEARTBEAT chunk. Secondary connection is 94 + established. 97 95 */ 98 96 99 97 /* TODO ··· 107 97 - Check the error type in the reply dir before transitioning from 108 98 cookie echoed to closed. 109 99 - Sec 5.2.4 of RFC 2960 110 - - Multi Homing support. 100 + - Full Multi Homing support. 111 101 */ 112 102 113 103 /* SCTP conntrack state transitions */ 114 - static const u8 sctp_conntracks[2][9][SCTP_CONNTRACK_MAX] = { 104 + static const u8 sctp_conntracks[2][11][SCTP_CONNTRACK_MAX] = { 115 105 { 116 106 /* ORIGINAL */ 117 - /* sNO, sCL, sCW, sCE, sES, sSS, sSR, sSA */ 118 - /* init */ {sCW, sCW, sCW, sCE, sES, sSS, sSR, sSA}, 119 - /* init_ack */ {sCL, sCL, sCW, sCE, sES, sSS, sSR, sSA}, 120 - /* abort */ {sCL, sCL, sCL, sCL, sCL, sCL, sCL, sCL}, 121 - /* shutdown */ {sCL, sCL, sCW, sCE, sSS, sSS, sSR, sSA}, 122 - /* shutdown_ack */ {sSA, sCL, sCW, sCE, sES, sSA, sSA, sSA}, 123 - /* error */ {sCL, sCL, sCW, sCE, sES, sSS, sSR, sSA},/* Can't have Stale cookie*/ 124 - /* cookie_echo */ {sCL, sCL, sCE, sCE, sES, sSS, sSR, sSA},/* 5.2.4 - Big TODO */ 125 - /* cookie_ack */ {sCL, sCL, sCW, sCE, sES, sSS, sSR, sSA},/* Can't come in orig dir */ 126 - /* shutdown_comp*/ {sCL, sCL, sCW, sCE, sES, sSS, sSR, sCL} 107 + /* sNO, sCL, sCW, sCE, sES, sSS, sSR, sSA, sHS, sHA */ 108 + /* init */ {sCW, sCW, sCW, sCE, sES, sSS, sSR, sSA, sCW, sHA}, 109 + /* init_ack */ {sCL, sCL, sCW, sCE, sES, sSS, sSR, sSA, sCL, sHA}, 110 + /* abort */ {sCL, sCL, sCL, sCL, sCL, sCL, sCL, sCL, sCL, sCL}, 111 + /* shutdown */ {sCL, sCL, sCW, sCE, sSS, sSS, sSR, sSA, sCL, sSS}, 112 + /* shutdown_ack */ {sSA, sCL, sCW, sCE, sES, sSA, sSA, sSA, sSA, sHA}, 113 + /* error */ {sCL, sCL, sCW, sCE, sES, sSS, sSR, sSA, sCL, sHA},/* Can't have Stale cookie*/ 114 + /* cookie_echo */ {sCL, sCL, sCE, sCE, sES, sSS, sSR, sSA, sCL, sHA},/* 5.2.4 - Big TODO */ 115 + /* cookie_ack */ {sCL, sCL, sCW, sCE, sES, sSS, sSR, sSA, sCL, sHA},/* Can't come in orig dir */ 116 + /* shutdown_comp*/ {sCL, sCL, sCW, sCE, sES, sSS, sSR, sCL, sCL, sHA}, 117 + /* heartbeat */ {sHS, sCL, sCW, sCE, sES, sSS, sSR, sSA, sHS, sHA}, 118 + /* heartbeat_ack*/ {sCL, sCL, sCW, sCE, sES, sSS, sSR, sSA, sHS, sHA} 127 119 }, 128 120 { 129 121 /* REPLY */ 130 - /* sNO, sCL, sCW, sCE, sES, sSS, sSR, sSA */ 131 - /* init */ {sIV, sCL, sCW, sCE, sES, sSS, sSR, sSA},/* INIT in sCL Big TODO */ 132 - /* init_ack */ {sIV, sCL, sCW, sCE, sES, sSS, sSR, sSA}, 133 - /* abort */ {sIV, sCL, sCL, sCL, sCL, sCL, sCL, sCL}, 134 - /* shutdown */ {sIV, sCL, sCW, sCE, sSR, sSS, sSR, sSA}, 135 - /* shutdown_ack */ {sIV, sCL, sCW, sCE, sES, sSA, sSA, sSA}, 136 - /* error */ {sIV, sCL, sCW, sCL, sES, sSS, sSR, sSA}, 137 - /* cookie_echo */ {sIV, sCL, sCW, sCE, sES, sSS, sSR, sSA},/* Can't come in reply dir */ 138 - /* cookie_ack */ {sIV, sCL, sCW, sES, sES, sSS, sSR, sSA}, 139 - /* shutdown_comp*/ {sIV, sCL, sCW, sCE, sES, sSS, sSR, sCL} 122 + /* sNO, sCL, sCW, sCE, sES, sSS, sSR, sSA, sHS, sHA */ 123 + /* init */ {sIV, sCL, sCW, sCE, sES, sSS, sSR, sSA, sIV, sHA},/* INIT in sCL Big TODO */ 124 + /* init_ack */ {sIV, sCL, sCW, sCE, sES, sSS, sSR, sSA, sIV, sHA}, 125 + /* abort */ {sIV, sCL, sCL, sCL, sCL, sCL, sCL, sCL, sIV, sCL}, 126 + /* shutdown */ {sIV, sCL, sCW, sCE, sSR, sSS, sSR, sSA, sIV, sSR}, 127 + /* shutdown_ack */ {sIV, sCL, sCW, sCE, sES, sSA, sSA, sSA, sIV, sHA}, 128 + /* error */ {sIV, sCL, sCW, sCL, sES, sSS, sSR, sSA, sIV, sHA}, 129 + /* cookie_echo */ {sIV, sCL, sCW, sCE, sES, sSS, sSR, sSA, sIV, sHA},/* Can't come in reply dir */ 130 + /* cookie_ack */ {sIV, sCL, sCW, sES, sES, sSS, sSR, sSA, sIV, sHA}, 131 + /* shutdown_comp*/ {sIV, sCL, sCW, sCE, sES, sSS, sSR, sCL, sIV, sHA}, 132 + /* heartbeat */ {sIV, sCL, sCW, sCE, sES, sSS, sSR, sSA, sHS, sHA}, 133 + /* heartbeat_ack*/ {sIV, sCL, sCW, sCE, sES, sSS, sSR, sSA, sHA, sHA} 140 134 } 141 135 }; 142 136 ··· 292 278 pr_debug("SCTP_CID_SHUTDOWN_COMPLETE\n"); 293 279 i = 8; 294 280 break; 281 + case SCTP_CID_HEARTBEAT: 282 + pr_debug("SCTP_CID_HEARTBEAT"); 283 + i = 9; 284 + break; 285 + case SCTP_CID_HEARTBEAT_ACK: 286 + pr_debug("SCTP_CID_HEARTBEAT_ACK"); 287 + i = 10; 288 + break; 295 289 default: 296 - /* Other chunks like DATA, SACK, HEARTBEAT and 297 - its ACK do not cause a change in state */ 290 + /* Other chunks like DATA or SACK do not change the state */ 298 291 pr_debug("Unknown chunk type, Will stay in %s\n", 299 292 sctp_conntrack_names[cur_state]); 300 293 return cur_state; ··· 350 329 !test_bit(SCTP_CID_COOKIE_ECHO, map) && 351 330 !test_bit(SCTP_CID_ABORT, map) && 352 331 !test_bit(SCTP_CID_SHUTDOWN_ACK, map) && 332 + !test_bit(SCTP_CID_HEARTBEAT, map) && 333 + !test_bit(SCTP_CID_HEARTBEAT_ACK, map) && 353 334 sh->vtag != ct->proto.sctp.vtag[dir]) { 354 335 pr_debug("Verification tag check failed\n"); 355 336 goto out; ··· 380 357 /* Sec 8.5.1 (D) */ 381 358 if (sh->vtag != ct->proto.sctp.vtag[dir]) 382 359 goto out_unlock; 360 + } else if (sch->type == SCTP_CID_HEARTBEAT || 361 + sch->type == SCTP_CID_HEARTBEAT_ACK) { 362 + if (ct->proto.sctp.vtag[dir] == 0) { 363 + pr_debug("Setting vtag %x for dir %d\n", 364 + sh->vtag, dir); 365 + ct->proto.sctp.vtag[dir] = sh->vtag; 366 + } else if (sh->vtag != ct->proto.sctp.vtag[dir]) { 367 + pr_debug("Verification tag check failed\n"); 368 + goto out_unlock; 369 + } 383 370 } 384 371 385 372 old_state = ct->proto.sctp.state; ··· 499 466 /* Sec 8.5.1 (A) */ 500 467 return false; 501 468 } 469 + } else if (sch->type == SCTP_CID_HEARTBEAT) { 470 + pr_debug("Setting vtag %x for secondary conntrack\n", 471 + sh->vtag); 472 + ct->proto.sctp.vtag[IP_CT_DIR_ORIGINAL] = sh->vtag; 502 473 } 503 474 /* If it is a shutdown ack OOTB packet, we expect a return 504 475 shutdown complete, otherwise an ABORT Sec 8.4 (5) and (8) */ ··· 647 610 [CTA_TIMEOUT_SCTP_SHUTDOWN_SENT] = { .type = NLA_U32 }, 648 611 [CTA_TIMEOUT_SCTP_SHUTDOWN_RECD] = { .type = NLA_U32 }, 649 612 [CTA_TIMEOUT_SCTP_SHUTDOWN_ACK_SENT] = { .type = NLA_U32 }, 613 + [CTA_TIMEOUT_SCTP_HEARTBEAT_SENT] = { .type = NLA_U32 }, 614 + [CTA_TIMEOUT_SCTP_HEARTBEAT_ACKED] = { .type = NLA_U32 }, 650 615 }; 651 616 #endif /* CONFIG_NF_CT_NETLINK_TIMEOUT */ 652 617 ··· 693 654 }, 694 655 { 695 656 .procname = "nf_conntrack_sctp_timeout_shutdown_ack_sent", 657 + .maxlen = sizeof(unsigned int), 658 + .mode = 0644, 659 + .proc_handler = proc_dointvec_jiffies, 660 + }, 661 + { 662 + .procname = "nf_conntrack_sctp_timeout_heartbeat_sent", 663 + .maxlen = sizeof(unsigned int), 664 + .mode = 0644, 665 + .proc_handler = proc_dointvec_jiffies, 666 + }, 667 + { 668 + .procname = "nf_conntrack_sctp_timeout_heartbeat_acked", 696 669 .maxlen = sizeof(unsigned int), 697 670 .mode = 0644, 698 671 .proc_handler = proc_dointvec_jiffies, ··· 781 730 pn->ctl_table[4].data = &sn->timeouts[SCTP_CONNTRACK_SHUTDOWN_SENT]; 782 731 pn->ctl_table[5].data = &sn->timeouts[SCTP_CONNTRACK_SHUTDOWN_RECD]; 783 732 pn->ctl_table[6].data = &sn->timeouts[SCTP_CONNTRACK_SHUTDOWN_ACK_SENT]; 733 + pn->ctl_table[7].data = &sn->timeouts[SCTP_CONNTRACK_HEARTBEAT_SENT]; 734 + pn->ctl_table[8].data = &sn->timeouts[SCTP_CONNTRACK_HEARTBEAT_ACKED]; 784 735 #endif 785 736 return 0; 786 737 }