diff --git a/aiocoap/cli/client.py b/aiocoap/cli/client.py index 518fcc2b..a03821f3 100644 --- a/aiocoap/cli/client.py +++ b/aiocoap/cli/client.py @@ -44,6 +44,7 @@ def build_parser(): p.add_argument('--payload-initial-szx', help="Size exponent to limit the initial block's size (0 ≙ 16 Byte, 6 ≙ 1024 Byte)", metavar="SZX", type=int) p.add_argument('--content-format', help="Content format of the --payload data. If a known format is given and --payload has a non-file argument, conversion is attempted (currently only JSON/Python-literals to CBOR).", metavar="MIME") p.add_argument('--no-set-hostname', help="Suppress transmission of Uri-Host even if the host name is not an IP literal", dest="set_hostname", action='store_false', default=True) + p.add_argument('-b', '--broadcast', help="Set SO_BROADCAST for UDP non-interative/single requests", dest="broadcast", action='store_true', default=False) p.add_argument('-v', '--verbose', help="Increase the debug output", action="count") p.add_argument('-q', '--quiet', help="Decrease the debug output", action="count") p.add_argument('--interactive', help="Enter interactive mode", action="store_true") # careful: picked before parsing @@ -335,7 +336,10 @@ async def single_request(args, context): async def single_request_with_context(args): """Wrapper around single_request until sync_main gets made fully async, and async context managers are used to manage contexts.""" - context = await aiocoap.Context.create_client_context() + parser = build_parser() + options = parser.parse_args(args) + + context = await aiocoap.Context.create_client_context(broadcast=options.broadcast) try: await single_request(args, context) finally: diff --git a/aiocoap/protocol.py b/aiocoap/protocol.py index 961cc0e1..47fe4d31 100644 --- a/aiocoap/protocol.py +++ b/aiocoap/protocol.py @@ -140,7 +140,7 @@ async def _append_tokenmanaged_transport(self, token_interface_constructor): self.request_interfaces.append(tman) @classmethod - async def create_client_context(cls, *, loggername="coap", loop=None, transports: Optional[List[str]] = None): + async def create_client_context(cls, *, loggername="coap", loop=None, transports: Optional[List[str]] = None, broadcast: bool = False): """Create a context bound to all addresses on a random listening port. This is the easiest way to get a context suitable for sending client @@ -159,7 +159,7 @@ async def create_client_context(cls, *, loggername="coap", loop=None, transports if transportname == 'udp6': from .transports.udp6 import MessageInterfaceUDP6 await self._append_tokenmanaged_messagemanaged_transport( - lambda mman: MessageInterfaceUDP6.create_client_transport_endpoint(mman, log=self.log, loop=loop)) + lambda mman: MessageInterfaceUDP6.create_client_transport_endpoint(mman, log=self.log, loop=loop, broadcast=broadcast)) elif transportname == 'simple6': from .transports.simple6 import MessageInterfaceSimple6 await self._append_tokenmanaged_messagemanaged_transport( @@ -241,13 +241,11 @@ async def create_server_context(cls, site, bind=None, *, loggername="coap-server lambda mman: MessageInterfaceSimple6.create_client_transport_endpoint(mman, log=self.log, loop=loop)) elif transportname == 'tinydtls': from .transports.tinydtls import MessageInterfaceTinyDTLS - await self._append_tokenmanaged_messagemanaged_transport( lambda mman: MessageInterfaceTinyDTLS.create_client_transport_endpoint(mman, log=self.log, loop=loop)) # FIXME end duplication elif transportname == 'tinydtls_server': from .transports.tinydtls_server import MessageInterfaceTinyDTLSServer - await self._append_tokenmanaged_messagemanaged_transport( lambda mman: MessageInterfaceTinyDTLSServer.create_server(bind, mman, log=self.log, loop=loop, server_credentials=self.server_credentials)) elif transportname == 'simplesocketserver': diff --git a/aiocoap/tokenmanager.py b/aiocoap/tokenmanager.py index 1f3bec03..60611068 100644 --- a/aiocoap/tokenmanager.py +++ b/aiocoap/tokenmanager.py @@ -256,7 +256,13 @@ def request(self, request): self.outgoing_requests[key] = request request.on_interest_end(functools.partial(self.outgoing_requests.pop, key, None)) -''' +'''Not implemented def multicast_request(self, request): return MulticastRequest(self, request).responses ''' + +'''Not implemented + def broadcast_request(self, request): + return MulticastRequest(self, request).responses +''' + diff --git a/aiocoap/transports/udp6.py b/aiocoap/transports/udp6.py index e2280f48..a38cd9da 100644 --- a/aiocoap/transports/udp6.py +++ b/aiocoap/transports/udp6.py @@ -247,11 +247,20 @@ def _local_port(self): return self.transport.get_extra_info('socket').getsockname()[1] @classmethod - async def _create_transport_endpoint(cls, sock, ctx: interfaces.MessageManager, log, loop, multicast=[]): + async def _create_transport_endpoint(cls, sock, ctx: interfaces.MessageManager, log, loop, multicast=[], broadcast: bool = False): try: sock.setsockopt(socket.IPPROTO_IPV6, socknumbers.IPV6_RECVPKTINFO, 1) except NameError: raise RuntimeError("RFC3542 PKTINFO flags are unavailable, unable to create a udp6 transport.") + if broadcast is True: + try: + # This shouldn't be needed, at least not when using --broadcast + # with aiocoap-client. + if not sock.getsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST): + sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) + except Exception: + log.fatal("Unable to set socket to SO_BROADCAST; setsockopt() failed") + raise if socknumbers.HAS_RECVERR: sock.setsockopt(socket.IPPROTO_IPV6, socknumbers.IPV6_RECVERR, 1) # i'm curious why this is required; didn't IPV6_V6ONLY=0 already make @@ -299,9 +308,15 @@ async def _create_transport_endpoint(cls, sock, ctx: interfaces.MessageManager, return protocol @classmethod - async def create_client_transport_endpoint(cls, ctx: interfaces.MessageManager, log, loop): + async def create_client_transport_endpoint(cls, ctx: interfaces.MessageManager, log, loop, broadcast: bool = False): sock = socket.socket(family=socket.AF_INET6, type=socket.SOCK_DGRAM) sock.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 0) + if broadcast is True: + try: + sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) + except Exception: + log.fatal("Unable to set socket to SO_BROADCAST; setsockopt() failed") + raise return await cls._create_transport_endpoint(sock, ctx, log, loop)