SCTP INIT chunks do not include multi-homing addresses if IPv6 is used

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



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;
}

[Index of Archives]     [Linux Networking Development]     [Linux OMAP]     [Linux USB Devel]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     SCTP

  Powered by Linux