Index: src/OFRunLoop+Private.h ================================================================== --- src/OFRunLoop+Private.h +++ src/OFRunLoop+Private.h @@ -51,10 +51,14 @@ buffer: (const void *)buffer length: (size_t)length target: (id)target selector: (SEL)selector context: (nullable id)context; ++ (void)of_addAsyncConnectForTCPSocket: (OFTCPSocket *)socket + target: (id)target + selector: (SEL)selector + context: (nullable id)context; + (void)of_addAsyncAcceptForTCPSocket: (OFTCPSocket *)socket target: (id)target selector: (SEL)selector context: (nullable id)context; + (void)of_addAsyncReceiveForUDPSocket: (OFUDPSocket *)socket Index: src/OFRunLoop.m ================================================================== --- src/OFRunLoop.m +++ src/OFRunLoop.m @@ -23,10 +23,12 @@ #import "OFRunLoop.h" #import "OFRunLoop+Private.h" #import "OFDictionary.h" #ifdef OF_HAVE_SOCKETS # import "OFKernelEventObserver.h" +# import "OFTCPSocket.h" +# import "OFTCPSocket+Private.h" #endif #import "OFThread.h" #ifdef OF_HAVE_THREADS # import "OFMutex.h" # import "OFCondition.h" @@ -35,10 +37,13 @@ #import "OFTimer.h" #import "OFTimer+Private.h" #import "OFDate.h" #import "OFObserveFailedException.h" +#ifdef OF_HAVE_SOCKETS +# import "OFConnectionFailedException.h" +#endif static OFRunLoop *mainRunLoop = nil; #ifdef OF_HAVE_SOCKETS @interface OFRunLoop_QueueItem: OFObject @@ -92,10 +97,13 @@ # endif const void *_buffer; size_t _length, _writtenLength; } @end + +@interface OFRunLoop_ConnectQueueItem: OFRunLoop_QueueItem +@end @interface OFRunLoop_AcceptQueueItem: OFRunLoop_QueueItem { @public # ifdef OF_HAVE_BLOCKS @@ -332,10 +340,32 @@ [super dealloc]; } # endif @end + +@implementation OFRunLoop_ConnectQueueItem +- (bool)handleObject: (id)object +{ + id exception = nil; + int errNo; + void (*func)(id, SEL, OFTCPSocket *, id, id); + + if ((errNo = [object of_socketError]) != 0) + exception = [OFConnectionFailedException + exceptionWithHost: nil + port: 0 + socket: object + errNo: errNo]; + + func = (void (*)(id, SEL, OFTCPSocket *, id, id)) + [_target methodForSelector: _selector]; + func(_target, _selector, object, _context, exception); + + return false; +} +@end @implementation OFRunLoop_AcceptQueueItem - (bool)handleObject: (id)object { OFTCPSocket *newSocket; @@ -593,10 +623,22 @@ queueItem->_context = [context retain]; queueItem->_buffer = buffer; queueItem->_length = length; }) } + ++ (void)of_addAsyncConnectForTCPSocket: (OFTCPSocket *)stream + target: (id)target + selector: (SEL)selector + context: (id)context +{ + ADD_WRITE(OFRunLoop_ConnectQueueItem, stream, { + queueItem->_target = [target retain]; + queueItem->_selector = selector; + queueItem->_context = [context retain]; + }) +} + (void)of_addAsyncAcceptForTCPSocket: (OFTCPSocket *)stream target: (id)target selector: (SEL)selector context: (id)context Index: src/OFStream.h ================================================================== --- src/OFStream.h +++ src/OFStream.h @@ -137,11 +137,11 @@ * @brief Whether the stream is in blocking mode. * * By default, a stream is in blocking mode. * On Win32, setting this currently only works for sockets! */ -@property (readonly, nonatomic, getter=isBlocking) bool blocking; +@property (nonatomic, getter=isBlocking) bool blocking; /*! * @brief Reads *at most* size bytes from the stream into a buffer. * * On network streams, this might read less than the specified number of bytes. ADDED src/OFTCPSocket+Private.h Index: src/OFTCPSocket+Private.h ================================================================== --- src/OFTCPSocket+Private.h +++ src/OFTCPSocket+Private.h @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, + * 2018 + * 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 () +@property (readonly, nonatomic) int of_socketError; + +- (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.m ================================================================== --- src/OFTCPSocket.m +++ src/OFTCPSocket.m @@ -29,10 +29,11 @@ #ifdef HAVE_FCNTL_H # include #endif #import "OFTCPSocket.h" +#import "OFTCPSocket+Private.h" #import "OFTCPSocket+SOCKS5.h" #import "OFString.h" #import "OFThread.h" #import "OFTimer.h" #import "OFRunLoop.h" @@ -252,10 +253,66 @@ { [_SOCKS5Host release]; [super dealloc]; } + +- (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, 0)) == 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; +} + +- (int)of_socketError +{ + int errNo; + socklen_t len = sizeof(errNo); + + if (getsockopt(_socket, SOL_SOCKET, SO_ERROR, &errNo, &len) != 0) + return of_socket_errno(); + + return errNo; +} - (void)connectToHost: (OFString *)host port: (uint16_t)port { OFString *destinationHost = host; @@ -274,36 +331,42 @@ results = of_resolve_host(host, port, SOCK_STREAM); for (iter = results; *iter != NULL; iter++) { of_resolver_result_t *result = *iter; -#if SOCK_CLOEXEC == 0 && defined(HAVE_FCNTL) && defined(FD_CLOEXEC) - int flags; -#endif - - if ((_socket = socket(result->family, - result->type | SOCK_CLOEXEC, - result->protocol)) == INVALID_SOCKET) { - errNo = of_socket_errno(); - + of_socket_address_t address; + + switch (result->family) { + case AF_INET: + address.family = OF_SOCKET_ADDRESS_FAMILY_IPV4; + break; + case AF_INET6: + address.family = OF_SOCKET_ADDRESS_FAMILY_IPV6; + break; + default: + errNo = EAFNOSUPPORT; + continue; + } + + if (result->addressLength > sizeof(address)) { + errNo = EOVERFLOW; continue; } + + address.length = result->addressLength; + memcpy(&address.sockaddr.sockaddr, result->address, + result->addressLength); + + if (![self of_createSocketForAddress: &address + errNo: &errNo]) + continue; _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 - - if (connect(_socket, result->address, - result->addressLength) == -1) { - errNo = of_socket_errno(); - - closesocket(_socket); - _socket = INVALID_SOCKET; - + if (![self of_connectSocketToAddress: &address + errNo: &errNo]) { + [self of_closeSocket]; continue; } break; } Index: src/exceptions/OFConnectionFailedException.h ================================================================== --- src/exceptions/OFConnectionFailedException.h +++ src/exceptions/OFConnectionFailedException.h @@ -43,11 +43,11 @@ @property (readonly, nonatomic) id socket; /*! * @brief The host to which the connection failed. */ -@property (readonly, nonatomic) OFString *host; +@property OF_NULLABLE_PROPERTY (readonly, nonatomic) OFString *host; /*! * @brief The port on the host to which the connection failed. */ @property (readonly, nonatomic) uint16_t port; @@ -66,11 +66,11 @@ * @param port The port on the host to which the connection failed * @param socket The socket which could not connect * @param errNo The errno of the error that occurred * @return A new, autoreleased connection failed exception */ -+ (instancetype)exceptionWithHost: (OFString *)host ++ (instancetype)exceptionWithHost: (nullable OFString *)host port: (uint16_t)port socket: (id)socket errNo: (int)errNo; - (instancetype)init OF_UNAVAILABLE; @@ -82,12 +82,12 @@ * @param port The port on the host to which the connection failed * @param socket The socket which could not connect * @param errNo The errno of the error that occurred * @return An initialized connection failed exception */ -- (instancetype)initWithHost: (OFString *)host +- (instancetype)initWithHost: (nullable OFString *)host port: (uint16_t)port socket: (id)socket errNo: (int)errNo OF_DESIGNATED_INITIALIZER; @end OF_ASSUME_NONNULL_END Index: src/exceptions/OFConnectionFailedException.m ================================================================== --- src/exceptions/OFConnectionFailedException.m +++ src/exceptions/OFConnectionFailedException.m @@ -72,11 +72,17 @@ [super dealloc]; } - (OFString *)description { - return [OFString stringWithFormat: - @"A connection to %@ on port %" @PRIu16 @" could not be " - @"established in socket of type %@: %@", - _host, _port, [_socket class], of_strerror(_errNo)]; + if (_host != nil) + return [OFString stringWithFormat: + @"A connection to %@ on port %" @PRIu16 @" could not be " + @"established in socket of type %@: %@", + _host, _port, [_socket class], of_strerror(_errNo)]; + else + return [OFString stringWithFormat: + @"A connection could not be established in socket of " + @"type %@: %@", + [_socket class], of_strerror(_errNo)]; } @end Index: src/exceptions/OFWriteFailedException.m ================================================================== --- src/exceptions/OFWriteFailedException.m +++ src/exceptions/OFWriteFailedException.m @@ -65,11 +65,11 @@ } - (OFString *)description { return [OFString stringWithFormat: - @"Failed to write %zu bytes (after %zu bytes written) to an " + @"Failed to write %zu bytes (after %zu bytes written) to an " @"object of type %@: %@", _requestedLength, _bytesWritten, [_object class], of_strerror(_errNo)]; } @end