Index: configure.ac ================================================================== --- configure.ac +++ configure.ac @@ -1327,10 +1327,16 @@ ]) AC_CHECK_HEADER(netinet/tcp.h, [ AC_DEFINE(OF_HAVE_NETINET_TCP_H, 1, [Whether we have netinet/tcp.h]) ]) + AC_CHECK_HEADER(netinet/sctp.h, [ + AC_DEFINE(OF_HAVE_SCTP, 1, [Whether we have SCTP]) + AC_DEFINE(OF_HAVE_NETINET_SCTP_H, 1, + [Whether we have netinet/sctp.h]) + AC_SUBST(OF_SCTP_SOCKET_M, OFSCTPSocket.m) + ]) AC_CHECK_HEADERS([arpa/inet.h netdb.h]) AC_CHECK_HEADER(netipx/ipx.h, [ AC_DEFINE(OF_HAVE_NETIPX_IPX_H, 1, [Whether we have netipx/ipx.h]) ]) Index: extra.mk.in ================================================================== --- extra.mk.in +++ extra.mk.in @@ -62,10 +62,11 @@ OF_EPOLL_KERNEL_EVENT_OBSERVER_M = @OF_EPOLL_KERNEL_EVENT_OBSERVER_M@ OF_HTTP_CLIENT_TESTS_M = @OF_HTTP_CLIENT_TESTS_M@ OF_KQUEUE_KERNEL_EVENT_OBSERVER_M = @OF_KQUEUE_KERNEL_EVENT_OBSERVER_M@ OF_POLL_KERNEL_EVENT_OBSERVER_M = @OF_POLL_KERNEL_EVENT_OBSERVER_M@ OF_PROCESS_M = @OF_PROCESS_M@ +OF_SCTP_SOCKET_M = @OF_SCTP_SOCKET_M@ OF_SELECT_KERNEL_EVENT_OBSERVER_M = @OF_SELECT_KERNEL_EVENT_OBSERVER_M@ REEXPORT_RUNTIME = @REEXPORT_RUNTIME@ REEXPORT_RUNTIME_FRAMEWORK = @REEXPORT_RUNTIME_FRAMEWORK@ RUNTIME = @RUNTIME@ RUNTIME_FRAMEWORK_LIBS = @RUNTIME_FRAMEWORK_LIBS@ Index: src/Makefile ================================================================== --- src/Makefile +++ src/Makefile @@ -147,10 +147,11 @@ OFHTTPCookie.m \ OFHTTPCookieManager.m \ OFHTTPRequest.m \ OFHTTPResponse.m \ OFHTTPServer.m \ + ${OF_SCTP_SOCKET_M} \ OFSequencedPacketSocket.m \ OFStreamSocket.m \ OFTCPSocket.m \ OFUDPSocket.m \ socket.m Index: src/OFIPSocketAsyncConnector.h ================================================================== --- src/OFIPSocketAsyncConnector.h +++ src/OFIPSocketAsyncConnector.h @@ -40,11 +40,11 @@ id _Nullable _exception; OFData *_Nullable _socketAddresses; size_t _socketAddressesIndex; } -- (instancetype)initWithSocket: (OFTCPSocket *)sock +- (instancetype)initWithSocket: (id)sock host: (OFString *)host port: (uint16_t)port delegate: (nullable id)delegate block: (nullable id)block; - (void)didConnect; Index: src/OFIPSocketAsyncConnector.m ================================================================== --- src/OFIPSocketAsyncConnector.m +++ src/OFIPSocketAsyncConnector.m @@ -19,10 +19,13 @@ #include #import "OFIPSocketAsyncConnector.h" #import "OFData.h" +#ifdef OF_HAVE_SCTP +# import "OFSCTPSocket.h" +#endif #import "OFTCPSocket.h" #import "OFThread.h" #import "OFTimer.h" #import "OFConnectionFailedException.h" @@ -71,10 +74,15 @@ #ifdef OF_HAVE_BLOCKS if (_block != NULL) { if ([_socket isKindOfClass: [OFTCPSocket class]]) ((of_tcp_socket_async_connect_block_t)_block)( _exception); +# ifdef OF_HAVE_SCTP + else if ([_socket isKindOfClass: [OFSCTPSocket class]]) + ((of_sctp_socket_async_connect_block_t)_block)( + _exception); +# endif else OF_ENSURE(0); } else { #endif if ([_delegate respondsToSelector: ADDED src/OFSCTPSocket.h Index: src/OFSCTPSocket.h ================================================================== --- src/OFSCTPSocket.h +++ src/OFSCTPSocket.h @@ -0,0 +1,158 @@ +/* + * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, + * 2018, 2019, 2020 + * Jonathan Schleifer + * + * All rights reserved. + * + * This file is part of ObjFW. It may be distributed under the terms of the + * Q Public License 1.0, which can be found in the file LICENSE.QPL included in + * the packaging of this file. + * + * Alternatively, it may be distributed under the terms of the GNU General + * Public License, either version 2 or 3, which can be found in the file + * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this + * file. + */ + +#import "OFSequencedPacketSocket.h" +#import "OFRunLoop.h" + +#import "socket.h" + +OF_ASSUME_NONNULL_BEGIN + +/*! @file */ + +@class OFSCTPSocket; +@class OFString; + +#ifdef OF_HAVE_BLOCKS +/*! + * @brief A block which is called when the socket connected. + * + * @param exception An exception which occurred while connecting the socket or + * `nil` on success + */ +typedef void (^of_sctp_socket_async_connect_block_t)(id _Nullable exception); +#endif + +/*! + * @protocol OFSCTPSocketDelegate OFSCTPSocket.h ObjFW/OFSCTPSocket.h + * + * A delegate for OFSCTPSocket. + */ +@protocol OFSCTPSocketDelegate +@optional +/*! + * @brief A method which is called when a socket connected. + * + * @param socket The socket which connected + * @param host The host connected to + * @param port The port on the host connected to + * @param exception An exception that occurred while connecting, or nil on + * success + */ +- (void)socket: (OFSCTPSocket *)socket + didConnectToHost: (OFString *)host + port: (uint16_t)port + exception: (nullable id)exception; +@end + +/*! + * @class OFSCTPSocket OFSCTPSocket.h ObjFW/OFSCTPSocket.h + * + * @brief A class which provides methods to create and use SCTP sockets in + * one-to-one mode. + * + * To connect to a server, create a socket and connect it. + * To create a server, create a socket, bind it and listen on it. + */ +@interface OFSCTPSocket: OFSequencedPacketSocket +{ + OF_RESERVE_IVARS(4) +} + +/*! + * @brief Whether SCTP_NODELAY is enabled for the connection + */ +@property (nonatomic, getter=isNoDelayEnabled) bool noDelayEnabled; + +/*! + * @brief The delegate for asynchronous operations on the socket. + * + * @note The delegate is retained for as long as asynchronous operations are + * still ongoing. + */ +@property OF_NULLABLE_PROPERTY (assign, nonatomic) + id delegate; + +/*! + * @brief Connect the OFSCTPSocket to the specified destination. + * + * @param host The host to connect to + * @param port The port on the host to connect to + */ +- (void)connectToHost: (OFString *)host + port: (uint16_t)port; + +/*! + * @brief Asynchronously connect the OFSCTPSocket to the specified destination. + * + * @param host The host to connect to + * @param port The port on the host to connect to + */ +- (void)asyncConnectToHost: (OFString *)host + port: (uint16_t)port; + +/*! + * @brief Asynchronously connect the OFSCTPSocket to the specified destination. + * + * @param host The host to connect to + * @param port The port on the host to connect to + * @param runLoopMode The run loop mode in which to perform the async connect + */ +- (void)asyncConnectToHost: (OFString *)host + port: (uint16_t)port + runLoopMode: (of_run_loop_mode_t)runLoopMode; + +#ifdef OF_HAVE_BLOCKS +/*! + * @brief Asynchronously connect the OFSCTPSocket to the specified destination. + * + * @param host The host to connect to + * @param port The port on the host to connect to + * @param block The block to execute once the connection has been established + */ +- (void)asyncConnectToHost: (OFString *)host + port: (uint16_t)port + block: (of_sctp_socket_async_connect_block_t)block; + +/*! + * @brief Asynchronously connect the OFSCTPSocket to the specified destination. + * + * @param host The host to connect to + * @param port The port on the host to connect to + * @param runLoopMode The run loop mode in which to perform the async connect + * @param block The block to execute once the connection has been established + */ +- (void)asyncConnectToHost: (OFString *)host + port: (uint16_t)port + runLoopMode: (of_run_loop_mode_t)runLoopMode + block: (of_sctp_socket_async_connect_block_t)block; +#endif + +/*! + * @brief Bind the socket to the specified host and port. + * + * @param host The host to bind to. Use `@"0.0.0.0"` for IPv4 or `@"::"` for + * IPv6 to bind to all. + * @param port The port to bind to. If the port is 0, an unused port will be + * chosen, which can be obtained using the return value. + * @return The port the socket was bound to + */ +- (uint16_t)bindToHost: (OFString *)host + port: (uint16_t)port; +@end + +OF_ASSUME_NONNULL_END ADDED src/OFSCTPSocket.m Index: src/OFSCTPSocket.m ================================================================== --- src/OFSCTPSocket.m +++ src/OFSCTPSocket.m @@ -0,0 +1,332 @@ +/* + * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, + * 2018, 2019, 2020 + * Jonathan Schleifer + * + * All rights reserved. + * + * This file is part of ObjFW. It may be distributed under the terms of the + * Q Public License 1.0, which can be found in the file LICENSE.QPL included in + * the packaging of this file. + * + * Alternatively, it may be distributed under the terms of the GNU General + * Public License, either version 2 or 3, which can be found in the file + * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this + * file. + */ + +#include "config.h" + +#include +#include +#include +#include + +#ifdef HAVE_FCNTL_H +# include +#endif + +#import "OFSCTPSocket.h" +#import "OFDNSResolver.h" +#import "OFData.h" +#import "OFDate.h" +#import "OFIPSocketAsyncConnector.h" +#import "OFRunLoop.h" +#import "OFRunLoop+Private.h" +#import "OFString.h" +#import "OFThread.h" + +#import "OFAlreadyConnectedException.h" +#import "OFBindFailedException.h" +#import "OFGetOptionFailedException.h" +#import "OFNotOpenException.h" +#import "OFSetOptionFailedException.h" + +#import "socket.h" +#import "socket_helpers.h" + +static const of_run_loop_mode_t connectRunLoopMode = + @"of_sctp_socket_connect_mode"; + +@interface OFSCTPSocket () +@end + +@interface OFSCTPSocketConnectDelegate: OFObject +{ +@public + bool _done; + id _exception; +} +@end + +@implementation OFSCTPSocketConnectDelegate +- (void)dealloc +{ + [_exception release]; + + [super dealloc]; +} + +- (void)socket: (OFSCTPSocket *)sock + didConnectToHost: (OFString *)host + port: (uint16_t)port + exception: (id)exception +{ + _done = true; + _exception = [exception retain]; +} +@end + +@implementation OFSCTPSocket +@dynamic delegate; + +- (bool)of_createSocketForAddress: (const of_socket_address_t *)address + errNo: (int *)errNo +{ +#if SOCK_CLOEXEC == 0 && defined(HAVE_FCNTL) && defined(FD_CLOEXEC) + int flags; +#endif + + if (_socket != INVALID_SOCKET) + @throw [OFAlreadyConnectedException exceptionWithSocket: self]; + + if ((_socket = socket(address->sockaddr.sockaddr.sa_family, + SOCK_STREAM | SOCK_CLOEXEC, IPPROTO_SCTP)) == INVALID_SOCKET) { + *errNo = of_socket_errno(); + 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 of_socket_address_t *)address + errNo: (int *)errNo +{ + if (_socket == INVALID_SOCKET) + @throw [OFNotOpenException exceptionWithObject: self]; + + if (connect(_socket, &address->sockaddr.sockaddr, + address->length) != 0) { + *errNo = of_socket_errno(); + return false; + } + + return true; +} + +- (void)of_closeSocket +{ + closesocket(_socket); + _socket = INVALID_SOCKET; +} + +- (void)connectToHost: (OFString *)host + port: (uint16_t)port +{ + void *pool = objc_autoreleasePoolPush(); + id delegate = _delegate; + OFSCTPSocketConnectDelegate *connectDelegate = + [[[OFSCTPSocketConnectDelegate alloc] init] autorelease]; + OFRunLoop *runLoop = [OFRunLoop currentRunLoop]; + + self.delegate = connectDelegate; + [self asyncConnectToHost: host + port: port + runLoopMode: connectRunLoopMode]; + + while (!connectDelegate->_done) + [runLoop runMode: connectRunLoopMode + beforeDate: nil]; + + /* Cleanup */ + [runLoop runMode: connectRunLoopMode + beforeDate: [OFDate date]]; + + if (connectDelegate->_exception != nil) + @throw connectDelegate->_exception; + + self.delegate = delegate; + + objc_autoreleasePoolPop(pool); +} + +- (void)asyncConnectToHost: (OFString *)host + port: (uint16_t)port +{ + [self asyncConnectToHost: host + port: port + runLoopMode: of_run_loop_mode_default]; +} + +- (void)asyncConnectToHost: (OFString *)host + port: (uint16_t)port + runLoopMode: (of_run_loop_mode_t)runLoopMode +{ + void *pool = objc_autoreleasePoolPush(); + + if (_socket != INVALID_SOCKET) + @throw [OFAlreadyConnectedException exceptionWithSocket: self]; + + [[[[OFIPSocketAsyncConnector alloc] + initWithSocket: self + host: host + port: port + delegate: _delegate + block: NULL + ] autorelease] startWithRunLoopMode: runLoopMode]; + + objc_autoreleasePoolPop(pool); +} + +#ifdef OF_HAVE_BLOCKS +- (void)asyncConnectToHost: (OFString *)host + port: (uint16_t)port + block: (of_sctp_socket_async_connect_block_t)block +{ + [self asyncConnectToHost: host + port: port + runLoopMode: of_run_loop_mode_default + block: block]; +} + +- (void)asyncConnectToHost: (OFString *)host + port: (uint16_t)port + runLoopMode: (of_run_loop_mode_t)runLoopMode + block: (of_sctp_socket_async_connect_block_t)block +{ + void *pool = objc_autoreleasePoolPush(); + + if (_socket != INVALID_SOCKET) + @throw [OFAlreadyConnectedException exceptionWithSocket: self]; + + [[[[OFIPSocketAsyncConnector alloc] + initWithSocket: self + host: host + port: port + delegate: nil + block: block] autorelease] + startWithRunLoopMode: runLoopMode]; + + objc_autoreleasePoolPop(pool); +} +#endif + +- (uint16_t)bindToHost: (OFString *)host + port: (uint16_t)port +{ + const int one = 1; + void *pool = objc_autoreleasePoolPush(); + OFData *socketAddresses; + of_socket_address_t address; +#if SOCK_CLOEXEC == 0 && defined(HAVE_FCNTL) && defined(FD_CLOEXEC) + int flags; +#endif + + if (_socket != INVALID_SOCKET) + @throw [OFAlreadyConnectedException exceptionWithSocket: self]; + + socketAddresses = [[OFThread DNSResolver] + resolveAddressesForHost: host + addressFamily: OF_SOCKET_ADDRESS_FAMILY_ANY]; + + address = *(of_socket_address_t *)[socketAddresses itemAtIndex: 0]; + of_socket_address_set_port(&address, port); + + if ((_socket = socket(address.sockaddr.sockaddr.sa_family, + SOCK_STREAM | SOCK_CLOEXEC, IPPROTO_SCTP)) == INVALID_SOCKET) + @throw [OFBindFailedException + exceptionWithHost: host + port: port + socket: self + errNo: of_socket_errno()]; + + _blocking = 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 (bind(_socket, &address.sockaddr.sockaddr, address.length) != 0) { + int errNo = of_socket_errno(); + + closesocket(_socket); + _socket = INVALID_SOCKET; + + @throw [OFBindFailedException exceptionWithHost: host + port: port + socket: self + errNo: errNo]; + } + + objc_autoreleasePoolPop(pool); + + if (port > 0) + return port; + + memset(&address, 0, sizeof(address)); + + address.length = (socklen_t)sizeof(address.sockaddr); + if (of_getsockname(_socket, &address.sockaddr.sockaddr, + &address.length) != 0) { + int errNo = of_socket_errno(); + + closesocket(_socket); + _socket = INVALID_SOCKET; + + @throw [OFBindFailedException exceptionWithHost: host + port: port + socket: self + errNo: errNo]; + } + + if (address.sockaddr.sockaddr.sa_family == AF_INET) + return OF_BSWAP16_IF_LE(address.sockaddr.in.sin_port); +# ifdef OF_HAVE_IPV6 + else if (address.sockaddr.sockaddr.sa_family == AF_INET6) + return OF_BSWAP16_IF_LE(address.sockaddr.in6.sin6_port); +# endif + else { + closesocket(_socket); + _socket = INVALID_SOCKET; + + @throw [OFBindFailedException exceptionWithHost: host + port: port + socket: self + errNo: EAFNOSUPPORT]; + } +} + +- (void)setNoDelayEnabled: (bool)enabled +{ + int v = enabled; + + if (setsockopt(_socket, IPPROTO_SCTP, SCTP_NODELAY, + (char *)&v, (socklen_t)sizeof(v)) != 0) + @throw [OFSetOptionFailedException + exceptionWithObject: self + errNo: of_socket_errno()]; +} + +- (bool)isNoDelayEnabled +{ + int v; + socklen_t len = sizeof(v); + + if (getsockopt(_socket, IPPROTO_SCTP, SCTP_NODELAY, + (char *)&v, &len) != 0 || len != sizeof(v)) + @throw [OFGetOptionFailedException + exceptionWithObject: self + errNo: of_socket_errno()]; + + return v; +} +@end Index: src/OFSPXSocket.h ================================================================== --- src/OFSPXSocket.h +++ src/OFSPXSocket.h @@ -155,16 +155,15 @@ runLoopMode: (of_run_loop_mode_t)runLoopMode block: (of_spx_socket_async_connect_block_t)block; #endif /*! - * @brief Bind the socket to the specified network, node and port with the - * specified packet type. + * @brief Bind the socket to the specified network, node and port. * * @param port The port (sometimes called socket number) to bind to. 0 means to * pick one and return it. * @return The address on which this socket can be reached */ - (of_socket_address_t)bindToPort: (uint16_t)port; @end OF_ASSUME_NONNULL_END Index: src/OFSPXStreamSocket.h ================================================================== --- src/OFSPXStreamSocket.h +++ src/OFSPXStreamSocket.h @@ -161,16 +161,15 @@ runLoopMode: (of_run_loop_mode_t)runLoopMode block: (of_spx_stream_socket_async_connect_block_t)block; #endif /*! - * @brief Bind the socket to the specified network, node and port with the - * specified packet type. + * @brief Bind the socket to the specified network, node and port. * * @param port The port (sometimes called socket number) to bind to. 0 means to * pick one and return it. * @return The address on which this socket can be reached */ - (of_socket_address_t)bindToPort: (uint16_t)port; @end OF_ASSUME_NONNULL_END Index: src/OFSequencedPacketSocket.h ================================================================== --- src/OFSequencedPacketSocket.h +++ src/OFSequencedPacketSocket.h @@ -130,11 +130,11 @@ OFReadyForReadingObserving, OFReadyForWritingObserving> { of_socket_t _socket; bool _blocking, _listening; of_socket_address_t _remoteAddress; - id _Nullable _delegate; + id _Nullable _delegate; OF_RESERVE_IVARS(4) } /*! * @brief Whether the socket is in blocking mode. Index: src/OFTCPSocket.m ================================================================== --- src/OFTCPSocket.m +++ src/OFTCPSocket.m @@ -115,11 +115,10 @@ - (instancetype)init { self = [super init]; @try { - _socket = INVALID_SOCKET; _SOCKS5Host = [defaultSOCKS5Host copy]; _SOCKS5Port = defaultSOCKS5Port; } @catch (id e) { [self release]; @throw e; @@ -183,11 +182,11 @@ - (void)connectToHost: (OFString *)host port: (uint16_t)port { void *pool = objc_autoreleasePoolPush(); - id delegate = [_delegate retain]; + id delegate = _delegate; OFTCPSocketConnectDelegate *connectDelegate = [[[OFTCPSocketConnectDelegate alloc] init] autorelease]; OFRunLoop *runLoop = [OFRunLoop currentRunLoop]; self.delegate = connectDelegate; Index: src/ObjFW.h ================================================================== --- src/ObjFW.h +++ src/ObjFW.h @@ -81,10 +81,13 @@ # ifdef OF_HAVE_IPX # import "OFIPXSocket.h" # import "OFSPXSocket.h" # import "OFSPXStreamSocket.h" # endif +# ifdef OF_HAVE_SCTP +# import "OFSCTPSocket.h" +# endif #endif #ifdef OF_HAVE_SOCKETS # ifdef OF_HAVE_THREADS # import "OFHTTPClient.h" # endif Index: src/objfw-defs.h.in ================================================================== --- src/objfw-defs.h.in +++ src/objfw-defs.h.in @@ -18,10 +18,12 @@ #undef OF_HAVE_IPX #undef OF_HAVE_LIMITS_H #undef OF_HAVE_LINK #undef OF_HAVE_MAX_ALIGN_T #undef OF_HAVE_NETINET_IN_H +#undef OF_HAVE_NETINET_SCTP_H +#undef OF_HAVE_NETINET_TCP_H #undef OF_HAVE_NETIPX_IPX_H #undef OF_HAVE_OSATOMIC #undef OF_HAVE_OSATOMIC_64 #undef OF_HAVE_PIPE #undef OF_HAVE_PLEDGE Index: src/socket.h ================================================================== --- src/socket.h +++ src/socket.h @@ -31,10 +31,13 @@ #ifdef OF_HAVE_NETINET_IN_H # include #endif #ifdef OF_HAVE_NETINET_TCP_H # include +#endif +#ifdef OF_HAVE_NETINET_SCTP_H +# include #endif #ifdef OF_HAVE_NETIPX_IPX_H # include #endif