Hello.While debugging SCTP multi-homing in an application over the last couple of days, I discovered that the cause is because the application randomly prefers IPv4 or IPv6 (literally; it does the equivalent of a dice roll).
If the application passes an IPv4 address first to sctp_bindx() and sctp_connectx(), followed by an IPv6 address, then multi-homing works; the SCTP INIT chunk includes both IP addresses passed to sctp_bindx(), and /proc/net/sctp/assocs contains both. Blocking IPv4 traffic does not break the connection; it falls back to IPv6.
However, in the reverse situation (IPv6 address followed by an IPv4 address), it does not; there are no IP addresses in the INIT chunk at all. Thus, /proc/net/sctp/assocs contains only IPv6 addresses, and blocking IPv6 traffic breaks the connection.
I have written a minimal reproducer at [0] (which I am attaching to this message), and you can observe the results at [1] (if you do not wish to run it).
I have managed to reproduce it on the following software combinations: - lksctp-tools 1.0.18 with Linux kernel 5.4 (Linux Mint 20.3) - lksctp-tools 1.0.19 with Linux kernel 5.10 (Debian 11) - lksctp-tools 1.0.19 with Linux kernel 4.14.278 (Gentoo) I do not know whether this is a bug in libsctp or in the kernel. Regards, Aaron Jones [0] <https://paste.debian.net/hidden/998cad29/> [1] <https://imgur.com/a/qbGrXXV>
/* SPDX-License-Identifier: GPL-2.0 * * Reproducer for SCTP multihoming bug where an IPv6 address given first * in the list of addresses means that the SCTP INIT chunk does not * include any IP addresses and so the association does not multi-home. * * Copyright (C) 2022 Aaron M. D. Jones <me@xxxxxxxxxxxxxxxx> * * gcc -Wall -Wextra -Wpedantic -std=gnu99 -lsctp repro.c -o repro * ./repro <port> <remote> <remote> [<local> <local>] */ #include <errno.h> #include <inttypes.h> #include <stdint.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <arpa/inet.h> #include <netinet/in.h> #include <netinet/sctp.h> int main(int argc, char *argv[]) { struct sockaddr_storage ss; struct sockaddr_in *sa4 = (struct sockaddr_in *) &ss; struct sockaddr_in6 *sa6 = (struct sockaddr_in6 *) &ss; uint8_t laddrs[sizeof(struct sockaddr_storage) * 2]; uint8_t raddrs[sizeof(struct sockaddr_storage) * 2]; uint8_t *laddrp = laddrs; uint8_t *raddrp = raddrs; uint16_t port = 0; memset(laddrs, 0x00, sizeof laddrs); memset(raddrs, 0x00, sizeof raddrs); const int fd = socket(AF_INET6, SOCK_STREAM, IPPROTO_SCTP); if (fd == -1) { perror("socket"); return EXIT_FAILURE; } if (argc < 4 || argc == 5 || argc > 6) { fprintf(stderr, "Usage: ./repro <port> <remote> <remote> [<local> <local>]\n"); return EXIT_FAILURE; } if (sscanf(argv[1], "%" SCNu16, &port) != 1 || port == 0) { fprintf(stderr, "Invalid port \"%s\"\n", argv[1]); return EXIT_FAILURE; } for (int i = 2; i < 4; i++) { memset(&ss, 0x00, sizeof ss); if (inet_pton(AF_INET, argv[i], &sa4->sin_addr) == 1) { sa4->sin_family = AF_INET; sa4->sin_port = htons(port); memcpy(raddrp, sa4, sizeof *sa4); raddrp += sizeof *sa4; } else if (inet_pton(AF_INET6, argv[i], &sa6->sin6_addr) == 1) { sa6->sin6_family = AF_INET6; sa6->sin6_port = htons(port); memcpy(raddrp, sa6, sizeof *sa6); raddrp += sizeof *sa6; } else { fprintf(stderr, "Invalid IP address \"%s\"\n", argv[i]); return EXIT_FAILURE; } } for (int i = 4; i < argc; i++) { memset(&ss, 0x00, sizeof ss); if (inet_pton(AF_INET, argv[i], &sa4->sin_addr) == 1) { sa4->sin_family = AF_INET; sa4->sin_port = htons(port); memcpy(laddrp, sa4, sizeof *sa4); laddrp += sizeof *sa4; } else if (inet_pton(AF_INET6, argv[i], &sa6->sin6_addr) == 1) { sa6->sin6_family = AF_INET6; sa6->sin6_port = htons(port); memcpy(laddrp, sa6, sizeof *sa6); laddrp += sizeof *sa6; } else { fprintf(stderr, "Invalid IP address \"%s\"\n", argv[i]); return EXIT_FAILURE; } } if (argc == 6 && sctp_bindx(fd, (struct sockaddr *) laddrs, 2, SCTP_BINDX_ADD_ADDR) == -1) { perror("sctp_bindx"); return EXIT_FAILURE; } if (sctp_connectx(fd, (struct sockaddr *) raddrs, 2, NULL) == -1) { perror("sctp_connectx"); return EXIT_FAILURE; } close(fd); return EXIT_SUCCESS; }