Artifact 31cc0fb26019bdee366f5ce762896bdf673aef2dac00c8fb55e4844889b15b36:
- File
src/OFTCPSocket.m
— part of check-in
[1abe178915]
at
2024-10-30 21:11:27
on branch mptcp
— Always use AF_INET6 for MPTCP on Linux
This allows using IPv4 and IPv6 for the same connection. (user: js, size: 15414) [annotate] [blame] [check-ins using]
/* * Copyright (c) 2008-2024 Jonathan Schleifer <js@nil.im> * * All rights reserved. * * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License version 3.0 only, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License * version 3.0 for more details. * * You should have received a copy of the GNU Lesser General Public License * version 3.0 along with this program. If not, see * <https://www.gnu.org/licenses/>. */ #include "config.h" #ifndef _XOPEN_SOURCE_EXTENDED # define _XOPEN_SOURCE_EXTENDED #endif #define _HPUX_ALT_XOPEN_SOCKET_API #include <errno.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #ifdef HAVE_FCNTL_H # include <fcntl.h> #endif #import "OFTCPSocket.h" #import "OFAsyncIPSocketConnector.h" #import "OFDNSResolver.h" #import "OFData.h" #import "OFDate.h" #import "OFRunLoop.h" #import "OFRunLoop+Private.h" #import "OFSocket.h" #import "OFSocket+Private.h" #import "OFString.h" #import "OFTCPSocketSOCKS5Connector.h" #import "OFThread.h" #import "OFAlreadyOpenException.h" #import "OFBindIPSocketFailedException.h" #import "OFGetOptionFailedException.h" #import "OFNotImplementedException.h" #import "OFNotOpenException.h" #import "OFSetOptionFailedException.h" #ifdef OF_LINUX # include <linux/mptcp.h> #endif #if defined(OF_MACOS) || defined(OF_IOS) # ifndef AF_MULTIPATH # define AF_MULTIPATH 39 # endif #endif enum { flagUseMPTCP = 1, flagMapIPv4 = 2, flagUseConnectX = 4 }; static const OFRunLoopMode connectRunLoopMode = @"OFTCPSocketConnectRunLoopMode"; static OFString *defaultSOCKS5Host = nil; static uint16_t defaultSOCKS5Port = 1080; #if defined(OF_LINUX) && defined(IPPROTO_MPTCP) static OFSocketAddress mapIPv4(const OFSocketAddress *IPv4Address) { OFSocketAddress IPv6Address = { .family = OFSocketAddressFamilyIPv6, .length = sizeof(struct sockaddr_in6) }; IPv6Address.sockaddr.in6.sin6_family = AF_INET6; IPv6Address.sockaddr.in6.sin6_port = IPv4Address->sockaddr.in.sin_port; memcpy(&IPv6Address.sockaddr.in6.sin6_addr.s6_addr[12], &IPv4Address->sockaddr.in.sin_addr.s_addr, 4); IPv6Address.sockaddr.in6.sin6_addr.s6_addr[10] = 0xFF; IPv6Address.sockaddr.in6.sin6_addr.s6_addr[11] = 0xFF; return IPv6Address; } #endif @interface OFTCPSocket () <OFAsyncIPSocketConnecting> @end @interface OFTCPSocketConnectDelegate: OFObject <OFTCPSocketDelegate> { @public bool _done; id _exception; } @end @implementation OFTCPSocketConnectDelegate - (void)dealloc { [_exception release]; [super dealloc]; } - (void)socket: (OFTCPSocket *)sock didConnectToHost: (OFString *)host port: (uint16_t)port exception: (id)exception { _done = true; _exception = [exception retain]; } @end @implementation OFTCPSocket @synthesize SOCKS5Host = _SOCKS5Host, SOCKS5Port = _SOCKS5Port; @dynamic delegate; + (void)setSOCKS5Host: (OFString *)host { id old = defaultSOCKS5Host; defaultSOCKS5Host = [host copy]; [old release]; } + (OFString *)SOCKS5Host { return defaultSOCKS5Host; } + (void)setSOCKS5Port: (uint16_t)port { defaultSOCKS5Port = port; } + (uint16_t)SOCKS5Port { return defaultSOCKS5Port; } - (instancetype)init { self = [super init]; @try { _SOCKS5Host = [defaultSOCKS5Host copy]; _SOCKS5Port = defaultSOCKS5Port; } @catch (id e) { [self release]; @throw e; } return self; } - (void)dealloc { [_SOCKS5Host release]; [super dealloc]; } - (bool)of_createSocketForAddress: (const OFSocketAddress *)address errNo: (int *)errNo { #if SOCK_CLOEXEC == 0 && defined(HAVE_FCNTL) && defined(FD_CLOEXEC) int flags; #endif if (_socket != OFInvalidSocketHandle) @throw [OFAlreadyOpenException exceptionWithObject: self]; #if defined(OF_LINUX) && defined(IPPROTO_MPTCP) if (_flags & flagUseMPTCP) { /* * For MPTCP sockets, we always use AF_INET6, so that IPv4 and * IPv6 can both be used for a single connection. */ _socket = socket(AF_INET6, SOCK_STREAM | SOCK_CLOEXEC, IPPROTO_MPTCP); if (_socket != OFInvalidSocketHandle && address->family == OFSocketAddressFamilyIPv4) _flags |= flagMapIPv4; else _flags &= ~flagMapIPv4; } #elif defined(OF_MACOS) || defined(OF_IOS) if (_flags & flagUseMPTCP) { _socket = socket(AF_MULTIPATH, SOCK_STREAM | SOCK_CLOEXEC, IPPROTO_TCP); if (_socket != OFInvalidSocketHandle) _flags |= flagUseConnectX; else _flags &= ~flagUseConnectX; } #endif if (_socket == OFInvalidSocketHandle) { if ((_socket = socket( ((struct sockaddr *)&address->sockaddr)->sa_family, SOCK_STREAM | SOCK_CLOEXEC, 0)) == OFInvalidSocketHandle) { *errNo = _OFSocketErrNo(); return false; } } #if SOCK_CLOEXEC == 0 && defined(HAVE_FCNTL) && defined(FD_CLOEXEC) if ((flags = fcntl(_socket, F_GETFD, 0)) != -1) fcntl(_socket, F_SETFD, flags | FD_CLOEXEC); #endif return true; } - (bool)of_connectSocketToAddress: (const OFSocketAddress *)address errNo: (int *)errNo { #if defined(OF_LINUX) && defined(IPPROTO_MPTCP) OFSocketAddress mappedIPv4; #endif if (_socket == OFInvalidSocketHandle) { @throw [OFNotOpenException exceptionWithObject: self]; } #if defined(OF_LINUX) && defined(IPPROTO_MPTCP) if (_flags & flagMapIPv4) { /* * For MPTCP sockets, we always use AF_INET6, so that IPv4 and * IPv6 can both be used for a single connection. */ mappedIPv4 = mapIPv4(address); address = &mappedIPv4; } #endif #if defined(OF_MACOS) || defined(OF_IOS) if (_flags & flagUseConnectX) { sa_endpoints_t endpoints = { .sae_dstaddr = (struct sockaddr *)&address->sockaddr, .sae_dstaddrlen = address->length }; if (connectx(_socket, &endpoints, SAE_ASSOCID_ANY, 0, NULL, 0, NULL, NULL) != 0) { *errNo = _OFSocketErrNo(); return false; } } else #endif /* * Cast needed for AmigaOS, where the argument is declared * non-const. */ if (connect(_socket, (struct sockaddr *)&address->sockaddr, address->length) != 0) { *errNo = _OFSocketErrNo(); return false; } return true; } - (void)of_closeSocket { closesocket(_socket); _socket = OFInvalidSocketHandle; } - (void)connectToHost: (OFString *)host port: (uint16_t)port { void *pool = objc_autoreleasePoolPush(); id <OFTCPSocketDelegate> delegate = _delegate; OFTCPSocketConnectDelegate *connectDelegate = [[[OFTCPSocketConnectDelegate alloc] init] autorelease]; OFRunLoop *runLoop = [OFRunLoop currentRunLoop]; _delegate = connectDelegate; [self asyncConnectToHost: host port: port runLoopMode: connectRunLoopMode]; while (!connectDelegate->_done) [runLoop runMode: connectRunLoopMode beforeDate: nil]; /* Cleanup */ [runLoop runMode: connectRunLoopMode beforeDate: [OFDate date]]; _delegate = delegate; if (connectDelegate->_exception != nil) @throw connectDelegate->_exception; objc_autoreleasePoolPop(pool); } - (void)asyncConnectToHost: (OFString *)host port: (uint16_t)port { [self asyncConnectToHost: host port: port runLoopMode: OFDefaultRunLoopMode]; } - (void)asyncConnectToHost: (OFString *)host port: (uint16_t)port runLoopMode: (OFRunLoopMode)runLoopMode { void *pool = objc_autoreleasePoolPush(); id <OFTCPSocketDelegate> delegate; if (_SOCKS5Host != nil) { delegate = [[[OFTCPSocketSOCKS5Connector alloc] initWithSocket: self host: host port: port delegate: _delegate #ifdef OF_HAVE_BLOCKS handler: NULL #endif ] autorelease]; host = _SOCKS5Host; port = _SOCKS5Port; } else delegate = _delegate; [[[[OFAsyncIPSocketConnector alloc] initWithSocket: self host: host port: port delegate: delegate handler: NULL ] autorelease] startWithRunLoopMode: runLoopMode]; objc_autoreleasePoolPop(pool); } #ifdef OF_HAVE_BLOCKS - (void)asyncConnectToHost: (OFString *)host port: (uint16_t)port block: (OFTCPSocketAsyncConnectBlock)block { OFTCPSocketConnectedHandler handler = ^ (OFTCPSocket *socket, OFString *host_, uint16_t port_, id exception) { block(exception); }; [self asyncConnectToHost: host port: port runLoopMode: OFDefaultRunLoopMode handler: handler]; } - (void)asyncConnectToHost: (OFString *)host port: (uint16_t)port handler: (OFTCPSocketConnectedHandler)handler { [self asyncConnectToHost: host port: port runLoopMode: OFDefaultRunLoopMode handler: handler]; } - (void)asyncConnectToHost: (OFString *)host port: (uint16_t)port runLoopMode: (OFRunLoopMode)runLoopMode block: (OFTCPSocketAsyncConnectBlock)block { OFTCPSocketConnectedHandler handler = ^ (OFTCPSocket *socket, OFString *host_, uint16_t port_, id exception) { block(exception); }; [self asyncConnectToHost: host port: port runLoopMode: runLoopMode handler: handler]; } - (void)asyncConnectToHost: (OFString *)host port: (uint16_t)port runLoopMode: (OFRunLoopMode)runLoopMode handler: (OFTCPSocketConnectedHandler)handler { void *pool = objc_autoreleasePoolPush(); id <OFTCPSocketDelegate> delegate = nil; if (_SOCKS5Host != nil) { delegate = [[[OFTCPSocketSOCKS5Connector alloc] initWithSocket: self host: host port: port delegate: nil handler: handler] autorelease]; host = _SOCKS5Host; port = _SOCKS5Port; } [[[[OFAsyncIPSocketConnector alloc] initWithSocket: self host: host port: port delegate: delegate handler: (delegate == nil ? handler : NULL)] autorelease] startWithRunLoopMode: runLoopMode]; objc_autoreleasePoolPop(pool); } #endif - (OFSocketAddress)bindToHost: (OFString *)host port: (uint16_t)port { const int one = 1; void *pool = objc_autoreleasePoolPush(); OFData *socketAddresses; OFSocketAddress address; #if SOCK_CLOEXEC == 0 && defined(HAVE_FCNTL) && defined(FD_CLOEXEC) int flags; #endif if (_socket != OFInvalidSocketHandle) @throw [OFAlreadyOpenException exceptionWithObject: self]; if (_SOCKS5Host != nil) @throw [OFNotImplementedException exceptionWithSelector: _cmd object: self]; socketAddresses = [[OFThread DNSResolver] resolveAddressesForHost: host addressFamily: OFSocketAddressFamilyAny]; address = *(OFSocketAddress *)[socketAddresses itemAtIndex: 0]; OFSocketAddressSetIPPort(&address, port); #if defined(OF_LINUX) && defined(IPPROTO_MPTCP) if (_flags & flagUseMPTCP) { /* * For MPTCP sockets, we always use AF_INET6, so that IPv4 and * IPv6 can both be used for a single connection. */ _socket = socket(AF_INET6, SOCK_STREAM | SOCK_CLOEXEC, IPPROTO_MPTCP); if (_socket != OFInvalidSocketHandle && address.family == OFSocketAddressFamilyIPv4) address = mapIPv4(&address); } #endif if (_socket == OFInvalidSocketHandle) if ((_socket = socket( ((struct sockaddr *)&address.sockaddr)->sa_family, SOCK_STREAM | SOCK_CLOEXEC, 0)) == OFInvalidSocketHandle) @throw [OFBindIPSocketFailedException exceptionWithHost: host port: port socket: self errNo: _OFSocketErrNo()]; _canBlock = true; #if SOCK_CLOEXEC == 0 && defined(HAVE_FCNTL) && defined(FD_CLOEXEC) if ((flags = fcntl(_socket, F_GETFD, 0)) != -1) fcntl(_socket, F_SETFD, flags | FD_CLOEXEC); #endif setsockopt(_socket, SOL_SOCKET, SO_REUSEADDR, (char *)&one, (socklen_t)sizeof(one)); #if defined(OF_HPUX) || defined(OF_WII) || defined(OF_NINTENDO_3DS) if (port != 0) { #endif if (bind(_socket, (struct sockaddr *)&address.sockaddr, address.length) != 0) { int errNo = _OFSocketErrNo(); closesocket(_socket); _socket = OFInvalidSocketHandle; @throw [OFBindIPSocketFailedException exceptionWithHost: host port: port socket: self errNo: errNo]; } #if defined(OF_HPUX) || defined(OF_WII) || defined(OF_NINTENDO_3DS) } else { for (;;) { uint16_t rnd = 0; int ret; while (rnd < 1024) rnd = (uint16_t)rand(); OFSocketAddressSetIPPort(&address, rnd); if ((ret = bind(_socket, (struct sockaddr *)&address.sockaddr, address.length)) == 0) break; if (_OFSocketErrNo() != EADDRINUSE) { int errNo = _OFSocketErrNo(); closesocket(_socket); _socket = OFInvalidSocketHandle; @throw [OFBindIPSocketFailedException exceptionWithHost: host port: port socket: self errNo: errNo]; } } } #endif #if !defined(OF_HPUX) && !defined(OF_WII) && !defined(OF_NINTENDO_3DS) memset(&address, 0, sizeof(address)); address.length = (socklen_t)sizeof(address.sockaddr); if (_OFGetSockName(_socket, (struct sockaddr *)&address.sockaddr, &address.length) != 0) { int errNo = _OFSocketErrNo(); closesocket(_socket); _socket = OFInvalidSocketHandle; @throw [OFBindIPSocketFailedException exceptionWithHost: host port: port socket: self errNo: errNo]; } switch (((struct sockaddr *)&address.sockaddr)->sa_family) { case AF_INET: address.family = OFSocketAddressFamilyIPv4; break; # ifdef OF_HAVE_IPV6 case AF_INET6: address.family = OFSocketAddressFamilyIPv6; break; # endif default: closesocket(_socket); _socket = OFInvalidSocketHandle; @throw [OFBindIPSocketFailedException exceptionWithHost: host port: port socket: self errNo: EAFNOSUPPORT]; } #endif objc_autoreleasePoolPop(pool); return address; } #if !defined(OF_WII) && !defined(OF_NINTENDO_3DS) - (void)setSendsKeepAlives: (bool)sendsKeepAlives { int v = sendsKeepAlives; if (setsockopt(_socket, SOL_SOCKET, SO_KEEPALIVE, (char *)&v, (socklen_t)sizeof(v)) != 0) @throw [OFSetOptionFailedException exceptionWithObject: self errNo: _OFSocketErrNo()]; } - (bool)sendsKeepAlives { int v; socklen_t len = (socklen_t)sizeof(v); if (getsockopt(_socket, SOL_SOCKET, SO_KEEPALIVE, (char *)&v, &len) != 0 || len != sizeof(v)) @throw [OFGetOptionFailedException exceptionWithObject: self errNo: _OFSocketErrNo()]; return v; } #endif #ifndef OF_WII - (void)setCanDelaySendingSegments: (bool)canDelaySendingSegments { int v = !canDelaySendingSegments; if (setsockopt(_socket, IPPROTO_TCP, TCP_NODELAY, (char *)&v, (socklen_t)sizeof(v)) != 0) @throw [OFSetOptionFailedException exceptionWithObject: self errNo: _OFSocketErrNo()]; } - (bool)canDelaySendingSegments { int v; socklen_t len = (socklen_t)sizeof(v); if (getsockopt(_socket, IPPROTO_TCP, TCP_NODELAY, (char *)&v, &len) != 0 || len != sizeof(v)) @throw [OFGetOptionFailedException exceptionWithObject: self errNo: _OFSocketErrNo()]; return !v; } #endif - (void)setUsesMPTCP: (bool)usesMPTCP { if (usesMPTCP) _flags |= flagUseMPTCP; else _flags &= ~flagUseMPTCP; } - (bool)usesMPTCP { #if defined(OF_LINUX) && defined(SOL_MPTCP) && defined(MPTCP_INFO) struct mptcp_info info; socklen_t infoLen = (socklen_t)sizeof(info); if (getsockopt(_socket, SOL_MPTCP, MPTCP_INFO, &info, &infoLen) != -1) return true; #endif return false; } - (void)close { #ifdef OF_WII _port = 0; #endif [super close]; } @end