Index: src/OFDDPSocket.h ================================================================== --- src/OFDDPSocket.h +++ src/OFDDPSocket.h @@ -37,10 +37,15 @@ * @ref OFSocketAddressMakeAppleTalk to create an address or * @ref OFSocketAddressAppleTalkNetwork to get the AppleTalk network, * @ref OFSocketAddressAppleTalkNode to get the AppleTalk node and * @ref OFSocketAddressAppleTalkPort to get the port (sometimes also called * socket number). + * + * @note On some systems, packets received with the wrong protocol type just + * get filtered by the kernel, however, on other systems, the packet is + * queued up and will raise an @ref OFReadFailedException with the + * @ref errNo set to `ENOMSG` when being received. * * @warning Even though the OFCopying protocol is implemented, it does *not* * return an independent copy of the socket, but instead retains it. * This is so that the socket can be used as a key for a dictionary, * so context can be associated with a socket. Using a socket in more @@ -47,10 +52,13 @@ * than one thread at the same time is not thread-safe, even if copy * was called to create one "instance" for every thread! */ @interface OFDDPSocket: OFDatagramSocket { +#if !defined(OF_MACOS) && !defined(OF_WINDOWS) + uint8_t _protocolType; +#endif OF_RESERVE_IVARS(OFDDPSocket, 4) } /** * @brief The delegate for asynchronous operations on the socket. @@ -66,15 +74,19 @@ * * @param network The network to bind to. 0 means any. * @param node The node to bind to. 0 means "this node". * @param port The port to bind to. 0 means to pick one and return it via the * returned socket address. + * @param protocolType The DDP protocol type to use. Must not be 0. If you want + * to use DDP directly and not a protocol built on top of + * it, use 11 for compatibility with Open Transport. * @return The address on which this socket can be reached * @throw OFBindDDPSockeFailedException Binding failed * @throw OFAlreadyConnectedException The socket is already bound */ - (OFSocketAddress)bindToNetwork: (uint16_t)network node: (uint8_t)node - port: (uint8_t)port; + port: (uint8_t)port + protocolType: (uint8_t)protocolType; @end OF_ASSUME_NONNULL_END Index: src/OFDDPSocket.m ================================================================== --- src/OFDDPSocket.m +++ src/OFDDPSocket.m @@ -25,10 +25,11 @@ #import "OFSocket.h" #import "OFSocket+Private.h" #import "OFAlreadyConnectedException.h" #import "OFBindDDPSocketFailedException.h" +#import "OFInvalidArgumentException.h" #import "OFNotOpenException.h" #import "OFReadFailedException.h" #import "OFWriteFailedException.h" #ifdef OF_HAVE_NETAT_APPLETALK_H @@ -42,17 +43,22 @@ struct at_addr address, router; unsigned short netStart, netEnd; at_nvestr_t zoneName; }; #endif + +#ifndef ATPROTO_BASE +# define ATPROTO_BASE 0 +#endif @implementation OFDDPSocket @dynamic delegate; - (OFSocketAddress)bindToNetwork: (uint16_t)network node: (uint8_t)node port: (uint8_t)port + protocolType: (uint8_t)protocolType { #ifdef OF_MACOS const int one = 1; struct ATInterfaceConfig config = { { 0 } }; #endif @@ -59,26 +65,31 @@ OFSocketAddress address; #if SOCK_CLOEXEC == 0 && defined(HAVE_FCNTL_H) && defined(FD_CLOEXEC) int flags; #endif + if (protocolType == 0) + @throw [OFInvalidArgumentException exception]; + if (_socket != OFInvalidSocketHandle) @throw [OFAlreadyConnectedException exceptionWithSocket: self]; address = OFSocketAddressMakeAppleTalk(network, node, port); #ifdef OF_MACOS if ((_socket = socket(address.sockaddr.at.sat_family, - SOCK_RAW | SOCK_CLOEXEC, 0)) == OFInvalidSocketHandle) + SOCK_RAW | SOCK_CLOEXEC, protocolType)) == OFInvalidSocketHandle) #else if ((_socket = socket(address.sockaddr.at.sat_family, - SOCK_DGRAM | SOCK_CLOEXEC, 0)) == OFInvalidSocketHandle) + SOCK_DGRAM | SOCK_CLOEXEC, ATPROTO_BASE + protocolType)) == + OFInvalidSocketHandle) #endif @throw [OFBindDDPSocketFailedException exceptionWithNetwork: network node: node port: port + protocolType: protocolType socket: self errNo: OFSocketErrNo()]; _canBlock = true; @@ -96,10 +107,11 @@ @throw [OFBindDDPSocketFailedException exceptionWithNetwork: network node: node port: port + protocolType: protocolType socket: self errNo: errNo]; } memset(&address, 0, sizeof(address)); @@ -115,10 +127,11 @@ @throw [OFBindDDPSocketFailedException exceptionWithNetwork: network node: node port: port + protocolType: protocolType socket: self errNo: errNo]; } if (address.sockaddr.at.sat_family != AF_APPLETALK) { @@ -127,111 +140,118 @@ @throw [OFBindDDPSocketFailedException exceptionWithNetwork: network node: node port: port + protocolType: protocolType socket: self errNo: EAFNOSUPPORT]; } #ifdef OF_MACOS - if (setsockopt(_socket, ATPROTO_NONE, DDP_HDRINCL, &one, sizeof(one)) != - 0 || - ioctl(_socket, _IOWR('a', 2, struct ATInterfaceConfig), &config) != - 0) + if (setsockopt(_socket, ATPROTO_NONE, DDP_STRIPHDR, &one, + sizeof(one)) != 0 || ioctl(_socket, _IOWR('a', 2, + struct ATInterfaceConfig), &config) != 0) @throw [OFBindDDPSocketFailedException exceptionWithNetwork: network node: node port: port + protocolType: protocolType socket: self errNo: OFSocketErrNo()]; OFSocketAddressSetAppleTalkNetwork(&address, config.address.s_net); OFSocketAddressSetAppleTalkNode(&address, config.address.s_node); #endif + +#if !defined(OF_MACOS) && !defined(OF_WINDOWS) + _protocolType = protocolType; +#endif return address; } -#ifdef OF_MACOS +/* + * Everybody but macOS and Windows is probably using a netatalk-compatible + * implementation, which includes the protocol type as the first byte of the + * data instead of considering it part of the header. + * + * The following overrides prepend the protocol type when sending and compare + * and strip it when receiving. + * + * Unfortunately, the downside of this is that the only way to handle receiving + * a packet with the wrong protocol type is to throw an exception with errNo + * ENOMSG, while macOS and Windows just filter those out in the kernel. + * Returning 0 would mean this is indistinguishable from an empty packet, so it + * has to be an exception. + */ +#if !defined(OF_MACOS) && !defined(OF_WINDOWS) - (size_t)receiveIntoBuffer: (void *)buffer length: (size_t)length sender: (OFSocketAddress *)sender { ssize_t ret; - at_ddp_t header; + uint8_t protocolType; struct iovec iov[2] = { - { &header, offsetof(at_ddp_t, type) }, + { &protocolType, 1 }, { buffer, length } + }; + struct msghdr msg = { + .msg_name = (struct sockaddr *)&sender->sockaddr, + .msg_namelen = (socklen_t)sizeof(sender->sockaddr), + .msg_iov = iov, + .msg_iovlen = 2 }; if (_socket == OFInvalidSocketHandle) @throw [OFNotOpenException exceptionWithObject: self]; - if ((ret = readv(_socket, iov, 2)) < 0) + if ((ret = recvmsg(_socket, &msg, 0)) < 0) @throw [OFReadFailedException exceptionWithObject: self requestedLength: length errNo: OFSocketErrNo()]; - *sender = OFSocketAddressMakeAppleTalk( - header.src_net[0] << 8 | header.src_net[1], header.src_node, - header.src_socket); - - ret -= offsetof(at_ddp_t, type); - if (ret < 0) - return 0; - - return ret; + if (ret < 1 || protocolType != _protocolType) + @throw [OFReadFailedException exceptionWithObject: self + requestedLength: length + errNo: ENOMSG]; + + sender->length = msg.msg_namelen; + sender->family = OFSocketAddressFamilyAppleTalk; + + return ret - 1; } - (void)sendBuffer: (const void *)buffer length: (size_t)length receiver: (const OFSocketAddress *)receiver { - at_ddp_t header = { 0 }; struct iovec iov[2] = { - { &header, offsetof(at_ddp_t, type) }, + { &_protocolType, 1 }, { (void *)buffer, length } }; struct msghdr msg = { .msg_name = (struct sockaddr *)&receiver->sockaddr, .msg_namelen = receiver->length, .msg_iov = iov, .msg_iovlen = 2 }; - size_t packetLength = length + offsetof(at_ddp_t, type); - uint16_t net; ssize_t bytesWritten; if (_socket == OFInvalidSocketHandle) @throw [OFNotOpenException exceptionWithObject: self]; - if (packetLength > DDP_DATAGRAM_SIZE) - @throw [OFWriteFailedException exceptionWithObject: self - requestedLength: length - bytesWritten: 0 - errNo: EMSGSIZE]; - - net = OFSocketAddressAppleTalkNetwork(receiver); - - header.length_H = (packetLength >> 8) & 3; - header.length_L = packetLength & 0xFF; - header.dst_net[0] = net >> 8; - header.dst_net[1] = net & 0xFF; - header.dst_node = OFSocketAddressAppleTalkNode(receiver); - header.dst_socket = OFSocketAddressAppleTalkPort(receiver); - if ((bytesWritten = sendmsg(_socket, &msg, 0)) < 0) @throw [OFWriteFailedException exceptionWithObject: self requestedLength: length bytesWritten: 0 errNo: OFSocketErrNo()]; - if ((size_t)bytesWritten != packetLength) { - bytesWritten -= offsetof(at_ddp_t, type); + if ((size_t)bytesWritten != length + 1) { + bytesWritten--; if (bytesWritten < 0) bytesWritten = 0; @throw [OFWriteFailedException exceptionWithObject: self Index: src/exceptions/OFBindDDPSocketFailedException.h ================================================================== --- src/exceptions/OFBindDDPSocketFailedException.h +++ src/exceptions/OFBindDDPSocketFailedException.h @@ -26,11 +26,11 @@ */ OF_SUBCLASSING_RESTRICTED @interface OFBindDDPSocketFailedException: OFBindSocketFailedException { uint16_t _network; - uint8_t _node, _port; + uint8_t _node, _port, _protocolType; } /** * @brief The DDP network on which binding failed. */ @@ -44,23 +44,30 @@ /** * @brief The DDP port on which binding failed. */ @property (readonly, nonatomic) uint8_t port; +/** + * @brief The DDP protocol type for which binding failed. + */ +@property (readonly, nonatomic) uint8_t protocolType; + /** * @brief Creates a new, autoreleased bind DDP socket failed exception. * * @param network The DDP network on which binding failed * @param node The DDP node for which binding failed * @param port The DDP port on which binding failed + * @param protocolType The DDP protocol type for which binding failed. * @param socket The socket which could not be bound * @param errNo The errno of the error that occurred * @return A new, autoreleased bind DDP socket failed exception */ + (instancetype)exceptionWithNetwork: (uint16_t)network node: (uint8_t)node port: (uint8_t)port + protocolType: (uint8_t)protocolType socket: (id)socket errNo: (int)errNo; + (instancetype)exceptionWithSocket: (id)socket errNo: (int)errNo OF_UNAVAILABLE; @@ -69,19 +76,21 @@ * @brief Initializes an already allocated bind DDP socket failed exception. * * @param network The DDP network on which binding failed * @param node The DDP node for which binding failed * @param port The DDP port on which binding failed + * @param protocolType The DDP protocol type for which binding failed. * @param socket The socket which could not be bound * @param errNo The errno of the error that occurred * @return An initialized bind DDP socket failed exception */ - (instancetype)initWithNetwork: (uint16_t)network node: (uint8_t)node port: (uint8_t)port + protocolType: (uint8_t)protocolType socket: (id)socket errNo: (int)errNo OF_DESIGNATED_INITIALIZER; - (instancetype)initWithSocket: (id)socket errNo: (int)errNo OF_UNAVAILABLE; @end OF_ASSUME_NONNULL_END Index: src/exceptions/OFBindDDPSocketFailedException.m ================================================================== --- src/exceptions/OFBindDDPSocketFailedException.m +++ src/exceptions/OFBindDDPSocketFailedException.m @@ -19,25 +19,28 @@ #import "OFData.h" #import "OFString.h" @implementation OFBindDDPSocketFailedException @synthesize network = _network, node = _node, port = _port; +@synthesize protocolType = _protocolType; + (instancetype)exceptionWithSocket: (id)sock errNo: (int)errNo { OF_UNRECOGNIZED_SELECTOR } + (instancetype)exceptionWithNetwork: (uint16_t)network node: (uint8_t)node port: (uint8_t)port + protocolType: (uint8_t)protocolType socket: (id)sock errNo: (int)errNo { return [[[self alloc] initWithNetwork: network node: node port: port + protocolType: protocolType socket: sock errNo: errNo] autorelease]; } - (instancetype)initWithSocket: (id)sock errNo: (int)errNo @@ -46,19 +49,21 @@ } - (instancetype)initWithNetwork: (uint16_t)network node: (uint8_t)node port: (uint8_t)port + protocolType: (uint8_t)protocolType socket: (id)sock errNo: (int)errNo { self = [super initWithSocket: sock errNo: errNo]; @try { _network = network; _node = node; _port = port; + _protocolType = protocolType; } @catch (id e) { [self release]; @throw e; } @@ -67,9 +72,11 @@ - (OFString *)description { return [OFString stringWithFormat: @"Binding to port %" @PRIx8 @" of node %" @PRIx8 @" on network " - @"%" PRIx16 @" failed in socket of type %@: %@", - _port, _node, _network, [_socket class], OFStrError(_errNo)]; + @"%" PRIx16 @" with protocol type " @PRIx8 @" failed in socket of " + @"type %@: %@", + _port, _node, _network, _protocolType, [_socket class], + OFStrError(_errNo)]; } @end Index: tests/OFDDPSocketTests.m ================================================================== --- tests/OFDDPSocketTests.m +++ tests/OFDDPSocketTests.m @@ -31,24 +31,29 @@ TEST(@"+[socket]", (sock = [OFDDPSocket socket])) @try { TEST(@"-[bindToNetwork:node:port:]", - R(address1 = [sock bindToNetwork: 0 node: 0 port: 0])) + R(address1 = [sock bindToNetwork: 0 + node: 0 + port: 0 + protocolType: 11])) } @catch (OFBindSocketFailedException *e) { switch (e.errNo) { case EAFNOSUPPORT: [OFStdOut setForegroundColor: [OFColor lime]]; [OFStdOut writeLine: - @"\r[OFDDPSocket] -[bindToNetwork:node:port:] " - @"AppleTalk unsupported, skipping tests"]; + @"\r[OFDDPSocket] -[bindToNetwork:node:port:" + @"protocolType:] AppleTalk unsupported, skipping " + @"tests"]; break; case EADDRNOTAVAIL: [OFStdOut setForegroundColor: [OFColor lime]]; [OFStdOut writeLine: - @"\r[OFDDPSocket] -[bindToNetwork:node:port:] " - @"AppleTalk not configured, skipping tests"]; + @"\r[OFDDPSocket] -[bindToNetwork:node:port:" + @"protocolType:] AppleTalk not configured, " + @"skipping tests"]; break; default: @throw e; }