Index: src/Makefile ================================================================== --- src/Makefile +++ src/Makefile @@ -142,11 +142,10 @@ OFHTTPCookie.m \ OFHTTPCookieManager.m \ OFHTTPRequest.m \ OFHTTPResponse.m \ OFHTTPServer.m \ - OFIPStreamSocket.m \ OFStreamSocket.m \ OFTCPSocket.m \ OFUDPSocket.m \ socket.m SRCS_THREADS = OFCondition.m \ Index: src/OFDNSResolver.h ================================================================== --- src/OFDNSResolver.h +++ src/OFDNSResolver.h @@ -30,13 +30,13 @@ @class OFDNSResolver; @class OFDNSResolverContext; @class OFDNSResolverSettings; @class OFDate; @class OFDictionary OF_GENERIC(KeyType, ObjectType); -@class OFIPStreamSocket; @class OFMutableDictionary OF_GENERIC(KeyType, ObjectType); @class OFNumber; +@class OFTCPSocket; @class OFUDPSocket; /*! * @enum of_dns_resolver_error_t OFDNSResolver.h ObjFW/OFDNSResolver.h * @@ -130,12 +130,12 @@ OFUDPSocket *_IPv6Socket; #endif char _buffer[OF_DNS_RESOLVER_BUFFER_LENGTH]; OFMutableDictionary OF_GENERIC(OFNumber *, OFDNSResolverContext *) *_queries; - OFMutableDictionary OF_GENERIC(OFIPStreamSocket *, - OFDNSResolverContext *) *_TCPQueries; + OFMutableDictionary OF_GENERIC(OFTCPSocket *, OFDNSResolverContext *) + *_TCPQueries; } /*! * @brief A dictionary of static hosts. * Index: src/OFDNSResolver.m ================================================================== --- src/OFDNSResolver.m +++ src/OFDNSResolver.m @@ -1063,11 +1063,11 @@ return [self of_handleResponseBuffer: buffer length: length sender: sender]; } -- (void)socket: (OFIPStreamSocket *)sock +- (void)socket: (OFTCPSocket *)sock didConnectToHost: (OFString *)host port: (uint16_t)port exception: (id)exception { OFDNSResolverContext *context = [_TCPQueries objectForKey: sock]; Index: src/OFHTTPClient.m ================================================================== --- src/OFHTTPClient.m +++ src/OFHTTPClient.m @@ -624,11 +624,11 @@ [self raiseException: e]; return; } } -- (void)socket: (OFIPStreamSocket *)sock +- (void)socket: (OFTCPSocket *)sock didConnectToHost: (OFString *)host port: (uint16_t)port exception: (id)exception { sock.delegate = self; @@ -639,11 +639,11 @@ } if ([_client->_delegate respondsToSelector: @selector(client:didCreateSocket:request:)]) [_client->_delegate client: _client - didCreateSocket: (OFTCPSocket *)sock + didCreateSocket: sock request: _request]; [self performSelector: @selector(handleSocket:) withObject: sock afterDelay: 0]; Index: src/OFHTTPServer.m ================================================================== --- src/OFHTTPServer.m +++ src/OFHTTPServer.m @@ -984,12 +984,12 @@ acceptedSocket.delegate = connection; [acceptedSocket asyncReadLine]; } -- (bool)socket: (OFIPStreamSocket *)sock - didAcceptSocket: (OFIPStreamSocket *)acceptedSocket +- (bool)socket: (OFTCPSocket *)sock + didAcceptSocket: (OFTCPSocket *)acceptedSocket exception: (id)exception { if (exception != nil) { if (![_delegate respondsToSelector: @selector(server:didReceiveExceptionOnListeningSocket:)]) @@ -1011,10 +1011,10 @@ onThread: thread withObject: acceptedSocket waitUntilDone: false]; } else #endif - [self of_handleAcceptedSocket: (OFTCPSocket *)acceptedSocket]; + [self of_handleAcceptedSocket: acceptedSocket]; return true; } @end DELETED src/OFIPStreamSocket+Private.h Index: src/OFIPStreamSocket+Private.h ================================================================== --- src/OFIPStreamSocket+Private.h +++ src/OFIPStreamSocket+Private.h @@ -1,34 +0,0 @@ -/* - * 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 "OFIPStreamSocket.h" - -OF_ASSUME_NONNULL_BEGIN - -@interface OFIPStreamSocket () -#ifndef OF_WII -@property (readonly, nonatomic) int of_socketError; -#endif - -- (bool)of_createSocketForAddress: (const of_socket_address_t *)address - errNo: (int *)errNo; -- (bool)of_connectSocketToAddress: (const of_socket_address_t *)address - errNo: (int *)errNo; -- (void)of_closeSocket; -@end - -OF_ASSUME_NONNULL_END DELETED src/OFIPStreamSocket.h Index: src/OFIPStreamSocket.h ================================================================== --- src/OFIPStreamSocket.h +++ src/OFIPStreamSocket.h @@ -1,257 +0,0 @@ -/* - * 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 "OFStreamSocket.h" -#import "OFRunLoop.h" - -#import "socket.h" - -OF_ASSUME_NONNULL_BEGIN - -/*! @file */ - -@class OFIPStreamSocket; -@class OFString; - -#ifdef OF_HAVE_BLOCKS -/*! - * @brief A block which is called when the socket connected. - * - * @param socket The socket which connected - * @param exception An exception which occurred while connecting the socket or - * `nil` on success - */ -typedef void (^of_ip_stream_socket_async_connect_block_t)( - OFIPStreamSocket *socket, id _Nullable exception); - -/*! - * @brief A block which is called when the socket accepted a connection. - * - * @param socket The socket which accepted the connection - * @param acceptedSocket The socket which has been accepted - * @param exception An exception which occurred while accepting the socket or - * `nil` on success - * @return A bool whether the same block should be used for the next incoming - * connection - */ -typedef bool (^of_ip_stream_socket_async_accept_block_t)( - OFIPStreamSocket *socket, OFIPStreamSocket *acceptedSocket, - id _Nullable exception); -#endif - -/*! - * @protocol OFIPStreamSocketDelegate OFIPStreamSocket.h \ - * ObjFW/OFIPStreamSocket.h - * - * A delegate for OFIPStreamSocket. - */ -@protocol OFIPStreamSocketDelegate -@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: (OFIPStreamSocket *)socket - didConnectToHost: (OFString *)host - port: (uint16_t)port - exception: (nullable id)exception; - -/*! - * @brief A method which is called when a socket accepted a connection. - * - * @param socket The socket which accepted the connection - * @param acceptedSocket The socket which has been accepted - * @param exception An exception that occurred while accepting, or nil on - * success - * @return A bool whether to accept the next incoming connection - */ -- (bool)socket: (OFIPStreamSocket *)socket - didAcceptSocket: (OFIPStreamSocket *)acceptedSocket - exception: (nullable id)exception; -@end - -/*! - * @class OFIPStreamSocket OFIPStreamSocket.h ObjFW/OFIPStreamSocket.h - * - * @brief A class which provides methods to create and use IP stream sockets. - * - * 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 OFIPStreamSocket: OFStreamSocket -{ - bool _listening; - of_socket_address_t _remoteAddress; -#ifdef OF_WII - uint16_t _port; -#endif - OF_RESERVE_IVARS(4) -} - -/*! - * @brief Whether the socket is a listening socket. - */ -@property (readonly, nonatomic, getter=isListening) bool listening; - -/*! - * @brief The remote address. - * - * @note This only works for accepted sockets! - */ -@property (readonly, nonatomic) const of_socket_address_t *remoteAddress; - -/*! - * @brief The delegate for asynchronous operations on the socket. - * - * @note The delegate is retained for as long as asynchronous operations are - * still outstanding. - */ -@property OF_NULLABLE_PROPERTY (assign, nonatomic) - id delegate; - -/*! - * @brief Connect the OFIPStreamSocket 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 OFIPStreamSocket 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 OFIPStreamSocket 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 OFIPStreamSocket 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_ip_stream_socket_async_connect_block_t)block; - -/*! - * @brief Asynchronously connect the OFIPStreamSocket 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_ip_stream_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; - -/*! - * @brief Listen on the socket. - * - * @param backlog Maximum length for the queue of pending connections. - */ -- (void)listenWithBacklog: (int)backlog; - -/*! - * @brief Listen on the socket. - */ -- (void)listen; - -/*! - * @brief Accept an incoming connection. - * - * @return An autoreleased OFIPStreamSocket for the accepted connection. - */ -- (instancetype)accept; - -/*! - * @brief Asynchronously accept an incoming connection. - */ -- (void)asyncAccept; - -/*! - * @brief Asynchronously accept an incoming connection. - * - * @param runLoopMode The run loop mode in which to perform the async accept - */ -- (void)asyncAcceptWithRunLoopMode: (of_run_loop_mode_t)runLoopMode; - -#ifdef OF_HAVE_BLOCKS -/*! - * @brief Asynchronously accept an incoming connection. - * - * @param block The block to execute when a new connection has been accepted. - * Returns whether the next incoming connection should be accepted - * by the specified block as well. - */ -- (void)asyncAcceptWithBlock: (of_ip_stream_socket_async_accept_block_t)block; - -/*! - * @brief Asynchronously accept an incoming connection. - * - * @param runLoopMode The run loop mode in which to perform the async accept - * @param block The block to execute when a new connection has been accepted. - * Returns whether the next incoming connection should be accepted - * by the specified block as well. - */ -- (void)asyncAcceptWithRunLoopMode: (of_run_loop_mode_t)runLoopMode - block: (of_ip_stream_socket_async_accept_block_t) - block; -#endif -@end - -OF_ASSUME_NONNULL_END DELETED src/OFIPStreamSocket.m Index: src/OFIPStreamSocket.m ================================================================== --- src/OFIPStreamSocket.m +++ src/OFIPStreamSocket.m @@ -1,1016 +0,0 @@ -/* - * 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. - */ - -#define OF_IP_STREAM_SOCKET_M -#define __NO_EXT_QNX - -#include "config.h" - -#include -#include -#include -#include -#include - -#ifdef HAVE_FCNTL_H -# include -#endif - -#import "OFIPStreamSocket.h" -#import "OFIPStreamSocket+Private.h" -#import "OFDNSResolver.h" -#import "OFData.h" -#import "OFDate.h" -#import "OFRunLoop+Private.h" -#import "OFRunLoop.h" -#import "OFString.h" -#import "OFTCPSocket.h" -#import "OFThread.h" -#import "OFTimer.h" - -#import "OFAcceptFailedException.h" -#import "OFBindFailedException.h" -#import "OFConnectionFailedException.h" -#import "OFInvalidArgumentException.h" -#import "OFInvalidFormatException.h" -#import "OFListenFailedException.h" -#import "OFNotImplementedException.h" -#import "OFNotOpenException.h" -#import "OFOutOfMemoryException.h" -#import "OFOutOfRangeException.h" - -#import "socket.h" -#import "socket_helpers.h" - -/* TODO: Move SOCKS5 code to OFTCPSocket */ - -static const of_run_loop_mode_t connectRunLoopMode = - @"of_ip_stream_socket_connect_mode"; - -@interface OFIPStreamSocketAsyncConnectDelegate: OFObject < - OFIPStreamSocketDelegate, OFIPStreamSocketDelegate_Private, - OFDNSResolverHostDelegate> -{ - OFIPStreamSocket *_socket; - OFString *_host; - uint16_t _port; - id _delegate; -#ifdef OF_HAVE_BLOCKS - of_ip_stream_socket_async_connect_block_t _block; -#endif - id _exception; - OFData *_socketAddresses; - size_t _socketAddressesIndex; - enum { - SOCKS5_STATE_SEND_AUTHENTICATION = 1, - SOCKS5_STATE_READ_VERSION, - SOCKS5_STATE_SEND_REQUEST, - SOCKS5_STATE_READ_RESPONSE, - SOCKS5_STATE_READ_ADDRESS, - SOCKS5_STATE_READ_ADDRESS_LENGTH, - } _SOCKS5State; - /* Longest read is domain name (max 255 bytes) + port */ - unsigned char _buffer[257]; - OFMutableData *_request; -} - -- (instancetype)initWithSocket: (OFIPStreamSocket *)sock - host: (OFString *)host - port: (uint16_t)port - delegate: (id )delegate; -#ifdef OF_HAVE_BLOCKS -- (instancetype)initWithSocket: (OFIPStreamSocket *)sock - host: (OFString *)host - port: (uint16_t)port - block: (of_ip_stream_socket_async_connect_block_t) - block; -#endif -- (void)didConnect; -- (void)tryNextAddressWithRunLoopMode: (of_run_loop_mode_t)runLoopMode; -- (void)startWithRunLoopMode: (of_run_loop_mode_t)runLoopMode; -- (void)sendSOCKS5Request; -@end - -@interface OFIPStreamSocketConnectDelegate: OFObject -{ -@public - bool _done; - id _exception; -} -@end - -@implementation OFIPStreamSocketAsyncConnectDelegate -- (instancetype)initWithSocket: (OFIPStreamSocket *)sock - host: (OFString *)host - port: (uint16_t)port - delegate: (id )delegate -{ - self = [super init]; - - @try { - _socket = [sock retain]; - _host = [host copy]; - _port = port; - _delegate = [delegate retain]; - - _socket.delegate = self; - } @catch (id e) { - [self release]; - @throw e; - } - - return self; -} - -#ifdef OF_HAVE_BLOCKS -- (instancetype)initWithSocket: (OFIPStreamSocket *)sock - host: (OFString *)host - port: (uint16_t)port - block: (of_ip_stream_socket_async_connect_block_t)block -{ - self = [super init]; - - @try { - _socket = [sock retain]; - _host = [host copy]; - _port = port; - _block = [block copy]; - } @catch (id e) { - [self release]; - @throw e; - } - - return self; -} -#endif - -- (void)dealloc -{ -#ifdef OF_HAVE_BLOCKS - if (_block == NULL) -#endif - if (_socket.delegate == self) - _socket.delegate = _delegate; - - [_socket release]; - [_host release]; - [_delegate release]; -#ifdef OF_HAVE_BLOCKS - [_block release]; -#endif - [_exception release]; - [_socketAddresses release]; - [_request release]; - - [super dealloc]; -} - -- (void)didConnect -{ - if (_exception == nil) - _socket.blocking = true; - -#ifdef OF_HAVE_BLOCKS - if (_block != NULL) - _block(_socket, _exception); - else { -#endif - _socket.delegate = _delegate; - - if ([_delegate respondsToSelector: - @selector(socket:didConnectToHost:port:exception:)]) - [_delegate socket: _socket - didConnectToHost: _host - port: _port - exception: _exception]; -#ifdef OF_HAVE_BLOCKS - } -#endif -} - -- (void)of_socketDidConnect: (OFIPStreamSocket *)sock - exception: (id)exception -{ - if (exception != nil) { - /* - * self might be retained only by the pending async requests, - * which we're about to cancel. - */ - [[self retain] autorelease]; - - [sock cancelAsyncRequests]; - [sock of_closeSocket]; - - if (_socketAddressesIndex >= _socketAddresses.count) { - _exception = [exception retain]; - [self didConnect]; - } else { - /* - * We must not call it before returning, as otherwise - * the new socket would be removed from the queue upon - * return. - */ - OFRunLoop *runLoop = [OFRunLoop currentRunLoop]; - SEL selector = - @selector(tryNextAddressWithRunLoopMode:); - OFTimer *timer = [OFTimer - timerWithTimeInterval: 0 - target: self - selector: selector - object: runLoop.currentMode - repeats: false]; - [runLoop addTimer: timer - forMode: runLoop.currentMode]; - } - - return; - } - - if ([sock isKindOfClass: [OFTCPSocket class]] && - ((OFTCPSocket *)sock).SOCKS5Host != nil) - [self sendSOCKS5Request]; - else - [self didConnect]; -} - -- (void)tryNextAddressWithRunLoopMode: (of_run_loop_mode_t)runLoopMode -{ - of_socket_address_t address = *(const of_socket_address_t *) - [_socketAddresses itemAtIndex: _socketAddressesIndex++]; - int errNo; - - if ([_socket isKindOfClass: [OFTCPSocket class]] && - ((OFTCPSocket *)_socket).SOCKS5Host != nil) - of_socket_address_set_port(&address, - ((OFTCPSocket *)_socket).SOCKS5Port); - else - of_socket_address_set_port(&address, _port); - - if (![_socket of_createSocketForAddress: &address - errNo: &errNo]) { - if (_socketAddressesIndex >= _socketAddresses.count) { - _exception = [[OFConnectionFailedException alloc] - initWithHost: _host - port: _port - socket: _socket - errNo: errNo]; - [self didConnect]; - return; - } - - [self tryNextAddressWithRunLoopMode: runLoopMode]; - return; - } - -#if defined(OF_NINTENDO_3DS) || defined(OF_WII) - /* - * On Wii and 3DS, connect() fails if non-blocking is enabled. - * - * Additionally, on Wii, there is no getsockopt(), so it would not be - * possible to get the error (or success) after connecting anyway. - * - * So for now, connecting is blocking on Wii and 3DS. - * - * FIXME: Use a different thread as a work around. - */ - _socket.blocking = true; -#else - _socket.blocking = false; -#endif - - if (![_socket of_connectSocketToAddress: &address - errNo: &errNo]) { -#if !defined(OF_NINTENDO_3DS) && !defined(OF_WII) - if (errNo == EINPROGRESS) { - [OFRunLoop - of_addAsyncConnectForIPStreamSocket: _socket - mode: runLoopMode - delegate: self]; - return; - } else { -#endif - [_socket of_closeSocket]; - - if (_socketAddressesIndex >= _socketAddresses.count) { - _exception = [[OFConnectionFailedException - alloc] initWithHost: _host - port: _port - socket: _socket - errNo: errNo]; - [self didConnect]; - return; - } - - [self tryNextAddressWithRunLoopMode: runLoopMode]; - return; -#if !defined(OF_NINTENDO_3DS) && !defined(OF_WII) - } -#endif - } - -#if defined(OF_NINTENDO_3DS) || defined(OF_WII) - _socket.blocking = false; -#endif - - [self didConnect]; -} - -- (void)resolver: (OFDNSResolver *)resolver - didResolveHost: (OFString *)host - addresses: (OFData *)addresses - exception: (id)exception -{ - if (exception != nil) { - _exception = [exception retain]; - [self didConnect]; - return; - } - - _socketAddresses = [addresses copy]; - - [self tryNextAddressWithRunLoopMode: - [OFRunLoop currentRunLoop].currentMode]; -} - -- (void)startWithRunLoopMode: (of_run_loop_mode_t)runLoopMode -{ - OFString *host; - uint16_t port; - - if ([_socket isKindOfClass: [OFTCPSocket class]] && - ((OFTCPSocket *)_socket).SOCKS5Host != nil) { - if (_host.UTF8StringLength > 255) - @throw [OFOutOfRangeException exception]; - - host = ((OFTCPSocket *)_socket).SOCKS5Host; - port = ((OFTCPSocket *)_socket).SOCKS5Port; - } else { - host = _host; - port = _port; - } - - @try { - of_socket_address_t address = - of_socket_address_parse_ip(host, port); - - _socketAddresses = [[OFData alloc] - initWithItems: &address - itemSize: sizeof(address) - count: 1]; - - [self tryNextAddressWithRunLoopMode: runLoopMode]; - return; - } @catch (OFInvalidFormatException *e) { - } - - [[OFThread DNSResolver] - asyncResolveAddressesForHost: host - addressFamily: OF_SOCKET_ADDRESS_FAMILY_ANY - runLoopMode: runLoopMode - delegate: self]; -} - -- (void)sendSOCKS5Request -{ - OFData *data = [OFData dataWithItems: "\x05\x01\x00" - count: 3]; - - _SOCKS5State = SOCKS5_STATE_SEND_AUTHENTICATION; - [_socket asyncWriteData: data - runLoopMode: [OFRunLoop currentRunLoop].currentMode]; -} - -- (bool)stream: (OFStream *)sock - didReadIntoBuffer: (void *)buffer - length: (size_t)length - exception: (id)exception -{ - of_run_loop_mode_t runLoopMode; - unsigned char *SOCKSVersion; - uint8_t hostLength; - unsigned char port[2]; - unsigned char *response, *addressLength; - - if (exception != nil) { - _exception = [exception retain]; - [self didConnect]; - return false; - } - - runLoopMode = [OFRunLoop currentRunLoop].currentMode; - - switch (_SOCKS5State) { - case SOCKS5_STATE_READ_VERSION: - SOCKSVersion = buffer; - - if (SOCKSVersion[0] != 5 || SOCKSVersion[1] != 0) { - _exception = [[OFConnectionFailedException alloc] - initWithHost: _host - port: _port - socket: self - errNo: EPROTONOSUPPORT]; - [self didConnect]; - return false; - } - - [_request release]; - _request = [[OFMutableData alloc] init]; - - [_request addItems: "\x05\x01\x00\x03" - count: 4]; - - hostLength = (uint8_t)_host.UTF8StringLength; - [_request addItem: &hostLength]; - [_request addItems: _host.UTF8String - count: hostLength]; - - port[0] = _port >> 8; - port[1] = _port & 0xFF; - [_request addItems: port - count: 2]; - - _SOCKS5State = SOCKS5_STATE_SEND_REQUEST; - [_socket asyncWriteData: _request - runLoopMode: runLoopMode]; - return false; - case SOCKS5_STATE_READ_RESPONSE: - response = buffer; - - if (response[0] != 5 || response[2] != 0) { - _exception = [[OFConnectionFailedException alloc] - initWithHost: _host - port: _port - socket: self - errNo: EPROTONOSUPPORT]; - [self didConnect]; - return false; - } - - if (response[1] != 0) { - int errNo; - - switch (response[1]) { - case 0x02: - errNo = EPERM; - break; - case 0x03: - errNo = ENETUNREACH; - break; - case 0x04: - errNo = EHOSTUNREACH; - break; - case 0x05: - errNo = ECONNREFUSED; - break; - case 0x06: - errNo = ETIMEDOUT; - break; - case 0x07: - errNo = EOPNOTSUPP; - break; - case 0x08: - errNo = EAFNOSUPPORT; - break; - default: -#ifdef EPROTO - errNo = EPROTO; -#else - errNo = 0; -#endif - break; - } - - _exception = [[OFConnectionFailedException alloc] - initWithHost: _host - port: _port - socket: _socket - errNo: errNo]; - [self didConnect]; - return false; - } - - /* Skip the rest of the response */ - switch (response[3]) { - case 1: /* IPv4 */ - _SOCKS5State = SOCKS5_STATE_READ_ADDRESS; - [_socket asyncReadIntoBuffer: _buffer - exactLength: 4 + 2 - runLoopMode: runLoopMode]; - return false; - case 3: /* Domain name */ - _SOCKS5State = SOCKS5_STATE_READ_ADDRESS_LENGTH; - [_socket asyncReadIntoBuffer: _buffer - exactLength: 1 - runLoopMode: runLoopMode]; - return false; - case 4: /* IPv6 */ - _SOCKS5State = SOCKS5_STATE_READ_ADDRESS; - [_socket asyncReadIntoBuffer: _buffer - exactLength: 16 + 2 - runLoopMode: runLoopMode]; - return false; - default: - _exception = [[OFConnectionFailedException alloc] - initWithHost: _host - port: _port - socket: self - errNo: EPROTONOSUPPORT]; - [self didConnect]; - return false; - } - - return false; - case SOCKS5_STATE_READ_ADDRESS: - [self didConnect]; - return false; - case SOCKS5_STATE_READ_ADDRESS_LENGTH: - addressLength = buffer; - - _SOCKS5State = SOCKS5_STATE_READ_ADDRESS; - [_socket asyncReadIntoBuffer: _buffer - exactLength: addressLength[0] + 2 - runLoopMode: runLoopMode]; - return false; - default: - assert(0); - return false; - } -} - -- (OFData *)stream: (OFStream *)sock - didWriteData: (OFData *)data - bytesWritten: (size_t)bytesWritten - exception: (id)exception -{ - of_run_loop_mode_t runLoopMode; - - if (exception != nil) { - _exception = [exception retain]; - [self didConnect]; - return nil; - } - - runLoopMode = [OFRunLoop currentRunLoop].currentMode; - - switch (_SOCKS5State) { - case SOCKS5_STATE_SEND_AUTHENTICATION: - _SOCKS5State = SOCKS5_STATE_READ_VERSION; - [_socket asyncReadIntoBuffer: _buffer - exactLength: 2 - runLoopMode: runLoopMode]; - return nil; - case SOCKS5_STATE_SEND_REQUEST: - [_request release]; - _request = nil; - - _SOCKS5State = SOCKS5_STATE_READ_RESPONSE; - [_socket asyncReadIntoBuffer: _buffer - exactLength: 4 - runLoopMode: runLoopMode]; - return nil; - default: - assert(0); - return nil; - } -} -@end - -@implementation OFIPStreamSocketConnectDelegate -- (void)dealloc -{ - [_exception release]; - - [super dealloc]; -} - -- (void)socket: (OFIPStreamSocket *)sock - didConnectToHost: (OFString *)host - port: (uint16_t)port - exception: (id)exception -{ - _done = true; - _exception = [exception retain]; -} -@end - -@implementation OFIPStreamSocket -@dynamic delegate; - -- (instancetype)init -{ - self = [super init]; - - _socket = INVALID_SOCKET; - - return self; -} - -- (bool)of_createSocketForAddress: (const of_socket_address_t *)address - errNo: (int *)errNo -{ - @throw [OFNotImplementedException exceptionWithSelector: _cmd - object: self]; -} - -- (bool)of_connectSocketToAddress: (const of_socket_address_t *)address - errNo: (int *)errNo -{ - if (_socket == INVALID_SOCKET) - @throw [OFNotOpenException exceptionWithObject: self]; - - if (connect(_socket, (struct sockaddr *)&address->sockaddr.sockaddr, - address->length) != 0) { - *errNo = of_socket_errno(); - return false; - } - - return true; -} - -- (void)of_closeSocket -{ - closesocket(_socket); - _socket = INVALID_SOCKET; -} - -#ifndef OF_WII -- (int)of_socketError -{ - int errNo; - socklen_t len = sizeof(errNo); - - if (getsockopt(_socket, SOL_SOCKET, SO_ERROR, (char *)&errNo, - &len) != 0) - return of_socket_errno(); - - return errNo; -} -#endif - -- (void)connectToHost: (OFString *)host - port: (uint16_t)port -{ - void *pool = objc_autoreleasePoolPush(); - id delegate = [_delegate retain]; - OFIPStreamSocketConnectDelegate *connectDelegate = - [[[OFIPStreamSocketConnectDelegate 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(); - - [[[[OFIPStreamSocketAsyncConnectDelegate alloc] - initWithSocket: self - host: host - port: port - delegate: _delegate] autorelease] - startWithRunLoopMode: runLoopMode]; - - objc_autoreleasePoolPop(pool); -} - -#ifdef OF_HAVE_BLOCKS -- (void)asyncConnectToHost: (OFString *)host - port: (uint16_t)port - block: (of_ip_stream_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_ip_stream_socket_async_connect_block_t)block -{ - void *pool = objc_autoreleasePoolPush(); - - [[[[OFIPStreamSocketAsyncConnectDelegate alloc] - initWithSocket: self - host: host - port: port - 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; - int errNo; - - 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 (![self of_createSocketForAddress: &address - errNo: &errNo]) - @throw [OFBindFailedException - exceptionWithHost: host - port: port - socket: self - errNo: errNo]; - - setsockopt(_socket, SOL_SOCKET, SO_REUSEADDR, - (char *)&one, (socklen_t)sizeof(one)); - - _blocking = true; - -#if defined(OF_WII) || defined(OF_NINTENDO_3DS) - if (port != 0) { -#endif - if (bind(_socket, &address.sockaddr.sockaddr, - address.length) != 0) { - errNo = of_socket_errno(); - - closesocket(_socket); - _socket = INVALID_SOCKET; - - @throw [OFBindFailedException exceptionWithHost: host - port: port - socket: self - errNo: errNo]; - } -#if defined(OF_WII) || defined(OF_NINTENDO_3DS) - } else { - for (;;) { - uint16_t rnd = 0; - int ret; - - while (rnd < 1024) - rnd = (uint16_t)rand(); - - of_socket_address_set_port(&address, rnd); - - if ((ret = bind(_socket, &address.sockaddr.sockaddr, - address.length)) == 0) { - port = rnd; - break; - } - - if (of_socket_errno() != EADDRINUSE) { - errNo = of_socket_errno(); - - closesocket(_socket); - _socket = INVALID_SOCKET; - - @throw [OFBindFailedException - exceptionWithHost: host - port: port - socket: self - errNo: errNo]; - } - } - } -#endif - - objc_autoreleasePoolPop(pool); - - if (port > 0) - return port; - -#if !defined(OF_WII) && !defined(OF_NINTENDO_3DS) - memset(&address, 0, sizeof(address)); - - address.length = (socklen_t)sizeof(address.sockaddr); - if (of_getsockname(_socket, &address.sockaddr.sockaddr, - &address.length) != 0) { - 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]; - } -#else - closesocket(_socket); - _socket = INVALID_SOCKET; - @throw [OFBindFailedException exceptionWithHost: host - port: port - socket: self - errNo: EADDRNOTAVAIL]; -#endif -} - -- (void)listen -{ - [self listenWithBacklog: SOMAXCONN]; -} - -- (void)listenWithBacklog: (int)backlog -{ - if (_socket == INVALID_SOCKET) - @throw [OFNotOpenException exceptionWithObject: self]; - - if (listen(_socket, backlog) == -1) - @throw [OFListenFailedException - exceptionWithSocket: self - backlog: backlog - errNo: of_socket_errno()]; - - _listening = true; -} - -- (instancetype)accept -{ - OFIPStreamSocket *client = [[[[self class] alloc] init] autorelease]; -#if (!defined(HAVE_PACCEPT) && !defined(HAVE_ACCEPT4)) || !defined(SOCK_CLOEXEC) -# if defined(HAVE_FCNTL) && defined(FD_CLOEXEC) - int flags; -# endif -#endif - - client->_remoteAddress.length = - (socklen_t)sizeof(client->_remoteAddress.sockaddr); - -#if defined(HAVE_PACCEPT) && defined(SOCK_CLOEXEC) - if ((client->_socket = paccept(_socket, - &client->_remoteAddress.sockaddr.sockaddr, - &client->_remoteAddress.length, NULL, SOCK_CLOEXEC)) == - INVALID_SOCKET) - @throw [OFAcceptFailedException - exceptionWithSocket: self - errNo: of_socket_errno()]; -#elif defined(HAVE_ACCEPT4) && defined(SOCK_CLOEXEC) - if ((client->_socket = accept4(_socket, - &client->_remoteAddress.sockaddr.sockaddr, - &client->_remoteAddress.length, SOCK_CLOEXEC)) == INVALID_SOCKET) - @throw [OFAcceptFailedException - exceptionWithSocket: self - errNo: of_socket_errno()]; -#else - if ((client->_socket = accept(_socket, - &client->_remoteAddress.sockaddr.sockaddr, - &client->_remoteAddress.length)) == INVALID_SOCKET) - @throw [OFAcceptFailedException - exceptionWithSocket: self - errNo: of_socket_errno()]; - -# if defined(HAVE_FCNTL) && defined(FD_CLOEXEC) - if ((flags = fcntl(client->_socket, F_GETFD, 0)) != -1) - fcntl(client->_socket, F_SETFD, flags | FD_CLOEXEC); -# endif -#endif - - assert(client->_remoteAddress.length <= - (socklen_t)sizeof(client->_remoteAddress.sockaddr)); - - switch (client->_remoteAddress.sockaddr.sockaddr.sa_family) { - case AF_INET: - client->_remoteAddress.family = OF_SOCKET_ADDRESS_FAMILY_IPV4; - break; -#ifdef OF_HAVE_IPV6 - case AF_INET6: - client->_remoteAddress.family = OF_SOCKET_ADDRESS_FAMILY_IPV6; - break; -#endif - default: - client->_remoteAddress.family = - OF_SOCKET_ADDRESS_FAMILY_UNKNOWN; - break; - } - - return client; -} - -- (void)asyncAccept -{ - [self asyncAcceptWithRunLoopMode: of_run_loop_mode_default]; -} - -- (void)asyncAcceptWithRunLoopMode: (of_run_loop_mode_t)runLoopMode -{ - [OFRunLoop of_addAsyncAcceptForIPStreamSocket: self - mode: runLoopMode -# ifdef OF_HAVE_BLOCKS - block: NULL -# endif - delegate: _delegate]; -} - -#ifdef OF_HAVE_BLOCKS -- (void)asyncAcceptWithBlock: (of_ip_stream_socket_async_accept_block_t)block -{ - [self asyncAcceptWithRunLoopMode: of_run_loop_mode_default - block: block]; -} - -- (void)asyncAcceptWithRunLoopMode: (of_run_loop_mode_t)runLoopMode - block: (of_ip_stream_socket_async_accept_block_t) - block -{ - [OFRunLoop of_addAsyncAcceptForIPStreamSocket: self - mode: runLoopMode - block: block - delegate: nil]; -} -#endif - -- (const of_socket_address_t *)remoteAddress -{ - if (_socket == INVALID_SOCKET) - @throw [OFNotOpenException exceptionWithObject: self]; - - if (_remoteAddress.length == 0) - @throw [OFInvalidArgumentException exception]; - - if (_remoteAddress.length > (socklen_t)sizeof(_remoteAddress.sockaddr)) - @throw [OFOutOfRangeException exception]; - - return &_remoteAddress; -} - -- (bool)isListening -{ - return _listening; -} - -- (void)close -{ - _listening = false; - - memset(&_remoteAddress, 0, sizeof(_remoteAddress)); - -#ifdef OF_WII - _port = 0; -#endif - - [super close]; -} -@end Index: src/OFRunLoop+Private.h ================================================================== --- src/OFRunLoop+Private.h +++ src/OFRunLoop+Private.h @@ -16,19 +16,19 @@ */ #import "OFRunLoop.h" #import "OFStream.h" #ifdef OF_HAVE_SOCKETS -# import "OFIPStreamSocket.h" +# import "OFTCPSocket.h" # import "OFUDPSocket.h" #endif OF_ASSUME_NONNULL_BEGIN #ifdef OF_HAVE_SOCKETS -@protocol OFIPStreamSocketDelegate_Private -- (void)of_socketDidConnect: (OFIPStreamSocket *)socket +@protocol OFTCPSocketDelegate_Private +- (void)of_socketDidConnect: (OFTCPSocket *)socket exception: (nullable id)exception; @end #endif @interface OFRunLoop () @@ -81,20 +81,24 @@ of_stream_async_write_string_block_t) block # endif delegate: (nullable id )delegate; # if !defined(OF_WII) && !defined(OF_NINTENDO_3DS) -+ (void)of_addAsyncConnectForIPStreamSocket: (OFIPStreamSocket *)socket - mode: (of_run_loop_mode_t)mode - delegate: (id ) delegate; ++ (void)of_addAsyncConnectForTCPSocket: (OFTCPSocket *)socket + mode: (of_run_loop_mode_t)mode + delegate: (id ) + delegate; # endif -+ (void)of_addAsyncAcceptForIPStreamSocket: (OFIPStreamSocket *)socket - mode: (of_run_loop_mode_t)mode ++ (void)of_addAsyncAcceptForTCPSocket: (OFTCPSocket *)socket + mode: (of_run_loop_mode_t)mode # ifdef OF_HAVE_BLOCKS - block: (nullable of_ip_stream_socket_async_accept_block_t)block + block: (nullable + of_tcp_socket_async_accept_block_t) + block # endif - delegate: (nullable id )delegate; + delegate: (nullable id ) + delegate; + (void)of_addAsyncReceiveForUDPSocket: (OFUDPSocket *)socket buffer: (void *)buffer length: (size_t)length mode: (of_run_loop_mode_t)mode # ifdef OF_HAVE_BLOCKS Index: src/OFRunLoop.m ================================================================== --- src/OFRunLoop.m +++ src/OFRunLoop.m @@ -25,12 +25,12 @@ #import "OFArray.h" #import "OFData.h" #import "OFDictionary.h" #ifdef OF_HAVE_SOCKETS # import "OFKernelEventObserver.h" -# import "OFIPStreamSocket.h" -# import "OFIPStreamSocket+Private.h" +# import "OFTCPSocket.h" +# import "OFTCPSocket+Private.h" #endif #import "OFThread.h" #ifdef OF_HAVE_THREADS # import "OFMutex.h" # import "OFCondition.h" @@ -155,11 +155,11 @@ @interface OFRunLoopAcceptQueueItem: OFRunLoopQueueItem { @public # ifdef OF_HAVE_BLOCKS - of_ip_stream_socket_async_accept_block_t _block; + of_tcp_socket_async_accept_block_t _block; # endif } @end @interface OFRunLoopUDPReceiveQueueItem: OFRunLoopQueueItem @@ -733,11 +733,11 @@ # endif @implementation OFRunLoopAcceptQueueItem - (bool)handleObject: (id)object { - OFIPStreamSocket *acceptedSocket; + OFTCPSocket *acceptedSocket; id exception = nil; @try { acceptedSocket = [object accept]; } @catch (id e) { @@ -1050,28 +1050,29 @@ QUEUE_ITEM } # if !defined(OF_WII) && !defined(OF_NINTENDO_3DS) -+ (void)of_addAsyncConnectForIPStreamSocket: (OFIPStreamSocket *)stream - mode: (of_run_loop_mode_t)mode - delegate: (id )delegate ++ (void)of_addAsyncConnectForTCPSocket: (OFTCPSocket *)stream + mode: (of_run_loop_mode_t)mode + delegate: (id ) + delegate { NEW_WRITE(OFRunLoopConnectQueueItem, stream, mode) queueItem->_delegate = [delegate retain]; QUEUE_ITEM } # endif -+ (void)of_addAsyncAcceptForIPStreamSocket: (OFIPStreamSocket *)stream - mode: (of_run_loop_mode_t)mode ++ (void)of_addAsyncAcceptForTCPSocket: (OFTCPSocket *)stream + mode: (of_run_loop_mode_t)mode # ifdef OF_HAVE_BLOCKS - block: (of_ip_stream_socket_async_accept_block_t)block + block: (of_tcp_socket_async_accept_block_t)block # endif - delegate: (id )delegate + delegate: (id )delegate { NEW_READ(OFRunLoopAcceptQueueItem, stream, mode) queueItem->_delegate = [delegate retain]; # ifdef OF_HAVE_BLOCKS Index: src/OFStreamSocket.h ================================================================== --- src/OFStreamSocket.h +++ src/OFStreamSocket.h @@ -33,13 +33,13 @@ bool _atEndOfStream; OF_RESERVE_IVARS(4) } /*! - * @brief Returns a new, autoreleased OFStreamSocket. + * @brief Returns a new, autoreleased OFTCPSocket. * - * @return A new, autoreleased OFStreamSocket + * @return A new, autoreleased OFTCPSocket */ + (instancetype)socket; @end OF_ASSUME_NONNULL_END ADDED src/OFTCPSocket+Private.h Index: src/OFTCPSocket+Private.h ================================================================== --- src/OFTCPSocket+Private.h +++ src/OFTCPSocket+Private.h @@ -0,0 +1,34 @@ +/* + * 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 "OFTCPSocket.h" + +OF_ASSUME_NONNULL_BEGIN + +@interface OFTCPSocket () +#ifndef OF_WII +@property (readonly, nonatomic) int of_socketError; +#endif + +- (bool)of_createSocketForAddress: (const of_socket_address_t *)address + errNo: (int *)errNo; +- (bool)of_connectSocketToAddress: (const of_socket_address_t *)address + errNo: (int *)errNo; +- (void)of_closeSocket; +@end + +OF_ASSUME_NONNULL_END Index: src/OFTCPSocket.h ================================================================== --- src/OFTCPSocket.h +++ src/OFTCPSocket.h @@ -13,20 +13,80 @@ * 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 "OFIPStreamSocket.h" +#import "OFStreamSocket.h" +#import "OFRunLoop.h" + +#import "socket.h" OF_ASSUME_NONNULL_BEGIN + +/*! @file */ + +@class OFTCPSocket; +@class OFString; + +#ifdef OF_HAVE_BLOCKS +/*! + * @brief A block which is called when the socket connected. + * + * @param socket The socket which connected + * @param exception An exception which occurred while connecting the socket or + * `nil` on success + */ +typedef void (^of_tcp_socket_async_connect_block_t)(OFTCPSocket *socket, + id _Nullable exception); + +/*! + * @brief A block which is called when the socket accepted a connection. + * + * @param socket The socket which accepted the connection + * @param acceptedSocket The socket which has been accepted + * @param exception An exception which occurred while accepting the socket or + * `nil` on success + * @return A bool whether the same block should be used for the next incoming + * connection + */ +typedef bool (^of_tcp_socket_async_accept_block_t)(OFTCPSocket *socket, + OFTCPSocket *acceptedSocket, id _Nullable exception); +#endif /*! * @protocol OFTCPSocketDelegate OFTCPSocket.h ObjFW/OFTCPSocket.h * - * A delegate for OFIPStreamSocket. + * A delegate for OFTCPSocket. + */ +@protocol OFTCPSocketDelegate +@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: (OFTCPSocket *)socket + didConnectToHost: (OFString *)host + port: (uint16_t)port + exception: (nullable id)exception; + +/*! + * @brief A method which is called when a socket accepted a connection. + * + * @param socket The socket which accepted the connection + * @param acceptedSocket The socket which has been accepted + * @param exception An exception that occurred while accepting, or nil on + * success + * @return A bool whether to accept the next incoming connection */ -@protocol OFTCPSocketDelegate +- (bool)socket: (OFTCPSocket *)socket + didAcceptSocket: (OFTCPSocket *)acceptedSocket + exception: (nullable id)exception; @end /*! * @class OFTCPSocket OFTCPSocket.h ObjFW/OFTCPSocket.h * @@ -33,22 +93,39 @@ * @brief A class which provides methods to create and use TCP sockets. * * 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 OFTCPSocket: OFIPStreamSocket +@interface OFTCPSocket: OFStreamSocket { + bool _listening; + of_socket_address_t _remoteAddress; OFString *_Nullable _SOCKS5Host; uint16_t _SOCKS5Port; +#ifdef OF_WII + uint16_t _port; +#endif OF_RESERVE_IVARS(4) } #ifdef OF_HAVE_CLASS_PROPERTIES @property (class, nullable, copy, nonatomic) OFString *SOCKS5Host; @property (class, nonatomic) uint16_t SOCKS5Port; #endif +/*! + * @brief Whether the socket is a listening socket. + */ +@property (readonly, nonatomic, getter=isListening) bool listening; + +/*! + * @brief The remote address. + * + * @note This only works for accepted sockets! + */ +@property (readonly, nonatomic) const of_socket_address_t *remoteAddress; + #if !defined(OF_WII) && !defined(OF_NINTENDO_3DS) /*! * @brief Whether keep alives are enabled for the connection. * * @warning This is not available on the Wii or Nintendo 3DS! @@ -60,11 +137,11 @@ /*! * @brief Whether TCP_NODELAY is enabled for the connection * * @warning This is not available on the Wii! */ -@property (nonatomic, getter=isNoDelayEnabled) bool noDelayEnabled; +@property (nonatomic, getter=isTCPNoDelayEnabled) bool TCPNoDelayEnabled; #endif /*! * @brief The host to use as a SOCKS5 proxy. */ @@ -110,10 +187,130 @@ * @brief Returns the port to use as a SOCKS5 proxy when creating a new socket * * @return The port to use as a SOCKS5 proxy when creating a new socket */ + (uint16_t)SOCKS5Port; + +/*! + * @brief Connect the OFTCPSocket 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 OFTCPSocket 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 OFTCPSocket 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 OFTCPSocket 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_tcp_socket_async_connect_block_t)block; + +/*! + * @brief Asynchronously connect the OFTCPSocket 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_tcp_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; + +/*! + * @brief Listen on the socket. + * + * @param backlog Maximum length for the queue of pending connections. + */ +- (void)listenWithBacklog: (int)backlog; + +/*! + * @brief Listen on the socket. + */ +- (void)listen; + +/*! + * @brief Accept an incoming connection. + * + * @return An autoreleased OFTCPSocket for the accepted connection. + */ +- (instancetype)accept; + +/*! + * @brief Asynchronously accept an incoming connection. + */ +- (void)asyncAccept; + +/*! + * @brief Asynchronously accept an incoming connection. + * + * @param runLoopMode The run loop mode in which to perform the async accept + */ +- (void)asyncAcceptWithRunLoopMode: (of_run_loop_mode_t)runLoopMode; + +#ifdef OF_HAVE_BLOCKS +/*! + * @brief Asynchronously accept an incoming connection. + * + * @param block The block to execute when a new connection has been accepted. + * Returns whether the next incoming connection should be accepted + * by the specified block as well. + */ +- (void)asyncAcceptWithBlock: (of_tcp_socket_async_accept_block_t)block; + +/*! + * @brief Asynchronously accept an incoming connection. + * + * @param runLoopMode The run loop mode in which to perform the async accept + * @param block The block to execute when a new connection has been accepted. + * Returns whether the next incoming connection should be accepted + * by the specified block as well. + */ +- (void)asyncAcceptWithRunLoopMode: (of_run_loop_mode_t)runLoopMode + block: (of_tcp_socket_async_accept_block_t)block; +#endif @end #ifdef __cplusplus extern "C" { #endif Index: src/OFTCPSocket.m ================================================================== --- src/OFTCPSocket.m +++ src/OFTCPSocket.m @@ -13,30 +13,614 @@ * 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. */ +#define OF_TCP_SOCKET_M +#define __NO_EXT_QNX + #include "config.h" -#import "OFTCPSocket.h" +#include +#include +#include +#include +#include #ifdef HAVE_FCNTL_H # include #endif +#import "OFTCPSocket.h" +#import "OFTCPSocket+Private.h" +#import "OFDate.h" +#import "OFDNSResolver.h" +#import "OFData.h" +#import "OFRunLoop.h" +#import "OFRunLoop+Private.h" +#import "OFString.h" +#import "OFThread.h" +#import "OFTimer.h" + +#import "OFAcceptFailedException.h" #import "OFAlreadyConnectedException.h" +#import "OFBindFailedException.h" +#import "OFConnectionFailedException.h" #import "OFGetOptionFailedException.h" +#import "OFInvalidArgumentException.h" +#import "OFInvalidFormatException.h" +#import "OFListenFailedException.h" #import "OFNotImplementedException.h" +#import "OFNotOpenException.h" +#import "OFOutOfMemoryException.h" +#import "OFOutOfRangeException.h" #import "OFSetOptionFailedException.h" #import "socket.h" #import "socket_helpers.h" +static const of_run_loop_mode_t connectRunLoopMode = + @"of_tcp_socket_connect_mode"; + Class of_tls_socket_class = Nil; static OFString *defaultSOCKS5Host = nil; static uint16_t defaultSOCKS5Port = 1080; + +@interface OFTCPSocketAsyncConnectDelegate: OFObject +{ + OFTCPSocket *_socket; + OFString *_host; + uint16_t _port; + OFString *_SOCKS5Host; + uint16_t _SOCKS5Port; + id _delegate; +#ifdef OF_HAVE_BLOCKS + of_tcp_socket_async_connect_block_t _block; +#endif + id _exception; + OFData *_socketAddresses; + size_t _socketAddressesIndex; + enum { + SOCKS5_STATE_SEND_AUTHENTICATION = 1, + SOCKS5_STATE_READ_VERSION, + SOCKS5_STATE_SEND_REQUEST, + SOCKS5_STATE_READ_RESPONSE, + SOCKS5_STATE_READ_ADDRESS, + SOCKS5_STATE_READ_ADDRESS_LENGTH, + } _SOCKS5State; + /* Longest read is domain name (max 255 bytes) + port */ + unsigned char _buffer[257]; + OFMutableData *_request; +} + +- (instancetype)initWithSocket: (OFTCPSocket *)sock + host: (OFString *)host + port: (uint16_t)port + SOCKS5Host: (OFString *)SOCKS5Host + SOCKS5Port: (uint16_t)SOCKS5Port + delegate: (id )delegate; +#ifdef OF_HAVE_BLOCKS +- (instancetype)initWithSocket: (OFTCPSocket *)sock + host: (OFString *)host + port: (uint16_t)port + SOCKS5Host: (OFString *)SOCKS5Host + SOCKS5Port: (uint16_t)SOCKS5Port + block: (of_tcp_socket_async_connect_block_t)block; +#endif +- (void)didConnect; +- (void)tryNextAddressWithRunLoopMode: (of_run_loop_mode_t)runLoopMode; +- (void)startWithRunLoopMode: (of_run_loop_mode_t)runLoopMode; +- (void)sendSOCKS5Request; +@end + +@interface OFTCPSocketConnectDelegate: OFObject +{ +@public + bool _done; + id _exception; +} +@end + +@implementation OFTCPSocketAsyncConnectDelegate +- (instancetype)initWithSocket: (OFTCPSocket *)sock + host: (OFString *)host + port: (uint16_t)port + SOCKS5Host: (OFString *)SOCKS5Host + SOCKS5Port: (uint16_t)SOCKS5Port + delegate: (id )delegate +{ + self = [super init]; + + @try { + _socket = [sock retain]; + _host = [host copy]; + _port = port; + _SOCKS5Host = [SOCKS5Host copy]; + _SOCKS5Port = SOCKS5Port; + _delegate = [delegate retain]; + + _socket.delegate = self; + } @catch (id e) { + [self release]; + @throw e; + } + + return self; +} + +#ifdef OF_HAVE_BLOCKS +- (instancetype)initWithSocket: (OFTCPSocket *)sock + host: (OFString *)host + port: (uint16_t)port + SOCKS5Host: (OFString *)SOCKS5Host + SOCKS5Port: (uint16_t)SOCKS5Port + block: (of_tcp_socket_async_connect_block_t)block +{ + self = [super init]; + + @try { + _socket = [sock retain]; + _host = [host copy]; + _port = port; + _SOCKS5Host = [SOCKS5Host copy]; + _SOCKS5Port = SOCKS5Port; + _block = [block copy]; + } @catch (id e) { + [self release]; + @throw e; + } + + return self; +} +#endif + +- (void)dealloc +{ +#ifdef OF_HAVE_BLOCKS + if (_block == NULL) +#endif + if (_socket.delegate == self) + _socket.delegate = _delegate; + + [_socket release]; + [_host release]; + [_SOCKS5Host release]; + [_delegate release]; +#ifdef OF_HAVE_BLOCKS + [_block release]; +#endif + [_exception release]; + [_socketAddresses release]; + [_request release]; + + [super dealloc]; +} + +- (void)didConnect +{ + if (_exception == nil) + _socket.blocking = true; + +#ifdef OF_HAVE_BLOCKS + if (_block != NULL) + _block(_socket, _exception); + else { +#endif + _socket.delegate = _delegate; + + if ([_delegate respondsToSelector: + @selector(socket:didConnectToHost:port:exception:)]) + [_delegate socket: _socket + didConnectToHost: _host + port: _port + exception: _exception]; +#ifdef OF_HAVE_BLOCKS + } +#endif +} + +- (void)of_socketDidConnect: (OFTCPSocket *)sock + exception: (id)exception +{ + if (exception != nil) { + /* + * self might be retained only by the pending async requests, + * which we're about to cancel. + */ + [[self retain] autorelease]; + + [sock cancelAsyncRequests]; + [sock of_closeSocket]; + + if (_socketAddressesIndex >= _socketAddresses.count) { + _exception = [exception retain]; + [self didConnect]; + } else { + /* + * We must not call it before returning, as otherwise + * the new socket would be removed from the queue upon + * return. + */ + OFRunLoop *runLoop = [OFRunLoop currentRunLoop]; + SEL selector = + @selector(tryNextAddressWithRunLoopMode:); + OFTimer *timer = [OFTimer + timerWithTimeInterval: 0 + target: self + selector: selector + object: runLoop.currentMode + repeats: false]; + [runLoop addTimer: timer + forMode: runLoop.currentMode]; + } + + return; + } + + if (_SOCKS5Host != nil) + [self sendSOCKS5Request]; + else + [self didConnect]; +} + +- (void)tryNextAddressWithRunLoopMode: (of_run_loop_mode_t)runLoopMode +{ + of_socket_address_t address = *(const of_socket_address_t *) + [_socketAddresses itemAtIndex: _socketAddressesIndex++]; + int errNo; + + if (_SOCKS5Host != nil) + of_socket_address_set_port(&address, _SOCKS5Port); + else + of_socket_address_set_port(&address, _port); + + if (![_socket of_createSocketForAddress: &address + errNo: &errNo]) { + if (_socketAddressesIndex >= _socketAddresses.count) { + _exception = [[OFConnectionFailedException alloc] + initWithHost: _host + port: _port + socket: _socket + errNo: errNo]; + [self didConnect]; + return; + } + + [self tryNextAddressWithRunLoopMode: runLoopMode]; + return; + } + +#if defined(OF_NINTENDO_3DS) || defined(OF_WII) + /* + * On Wii and 3DS, connect() fails if non-blocking is enabled. + * + * Additionally, on Wii, there is no getsockopt(), so it would not be + * possible to get the error (or success) after connecting anyway. + * + * So for now, connecting is blocking on Wii and 3DS. + * + * FIXME: Use a different thread as a work around. + */ + _socket.blocking = true; +#else + _socket.blocking = false; +#endif + + if (![_socket of_connectSocketToAddress: &address + errNo: &errNo]) { +#if !defined(OF_NINTENDO_3DS) && !defined(OF_WII) + if (errNo == EINPROGRESS) { + [OFRunLoop of_addAsyncConnectForTCPSocket: _socket + mode: runLoopMode + delegate: self]; + return; + } else { +#endif + [_socket of_closeSocket]; + + if (_socketAddressesIndex >= _socketAddresses.count) { + _exception = [[OFConnectionFailedException + alloc] initWithHost: _host + port: _port + socket: _socket + errNo: errNo]; + [self didConnect]; + return; + } + + [self tryNextAddressWithRunLoopMode: runLoopMode]; + return; +#if !defined(OF_NINTENDO_3DS) && !defined(OF_WII) + } +#endif + } + +#if defined(OF_NINTENDO_3DS) || defined(OF_WII) + _socket.blocking = false; +#endif + + [self didConnect]; +} + +- (void)resolver: (OFDNSResolver *)resolver + didResolveHost: (OFString *)host + addresses: (OFData *)addresses + exception: (id)exception +{ + if (exception != nil) { + _exception = [exception retain]; + [self didConnect]; + return; + } + + _socketAddresses = [addresses copy]; + + [self tryNextAddressWithRunLoopMode: + [OFRunLoop currentRunLoop].currentMode]; +} + +- (void)startWithRunLoopMode: (of_run_loop_mode_t)runLoopMode +{ + OFString *host; + uint16_t port; + + if (_SOCKS5Host != nil) { + if (_host.UTF8StringLength > 255) + @throw [OFOutOfRangeException exception]; + + host = _SOCKS5Host; + port = _SOCKS5Port; + } else { + host = _host; + port = _port; + } + + @try { + of_socket_address_t address = + of_socket_address_parse_ip(host, port); + + _socketAddresses = [[OFData alloc] + initWithItems: &address + itemSize: sizeof(address) + count: 1]; + + [self tryNextAddressWithRunLoopMode: runLoopMode]; + return; + } @catch (OFInvalidFormatException *e) { + } + + [[OFThread DNSResolver] + asyncResolveAddressesForHost: host + addressFamily: OF_SOCKET_ADDRESS_FAMILY_ANY + runLoopMode: runLoopMode + delegate: self]; +} + +- (void)sendSOCKS5Request +{ + OFData *data = [OFData dataWithItems: "\x05\x01\x00" + count: 3]; + + _SOCKS5State = SOCKS5_STATE_SEND_AUTHENTICATION; + [_socket asyncWriteData: data + runLoopMode: [OFRunLoop currentRunLoop].currentMode]; +} + +- (bool)stream: (OFStream *)sock + didReadIntoBuffer: (void *)buffer + length: (size_t)length + exception: (id)exception +{ + of_run_loop_mode_t runLoopMode; + unsigned char *SOCKSVersion; + uint8_t hostLength; + unsigned char port[2]; + unsigned char *response, *addressLength; + + if (exception != nil) { + _exception = [exception retain]; + [self didConnect]; + return false; + } + + runLoopMode = [OFRunLoop currentRunLoop].currentMode; + + switch (_SOCKS5State) { + case SOCKS5_STATE_READ_VERSION: + SOCKSVersion = buffer; + + if (SOCKSVersion[0] != 5 || SOCKSVersion[1] != 0) { + _exception = [[OFConnectionFailedException alloc] + initWithHost: _host + port: _port + socket: self + errNo: EPROTONOSUPPORT]; + [self didConnect]; + return false; + } + + [_request release]; + _request = [[OFMutableData alloc] init]; + + [_request addItems: "\x05\x01\x00\x03" + count: 4]; + + hostLength = (uint8_t)_host.UTF8StringLength; + [_request addItem: &hostLength]; + [_request addItems: _host.UTF8String + count: hostLength]; + + port[0] = _port >> 8; + port[1] = _port & 0xFF; + [_request addItems: port + count: 2]; + + _SOCKS5State = SOCKS5_STATE_SEND_REQUEST; + [_socket asyncWriteData: _request + runLoopMode: runLoopMode]; + return false; + case SOCKS5_STATE_READ_RESPONSE: + response = buffer; + + if (response[0] != 5 || response[2] != 0) { + _exception = [[OFConnectionFailedException alloc] + initWithHost: _host + port: _port + socket: self + errNo: EPROTONOSUPPORT]; + [self didConnect]; + return false; + } + + if (response[1] != 0) { + int errNo; + + switch (response[1]) { + case 0x02: + errNo = EPERM; + break; + case 0x03: + errNo = ENETUNREACH; + break; + case 0x04: + errNo = EHOSTUNREACH; + break; + case 0x05: + errNo = ECONNREFUSED; + break; + case 0x06: + errNo = ETIMEDOUT; + break; + case 0x07: + errNo = EOPNOTSUPP; + break; + case 0x08: + errNo = EAFNOSUPPORT; + break; + default: +#ifdef EPROTO + errNo = EPROTO; +#else + errNo = 0; +#endif + break; + } + + _exception = [[OFConnectionFailedException alloc] + initWithHost: _host + port: _port + socket: _socket + errNo: errNo]; + [self didConnect]; + return false; + } + + /* Skip the rest of the response */ + switch (response[3]) { + case 1: /* IPv4 */ + _SOCKS5State = SOCKS5_STATE_READ_ADDRESS; + [_socket asyncReadIntoBuffer: _buffer + exactLength: 4 + 2 + runLoopMode: runLoopMode]; + return false; + case 3: /* Domain name */ + _SOCKS5State = SOCKS5_STATE_READ_ADDRESS_LENGTH; + [_socket asyncReadIntoBuffer: _buffer + exactLength: 1 + runLoopMode: runLoopMode]; + return false; + case 4: /* IPv6 */ + _SOCKS5State = SOCKS5_STATE_READ_ADDRESS; + [_socket asyncReadIntoBuffer: _buffer + exactLength: 16 + 2 + runLoopMode: runLoopMode]; + return false; + default: + _exception = [[OFConnectionFailedException alloc] + initWithHost: _host + port: _port + socket: self + errNo: EPROTONOSUPPORT]; + [self didConnect]; + return false; + } + + return false; + case SOCKS5_STATE_READ_ADDRESS: + [self didConnect]; + return false; + case SOCKS5_STATE_READ_ADDRESS_LENGTH: + addressLength = buffer; + + _SOCKS5State = SOCKS5_STATE_READ_ADDRESS; + [_socket asyncReadIntoBuffer: _buffer + exactLength: addressLength[0] + 2 + runLoopMode: runLoopMode]; + return false; + default: + assert(0); + return false; + } +} + +- (OFData *)stream: (OFStream *)sock + didWriteData: (OFData *)data + bytesWritten: (size_t)bytesWritten + exception: (id)exception +{ + of_run_loop_mode_t runLoopMode; + + if (exception != nil) { + _exception = [exception retain]; + [self didConnect]; + return nil; + } + + runLoopMode = [OFRunLoop currentRunLoop].currentMode; + + switch (_SOCKS5State) { + case SOCKS5_STATE_SEND_AUTHENTICATION: + _SOCKS5State = SOCKS5_STATE_READ_VERSION; + [_socket asyncReadIntoBuffer: _buffer + exactLength: 2 + runLoopMode: runLoopMode]; + return nil; + case SOCKS5_STATE_SEND_REQUEST: + [_request release]; + _request = nil; + + _SOCKS5State = SOCKS5_STATE_READ_RESPONSE; + [_socket asyncReadIntoBuffer: _buffer + exactLength: 4 + runLoopMode: runLoopMode]; + return nil; + default: + assert(0); + return nil; + } +} +@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; @@ -65,10 +649,11 @@ - (instancetype)init { self = [super init]; @try { + _socket = INVALID_SOCKET; _SOCKS5Host = [defaultSOCKS5Host copy]; _SOCKS5Port = defaultSOCKS5Port; } @catch (id e) { [self release]; @throw e; @@ -105,10 +690,402 @@ 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, (struct sockaddr *)&address->sockaddr.sockaddr, + address->length) != 0) { + *errNo = of_socket_errno(); + return false; + } + + return true; +} + +- (void)of_closeSocket +{ + closesocket(_socket); + _socket = INVALID_SOCKET; +} + +#ifndef OF_WII +- (int)of_socketError +{ + int errNo; + socklen_t len = sizeof(errNo); + + if (getsockopt(_socket, SOL_SOCKET, SO_ERROR, (char *)&errNo, + &len) != 0) + return of_socket_errno(); + + return errNo; +} +#endif + +- (void)connectToHost: (OFString *)host + port: (uint16_t)port +{ + void *pool = objc_autoreleasePoolPush(); + id delegate = [_delegate retain]; + OFTCPSocketConnectDelegate *connectDelegate = + [[[OFTCPSocketConnectDelegate 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(); + + [[[[OFTCPSocketAsyncConnectDelegate alloc] + initWithSocket: self + host: host + port: port + SOCKS5Host: _SOCKS5Host + SOCKS5Port: _SOCKS5Port + delegate: _delegate] autorelease] + startWithRunLoopMode: runLoopMode]; + + objc_autoreleasePoolPop(pool); +} + +#ifdef OF_HAVE_BLOCKS +- (void)asyncConnectToHost: (OFString *)host + port: (uint16_t)port + block: (of_tcp_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_tcp_socket_async_connect_block_t)block +{ + void *pool = objc_autoreleasePoolPush(); + + [[[[OFTCPSocketAsyncConnectDelegate alloc] + initWithSocket: self + host: host + port: port + SOCKS5Host: _SOCKS5Host + SOCKS5Port: _SOCKS5Port + 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]; + + if (_SOCKS5Host != nil) + @throw [OFNotImplementedException exceptionWithSelector: _cmd + object: 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, 0)) == 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 defined(OF_WII) || defined(OF_NINTENDO_3DS) + if (port != 0) { +#endif + 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]; + } +#if defined(OF_WII) || defined(OF_NINTENDO_3DS) + } else { + for (;;) { + uint16_t rnd = 0; + int ret; + + while (rnd < 1024) + rnd = (uint16_t)rand(); + + of_socket_address_set_port(&address, rnd); + + if ((ret = bind(_socket, &address.sockaddr.sockaddr, + address.length)) == 0) { + port = rnd; + break; + } + + if (of_socket_errno() != EADDRINUSE) { + int errNo = of_socket_errno(); + + closesocket(_socket); + _socket = INVALID_SOCKET; + + @throw [OFBindFailedException + exceptionWithHost: host + port: port + socket: self + errNo: errNo]; + } + } + } +#endif + + objc_autoreleasePoolPop(pool); + + if (port > 0) + return port; + +#if !defined(OF_WII) && !defined(OF_NINTENDO_3DS) + 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]; + } +#else + closesocket(_socket); + _socket = INVALID_SOCKET; + @throw [OFBindFailedException exceptionWithHost: host + port: port + socket: self + errNo: EADDRNOTAVAIL]; +#endif +} + +- (void)listen +{ + [self listenWithBacklog: SOMAXCONN]; +} + +- (void)listenWithBacklog: (int)backlog +{ + if (_socket == INVALID_SOCKET) + @throw [OFNotOpenException exceptionWithObject: self]; + + if (listen(_socket, backlog) == -1) + @throw [OFListenFailedException + exceptionWithSocket: self + backlog: backlog + errNo: of_socket_errno()]; + + _listening = true; +} + +- (instancetype)accept +{ + OFTCPSocket *client = [[[[self class] alloc] init] autorelease]; +#if (!defined(HAVE_PACCEPT) && !defined(HAVE_ACCEPT4)) || !defined(SOCK_CLOEXEC) +# if defined(HAVE_FCNTL) && defined(FD_CLOEXEC) + int flags; +# endif +#endif + + client->_remoteAddress.length = + (socklen_t)sizeof(client->_remoteAddress.sockaddr); + +#if defined(HAVE_PACCEPT) && defined(SOCK_CLOEXEC) + if ((client->_socket = paccept(_socket, + &client->_remoteAddress.sockaddr.sockaddr, + &client->_remoteAddress.length, NULL, SOCK_CLOEXEC)) == + INVALID_SOCKET) + @throw [OFAcceptFailedException + exceptionWithSocket: self + errNo: of_socket_errno()]; +#elif defined(HAVE_ACCEPT4) && defined(SOCK_CLOEXEC) + if ((client->_socket = accept4(_socket, + &client->_remoteAddress.sockaddr.sockaddr, + &client->_remoteAddress.length, SOCK_CLOEXEC)) == INVALID_SOCKET) + @throw [OFAcceptFailedException + exceptionWithSocket: self + errNo: of_socket_errno()]; +#else + if ((client->_socket = accept(_socket, + &client->_remoteAddress.sockaddr.sockaddr, + &client->_remoteAddress.length)) == INVALID_SOCKET) + @throw [OFAcceptFailedException + exceptionWithSocket: self + errNo: of_socket_errno()]; + +# if defined(HAVE_FCNTL) && defined(FD_CLOEXEC) + if ((flags = fcntl(client->_socket, F_GETFD, 0)) != -1) + fcntl(client->_socket, F_SETFD, flags | FD_CLOEXEC); +# endif +#endif + + assert(client->_remoteAddress.length <= + (socklen_t)sizeof(client->_remoteAddress.sockaddr)); + + switch (client->_remoteAddress.sockaddr.sockaddr.sa_family) { + case AF_INET: + client->_remoteAddress.family = OF_SOCKET_ADDRESS_FAMILY_IPV4; + break; +#ifdef OF_HAVE_IPV6 + case AF_INET6: + client->_remoteAddress.family = OF_SOCKET_ADDRESS_FAMILY_IPV6; + break; +#endif + default: + client->_remoteAddress.family = + OF_SOCKET_ADDRESS_FAMILY_UNKNOWN; + break; + } + + return client; +} + +- (void)asyncAccept +{ + [self asyncAcceptWithRunLoopMode: of_run_loop_mode_default]; +} + +- (void)asyncAcceptWithRunLoopMode: (of_run_loop_mode_t)runLoopMode +{ + [OFRunLoop of_addAsyncAcceptForTCPSocket: self + mode: runLoopMode +# ifdef OF_HAVE_BLOCKS + block: NULL +# endif + delegate: _delegate]; +} + +#ifdef OF_HAVE_BLOCKS +- (void)asyncAcceptWithBlock: (of_tcp_socket_async_accept_block_t)block +{ + [self asyncAcceptWithRunLoopMode: of_run_loop_mode_default + block: block]; +} + +- (void)asyncAcceptWithRunLoopMode: (of_run_loop_mode_t)runLoopMode + block: (of_tcp_socket_async_accept_block_t)block +{ + [OFRunLoop of_addAsyncAcceptForTCPSocket: self + mode: runLoopMode + block: block + delegate: nil]; +} +#endif + +- (const of_socket_address_t *)remoteAddress +{ + if (_socket == INVALID_SOCKET) + @throw [OFNotOpenException exceptionWithObject: self]; + + if (_remoteAddress.length == 0) + @throw [OFInvalidArgumentException exception]; + + if (_remoteAddress.length > (socklen_t)sizeof(_remoteAddress.sockaddr)) + @throw [OFOutOfRangeException exception]; + + return &_remoteAddress; +} + +- (bool)isListening +{ + return _listening; +} #if !defined(OF_WII) && !defined(OF_NINTENDO_3DS) - (void)setKeepAliveEnabled: (bool)enabled { int v = enabled; @@ -134,11 +1111,11 @@ return v; } #endif #ifndef OF_WII -- (void)setNoDelayEnabled: (bool)enabled +- (void)setTCPNoDelayEnabled: (bool)enabled { int v = enabled; if (setsockopt(_socket, IPPROTO_TCP, TCP_NODELAY, (char *)&v, (socklen_t)sizeof(v)) != 0) @@ -145,11 +1122,11 @@ @throw [OFSetOptionFailedException exceptionWithObject: self errNo: of_socket_errno()]; } -- (bool)isNoDelayEnabled +- (bool)isTCPNoDelayEnabled { int v; socklen_t len = sizeof(v); if (getsockopt(_socket, IPPROTO_TCP, TCP_NODELAY, @@ -160,16 +1137,18 @@ return v; } #endif -- (uint16_t)bindToHost: (OFString *)host - port: (uint16_t)port -{ - if (_SOCKS5Host != nil) - @throw [OFNotImplementedException exceptionWithSelector: _cmd - object: self]; - - return [super bindToHost: host - port: port]; +- (void)close +{ + _listening = false; + + memset(&_remoteAddress, 0, sizeof(_remoteAddress)); + +#ifdef OF_WII + _port = 0; +#endif + + [super close]; } @end