From be19a5655846abceee68dde2a06647cd6addc285 Mon Sep 17 00:00:00 2001 From: Daniel Murfin Date: Fri, 16 Oct 2020 12:05:58 +0100 Subject: [PATCH] Added methods for setting the interface on which multicast messages will be sent. --- Source/GCD/GCDAsyncUdpSocket.h | 12 ++++ Source/GCD/GCDAsyncUdpSocket.m | 115 +++++++++++++++++++++++++++++++++ 2 files changed, 127 insertions(+) diff --git a/Source/GCD/GCDAsyncUdpSocket.h b/Source/GCD/GCDAsyncUdpSocket.h index c98a4480..af327e08 100644 --- a/Source/GCD/GCDAsyncUdpSocket.h +++ b/Source/GCD/GCDAsyncUdpSocket.h @@ -490,6 +490,18 @@ typedef BOOL (^GCDAsyncUdpSocketSendFilterBlock)(NSData *data, NSData *address, - (BOOL)leaveMulticastGroup:(NSString *)group error:(NSError **)errPtr; - (BOOL)leaveMulticastGroup:(NSString *)group onInterface:(nullable NSString *)interface error:(NSError **)errPtr; +/** + * Send multicast on a specified interface. + * For IPv4, interface should be the the IP address of the interface (eg @"192.168.10.1"). + * For IPv6, interface should be the a network interface name (eg @"en0"). + * + * On success, returns YES. + * Otherwise returns NO, and sets errPtr. If you don't care about the error, you can pass nil for errPtr. +**/ + +- (BOOL)sendIPv4MulticastOnInterface:(NSString*)interface error:(NSError **)errPtr; +- (BOOL)sendIPv6MulticastOnInterface:(NSString*)interface error:(NSError **)errPtr; + #pragma mark Reuse Port /** diff --git a/Source/GCD/GCDAsyncUdpSocket.m b/Source/GCD/GCDAsyncUdpSocket.m index e2979913..b0c59c31 100755 --- a/Source/GCD/GCDAsyncUdpSocket.m +++ b/Source/GCD/GCDAsyncUdpSocket.m @@ -3531,6 +3531,121 @@ - (BOOL)performMulticastRequest:(int)requestType return result; } +- (BOOL)sendIPv4MulticastOnInterface:(NSString*)interface error:(NSError **)errPtr +{ + __block BOOL result = NO; + __block NSError *err = nil; + + dispatch_block_t block = ^{ @autoreleasepool { + + if (![self preOp:&err]) + { + return_from_block; + } + + if ((self->flags & kDidCreateSockets) == 0) + { + if (![self createSockets:&err]) + { + return_from_block; + } + } + + // Convert interface to address + + NSData *interfaceAddr4 = nil; + NSData *interfaceAddr6 = nil; + + [self convertIntefaceDescription:interface port:0 intoAddress4:&interfaceAddr4 address6:&interfaceAddr6]; + + if (interfaceAddr4 == nil) + { + NSString *msg = @"Unknown interface. Specify valid interface by IP address."; + err = [self badParamError:msg]; + return_from_block; + } + + if (self->socket4FD != SOCKET_NULL) { + const struct sockaddr_in *nativeIface = (struct sockaddr_in *)[interfaceAddr4 bytes]; + struct in_addr interface_addr = nativeIface->sin_addr; + int status = setsockopt(self->socket4FD, IPPROTO_IP, IP_MULTICAST_IF, &interface_addr, sizeof(interface_addr)); + if (status != 0) { + err = [self errnoErrorWithReason:@"Error in setsockopt() function"]; + return_from_block; + result = YES; + } + } + + }}; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, block); + + if (errPtr) + *errPtr = err; + + return result; +} + +- (BOOL)sendIPv6MulticastOnInterface:(NSString*)interface error:(NSError **)errPtr +{ + __block BOOL result = NO; + __block NSError *err = nil; + + dispatch_block_t block = ^{ @autoreleasepool { + + if (![self preOp:&err]) + { + return_from_block; + } + + if ((self->flags & kDidCreateSockets) == 0) + { + if (![self createSockets:&err]) + { + return_from_block; + } + } + + // Convert interface to address + + NSData *interfaceAddr4 = nil; + NSData *interfaceAddr6 = nil; + + [self convertIntefaceDescription:interface port:0 intoAddress4:&interfaceAddr4 address6:&interfaceAddr6]; + + if (interfaceAddr6 == nil) + { + NSString *msg = @"Unknown interface. Specify valid interface by name (e.g. \"en1\")."; + err = [self badParamError:msg]; + return_from_block; + } + + if ((self->socket6FD != SOCKET_NULL)) { + uint32_t scope_id = [self indexOfInterfaceAddr6:interfaceAddr6]; + int status = setsockopt(self->socket6FD, IPPROTO_IPV6, IPV6_MULTICAST_IF, &scope_id, sizeof(scope_id)); + if (status != 0) { + err = [self errnoErrorWithReason:@"Error in setsockopt() function"]; + return_from_block; + } + result = YES; + } + + }}; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, block); + + if (errPtr) + *errPtr = err; + + return result; +} + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #pragma mark Reuse port ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////