From 0ea5574fa5b516a3ed6e9912dc3c1c7bc695cce9 Mon Sep 17 00:00:00 2001 From: nickkelsey Date: Mon, 10 Oct 2022 20:31:24 -0700 Subject: [PATCH] IPv6 support --- Makefile | 81 +- README.md | 2 +- hdhomerun_config.c | 139 ++- hdhomerun_control.c | 148 ++- hdhomerun_control.h | 7 +- hdhomerun_device.c | 460 ++++++--- hdhomerun_device.h | 9 +- hdhomerun_discover.c | 1894 +++++++++++++++++++++++++++-------- hdhomerun_discover.h | 145 ++- hdhomerun_pkt.h | 3 +- hdhomerun_sock.c | 380 +++++++ hdhomerun_sock.h | 43 +- hdhomerun_sock_getifaddrs.c | 130 +++ hdhomerun_sock_netdevice.c | 130 +++ hdhomerun_sock_netlink.c | 192 ++++ hdhomerun_sock_posix.c | 382 +++---- hdhomerun_sock_windows.c | 383 +++---- hdhomerun_video.c | 91 +- hdhomerun_video.h | 6 +- 19 files changed, 3527 insertions(+), 1098 deletions(-) create mode 100644 hdhomerun_sock.c create mode 100644 hdhomerun_sock_getifaddrs.c create mode 100644 hdhomerun_sock_netdevice.c create mode 100644 hdhomerun_sock_netlink.c diff --git a/Makefile b/Makefile index 39cad38..c22bc98 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,31 @@ +ifneq ($(OS),Windows_NT) +OS := $(shell uname -s) +endif + +CC := $(CROSS_COMPILE)gcc +STRIP := $(CROSS_COMPILE)strip + +CFLAGS += -O2 -Wall -Wextra -Wmissing-declarations -Wmissing-prototypes -Wstrict-prototypes -Wpointer-arith -Wno-unused-parameter +LDFLAGS += -lpthread +SHARED = -shared -Wl,-soname,libhdhomerun$(LIBEXT) + +IF_DETECT := getifaddrs +BINEXT := +LIBEXT := .so + +ifeq ($(OS),Windows_NT) + IF_DETECT := netdevice + BINEXT := .exe + LIBEXT := .dll + LDFLAGS += -liphlpapi +endif + +ifeq ($(OS),Linux) + IF_DETECT := netlink + LDFLAGS += -lrt +endif + LIBSRCS += hdhomerun_channels.c LIBSRCS += hdhomerun_channelscan.c LIBSRCS += hdhomerun_control.c @@ -8,35 +35,39 @@ LIBSRCS += hdhomerun_device_selector.c LIBSRCS += hdhomerun_discover.c LIBSRCS += hdhomerun_os_posix.c LIBSRCS += hdhomerun_pkt.c +LIBSRCS += hdhomerun_sock.c LIBSRCS += hdhomerun_sock_posix.c +LIBSRCS += hdhomerun_sock_$(IF_DETECT).c LIBSRCS += hdhomerun_video.c -CC := $(CROSS_COMPILE)gcc -STRIP := $(CROSS_COMPILE)strip +ifeq ($(OS),Darwin) -CFLAGS += -O2 -Wall -Wextra -Wmissing-declarations -Wmissing-prototypes -Wstrict-prototypes -Wpointer-arith -Wno-unused-parameter -LDFLAGS += -lpthread -SHARED = -shared -Wl,-soname,libhdhomerun$(LIBEXT) +TARGET_X64 := -target x86_64-apple-macos10.11 +TARGET_ARM64 := -target arm64-apple-macos11 + +all : hdhomerun_config libhdhomerun.dylib + +hdhomerun_config_x64 : hdhomerun_config.c $(LIBSRCS) + $(CC) $(TARGET_X64) $(CFLAGS) $+ $(LDFLAGS) -o $@ + $(STRIP) $@ + +hdhomerun_config_arm64 : hdhomerun_config.c $(LIBSRCS) + $(CC) $(TARGET_ARM64) $(CFLAGS) $+ $(LDFLAGS) -o $@ + $(STRIP) $@ + +hdhomerun_config : hdhomerun_config_x64 hdhomerun_config_arm64 + lipo -create -output hdhomerun_config hdhomerun_config_x64 hdhomerun_config_arm64 + +libhdhomerun_x64.dylib : $(LIBSRCS) + $(CC) $(TARGET_X64) $(CFLAGS) -DDLL_EXPORT -fPIC -dynamiclib $+ $(LDFLAGS) -o $@ + +libhdhomerun_arm64.dylib : $(LIBSRCS) + $(CC) $(TARGET_ARM64) $(CFLAGS) -DDLL_EXPORT -fPIC -dynamiclib $+ $(LDFLAGS) -o $@ + +libhdhomerun.dylib : libhdhomerun_x64.dylib libhdhomerun_arm64.dylib + lipo -create -output libhdhomerun.dylib libhdhomerun_x64.dylib libhdhomerun_arm64.dylib -ifeq ($(OS),Windows_NT) - BINEXT := .exe - LIBEXT := .dll - LDFLAGS += -liphlpapi else - OS := $(shell uname -s) - LIBEXT := .so - ifeq ($(OS),Linux) - LDFLAGS += -lrt - endif - ifeq ($(OS),SunOS) - LDFLAGS += -lsocket - endif - ifeq ($(OS),Darwin) - CFLAGS += -arch x86_64 - LIBEXT := .dylib - SHARED := -dynamiclib -install_name libhdhomerun$(LIBEXT) - endif -endif all : hdhomerun_config$(BINEXT) libhdhomerun$(LIBEXT) @@ -45,7 +76,9 @@ hdhomerun_config$(BINEXT) : hdhomerun_config.c $(LIBSRCS) $(STRIP) $@ libhdhomerun$(LIBEXT) : $(LIBSRCS) - $(CC) $(CFLAGS) -fPIC -DDLL_EXPORT $(SHARED) $+ $(LDFLAGS) -o $@ + $(CC) $(CFLAGS) -DDLL_EXPORT -fPIC $(SHARED) $+ $(LDFLAGS) -o $@ + +endif clean : -rm -f hdhomerun_config$(BINEXT) diff --git a/README.md b/README.md index c73e2a8..1f3a6dd 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -Copyright © 2005-2017 Silicondust USA Inc. . +Copyright © 2005-2022 Silicondust USA Inc. . This library implements the libhdhomerun protocol for use with Silicondust HDHomeRun TV tuners. diff --git a/hdhomerun_config.c b/hdhomerun_config.c index ec289ed..c97e384 100644 --- a/hdhomerun_config.c +++ b/hdhomerun_config.c @@ -1,7 +1,7 @@ /* * hdhomerun_config.c * - * Copyright © 2006-2017 Silicondust USA Inc. . + * Copyright © 2006-2022 Silicondust USA Inc. . * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -27,7 +27,7 @@ struct hdhomerun_device_t *hd; static int help(void) { printf("Usage:\n"); - printf("\t%s discover\n", appname); + printf("\t%s discover [-4] [-6] [--dedupe] []\n", appname); printf("\t%s get help\n", appname); printf("\t%s get \n", appname); printf("\t%s set \n", appname); @@ -69,59 +69,102 @@ static bool contains(const char *arg, const char *cmpstr) return false; } -static uint32_t parse_ip_addr(const char *str) +static int discover_print(int argc, char *argv[]) { - unsigned int a[4]; - if (sscanf(str, "%u.%u.%u.%u", &a[0], &a[1], &a[2], &a[3]) != 4) { - return 0; - } + const char *target_ip_str = NULL; + uint32_t flags = 0; + bool dedupe = false; - return (uint32_t)((a[0] << 24) | (a[1] << 16) | (a[2] << 8) | (a[3] << 0)); -} - -static int discover_print(char *target_ip_str) -{ - uint32_t target_ip = 0; - if (target_ip_str) { - target_ip = parse_ip_addr(target_ip_str); - if (target_ip == 0) { - fprintf(stderr, "invalid ip address: %s\n", target_ip_str); - return -1; - } - } - - struct hdhomerun_discover_device_t result_list[64]; - int result_count = hdhomerun_discover_find_devices_custom_v2(target_ip, HDHOMERUN_DEVICE_TYPE_WILDCARD, HDHOMERUN_DEVICE_ID_WILDCARD, result_list, 64); - if (result_count < 0) { - fprintf(stderr, "error sending discover request\n"); - return -1; - } - - struct hdhomerun_discover_device_t *result = result_list; - struct hdhomerun_discover_device_t *result_end = result_list + result_count; - - int valid_count = 0; - while (result < result_end) { - if (result->device_id == 0) { - result++; + while (argc--) { + char *param = *argv++; + if (param[0] != '-') { + target_ip_str = param; continue; } - printf("hdhomerun device %08X found at %u.%u.%u.%u\n", - (unsigned int)result->device_id, - (unsigned int)(result->ip_addr >> 24) & 0x0FF, (unsigned int)(result->ip_addr >> 16) & 0x0FF, - (unsigned int)(result->ip_addr >> 8) & 0x0FF, (unsigned int)(result->ip_addr >> 0) & 0x0FF - ); + if (contains(param, "-4")) { + flags |= HDHOMERUN_DISCOVER_FLAGS_IPV4_GENERAL; + continue; + } + if (contains(param, "-6")) { + flags |= HDHOMERUN_DISCOVER_FLAGS_IPV6_GENERAL | HDHOMERUN_DISCOVER_FLAGS_IPV6_LINKLOCAL; + continue; + } - valid_count++; - result++; + if (contains(param, "dedupe")) { + dedupe = true; + continue; + } } - if (valid_count == 0) { + if (flags == 0) { + flags = HDHOMERUN_DISCOVER_FLAGS_IPV4_GENERAL | HDHOMERUN_DISCOVER_FLAGS_IPV6_GENERAL | HDHOMERUN_DISCOVER_FLAGS_IPV6_LINKLOCAL; + } + + struct hdhomerun_discover_t *ds = hdhomerun_discover_create(NULL); + if (!ds) { + fprintf(stderr, "resource error\n"); + return -1; + } + + /* Most applications will specify a list of specific types rather than wildcard */ + uint32_t device_types[1]; + device_types[0] = HDHOMERUN_DEVICE_TYPE_WILDCARD; + + int ret; + if (target_ip_str) { + struct sockaddr_storage target_addr; + if (!hdhomerun_sock_ip_str_to_sockaddr(target_ip_str , &target_addr)) { + fprintf(stderr, "invalid ip address: %s\n", target_ip_str); + hdhomerun_discover_destroy(ds); + return -1; + } + ret = hdhomerun_discover2_find_devices_targeted(ds, (struct sockaddr *)&target_addr, device_types, 1); + } else { + ret = hdhomerun_discover2_find_devices_broadcast(ds, flags, device_types, 1); + } + + if (ret < 0) { + fprintf(stderr, "error sending discover request\n"); + hdhomerun_discover_destroy(ds); + return -1; + } + + if (ret == 0) { printf("no devices found\n"); + hdhomerun_discover_destroy(ds); + return 0; } - return valid_count; + struct hdhomerun_discover2_device_t *device = hdhomerun_discover2_iter_device_first(ds); + while (device) { + uint32_t device_id = hdhomerun_discover2_device_get_device_id(device); + if (device_id == 0) { + device = hdhomerun_discover2_iter_device_next(device); + continue; + } + + struct hdhomerun_discover2_device_if_t *device_if = hdhomerun_discover2_iter_device_if_first(device); + while (device_if) { + struct sockaddr_storage ip_addr; + hdhomerun_discover2_device_if_get_ip_addr(device_if, &ip_addr); + + char ip_str[64]; + hdhomerun_sock_sockaddr_to_ip_str(ip_str, (struct sockaddr *)&ip_addr, true); + printf("hdhomerun device %08X found at %s\n", device_id, ip_str); + + if (dedupe) { + break; + } + + device_if = hdhomerun_discover2_iter_device_if_next(device_if); + } + + device = hdhomerun_discover2_iter_device_next(device); + } + + hdhomerun_discover_destroy(ds); + return 1; } static int cmd_get(const char *item) @@ -632,7 +675,7 @@ static int main_internal(int argc, char *argv[]) /* Initialize network socket support. */ WORD wVersionRequested = MAKEWORD(2, 0); WSADATA wsaData; - WSAStartup(wVersionRequested, &wsaData); + (void)WSAStartup(wVersionRequested, &wsaData); #endif extract_appname(argv[0]); @@ -648,11 +691,7 @@ static int main_internal(int argc, char *argv[]) return help(); } if (contains(id_str, "discover")) { - if (argc < 1) { - return discover_print(NULL); - } else { - return discover_print(argv[0]); - } + return discover_print(argc, argv); } /* Device object. */ diff --git a/hdhomerun_control.c b/hdhomerun_control.c index d9a21e5..d09d607 100644 --- a/hdhomerun_control.c +++ b/hdhomerun_control.c @@ -1,7 +1,7 @@ /* * hdhomerun_control.c * - * Copyright © 2006-2016 Silicondust USA Inc. . + * Copyright © 2006-2022 Silicondust USA Inc. . * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -27,9 +27,9 @@ struct hdhomerun_control_sock_t { uint32_t desired_device_id; - uint32_t desired_device_ip; uint32_t actual_device_id; - uint32_t actual_device_ip; + struct sockaddr_storage desired_device_addr; + struct sockaddr_storage actual_device_addr; struct hdhomerun_sock_t *sock; struct hdhomerun_debug_t *dbg; struct hdhomerun_pkt_t tx_pkt; @@ -47,16 +47,36 @@ static void hdhomerun_control_close_sock(struct hdhomerun_control_sock_t *cs) } void hdhomerun_control_set_device(struct hdhomerun_control_sock_t *cs, uint32_t device_id, uint32_t device_ip) +{ + struct sockaddr_in device_addr; + memset(&device_addr, 0, sizeof(device_addr)); + device_addr.sin_family = AF_INET; + device_addr.sin_addr.s_addr = htonl(device_ip); + + hdhomerun_control_set_device_ex(cs, device_id, (const struct sockaddr *)&device_addr); +} + +void hdhomerun_control_set_device_ex(struct hdhomerun_control_sock_t *cs, uint32_t device_id, const struct sockaddr *device_addr) { hdhomerun_control_close_sock(cs); cs->desired_device_id = device_id; - cs->desired_device_ip = device_ip; cs->actual_device_id = 0; - cs->actual_device_ip = 0; + hdhomerun_sock_sockaddr_copy(&cs->desired_device_addr, device_addr); + memset(&cs->actual_device_addr, 0, sizeof(cs->actual_device_addr)); } struct hdhomerun_control_sock_t *hdhomerun_control_create(uint32_t device_id, uint32_t device_ip, struct hdhomerun_debug_t *dbg) +{ + struct sockaddr_in device_addr; + memset(&device_addr, 0, sizeof(device_addr)); + device_addr.sin_family = AF_INET; + device_addr.sin_addr.s_addr = htonl(device_ip); + + return hdhomerun_control_create_ex(device_id, (const struct sockaddr *)&device_addr, dbg); +} + +struct hdhomerun_control_sock_t *hdhomerun_control_create_ex(uint32_t device_id, const struct sockaddr *device_addr, struct hdhomerun_debug_t *dbg) { struct hdhomerun_control_sock_t *cs = (struct hdhomerun_control_sock_t *)calloc(1, sizeof(struct hdhomerun_control_sock_t)); if (!cs) { @@ -65,7 +85,7 @@ struct hdhomerun_control_sock_t *hdhomerun_control_create(uint32_t device_id, ui } cs->dbg = dbg; - hdhomerun_control_set_device(cs, device_id, device_ip); + hdhomerun_control_set_device_ex(cs, device_id, device_addr); return cs; } @@ -76,39 +96,82 @@ void hdhomerun_control_destroy(struct hdhomerun_control_sock_t *cs) free(cs); } +static bool hdhomerun_control_connect_sock_discover(struct hdhomerun_control_sock_t *cs) +{ + struct hdhomerun_discover_t *ds = hdhomerun_discover_create(NULL); + if (!ds) { + return false; + } + + uint32_t device_types[1]; + device_types[0] = HDHOMERUN_DEVICE_TYPE_WILDCARD; + + uint32_t device_id = cs->desired_device_id; + if (device_id == 0) { + device_id = HDHOMERUN_DEVICE_ID_WILDCARD; + } + + int ret; + if (hdhomerun_sock_sockaddr_is_addr((struct sockaddr *)&cs->desired_device_addr)) { + if (device_id == HDHOMERUN_DEVICE_ID_WILDCARD) { + ret = hdhomerun_discover2_find_devices_targeted(ds, (struct sockaddr *)&cs->desired_device_addr, device_types, 1); + } else { + ret = hdhomerun_discover2_find_device_id_targeted(ds, (struct sockaddr *)&cs->desired_device_addr, device_id); + } + } else { + uint32_t flags = HDHOMERUN_DISCOVER_FLAGS_IPV4_GENERAL; + if (device_id == HDHOMERUN_DEVICE_ID_WILDCARD) { + ret = hdhomerun_discover2_find_devices_broadcast(ds, flags, device_types, 1); + } else { + ret = hdhomerun_discover2_find_device_id_broadcast(ds, flags, device_id); + } + } + if (ret <= 0) { + hdhomerun_debug_printf(cs->dbg, "hdhomerun_control_connect_sock: device not found\n"); + hdhomerun_discover_destroy(ds); + return false; + } + + struct hdhomerun_discover2_device_t *device = hdhomerun_discover2_iter_device_first(ds); + cs->actual_device_id = hdhomerun_discover2_device_get_device_id(device); + + struct hdhomerun_discover2_device_if_t *device_if = hdhomerun_discover2_iter_device_if_first(device); + hdhomerun_discover2_device_if_get_ip_addr(device_if, &cs->actual_device_addr); + + hdhomerun_discover_destroy(ds); + return true; +} + static bool hdhomerun_control_connect_sock(struct hdhomerun_control_sock_t *cs) { if (cs->sock) { return true; } - if ((cs->desired_device_id == 0) && (cs->desired_device_ip == 0)) { + if ((cs->desired_device_id == 0) && !hdhomerun_sock_sockaddr_is_addr((struct sockaddr *)&cs->desired_device_addr)) { hdhomerun_debug_printf(cs->dbg, "hdhomerun_control_connect_sock: no device specified\n"); return false; } - if (hdhomerun_discover_is_ip_multicast(cs->desired_device_ip)) { + if (hdhomerun_discover_is_ip_multicast_ex((struct sockaddr *)&cs->desired_device_addr)) { hdhomerun_debug_printf(cs->dbg, "hdhomerun_control_connect_sock: cannot use multicast ip address for device operations\n"); return false; } /* Find device. */ - struct hdhomerun_discover_device_t result; - if (hdhomerun_discover_find_devices_custom_v2(cs->desired_device_ip, HDHOMERUN_DEVICE_TYPE_WILDCARD, cs->desired_device_id, &result, 1) <= 0) { - hdhomerun_debug_printf(cs->dbg, "hdhomerun_control_connect_sock: device not found\n"); + if (!hdhomerun_control_connect_sock_discover(cs)) { return false; } - cs->actual_device_ip = result.ip_addr; - cs->actual_device_id = result.device_id; /* Create socket. */ - cs->sock = hdhomerun_sock_create_tcp(); + cs->sock = hdhomerun_sock_create_tcp_ex(cs->actual_device_addr.ss_family); if (!cs->sock) { hdhomerun_debug_printf(cs->dbg, "hdhomerun_control_connect_sock: failed to create socket (%d)\n", hdhomerun_sock_getlasterror()); return false; } /* Initiate connection. */ - if (!hdhomerun_sock_connect(cs->sock, cs->actual_device_ip, HDHOMERUN_CONTROL_TCP_PORT, HDHOMERUN_CONTROL_CONNECT_TIMEOUT)) { + hdhomerun_sock_sockaddr_set_port((struct sockaddr *)&cs->actual_device_addr, HDHOMERUN_CONTROL_TCP_PORT); + if (!hdhomerun_sock_connect_ex(cs->sock, (struct sockaddr *)&cs->actual_device_addr, HDHOMERUN_CONTROL_CONNECT_TIMEOUT)) { hdhomerun_debug_printf(cs->dbg, "hdhomerun_control_connect_sock: failed to connect (%d)\n", hdhomerun_sock_getlasterror()); hdhomerun_control_close_sock(cs); return false; @@ -135,7 +198,24 @@ uint32_t hdhomerun_control_get_device_ip(struct hdhomerun_control_sock_t *cs) return 0; } - return cs->actual_device_ip; + if (cs->actual_device_addr.ss_family != AF_INET) { + return 0; + } + + struct sockaddr_in *actual_device_addr_in = (struct sockaddr_in *)&cs->actual_device_addr; + return ntohl(actual_device_addr_in->sin_addr.s_addr); +} + +bool hdhomerun_control_get_device_addr(struct hdhomerun_control_sock_t *cs, struct sockaddr_storage *result) +{ + if (!hdhomerun_control_connect_sock(cs)) { + hdhomerun_debug_printf(cs->dbg, "hdhomerun_control_get_device_ip: connect failed\n"); + memset(result, 0, sizeof(struct sockaddr_storage)); + return false; + } + + *result = cs->actual_device_addr; + return hdhomerun_sock_sockaddr_is_addr((struct sockaddr *)result); } uint32_t hdhomerun_control_get_device_id_requested(struct hdhomerun_control_sock_t *cs) @@ -145,23 +225,47 @@ uint32_t hdhomerun_control_get_device_id_requested(struct hdhomerun_control_sock uint32_t hdhomerun_control_get_device_ip_requested(struct hdhomerun_control_sock_t *cs) { - return cs->desired_device_ip; + if (cs->desired_device_addr.ss_family != AF_INET) { + return 0; + } + + struct sockaddr_in *desired_device_addr_in = (struct sockaddr_in *)&cs->desired_device_addr; + return ntohl(desired_device_addr_in->sin_addr.s_addr); +} + +bool hdhomerun_control_get_device_addr_requested(struct hdhomerun_control_sock_t *cs, struct sockaddr_storage *result) +{ + *result = cs->desired_device_addr; + return hdhomerun_sock_sockaddr_is_addr((struct sockaddr *)result); } uint32_t hdhomerun_control_get_local_addr(struct hdhomerun_control_sock_t *cs) +{ + struct sockaddr_storage local_addr; + if (!hdhomerun_control_get_local_addr_ex(cs, &local_addr)) { + return 0; + } + if (local_addr.ss_family != AF_INET) { + return 0; + } + + struct sockaddr_in *local_addr_in = (struct sockaddr_in *)&local_addr; + return ntohl(local_addr_in->sin_addr.s_addr); +} + +bool hdhomerun_control_get_local_addr_ex(struct hdhomerun_control_sock_t *cs, struct sockaddr_storage *result) { if (!hdhomerun_control_connect_sock(cs)) { hdhomerun_debug_printf(cs->dbg, "hdhomerun_control_get_local_addr: connect failed\n"); - return 0; + return false; } - uint32_t addr = hdhomerun_sock_getsockname_addr(cs->sock); - if (addr == 0) { + if (!hdhomerun_sock_getsockname_addr_ex(cs->sock, result)) { hdhomerun_debug_printf(cs->dbg, "hdhomerun_control_get_local_addr: getsockname failed (%d)\n", hdhomerun_sock_getlasterror()); - return 0; + return false; } - return addr; + return true; } static bool hdhomerun_control_send_sock(struct hdhomerun_control_sock_t *cs, struct hdhomerun_pkt_t *tx_pkt) diff --git a/hdhomerun_control.h b/hdhomerun_control.h index a598ce2..e830b58 100644 --- a/hdhomerun_control.h +++ b/hdhomerun_control.h @@ -1,7 +1,7 @@ /* * hdhomerun_control.h * - * Copyright © 2006-2015 Silicondust USA Inc. . + * Copyright © 2006-2022 Silicondust USA Inc. . * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -38,6 +38,7 @@ struct hdhomerun_control_sock_t; * When no longer needed, the socket should be destroyed by calling hdhomerun_control_destroy. */ extern LIBHDHOMERUN_API struct hdhomerun_control_sock_t *hdhomerun_control_create(uint32_t device_id, uint32_t device_ip, struct hdhomerun_debug_t *dbg); +extern LIBHDHOMERUN_API struct hdhomerun_control_sock_t *hdhomerun_control_create_ex(uint32_t device_id, const struct sockaddr *device_addr, struct hdhomerun_debug_t *dbg); extern LIBHDHOMERUN_API void hdhomerun_control_destroy(struct hdhomerun_control_sock_t *cs); /* @@ -47,10 +48,13 @@ extern LIBHDHOMERUN_API void hdhomerun_control_destroy(struct hdhomerun_control_ */ extern LIBHDHOMERUN_API uint32_t hdhomerun_control_get_device_id(struct hdhomerun_control_sock_t *cs); extern LIBHDHOMERUN_API uint32_t hdhomerun_control_get_device_ip(struct hdhomerun_control_sock_t *cs); +extern LIBHDHOMERUN_API bool hdhomerun_control_get_device_addr(struct hdhomerun_control_sock_t *cs, struct sockaddr_storage *result); extern LIBHDHOMERUN_API uint32_t hdhomerun_control_get_device_id_requested(struct hdhomerun_control_sock_t *cs); extern LIBHDHOMERUN_API uint32_t hdhomerun_control_get_device_ip_requested(struct hdhomerun_control_sock_t *cs); +extern LIBHDHOMERUN_API bool hdhomerun_control_get_device_addr_requested(struct hdhomerun_control_sock_t *cs, struct sockaddr_storage *result); extern LIBHDHOMERUN_API void hdhomerun_control_set_device(struct hdhomerun_control_sock_t *cs, uint32_t device_id, uint32_t device_ip); +extern LIBHDHOMERUN_API void hdhomerun_control_set_device_ex(struct hdhomerun_control_sock_t *cs, uint32_t device_id, const struct sockaddr *device_addr); /* * Get the local machine IP address used when communicating with the device. @@ -60,6 +64,7 @@ extern LIBHDHOMERUN_API void hdhomerun_control_set_device(struct hdhomerun_contr * Returns 32-bit IP address with native endianness, or 0 on error. */ extern LIBHDHOMERUN_API uint32_t hdhomerun_control_get_local_addr(struct hdhomerun_control_sock_t *cs); +extern LIBHDHOMERUN_API bool hdhomerun_control_get_local_addr_ex(struct hdhomerun_control_sock_t *cs, struct sockaddr_storage *result); /* * Low-level communication. diff --git a/hdhomerun_device.c b/hdhomerun_device.c index dbcefe8..161abae 100644 --- a/hdhomerun_device.c +++ b/hdhomerun_device.c @@ -1,7 +1,7 @@ /* * hdhomerun_device.c * - * Copyright © 2006-2015 Silicondust USA Inc. . + * Copyright © 2006-2022 Silicondust USA Inc. . * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -25,8 +25,7 @@ struct hdhomerun_device_t { struct hdhomerun_video_sock_t *vs; struct hdhomerun_debug_t *dbg; struct hdhomerun_channelscan_t *scan; - uint32_t multicast_ip; - uint16_t multicast_port; + struct sockaddr_storage multicast_addr; uint32_t device_id; unsigned int tuner; uint32_t lockkey; @@ -36,13 +35,23 @@ struct hdhomerun_device_t { int hdhomerun_device_set_device(struct hdhomerun_device_t *hd, uint32_t device_id, uint32_t device_ip) { - if ((device_id == 0) && (device_ip == 0)) { + struct sockaddr_in device_addr; + memset(&device_addr, 0, sizeof(device_addr)); + device_addr.sin_family = AF_INET; + device_addr.sin_addr.s_addr = htonl(device_ip); + + return hdhomerun_device_set_device_ex(hd, device_id, (const struct sockaddr *)&device_addr); +} + +int hdhomerun_device_set_device_ex(struct hdhomerun_device_t *hd, uint32_t device_id, const struct sockaddr *device_addr) +{ + if ((device_id == 0) && !hdhomerun_sock_sockaddr_is_addr(device_addr)) { hdhomerun_debug_printf(hd->dbg, "hdhomerun_device_set_device: device not specified\n"); return -1; } - if (hdhomerun_discover_is_ip_multicast(device_ip)) { - hdhomerun_debug_printf(hd->dbg, "hdhomerun_device_set_device: invalid address %08X\n", (unsigned int)device_ip); + if (hdhomerun_discover_is_ip_multicast_ex(device_addr)) { + hdhomerun_debug_printf(hd->dbg, "hdhomerun_device_set_device: invalid address\n"); return -1; } @@ -54,14 +63,13 @@ int hdhomerun_device_set_device(struct hdhomerun_device_t *hd, uint32_t device_i } } - hdhomerun_control_set_device(hd->cs, device_id, device_ip); + hdhomerun_control_set_device_ex(hd->cs, device_id, device_addr); if ((device_id == 0) || (device_id == HDHOMERUN_DEVICE_ID_WILDCARD)) { device_id = hdhomerun_control_get_device_id(hd->cs); } - hd->multicast_ip = 0; - hd->multicast_port = 0; + memset(&hd->multicast_addr, 0, sizeof(hd->multicast_addr)); hd->device_id = device_id; hd->tuner = 0; hd->lockkey = 0; @@ -74,11 +82,23 @@ int hdhomerun_device_set_device(struct hdhomerun_device_t *hd, uint32_t device_i int hdhomerun_device_set_multicast(struct hdhomerun_device_t *hd, uint32_t multicast_ip, uint16_t multicast_port) { - if (!hdhomerun_discover_is_ip_multicast(multicast_ip)) { - hdhomerun_debug_printf(hd->dbg, "hdhomerun_device_set_device_multicast: invalid address %08X\n", (unsigned int)multicast_ip); + struct sockaddr_in multicast_addr; + memset(&multicast_addr, 0, sizeof(multicast_addr)); + multicast_addr.sin_family = AF_INET; + multicast_addr.sin_addr.s_addr = htonl(multicast_ip); + multicast_addr.sin_port = htons(multicast_port); + + return hdhomerun_device_set_multicast_ex(hd, (const struct sockaddr *)&multicast_addr); +} + +int hdhomerun_device_set_multicast_ex(struct hdhomerun_device_t *hd, const struct sockaddr *multicast_addr) +{ + if (!hdhomerun_discover_is_ip_multicast_ex(multicast_addr)) { + hdhomerun_debug_printf(hd->dbg, "hdhomerun_device_set_device_multicast: invalid address\n"); return -1; } + uint16_t multicast_port = hdhomerun_sock_sockaddr_get_port(multicast_addr); if (multicast_port == 0) { hdhomerun_debug_printf(hd->dbg, "hdhomerun_device_set_device_multicast: invalid port %u\n", (unsigned int)multicast_port); return -1; @@ -89,14 +109,12 @@ int hdhomerun_device_set_multicast(struct hdhomerun_device_t *hd, uint32_t multi hd->cs = NULL; } - hd->multicast_ip = multicast_ip; - hd->multicast_port = multicast_port; + hdhomerun_sock_sockaddr_copy(&hd->multicast_addr, multicast_addr); hd->device_id = 0; hd->tuner = 0; hd->lockkey = 0; - unsigned int ip = multicast_ip; - hdhomerun_sprintf(hd->name, hd->name + sizeof(hd->name), "%u.%u.%u.%u:%u", (ip >> 24) & 0xFF, (ip >> 16) & 0xFF, (ip >> 8) & 0xFF, (ip >> 0) & 0xFF, (unsigned int)multicast_port); + hdhomerun_sprintf(hd->name, hd->name + sizeof(hd->name), "multicast:%u", (unsigned int)multicast_port); hdhomerun_sprintf(hd->model, hd->model + sizeof(hd->model), "multicast"); return 1; @@ -104,7 +122,7 @@ int hdhomerun_device_set_multicast(struct hdhomerun_device_t *hd, uint32_t multi int hdhomerun_device_set_tuner(struct hdhomerun_device_t *hd, unsigned int tuner) { - if (hd->multicast_ip != 0) { + if (hdhomerun_sock_sockaddr_is_addr((struct sockaddr *)&hd->multicast_addr)) { if (tuner != 0) { hdhomerun_debug_printf(hd->dbg, "hdhomerun_device_set_tuner: tuner cannot be specified in multicast mode\n"); return -1; @@ -148,16 +166,30 @@ static struct hdhomerun_device_t *hdhomerun_device_create_internal(struct hdhome struct hdhomerun_device_t *hdhomerun_device_create(uint32_t device_id, uint32_t device_ip, unsigned int tuner, struct hdhomerun_debug_t *dbg) { + struct sockaddr_in device_addr; + memset(&device_addr, 0, sizeof(device_addr)); + device_addr.sin_family = AF_INET; + device_addr.sin_addr.s_addr = htonl(device_ip); + + return hdhomerun_device_create_ex(device_id, (const struct sockaddr *)&device_addr, tuner, dbg); +} + +struct hdhomerun_device_t *hdhomerun_device_create_ex(uint32_t device_id, const struct sockaddr *device_addr, unsigned int tuner, struct hdhomerun_debug_t *dbg) +{ + if ((device_id != 0) && !hdhomerun_discover_validate_device_id(device_id)) { + return NULL; + } + struct hdhomerun_device_t *hd = hdhomerun_device_create_internal(dbg); if (!hd) { return NULL; } - if ((device_id == 0) && (device_ip == 0) && (tuner == 0)) { + if ((device_id == 0) && !hdhomerun_sock_sockaddr_is_addr(device_addr) && (tuner == 0)) { return hd; } - if (hdhomerun_device_set_device(hd, device_id, device_ip) <= 0) { + if (hdhomerun_device_set_device_ex(hd, device_id, device_addr) <= 0) { free(hd); return NULL; } @@ -170,13 +202,24 @@ struct hdhomerun_device_t *hdhomerun_device_create(uint32_t device_id, uint32_t } struct hdhomerun_device_t *hdhomerun_device_create_multicast(uint32_t multicast_ip, uint16_t multicast_port, struct hdhomerun_debug_t *dbg) +{ + struct sockaddr_in multicast_addr; + memset(&multicast_addr, 0, sizeof(multicast_addr)); + multicast_addr.sin_family = AF_INET; + multicast_addr.sin_addr.s_addr = htonl(multicast_ip); + multicast_addr.sin_port = htons(multicast_port); + + return hdhomerun_device_create_multicast_ex((const struct sockaddr *)&multicast_addr, dbg); +} + +struct hdhomerun_device_t *hdhomerun_device_create_multicast_ex(const struct sockaddr *multicast_addr, struct hdhomerun_debug_t *dbg) { struct hdhomerun_device_t *hd = hdhomerun_device_create_internal(dbg); if (!hd) { return NULL; } - if (hdhomerun_device_set_multicast(hd, multicast_ip, multicast_port) <= 0) { + if (hdhomerun_device_set_multicast_ex(hd, multicast_addr) <= 0) { free(hd); return NULL; } @@ -201,80 +244,186 @@ void hdhomerun_device_destroy(struct hdhomerun_device_t *hd) free(hd); } +static bool hdhomerun_device_create_from_str_parse_device_id(const char *name, uint32_t *pdevice_id) +{ + char *end; + uint32_t device_id = (uint32_t)strtoul(name, &end, 16); + if (end != name + 8) { + return false; + } + + if (*end != 0) { + return false; + } + + *pdevice_id = device_id; + return true; +} + +static bool hdhomerun_device_create_from_str_parse_dns(const char *name, struct sockaddr_storage *device_addr) +{ + const char *ptr = name; + if (*ptr == 0) { + return false; + } + + while (1) { + char c = *ptr++; + if (c == 0) { + break; + } + + if ((c >= '0') && (c <= '9')) { + continue; + } + if ((c >= 'a') && (c <= 'z')) { + continue; + } + if ((c >= 'A') && (c <= 'Z')) { + continue; + } + if ((c == '.') || (c == '-')) { + continue; + } + + return false; + } + + return hdhomerun_sock_getaddrinfo_addr_ex(AF_INET, name, device_addr); +} + +static struct hdhomerun_device_t *hdhomerun_device_create_from_str_tail(const char *tail, uint32_t device_id, struct sockaddr_storage *device_addr, struct hdhomerun_debug_t *dbg) +{ + const char *ptr = tail; + if (*ptr == 0) { + return hdhomerun_device_create_ex(device_id, (struct sockaddr *)device_addr, 0, dbg); + } + + if (*ptr == ':') { + ptr++; + + char *end; + unsigned long port = strtoul(ptr + 1, &end, 10); + if (*end != 0) { + return NULL; + } + if ((port < 1024) || (port > 65535)) { + return NULL; + } + + if (device_addr->ss_family == AF_INET) { + struct sockaddr_in *device_addr_in = (struct sockaddr_in *)device_addr; + device_addr_in->sin_port = htons((uint16_t)port); + return hdhomerun_device_create_multicast_ex((struct sockaddr *)device_addr, dbg); + } + + if (device_addr->ss_family == AF_INET6) { + struct sockaddr_in6 *device_addr_in = (struct sockaddr_in6 *)device_addr; + device_addr_in->sin6_port = htons((uint16_t)port); + return hdhomerun_device_create_multicast_ex((struct sockaddr *)device_addr, dbg); + } + + return NULL; + } + + if (*ptr == '-') { + ptr++; + + char *end; + unsigned int tuner_index = (unsigned int)strtoul(ptr, &end, 10); + if (*end != 0) { + return NULL; + } + + return hdhomerun_device_create_ex(device_id, (struct sockaddr *)device_addr, tuner_index, dbg); + } + + return NULL; +} + struct hdhomerun_device_t *hdhomerun_device_create_from_str(const char *device_str, struct hdhomerun_debug_t *dbg) { - /* - * IP address based device_str. - */ - unsigned int a[4]; - if (sscanf(device_str, "%u.%u.%u.%u", &a[0], &a[1], &a[2], &a[3]) == 4) { - uint32_t ip_addr = (uint32_t)((a[0] << 24) | (a[1] << 16) | (a[2] << 8) | (a[3] << 0)); - - /* - * Multicast IP address. - */ - unsigned int port; - if (sscanf(device_str, "%u.%u.%u.%u:%u", &a[0], &a[1], &a[2], &a[3], &port) == 5) { - return hdhomerun_device_create_multicast(ip_addr, (uint16_t)port, dbg); - } - - /* - * IP address + tuner number. - */ - unsigned int tuner; - if (sscanf(device_str, "%u.%u.%u.%u-%u", &a[0], &a[1], &a[2], &a[3], &tuner) == 5) { - return hdhomerun_device_create(HDHOMERUN_DEVICE_ID_WILDCARD, ip_addr, tuner, dbg); - } - - /* - * IP address only. - */ - return hdhomerun_device_create(HDHOMERUN_DEVICE_ID_WILDCARD, ip_addr, 0, dbg); - } - - /* - * Device ID based device_str. - */ - char *end; - uint32_t device_id = (uint32_t)strtoul(device_str, &end, 16); - if ((end == device_str + 8) && hdhomerun_discover_validate_device_id(device_id)) { - /* - * IP address + tuner number. - */ - if (*end == '-') { - unsigned int tuner = (unsigned int)strtoul(end + 1, NULL, 10); - return hdhomerun_device_create(device_id, 0, tuner, dbg); - } - - /* - * Device ID only. - */ - return hdhomerun_device_create(device_id, 0, 0, dbg); - } - - /* - * DNS based device_str. - */ - struct addrinfo hints; - memset(&hints, 0, sizeof(hints)); - hints.ai_family = AF_INET; - hints.ai_socktype = SOCK_STREAM; - hints.ai_protocol = IPPROTO_TCP; - - struct addrinfo *sock_info; - if (getaddrinfo(device_str, "65001", &hints, &sock_info) != 0) { + char str[64]; + if (!hdhomerun_sprintf(str, str + sizeof(str), "%s", device_str)) { return NULL; } - struct sockaddr_in *sock_addr = (struct sockaddr_in *)sock_info->ai_addr; - uint32_t ip_addr = (uint32_t)ntohl(sock_addr->sin_addr.s_addr); - freeaddrinfo(sock_info); + uint32_t device_id = HDHOMERUN_DEVICE_ID_WILDCARD; + struct sockaddr_storage device_addr; + device_addr.ss_family = 0; + + char *ptr = str; + bool framed = (*ptr == '['); + if (framed) { + ptr++; + + char *end = strchr(ptr, ']'); + if (!end) { + return NULL; + } + + *end++ = 0; + + if (hdhomerun_sock_ip_str_to_sockaddr(ptr, &device_addr)) { + return hdhomerun_device_create_from_str_tail(end, device_id, &device_addr, dbg); + } + + return NULL; + } + + char *dash = strchr(ptr, '-'); + if (dash) { + *dash = 0; + + if (hdhomerun_device_create_from_str_parse_device_id(ptr, &device_id)) { + *dash = '-'; + return hdhomerun_device_create_from_str_tail(dash, device_id, &device_addr, dbg); + } + if (hdhomerun_sock_ip_str_to_sockaddr(ptr, &device_addr)) { + *dash = '-'; + return hdhomerun_device_create_from_str_tail(dash, device_id, &device_addr, dbg); + } + + *dash = '-'; + if (hdhomerun_device_create_from_str_parse_dns(ptr, &device_addr)) { + return hdhomerun_device_create_ex(device_id, (struct sockaddr *)&device_addr, 0, dbg); + } - if (ip_addr == 0) { return NULL; } - return hdhomerun_device_create(HDHOMERUN_DEVICE_ID_WILDCARD, ip_addr, 0, dbg); + char *colon = strchr(ptr, ':'); + if (colon) { + char *second_colon = strchr(colon, ':'); + if (second_colon) { + if (hdhomerun_sock_ip_str_to_sockaddr(ptr, &device_addr)) { + return hdhomerun_device_create_ex(device_id, (struct sockaddr *)&device_addr, 0, dbg); + } + + return NULL; + } + + *colon = 0; + + if (hdhomerun_sock_ip_str_to_sockaddr(ptr, &device_addr)) { + *colon = ':'; + return hdhomerun_device_create_from_str_tail(colon, device_id, &device_addr, dbg); + } + + return NULL; + } + + if (hdhomerun_device_create_from_str_parse_device_id(ptr, &device_id)) { + return hdhomerun_device_create_ex(device_id, (struct sockaddr *)&device_addr, 0, dbg); + } + if (hdhomerun_sock_ip_str_to_sockaddr(ptr, &device_addr)) { + return hdhomerun_device_create_ex(device_id, (struct sockaddr *)&device_addr, 0, dbg); + } + if (hdhomerun_device_create_from_str_parse_dns(ptr, &device_addr)) { + return hdhomerun_device_create_ex(device_id, (struct sockaddr *)&device_addr, 0, dbg); + } + + return NULL; } const char *hdhomerun_device_get_name(struct hdhomerun_device_t *hd) @@ -289,38 +438,73 @@ uint32_t hdhomerun_device_get_device_id(struct hdhomerun_device_t *hd) uint32_t hdhomerun_device_get_device_ip(struct hdhomerun_device_t *hd) { - if (hd->multicast_ip != 0) { - return hd->multicast_ip; + struct sockaddr_storage device_addr; + if (!hdhomerun_device_get_device_addr(hd, &device_addr)) { + return 0; } - if (hd->cs) { - return hdhomerun_control_get_device_ip(hd->cs); + if (device_addr.ss_family != AF_INET) { + return 0; } - return 0; + struct sockaddr_in *device_addr_in = (struct sockaddr_in *)&device_addr; + return ntohl(device_addr_in->sin_addr.s_addr); +} + +bool hdhomerun_device_get_device_addr(struct hdhomerun_device_t *hd, struct sockaddr_storage *result) +{ + if (hdhomerun_sock_sockaddr_is_addr((struct sockaddr *)&hd->multicast_addr)) { + *result = hd->multicast_addr; + return true; + } + + if (!hd->cs) { + memset(result, 0, sizeof(struct sockaddr_storage)); + return false; + } + + return hdhomerun_control_get_device_addr(hd->cs, result); } uint32_t hdhomerun_device_get_device_id_requested(struct hdhomerun_device_t *hd) { - if (hd->multicast_ip != 0) { + if (hdhomerun_sock_sockaddr_is_addr((struct sockaddr *)&hd->multicast_addr)) { return 0; } - if (hd->cs) { - return hdhomerun_control_get_device_id_requested(hd->cs); + + if (!hd->cs) { + return 0; } - return 0; + return hdhomerun_control_get_device_id_requested(hd->cs); } uint32_t hdhomerun_device_get_device_ip_requested(struct hdhomerun_device_t *hd) { - if (hd->multicast_ip != 0) { - return hd->multicast_ip; + struct sockaddr_storage device_addr; + if (!hdhomerun_device_get_device_addr_requested(hd, &device_addr)) { + return 0; } - if (hd->cs) { - return hdhomerun_control_get_device_ip_requested(hd->cs); + if (device_addr.ss_family != AF_INET) { + return 0; } - return 0; + struct sockaddr_in *device_addr_in = (struct sockaddr_in *)&device_addr; + return ntohl(device_addr_in->sin_addr.s_addr); +} + +bool hdhomerun_device_get_device_addr_requested(struct hdhomerun_device_t *hd, struct sockaddr_storage *result) +{ + if (hdhomerun_sock_sockaddr_is_addr((struct sockaddr *)&hd->multicast_addr)) { + *result = hd->multicast_addr; + return true; + } + + if (!hd->cs) { + memset(result, 0, sizeof(struct sockaddr_storage)); + return false; + } + + return hdhomerun_control_get_device_addr_requested(hd->cs, result); } unsigned int hdhomerun_device_get_tuner(struct hdhomerun_device_t *hd) @@ -339,9 +523,24 @@ struct hdhomerun_video_sock_t *hdhomerun_device_get_video_sock(struct hdhomerun_ return hd->vs; } - bool allow_port_reuse = (hd->multicast_port != 0); + bool allow_port_reuse = false; + struct sockaddr_storage listen_addr; + memset(&listen_addr, 0, sizeof(listen_addr)); - hd->vs = hdhomerun_video_create(hd->multicast_port, allow_port_reuse, VIDEO_DATA_BUFFER_SIZE_1S * 2, hd->dbg); + if (hdhomerun_sock_sockaddr_is_addr((struct sockaddr *)&hd->multicast_addr)) { + listen_addr.ss_family = hd->multicast_addr.ss_family; + hdhomerun_sock_sockaddr_set_port((struct sockaddr *)&listen_addr, hdhomerun_sock_sockaddr_get_port((struct sockaddr *)&hd->multicast_addr)); + allow_port_reuse = true; + } + + struct sockaddr_storage device_addr; + if (!hdhomerun_control_get_device_addr(hd->cs, &device_addr)) { + return NULL; + } + + listen_addr.ss_family = device_addr.ss_family; + + hd->vs = hdhomerun_video_create_ex((struct sockaddr *)&listen_addr, allow_port_reuse, VIDEO_DATA_BUFFER_SIZE_1S * 2, hd->dbg); if (!hd->vs) { hdhomerun_debug_printf(hd->dbg, "hdhomerun_device_get_video_sock: failed to create video object\n"); return NULL; @@ -352,11 +551,26 @@ struct hdhomerun_video_sock_t *hdhomerun_device_get_video_sock(struct hdhomerun_ uint32_t hdhomerun_device_get_local_machine_addr(struct hdhomerun_device_t *hd) { - if (hd->cs) { - return hdhomerun_control_get_local_addr(hd->cs); + struct sockaddr_storage local_addr; + if (!hdhomerun_device_get_local_machine_addr_ex(hd, &local_addr)) { + return 0; + } + if (local_addr.ss_family != AF_INET) { + return 0; } - return 0; + struct sockaddr_in *local_addr_in = (struct sockaddr_in *)&local_addr; + return ntohl(local_addr_in->sin_addr.s_addr); +} + +bool hdhomerun_device_get_local_machine_addr_ex(struct hdhomerun_device_t *hd, struct sockaddr_storage *result) +{ + if (!hd->cs) { + memset(result, 0, sizeof(struct sockaddr_storage)); + return false; + } + + return hdhomerun_control_get_local_addr_ex(hd->cs, result); } static uint32_t hdhomerun_device_get_status_parse(const char *status_str, const char *tag) @@ -367,7 +581,7 @@ static uint32_t hdhomerun_device_get_status_parse(const char *status_str, const } unsigned int value = 0; - sscanf(ptr + strlen(tag), "%u", &value); + (void)sscanf(ptr + strlen(tag), "%u", &value); return (uint32_t)value; } @@ -466,12 +680,12 @@ int hdhomerun_device_get_tuner_status(struct hdhomerun_device_t *hd, char **psta if (status) { char *channel = strstr(status_str, "ch="); if (channel) { - sscanf(channel + 3, "%31s", status->channel); + (void)sscanf(channel + 3, "%31s", status->channel); } char *lock = strstr(status_str, "lock="); if (lock) { - sscanf(lock + 5, "%31s", status->lock_str); + (void)sscanf(lock + 5, "%31s", status->lock_str); } status->signal_strength = (unsigned int)hdhomerun_device_get_status_parse(status_str, "ss="); @@ -516,12 +730,12 @@ int hdhomerun_device_get_oob_status(struct hdhomerun_device_t *hd, char **pstatu if (status) { char *channel = strstr(status_str, "ch="); if (channel) { - sscanf(channel + 3, "%31s", status->channel); + (void)sscanf(channel + 3, "%31s", status->channel); } char *lock = strstr(status_str, "lock="); if (lock) { - sscanf(lock + 5, "%31s", status->lock_str); + (void)sscanf(lock + 5, "%31s", status->lock_str); } status->signal_strength = (unsigned int)hdhomerun_device_get_status_parse(status_str, "ss="); @@ -558,27 +772,27 @@ int hdhomerun_device_get_tuner_vstatus(struct hdhomerun_device_t *hd, char **pvs if (vstatus) { char *vch = strstr(vstatus_str, "vch="); if (vch) { - sscanf(vch + 4, "%31s", vstatus->vchannel); + (void)sscanf(vch + 4, "%31s", vstatus->vchannel); } char *name = strstr(vstatus_str, "name="); if (name) { - sscanf(name + 5, "%31s", vstatus->name); + (void)sscanf(name + 5, "%31s", vstatus->name); } char *auth = strstr(vstatus_str, "auth="); if (auth) { - sscanf(auth + 5, "%31s", vstatus->auth); + (void)sscanf(auth + 5, "%31s", vstatus->auth); } char *cci = strstr(vstatus_str, "cci="); if (cci) { - sscanf(cci + 4, "%31s", vstatus->cci); + (void)sscanf(cci + 4, "%31s", vstatus->cci); } char *cgms = strstr(vstatus_str, "cgms="); if (cgms) { - sscanf(cgms + 5, "%31s", vstatus->cgms); + (void)sscanf(cgms + 5, "%31s", vstatus->cgms); } if (strncmp(vstatus->auth, "not-subscribed", 14) == 0) { @@ -1049,7 +1263,7 @@ int hdhomerun_device_set_var(struct hdhomerun_device_t *hd, const char *name, co int hdhomerun_device_tuner_lockkey_request(struct hdhomerun_device_t *hd, char **perror) { - if (hd->multicast_ip != 0) { + if (hdhomerun_sock_sockaddr_is_addr((struct sockaddr *)&hd->multicast_addr)) { return 1; } if (!hd->cs) { @@ -1077,7 +1291,7 @@ int hdhomerun_device_tuner_lockkey_request(struct hdhomerun_device_t *hd, char * int hdhomerun_device_tuner_lockkey_release(struct hdhomerun_device_t *hd) { - if (hd->multicast_ip != 0) { + if (hdhomerun_sock_sockaddr_is_addr((struct sockaddr *)&hd->multicast_addr)) { return 1; } if (!hd->cs) { @@ -1099,7 +1313,7 @@ int hdhomerun_device_tuner_lockkey_release(struct hdhomerun_device_t *hd) int hdhomerun_device_tuner_lockkey_force(struct hdhomerun_device_t *hd) { - if (hd->multicast_ip != 0) { + if (hdhomerun_sock_sockaddr_is_addr((struct sockaddr *)&hd->multicast_addr)) { return 1; } if (!hd->cs) { @@ -1117,7 +1331,7 @@ int hdhomerun_device_tuner_lockkey_force(struct hdhomerun_device_t *hd) void hdhomerun_device_tuner_lockkey_use_value(struct hdhomerun_device_t *hd, uint32_t lockkey) { - if (hd->multicast_ip != 0) { + if (hdhomerun_sock_sockaddr_is_addr((struct sockaddr *)&hd->multicast_addr)) { return; } @@ -1163,8 +1377,10 @@ int hdhomerun_device_stream_start(struct hdhomerun_device_t *hd) hdhomerun_video_set_keepalive(hd->vs, 0, 0, 0); /* Set target. */ - if (hd->multicast_ip != 0) { - int ret = hdhomerun_video_join_multicast_group(hd->vs, hd->multicast_ip, 0); + if (hdhomerun_sock_sockaddr_is_addr((struct sockaddr *)&hd->multicast_addr)) { + struct sockaddr local_ip; + memset(&local_ip, 0, sizeof(local_ip)); + int ret = hdhomerun_video_join_multicast_group_ex(hd->vs, (struct sockaddr *)&hd->multicast_addr, &local_ip); if (ret <= 0) { return ret; } @@ -1216,8 +1432,10 @@ void hdhomerun_device_stream_stop(struct hdhomerun_device_t *hd) return; } - if (hd->multicast_ip != 0) { - hdhomerun_video_leave_multicast_group(hd->vs, hd->multicast_ip, 0); + if (hdhomerun_sock_sockaddr_is_addr((struct sockaddr *)&hd->multicast_addr)) { + struct sockaddr local_ip; + memset(&local_ip, 0, sizeof(local_ip)); + hdhomerun_video_leave_multicast_group_ex(hd->vs, (struct sockaddr *)&hd->multicast_addr, &local_ip); } else { hdhomerun_device_set_tuner_target(hd, "none"); } diff --git a/hdhomerun_device.h b/hdhomerun_device.h index 5c53012..028df19 100644 --- a/hdhomerun_device.h +++ b/hdhomerun_device.h @@ -1,7 +1,7 @@ /* * hdhomerun_device.h * - * Copyright © 2006-2015 Silicondust USA Inc. . + * Copyright © 2006-2022 Silicondust USA Inc. . * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -64,7 +64,9 @@ extern "C" { * /tuner */ extern LIBHDHOMERUN_API struct hdhomerun_device_t *hdhomerun_device_create(uint32_t device_id, uint32_t device_ip, unsigned int tuner, struct hdhomerun_debug_t *dbg); +extern LIBHDHOMERUN_API struct hdhomerun_device_t *hdhomerun_device_create_ex(uint32_t device_id, const struct sockaddr *device_addr, unsigned int tuner, struct hdhomerun_debug_t *dbg); extern LIBHDHOMERUN_API struct hdhomerun_device_t *hdhomerun_device_create_multicast(uint32_t multicast_ip, uint16_t multicast_port, struct hdhomerun_debug_t *dbg); +extern LIBHDHOMERUN_API struct hdhomerun_device_t *hdhomerun_device_create_multicast_ex(const struct sockaddr *multicast_addr, struct hdhomerun_debug_t *dbg); extern LIBHDHOMERUN_API struct hdhomerun_device_t *hdhomerun_device_create_from_str(const char *device_str, struct hdhomerun_debug_t *dbg); extern LIBHDHOMERUN_API void hdhomerun_device_destroy(struct hdhomerun_device_t *hd); @@ -74,12 +76,16 @@ extern LIBHDHOMERUN_API void hdhomerun_device_destroy(struct hdhomerun_device_t extern LIBHDHOMERUN_API const char *hdhomerun_device_get_name(struct hdhomerun_device_t *hd); extern LIBHDHOMERUN_API uint32_t hdhomerun_device_get_device_id(struct hdhomerun_device_t *hd); extern LIBHDHOMERUN_API uint32_t hdhomerun_device_get_device_ip(struct hdhomerun_device_t *hd); +extern LIBHDHOMERUN_API bool hdhomerun_device_get_device_addr(struct hdhomerun_device_t *hd, struct sockaddr_storage *result); extern LIBHDHOMERUN_API uint32_t hdhomerun_device_get_device_id_requested(struct hdhomerun_device_t *hd); extern LIBHDHOMERUN_API uint32_t hdhomerun_device_get_device_ip_requested(struct hdhomerun_device_t *hd); +extern LIBHDHOMERUN_API bool hdhomerun_device_get_device_addr_requested(struct hdhomerun_device_t *hd, struct sockaddr_storage *result); extern LIBHDHOMERUN_API unsigned int hdhomerun_device_get_tuner(struct hdhomerun_device_t *hd); extern LIBHDHOMERUN_API int hdhomerun_device_set_device(struct hdhomerun_device_t *hd, uint32_t device_id, uint32_t device_ip); +extern LIBHDHOMERUN_API int hdhomerun_device_set_device_ex(struct hdhomerun_device_t *hd, uint32_t device_id, const struct sockaddr *device_addr); extern LIBHDHOMERUN_API int hdhomerun_device_set_multicast(struct hdhomerun_device_t *hd, uint32_t multicast_ip, uint16_t multicast_port); +extern LIBHDHOMERUN_API int hdhomerun_device_set_multicast_ex(struct hdhomerun_device_t *hd, const struct sockaddr *multicast_addr); extern LIBHDHOMERUN_API int hdhomerun_device_set_tuner(struct hdhomerun_device_t *hd, unsigned int tuner); extern LIBHDHOMERUN_API int hdhomerun_device_set_tuner_from_str(struct hdhomerun_device_t *hd, const char *tuner_str); @@ -91,6 +97,7 @@ extern LIBHDHOMERUN_API int hdhomerun_device_set_tuner_from_str(struct hdhomerun * Returns 32-bit IP address with native endianness, or 0 on error. */ extern LIBHDHOMERUN_API uint32_t hdhomerun_device_get_local_machine_addr(struct hdhomerun_device_t *hd); +extern LIBHDHOMERUN_API bool hdhomerun_device_get_local_machine_addr_ex(struct hdhomerun_device_t *hd, struct sockaddr_storage *result); /* * Get operations. diff --git a/hdhomerun_discover.c b/hdhomerun_discover.c index 988508f..0ce17d4 100644 --- a/hdhomerun_discover.c +++ b/hdhomerun_discover.c @@ -1,7 +1,7 @@ /* * hdhomerun_discover.c * - * Copyright © 2006-2017 Silicondust USA Inc. . + * Copyright © 2006-2022 Silicondust USA Inc. . * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -20,61 +20,247 @@ #include "hdhomerun.h" -#define HDHOMERUN_DISCOVER_MAX_SOCK_COUNT 16 +struct hdhomerun_discover2_device_type_t { + struct hdhomerun_discover2_device_type_t *next; + uint32_t device_type; +}; + +struct hdhomerun_discover2_device_if_t { + struct hdhomerun_discover2_device_if_t *next; + struct sockaddr_storage ip_addr; + char *base_url; + char *lineup_url; + char *storage_url; + uint8_t priority; +}; + +struct hdhomerun_discover2_device_t { + struct hdhomerun_discover2_device_t *next; + struct hdhomerun_discover2_device_if_t *if_list; + struct hdhomerun_discover2_device_type_t *type_list; + uint32_t device_id; + uint8_t tuner_count; + char *device_auth; + char *storage_id; +}; struct hdhomerun_discover_sock_t { + struct hdhomerun_discover_sock_t *next; + struct hdhomerun_discover_sock_t *recv_next; struct hdhomerun_sock_t *sock; - bool detected; - uint32_t local_ip; - uint32_t subnet_mask; + struct sockaddr_storage local_ip; + uint32_t ifindex; + uint32_t ipv4_subnet_mask; + bool new_flag; }; struct hdhomerun_discover_t { - struct hdhomerun_discover_sock_t socks[HDHOMERUN_DISCOVER_MAX_SOCK_COUNT]; - unsigned int sock_count; + struct hdhomerun_discover2_device_t *device_list; + struct hdhomerun_discover_sock_t *ipv6_socks; + struct hdhomerun_discover_sock_t *ipv4_socks; + struct hdhomerun_discover_sock_t *ipv6_localhost; + struct hdhomerun_discover_sock_t *ipv4_localhost; struct hdhomerun_pkt_t tx_pkt; struct hdhomerun_pkt_t rx_pkt; struct hdhomerun_debug_t *dbg; }; -static bool hdhomerun_discover_sock_add(struct hdhomerun_discover_t *ds, uint32_t local_ip, uint32_t subnet_mask) -{ - unsigned int i; - for (i = 1; i < ds->sock_count; i++) { - struct hdhomerun_discover_sock_t *dss = &ds->socks[i]; +static uint8_t hdhomerun_discover_ipv6_multicast_ip[16] = { 0xFF, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x76 }; - if ((dss->local_ip == local_ip) && (dss->subnet_mask == subnet_mask)) { - dss->detected = true; - return true; - } +static void hdhomerun_discover_sock_free(struct hdhomerun_discover_sock_t *dss) +{ + hdhomerun_sock_destroy(dss->sock); + free(dss); +} + +static uint16_t hdhomerun_discover_get_local_port(struct hdhomerun_discover_t *ds) +{ + if (ds->ipv6_socks) { + return hdhomerun_sock_getsockname_port(ds->ipv6_socks->sock); + } + if (ds->ipv4_socks) { + return hdhomerun_sock_getsockname_port(ds->ipv4_socks->sock); + } + if (ds->ipv6_localhost) { + return hdhomerun_sock_getsockname_port(ds->ipv6_localhost->sock); + } + if (ds->ipv4_localhost) { + return hdhomerun_sock_getsockname_port(ds->ipv4_localhost->sock); + } + return 0; +} + +static void hdhomerun_discover_sock_add_ipv6(void *arg, uint32_t ifindex, const struct sockaddr *local_ip, uint8_t cidr) +{ + if (local_ip->sa_family != AF_INET6) { + return; } - if (ds->sock_count >= HDHOMERUN_DISCOVER_MAX_SOCK_COUNT) { - return false; + struct hdhomerun_discover_t *ds = (struct hdhomerun_discover_t *)arg; + struct sockaddr_in6 *detected_ip = (struct sockaddr_in6 *)local_ip; + + struct hdhomerun_discover_sock_t **pprev = &ds->ipv6_socks; + struct hdhomerun_discover_sock_t *p = ds->ipv6_socks; + while (p) { + struct sockaddr_in6 *p_ip = (struct sockaddr_in6 *)&p->local_ip; + if ((p->ifindex == ifindex) && (memcmp(p_ip->sin6_addr.s6_addr, detected_ip->sin6_addr.s6_addr, 16) == 0)) { + p->new_flag = true; + return; + } + + pprev = &p->next; + p = p->next; } /* Create socket. */ - struct hdhomerun_sock_t *sock = hdhomerun_sock_create_udp(); - if (!sock) { + struct hdhomerun_discover_sock_t *dss = (struct hdhomerun_discover_sock_t *)calloc(sizeof(struct hdhomerun_discover_sock_t), 1); + if (!dss) { + hdhomerun_debug_printf(ds->dbg, "discover: resource error\n"); + return; + } + + dss->sock = hdhomerun_sock_create_udp_ex(AF_INET6); + if (!dss->sock) { hdhomerun_debug_printf(ds->dbg, "discover: failed to allocate socket (%d)\n", hdhomerun_sock_getlasterror()); - return false; + free(dss); + return; } /* Bind socket. */ - if (!hdhomerun_sock_bind(sock, local_ip, 0, false)) { - hdhomerun_debug_printf(ds->dbg, "discover: failed to bind to %u.%u.%u.%u:0\n", (unsigned int)(local_ip >> 24) & 0xFF, (unsigned int)(local_ip >> 16) & 0xFF, (unsigned int)(local_ip >> 8) & 0xFF, (unsigned int)(local_ip >> 0) & 0xFF); - hdhomerun_sock_destroy(sock); - return false; + detected_ip->sin6_port = htons(hdhomerun_discover_get_local_port(ds)); + + if (!hdhomerun_sock_bind_ex(dss->sock, (const struct sockaddr *)detected_ip, true)) { + hdhomerun_debug_printf(ds->dbg, "discover: failed to bind to local ip (%d)\n", hdhomerun_sock_getlasterror()); + hdhomerun_discover_sock_free(dss); + return; } - /* Write sock entry. */ - struct hdhomerun_discover_sock_t *dss = &ds->socks[ds->sock_count++]; - dss->sock = sock; - dss->detected = true; - dss->local_ip = local_ip; - dss->subnet_mask = subnet_mask; + /* Success. */ + memcpy(&dss->local_ip, detected_ip, sizeof(struct sockaddr_in6)); + dss->ifindex = ifindex; + dss->new_flag = true; + *pprev = dss; +} - return true; +static void hdhomerun_discover_sock_add_ipv4(void *arg, uint32_t ifindex, const struct sockaddr *local_ip, uint8_t cidr) +{ + if (local_ip->sa_family != AF_INET) { + return; + } + + struct hdhomerun_discover_t *ds = (struct hdhomerun_discover_t *)arg; + struct sockaddr_in *detected_ip = (struct sockaddr_in *)local_ip; + uint32_t detected_subnet_mask = 0xFFFFFFFF << (32 - cidr); + + struct hdhomerun_discover_sock_t **pprev = &ds->ipv4_socks; + struct hdhomerun_discover_sock_t *p = ds->ipv4_socks; + while (p) { + struct sockaddr_in *p_ip = (struct sockaddr_in *)&p->local_ip; + if ((p->ifindex == ifindex) && (p_ip->sin_addr.s_addr == detected_ip->sin_addr.s_addr) && (p->ipv4_subnet_mask == detected_subnet_mask)) { + p->new_flag = true; + return; + } + + pprev = &p->next; + p = p->next; + } + + /* Create socket. */ + struct hdhomerun_discover_sock_t *dss = (struct hdhomerun_discover_sock_t *)calloc(sizeof(struct hdhomerun_discover_sock_t), 1); + if (!dss) { + hdhomerun_debug_printf(ds->dbg, "discover: resource error\n"); + return; + } + + dss->sock = hdhomerun_sock_create_udp_ex(AF_INET); + if (!dss->sock) { + hdhomerun_debug_printf(ds->dbg, "discover: failed to allocate socket (%d)\n", hdhomerun_sock_getlasterror()); + free(dss); + return; + } + + /* Bind socket. */ + detected_ip->sin_port = htons(hdhomerun_discover_get_local_port(ds)); + + if (!hdhomerun_sock_bind_ex(dss->sock, (const struct sockaddr *)detected_ip, true)) { + hdhomerun_debug_printf(ds->dbg, "discover: failed to bind to local ip (%d)\n", hdhomerun_sock_getlasterror()); + hdhomerun_discover_sock_free(dss); + return; + } + + /* Success. */ + memcpy(&dss->local_ip, detected_ip, sizeof(struct sockaddr_in)); + dss->ifindex = ifindex; + dss->ipv4_subnet_mask = detected_subnet_mask; + dss->new_flag = true; + *pprev = dss; +} + +static void hdhomerun_discover_device_if_free(struct hdhomerun_discover2_device_if_t *device_if) +{ + if (device_if->base_url) { + free(device_if->base_url); + } + if (device_if->lineup_url) { + free(device_if->lineup_url); + } + if (device_if->storage_url) { + free(device_if->storage_url); + } + + free(device_if); +} + +static void hdhomerun_discover_device_free(struct hdhomerun_discover2_device_t *device) +{ + while (device->if_list) { + struct hdhomerun_discover2_device_if_t *device_if = device->if_list; + device->if_list = device_if->next; + hdhomerun_discover_device_if_free(device_if); + } + + while (device->type_list) { + struct hdhomerun_discover2_device_type_t *device_type = device->type_list; + device->type_list = device_type->next; + free(device_type); + } + + if (device->device_auth) { + free(device->device_auth); + } + if (device->storage_id) { + free(device->storage_id); + } + + free(device); +} + +static void hdhomerun_discover_free_device_list(struct hdhomerun_discover_t *ds) +{ + while (ds->device_list) { + struct hdhomerun_discover2_device_t *device = ds->device_list; + ds->device_list = device->next; + hdhomerun_discover_device_free(device); + } +} + +void hdhomerun_discover_destroy(struct hdhomerun_discover_t *ds) +{ + hdhomerun_discover_free_device_list(ds); + + while (ds->ipv6_socks) { + struct hdhomerun_discover_sock_t *dss = ds->ipv6_socks; + ds->ipv6_socks = dss->next; + hdhomerun_discover_sock_free(dss); + } + + while (ds->ipv4_socks) { + struct hdhomerun_discover_sock_t *dss = ds->ipv4_socks; + ds->ipv4_socks = dss->next; + hdhomerun_discover_sock_free(dss); + } + + free(ds); } struct hdhomerun_discover_t *hdhomerun_discover_create(struct hdhomerun_debug_t *dbg) @@ -85,187 +271,1128 @@ struct hdhomerun_discover_t *hdhomerun_discover_create(struct hdhomerun_debug_t } ds->dbg = dbg; - - /* Create a routable socket (always first entry). */ - if (!hdhomerun_discover_sock_add(ds, 0, 0)) { - free(ds); - return NULL; - } - - /* Success. */ return ds; } -void hdhomerun_discover_destroy(struct hdhomerun_discover_t *ds) +static void hdhomerun_discover_sock_detect_cleanup(struct hdhomerun_discover_sock_t *default_dss) { - unsigned int i; - for (i = 0; i < ds->sock_count; i++) { - struct hdhomerun_discover_sock_t *dss = &ds->socks[i]; - hdhomerun_sock_destroy(dss->sock); - } - - free(ds); -} - -static void hdhomerun_discover_sock_detect(struct hdhomerun_discover_t *ds) -{ - unsigned int i; - for (i = 1; i < ds->sock_count; i++) { - struct hdhomerun_discover_sock_t *dss = &ds->socks[i]; - dss->detected = false; - } - - struct hdhomerun_local_ip_info_t ip_info_list[HDHOMERUN_DISCOVER_MAX_SOCK_COUNT]; - int count = hdhomerun_local_ip_info(ip_info_list, HDHOMERUN_DISCOVER_MAX_SOCK_COUNT); - if (count < 0) { - hdhomerun_debug_printf(ds->dbg, "discover: hdhomerun_local_ip_info returned error\n"); - count = 0; - } - if (count > HDHOMERUN_DISCOVER_MAX_SOCK_COUNT) { - hdhomerun_debug_printf(ds->dbg, "discover: too many local IP addresses\n"); - count = HDHOMERUN_DISCOVER_MAX_SOCK_COUNT; - } - - int index; - for (index = 0; index < count; index++) { - struct hdhomerun_local_ip_info_t *ip_info = &ip_info_list[index]; - hdhomerun_discover_sock_add(ds, ip_info->ip_addr, ip_info->subnet_mask); - } - - struct hdhomerun_discover_sock_t *src = &ds->socks[1]; - struct hdhomerun_discover_sock_t *dst = &ds->socks[1]; - count = 1; - for (i = 1; i < ds->sock_count; i++) { - if (!src->detected) { - hdhomerun_sock_destroy(src->sock); - src++; + struct hdhomerun_discover_sock_t **pprev = &default_dss->next; + struct hdhomerun_discover_sock_t *p = default_dss->next; + while (p) { + if (!p->new_flag) { + hdhomerun_discover_sock_free(p); + p = *pprev; continue; } - if (dst != src) { - *dst = *src; - } - src++; - dst++; - count++; - } - ds->sock_count = (unsigned int)count; + p->new_flag = false; + pprev = &p->next; + p = p->next; + } } -static bool hdhomerun_discover_send_internal(struct hdhomerun_discover_t *ds, struct hdhomerun_discover_sock_t *dss, uint32_t target_ip, uint32_t device_type, uint32_t device_id) +static void hdhomerun_discover_sock_detect_ipv6(struct hdhomerun_discover_t *ds) { + if (!ds->ipv6_socks) { + /* Create a routable socket (always first entry). */ + struct sockaddr_in6 ipv6_addr; + memset(&ipv6_addr, 0, sizeof(ipv6_addr)); + ipv6_addr.sin6_family = AF_INET6; + hdhomerun_discover_sock_add_ipv6(ds, 0, (const struct sockaddr *)&ipv6_addr, 0); + + if (!ds->ipv6_socks) { + return; + } + } + + hdhomerun_local_ip_info2(AF_INET6, hdhomerun_discover_sock_add_ipv6, ds); + hdhomerun_discover_sock_detect_cleanup(ds->ipv6_socks); +} + +static void hdhomerun_discover_sock_detect_ipv4(struct hdhomerun_discover_t *ds) +{ + if (!ds->ipv4_socks) { + /* Create a routable socket (always first entry). */ + struct sockaddr_in ipv4_addr; + memset(&ipv4_addr, 0, sizeof(ipv4_addr)); + ipv4_addr.sin_family = AF_INET; + hdhomerun_discover_sock_add_ipv4(ds, 0, (const struct sockaddr *)&ipv4_addr, 0); + + if (!ds->ipv4_socks) { + return; + } + } + + hdhomerun_local_ip_info2(AF_INET, hdhomerun_discover_sock_add_ipv4, ds); + hdhomerun_discover_sock_detect_cleanup(ds->ipv4_socks); +} + +static void hdhomerun_discover_sock_detect_ipv6_localhost(struct hdhomerun_discover_t *ds) +{ + if (ds->ipv6_localhost) { + return; + } + + struct hdhomerun_discover_sock_t *dss = (struct hdhomerun_discover_sock_t *)calloc(sizeof(struct hdhomerun_discover_sock_t), 1); + if (!dss) { + return; + } + + dss->sock = hdhomerun_sock_create_udp_ex(AF_INET6); + if (!dss->sock) { + free(dss); + return; + } + + struct sockaddr_in6 *sock_addr = (struct sockaddr_in6 *)&dss->local_ip; + sock_addr->sin6_family = AF_INET6; + sock_addr->sin6_addr.s6_addr[15] = 1; + sock_addr->sin6_port = htons(hdhomerun_discover_get_local_port(ds)); + + if (!hdhomerun_sock_bind_ex(dss->sock, (const struct sockaddr *)sock_addr, true)) { + hdhomerun_discover_sock_free(dss); + return; + } + + ds->ipv6_localhost = dss; +} + +static void hdhomerun_discover_sock_detect_ipv4_localhost(struct hdhomerun_discover_t *ds) +{ + if (ds->ipv4_localhost) { + return; + } + + struct hdhomerun_discover_sock_t *dss = (struct hdhomerun_discover_sock_t *)calloc(sizeof(struct hdhomerun_discover_sock_t), 1); + if (!dss) { + return; + } + + dss->sock = hdhomerun_sock_create_udp_ex(AF_INET); + if (!dss->sock) { + free(dss); + return; + } + + struct sockaddr_in *sock_addr = (struct sockaddr_in *)&dss->local_ip; + sock_addr->sin_family = AF_INET; + sock_addr->sin_addr.s_addr = htonl(0x7F000001); + sock_addr->sin_port = htons(hdhomerun_discover_get_local_port(ds)); + dss->ipv4_subnet_mask = 0xFF000000; + + if (!hdhomerun_sock_bind_ex(dss->sock, (const struct sockaddr *)sock_addr, true)) { + hdhomerun_discover_sock_free(dss); + return; + } + + ds->ipv4_localhost = dss; +} + +static void hdhomerun_discover_sock_detect_all_from_flags(struct hdhomerun_discover_t *ds, uint32_t flags) +{ + if (flags & HDHOMERUN_DISCOVER_FLAGS_IPV6_LOCALHOST) { + hdhomerun_discover_sock_detect_ipv6_localhost(ds); + } + if (flags & (HDHOMERUN_DISCOVER_FLAGS_IPV6_GENERAL | HDHOMERUN_DISCOVER_FLAGS_IPV6_LINKLOCAL)) { + hdhomerun_discover_sock_detect_ipv6(ds); + } + if (flags & HDHOMERUN_DISCOVER_FLAGS_IPV4_LOCALHOST) { + hdhomerun_discover_sock_detect_ipv4_localhost(ds); + } + if (flags & HDHOMERUN_DISCOVER_FLAGS_IPV4_GENERAL) { + hdhomerun_discover_sock_detect_ipv4(ds); + } +} + +static void hdhomerun_discover_sock_flush_list(struct hdhomerun_discover_t *ds, struct hdhomerun_discover_sock_t *list) +{ + struct hdhomerun_pkt_t *rx_pkt = &ds->rx_pkt; + hdhomerun_pkt_reset(rx_pkt); + + struct hdhomerun_discover_sock_t *dss = list; + while (dss) { + while (1) { + struct sockaddr_storage remote_addr; + size_t length = rx_pkt->limit - rx_pkt->end; + if (!hdhomerun_sock_recvfrom_ex(dss->sock, &remote_addr, rx_pkt->end, &length, 0)) { + break; + } + } + + dss = dss->next; + } +} + +static void hdhomerun_discover_sock_recv_list_append_list(struct hdhomerun_discover_sock_t **recv_list, struct hdhomerun_discover_sock_t *list) +{ + struct hdhomerun_discover_sock_t *dss = list; + while (dss) { + dss->recv_next = *recv_list; + *recv_list = dss; + dss = dss->next; + } +} + +static bool hdhomerun_discover_send_request(struct hdhomerun_discover_t *ds, struct hdhomerun_discover_sock_t *dss, const struct sockaddr *remote_addr, const uint32_t device_types[], size_t device_types_count, uint32_t device_id) +{ + if (device_types_count == 0) { + return false; + } + struct hdhomerun_pkt_t *tx_pkt = &ds->tx_pkt; hdhomerun_pkt_reset(tx_pkt); - hdhomerun_pkt_write_u8(tx_pkt, HDHOMERUN_TAG_DEVICE_TYPE); - hdhomerun_pkt_write_var_length(tx_pkt, 4); - hdhomerun_pkt_write_u32(tx_pkt, device_type); - hdhomerun_pkt_write_u8(tx_pkt, HDHOMERUN_TAG_DEVICE_ID); - hdhomerun_pkt_write_var_length(tx_pkt, 4); - hdhomerun_pkt_write_u32(tx_pkt, device_id); + if (device_types_count == 1) { + hdhomerun_pkt_write_u8(tx_pkt, HDHOMERUN_TAG_DEVICE_TYPE); + hdhomerun_pkt_write_var_length(tx_pkt, 4); + hdhomerun_pkt_write_u32(tx_pkt, device_types[0]); + } else { + hdhomerun_pkt_write_u8(tx_pkt, HDHOMERUN_TAG_MULTI_TYPE); + hdhomerun_pkt_write_var_length(tx_pkt, device_types_count * 4); + while (device_types_count--) { + hdhomerun_pkt_write_u32(tx_pkt, *device_types++); + } + } + + if (device_id != HDHOMERUN_DEVICE_ID_WILDCARD) { + hdhomerun_pkt_write_u8(tx_pkt, HDHOMERUN_TAG_DEVICE_ID); + hdhomerun_pkt_write_var_length(tx_pkt, 4); + hdhomerun_pkt_write_u32(tx_pkt, device_id); + } + hdhomerun_pkt_seal_frame(tx_pkt, HDHOMERUN_TYPE_DISCOVER_REQ); - return hdhomerun_sock_sendto(dss->sock, target_ip, HDHOMERUN_DISCOVER_UDP_PORT, tx_pkt->start, tx_pkt->end - tx_pkt->start, 0); + return hdhomerun_sock_sendto_ex(dss->sock, remote_addr, tx_pkt->start, tx_pkt->end - tx_pkt->start, 0); } -static bool hdhomerun_discover_send_targeted(struct hdhomerun_discover_t *ds, uint32_t target_ip, uint32_t device_type, uint32_t device_id) +static inline bool hdhomerun_discover_is_ipv4_localhost(const struct sockaddr *ip_addr) { - /* - * Send targeted packet from any local ip that is in the same subnet. - * This is needed to support multiple separate 169.254.x.x interfaces. - */ - bool result = false; - unsigned int i; + if (ip_addr->sa_family != AF_INET) { + return false; + } - for (i = 1; i < ds->sock_count; i++) { - struct hdhomerun_discover_sock_t *dss = &ds->socks[i]; - if (dss->subnet_mask == 0) { + const struct sockaddr_in *sock_addr_in = (const struct sockaddr_in *)ip_addr; + uint8_t *addr_bytes = (uint8_t *)&sock_addr_in->sin_addr.s_addr; + return (addr_bytes[0] == 0x7F); +} + +static inline bool hdhomerun_discover_is_ipv4_autoip(const struct sockaddr *ip_addr) +{ + if (ip_addr->sa_family != AF_INET) { + return false; + } + + const struct sockaddr_in *sock_addr_in = (const struct sockaddr_in *)ip_addr; + uint8_t *addr_bytes = (uint8_t *)&sock_addr_in->sin_addr.s_addr; + return (addr_bytes[0] == 0xA9) && (addr_bytes[1] == 0xFE); +} + +static inline bool hdhomerun_discover_is_ipv6_localhost(const struct sockaddr *ip_addr) +{ + if (ip_addr->sa_family != AF_INET6) { + return false; + } + + const struct sockaddr_in6 *sock_addr_in6 = (const struct sockaddr_in6 *)ip_addr; + uint8_t *addr_bytes = (uint8_t *)sock_addr_in6->sin6_addr.s6_addr; + + static uint8_t hdhomerun_discover_ipv6_localhost[16] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 }; + return (memcmp(addr_bytes, hdhomerun_discover_ipv6_localhost, 16) == 0); +} + +static inline bool hdhomerun_discover_is_ipv6_routable(const struct sockaddr *ip_addr) +{ + if (ip_addr->sa_family != AF_INET6) { + return false; + } + + const struct sockaddr_in6 *sock_addr_in6 = (const struct sockaddr_in6 *)ip_addr; + uint8_t *addr_bytes = (uint8_t *)sock_addr_in6->sin6_addr.s6_addr; + return ((addr_bytes[0] & 0xE0) == 0x20); +} + +static inline bool hdhomerun_discover_is_ipv6_linklocal(const struct sockaddr *ip_addr) +{ + if (ip_addr->sa_family != AF_INET6) { + return false; + } + + const struct sockaddr_in6 *sock_addr_in6 = (const struct sockaddr_in6 *)ip_addr; + uint8_t *addr_bytes = (uint8_t *)sock_addr_in6->sin6_addr.s6_addr; + return (addr_bytes[0] == 0xFE) && ((addr_bytes[1] & 0xC0) == 0x80); +} + +static void hdhomerun_discover_send_ipv6_targeted(struct hdhomerun_discover_t *ds, const struct sockaddr *target_addr, uint32_t flags, const uint32_t device_types[], size_t device_types_count, uint32_t device_id, struct hdhomerun_discover_sock_t **recv_list) +{ + struct hdhomerun_discover_sock_t *default_dss = ds->ipv6_socks; + if (!default_dss) { + return; + } + + hdhomerun_discover_sock_flush_list(ds, ds->ipv6_socks); + hdhomerun_discover_sock_recv_list_append_list(recv_list, ds->ipv6_socks); + + struct sockaddr_in6 target_addr_in; + memcpy(&target_addr_in, target_addr, sizeof(target_addr_in)); + target_addr_in.sin6_port = htons(HDHOMERUN_DISCOVER_UDP_PORT); + + /* ipv6 linklocal - send out every interface if the scope id isn't provided */ + if (hdhomerun_discover_is_ipv6_linklocal(target_addr) && (target_addr_in.sin6_scope_id == 0)) { + struct hdhomerun_discover_sock_t *dss = default_dss->next; + while (dss) { + if (!hdhomerun_discover_is_ipv6_linklocal((const struct sockaddr *)&dss->local_ip)) { + dss = dss->next; + continue; + } + + struct sockaddr_in6 *local_addr_in = (struct sockaddr_in6 *)&dss->local_ip; + target_addr_in.sin6_scope_id = local_addr_in->sin6_scope_id; + + if (!hdhomerun_discover_send_request(ds, dss, (const struct sockaddr *)&target_addr_in, device_types, device_types_count, device_id)) { + dss = dss->next; + continue; + } + + dss = dss->next; + } + + return; + } + + hdhomerun_discover_send_request(ds, default_dss, (const struct sockaddr *)&target_addr_in, device_types, device_types_count, device_id); +} + +static void hdhomerun_discover_send_ipv4_targeted(struct hdhomerun_discover_t *ds, const struct sockaddr *target_addr, uint32_t flags, const uint32_t device_types[], size_t device_types_count, uint32_t device_id, struct hdhomerun_discover_sock_t **recv_list) +{ + struct hdhomerun_discover_sock_t *default_dss = ds->ipv4_socks; + if (!default_dss) { + return; + } + + hdhomerun_discover_sock_flush_list(ds, ds->ipv4_socks); + hdhomerun_discover_sock_recv_list_append_list(recv_list, ds->ipv4_socks); + + struct sockaddr_in target_addr_in; + memcpy(&target_addr_in, target_addr, sizeof(target_addr_in)); + target_addr_in.sin_port = htons(HDHOMERUN_DISCOVER_UDP_PORT); + + /* ipv4 autoip - send out every interface that has an autoip address */ + if (hdhomerun_discover_is_ipv4_autoip(target_addr)) { + struct hdhomerun_discover_sock_t *dss = default_dss->next; + while (dss) { + if (!hdhomerun_discover_is_ipv4_autoip((const struct sockaddr *)&dss->local_ip)) { + dss = dss->next; + continue; + } + + if (!hdhomerun_discover_send_request(ds, dss, (const struct sockaddr *)&target_addr_in, device_types, device_types_count, device_id)) { + dss = dss->next; + continue; + } + + dss = dss->next; + } + + return; + } + + hdhomerun_discover_send_request(ds, default_dss, (const struct sockaddr *)&target_addr_in, device_types, device_types_count, device_id); +} + +static void hdhomerun_discover_send_ipv6_multicast(struct hdhomerun_discover_t *ds, uint32_t flags, const uint32_t device_types[], size_t device_types_count, uint32_t device_id, struct hdhomerun_discover_sock_t **recv_list) +{ + struct hdhomerun_discover_sock_t *default_dss = ds->ipv6_socks; + if (!default_dss) { + return; + } + + hdhomerun_discover_sock_flush_list(ds, ds->ipv6_socks); + hdhomerun_discover_sock_recv_list_append_list(recv_list, ds->ipv6_socks); + + struct sockaddr_in6 sock_addr_in; + memset(&sock_addr_in, 0, sizeof(sock_addr_in)); + sock_addr_in.sin6_family = AF_INET6; + memcpy(sock_addr_in.sin6_addr.s6_addr, hdhomerun_discover_ipv6_multicast_ip, 16); + sock_addr_in.sin6_port = htons(HDHOMERUN_DISCOVER_UDP_PORT); + + bool send_general = (flags & HDHOMERUN_DISCOVER_FLAGS_IPV6_GENERAL) != 0; + bool send_linklocal = (flags & HDHOMERUN_DISCOVER_FLAGS_IPV6_LINKLOCAL) != 0; + + struct hdhomerun_discover_sock_t *dss = default_dss->next; + while (dss) { + bool linklocal = hdhomerun_discover_is_ipv6_linklocal((const struct sockaddr *)&dss->local_ip); + if ((linklocal && !send_linklocal) || (!linklocal && !send_general)) { + dss = dss->next; continue; } - if ((target_ip & dss->subnet_mask) != (dss->local_ip & dss->subnet_mask)) { + hdhomerun_sock_set_ipv6_multicast_ifindex(dss->sock, dss->ifindex); + + struct sockaddr_in6 *local_ip_in = (struct sockaddr_in6 *)&dss->local_ip; + sock_addr_in.sin6_scope_id = local_ip_in->sin6_scope_id; + + if (!hdhomerun_discover_send_request(ds, dss, (const struct sockaddr *)&sock_addr_in, device_types, device_types_count, device_id)) { + dss = dss->next; continue; } - result |= hdhomerun_discover_send_internal(ds, dss, target_ip, device_type, device_id); + dss = dss->next; } - - if (result) { - return true; - } - - /* - * Fall back to letting the OS choose the interface. - */ - struct hdhomerun_discover_sock_t *dss = &ds->socks[0]; - return hdhomerun_discover_send_internal(ds, dss, target_ip, device_type, device_id); } -static bool hdhomerun_discover_send_broadcast(struct hdhomerun_discover_t *ds, uint32_t device_type, uint32_t device_id) +static void hdhomerun_discover_send_ipv4_broadcast(struct hdhomerun_discover_t *ds, uint32_t flags, const uint32_t device_types[], size_t device_types_count, uint32_t device_id, struct hdhomerun_discover_sock_t **recv_list) { - struct hdhomerun_discover_sock_t *dss; - bool result = false; - unsigned int i; - -#if !defined(IP_ONESBCAST) - /* - * Send global broadcast from every local ip. - */ - for (i = 1; i < ds->sock_count; i++) { - dss = &ds->socks[i]; - result |= hdhomerun_discover_send_internal(ds, dss, 0xFFFFFFFF, device_type, device_id); + struct hdhomerun_discover_sock_t *default_dss = ds->ipv4_socks; + if (!default_dss) { + return; } - if (result) { - return true; - } + hdhomerun_discover_sock_flush_list(ds, ds->ipv4_socks); + hdhomerun_discover_sock_recv_list_append_list(recv_list, ds->ipv4_socks); - /* - * Fall back to letting the OS choose the interface. - */ - dss = &ds->socks[0]; - if (hdhomerun_discover_send_internal(ds, dss, 0xFFFFFFFF, device_type, device_id)) { - return true; - } + struct sockaddr_in sock_addr_in; + memset(&sock_addr_in, 0, sizeof(sock_addr_in)); + sock_addr_in.sin_family = AF_INET; + sock_addr_in.sin_addr.s_addr = htonl(0xFFFFFFFF); + sock_addr_in.sin_port = htons(HDHOMERUN_DISCOVER_UDP_PORT); + + struct hdhomerun_discover_sock_t *dss = default_dss->next; + while (dss) { +#if defined(IP_ONESBCAST) + struct sockaddr_in *local_ip_in = (struct sockaddr_in *)&dss->local_ip; + uint32_t local_ip_val = ntohl(local_ip_in->sin_addr.s_addr); + + uint32_t subnet_broadcast = local_ip_val | ~dss->ipv4_subnet_mask; + if ((subnet_broadcast == 0) || (subnet_broadcast >= 0xE0000000)) { + dss = dss->next; + continue; + } + + hdhomerun_sock_set_ipv4_onesbcast(dss->sock, 1); + sock_addr_in.sin_addr.s_addr = htonl(subnet_broadcast); #endif - /* - * Fall back to sending subnet broadcasts. - */ - for (i = 1; i < ds->sock_count; i++) { - dss = &ds->socks[i]; - if (dss->subnet_mask == 0) { + bool send_ok = hdhomerun_discover_send_request(ds, dss, (const struct sockaddr *)&sock_addr_in, device_types, device_types_count, device_id); + hdhomerun_sock_set_ipv4_onesbcast(dss->sock, 0); + if (!send_ok) { + dss = dss->next; continue; } - uint32_t send_ip = dss->local_ip | ~dss->subnet_mask; - if (send_ip >= 0xE0000000) { - continue; - } - - result |= hdhomerun_discover_send_internal(ds, dss, send_ip, device_type, device_id); + dss = dss->next; } - - return result; } -static bool hdhomerun_discover_send(struct hdhomerun_discover_t *ds, uint32_t target_ip, uint32_t device_type, uint32_t device_id) +static void hdhomerun_discover_send_ipv6_localhost(struct hdhomerun_discover_t *ds, const struct sockaddr *target_addr, uint32_t flags, const uint32_t device_types[], size_t device_types_count, uint32_t device_id, struct hdhomerun_discover_sock_t **recv_list) { - if (target_ip == 0xFFFFFFFF) { - return hdhomerun_discover_send_broadcast(ds, device_type, device_id); + struct hdhomerun_discover_sock_t *localhost_dss = ds->ipv6_localhost; + if (!localhost_dss) { + return; + } + + hdhomerun_discover_sock_flush_list(ds, localhost_dss); + hdhomerun_discover_sock_recv_list_append_list(recv_list, localhost_dss); + + struct sockaddr_in6 sock_addr_in; + if (target_addr) { + memcpy(&sock_addr_in, target_addr, sizeof(sock_addr_in)); } else { - return hdhomerun_discover_send_targeted(ds, target_ip, device_type, device_id); + memset(&sock_addr_in, 0, sizeof(sock_addr_in)); + sock_addr_in.sin6_family = AF_INET6; + sock_addr_in.sin6_addr.s6_addr[15] = 1; + } + + sock_addr_in.sin6_port = htons(HDHOMERUN_DISCOVER_UDP_PORT); + hdhomerun_discover_send_request(ds, localhost_dss, (const struct sockaddr *)&sock_addr_in, device_types, device_types_count, device_id); +} + +static void hdhomerun_discover_send_ipv4_localhost(struct hdhomerun_discover_t *ds, const struct sockaddr *target_addr, uint32_t flags, const uint32_t device_types[], size_t device_types_count, uint32_t device_id, struct hdhomerun_discover_sock_t **recv_list) +{ + struct hdhomerun_discover_sock_t *localhost_dss = ds->ipv4_localhost; + if (!localhost_dss) { + return; + } + + hdhomerun_discover_sock_flush_list(ds, localhost_dss); + hdhomerun_discover_sock_recv_list_append_list(recv_list, localhost_dss); + + struct sockaddr_in sock_addr_in; + if (target_addr) { + memcpy(&sock_addr_in, target_addr, sizeof(sock_addr_in)); + } else { + memset(&sock_addr_in, 0, sizeof(sock_addr_in)); + sock_addr_in.sin_family = AF_INET; + sock_addr_in.sin_addr.s_addr = htonl(0x7F000001); + } + + sock_addr_in.sin_port = htons(HDHOMERUN_DISCOVER_UDP_PORT); + hdhomerun_discover_send_request(ds, localhost_dss, (const struct sockaddr *)&sock_addr_in, device_types, device_types_count, device_id); +} + +static uint8_t hdhomerun_discover_compute_device_if_priority(struct hdhomerun_discover2_device_if_t *device_if) +{ + if (hdhomerun_discover_is_ipv6_localhost((const struct sockaddr *)&device_if->ip_addr)) { + return 0; /* highest priority */ + } + + if (hdhomerun_discover_is_ipv4_localhost((const struct sockaddr *)&device_if->ip_addr)) { + return 1; + } + + if (device_if->ip_addr.ss_family == AF_INET6) { + if (hdhomerun_discover_is_ipv6_routable((const struct sockaddr *)&device_if->ip_addr)) { + return 2; + } + + if (!hdhomerun_discover_is_ipv6_linklocal((const struct sockaddr *)&device_if->ip_addr)) { + return 3; + } + + return 4; + } + + if (!hdhomerun_discover_is_ipv4_autoip((const struct sockaddr *)&device_if->ip_addr)) { + return 5; + } + + return 6; +} + +static void hdhomerun_discover_recv_internal_device_type(struct hdhomerun_discover2_device_t *device, struct hdhomerun_pkt_t *rx_pkt) +{ + uint32_t device_type = hdhomerun_pkt_read_u32(rx_pkt); + if ((device_type == 0) || (device_type == HDHOMERUN_DEVICE_TYPE_WILDCARD)) { + return; + } + + struct hdhomerun_discover2_device_type_t **pprev = &device->type_list; + struct hdhomerun_discover2_device_type_t *p = device->type_list; + while (p) { + if (p->device_type >= device_type) { + if (p->device_type > device_type) { + break; + } + return; + } + + pprev = &p->next; + p = p->next; + } + + struct hdhomerun_discover2_device_type_t *new_type = (struct hdhomerun_discover2_device_type_t *)calloc(sizeof(struct hdhomerun_discover2_device_type_t), 1); + if (!new_type) { + return; + } + + new_type->device_type = device_type; + new_type->next = *pprev; + *pprev = new_type; +} + +static void hdhomerun_discover_recv_internal_string(char **poutput, struct hdhomerun_pkt_t *rx_pkt, size_t len) +{ + if (*poutput) { + return; + } + + char *str = (char *)malloc(len + 1); + if (!str) { + return; + } + + hdhomerun_pkt_read_mem(rx_pkt, str, len); + str[len] = 0; + + *poutput = str; +} + +static void hdhomerun_discover_recv_internal_auth_bin(char **poutput, struct hdhomerun_pkt_t *rx_pkt, size_t len) +{ + if (*poutput) { + return; + } + if (len != 18) { + return; + } + + char *str = (char *)malloc(24 + 1); + if (!str) { + return; + } + + int i; + for (i = 0; i < 24; i += 4) { + static char hdhomerun_discover_recv_base64_encode_table[64 + 1] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; + + uint32_t raw24; + raw24 = (uint32_t)hdhomerun_pkt_read_u8(rx_pkt) << 16; + raw24 |= (uint32_t)hdhomerun_pkt_read_u8(rx_pkt) << 8; + raw24 |= (uint32_t)hdhomerun_pkt_read_u8(rx_pkt) << 0; + str[i + 0] = hdhomerun_discover_recv_base64_encode_table[(raw24 >> 18) & 0x3F]; + str[i + 1] = hdhomerun_discover_recv_base64_encode_table[(raw24 >> 12) & 0x3F]; + str[i + 2] = hdhomerun_discover_recv_base64_encode_table[(raw24 >> 6) & 0x3F]; + str[i + 3] = hdhomerun_discover_recv_base64_encode_table[(raw24 >> 0) & 0x3F]; + } + + str[24] = 0; + + *poutput = str; +} + +static bool hdhomerun_discover_recv_internal(struct hdhomerun_discover_t *ds, struct hdhomerun_pkt_t *rx_pkt, struct hdhomerun_discover2_device_t *device) +{ + uint16_t type; + if (hdhomerun_pkt_open_frame(rx_pkt, &type) <= 0) { + return false; + } + if (type != HDHOMERUN_TYPE_DISCOVER_RPY) { + return false; + } + + struct hdhomerun_discover2_device_if_t *device_if = device->if_list; + + while (1) { + uint8_t tag; + size_t len; + uint8_t *next = hdhomerun_pkt_read_tlv(rx_pkt, &tag, &len); + if (!next) { + break; + } + + switch (tag) { + case HDHOMERUN_TAG_DEVICE_TYPE: + if (len != 4) { + break; + } + hdhomerun_discover_recv_internal_device_type(device, rx_pkt); + break; + + case HDHOMERUN_TAG_MULTI_TYPE: + while (len >= 4) { + hdhomerun_discover_recv_internal_device_type(device, rx_pkt); + len -= 4; + } + break; + + case HDHOMERUN_TAG_DEVICE_ID: + if (len != 4) { + break; + } + device->device_id = hdhomerun_pkt_read_u32(rx_pkt); + break; + + case HDHOMERUN_TAG_TUNER_COUNT: + if (len != 1) { + break; + } + device->tuner_count = hdhomerun_pkt_read_u8(rx_pkt); + break; + + case HDHOMERUN_TAG_DEVICE_AUTH_STR: + hdhomerun_discover_recv_internal_string(&device->device_auth, rx_pkt, len); + break; + + case HDHOMERUN_TAG_DEVICE_AUTH_BIN_DEPRECATED: + hdhomerun_discover_recv_internal_auth_bin(&device->device_auth, rx_pkt, len); + break; + + case HDHOMERUN_TAG_BASE_URL: + hdhomerun_discover_recv_internal_string(&device_if->base_url, rx_pkt, len); + break; + + case HDHOMERUN_TAG_STORAGE_ID: + hdhomerun_discover_recv_internal_string(&device->storage_id, rx_pkt, len); + break; + + case HDHOMERUN_TAG_LINEUP_URL: + hdhomerun_discover_recv_internal_string(&device_if->lineup_url, rx_pkt, len); + break; + + case HDHOMERUN_TAG_STORAGE_URL: + hdhomerun_discover_recv_internal_string(&device_if->storage_url, rx_pkt, len); + break; + + default: + break; + } + + rx_pkt->pos = next; + } + + if (!device->type_list) { + return false; + } + + /* + * Fixup for old firmware. + */ + if (hdhomerun_discover2_device_is_type(device, HDHOMERUN_DEVICE_TYPE_TUNER)) { + if (device->tuner_count == 0) { + switch (device->device_id >> 20) { + case 0x102: + device->tuner_count = 1; + break; + + case 0x100: + case 0x101: + case 0x121: + device->tuner_count = 2; + break; + + default: + break; + } + } + + if (!device_if->base_url && (device_if->ip_addr.ss_family == AF_INET)) { + char *str = (char *)malloc(32); + if (!str) { + return false; + } + + struct sockaddr_in *sock_addr_in = (struct sockaddr_in *)&device_if->ip_addr; + uint32_t ip = ntohl(sock_addr_in->sin_addr.s_addr); + + hdhomerun_sprintf(str, str + 32, "http://%u.%u.%u.%u:80", + (ip >> 24) & 0xFF, (ip >> 16) & 0xFF, (ip >> 8) & 0xFF, (ip >> 0) & 0xFF + ); + + device_if->base_url = str; + } + } + + device_if->priority = hdhomerun_discover_compute_device_if_priority(device_if); + return true; +} + +static void hdhomerun_discover_recv_merge_device_type(struct hdhomerun_discover2_device_t *device, struct hdhomerun_discover2_device_type_t *new_type) +{ + struct hdhomerun_discover2_device_type_t **pprev = &device->type_list; + struct hdhomerun_discover2_device_type_t *p = device->type_list; + while (p) { + if (p->device_type >= new_type->device_type) { + if (p->device_type > new_type->device_type) { + break; + } + free(new_type); + return; + } + + pprev = &p->next; + p = p->next; + } + + new_type->next = *pprev; + *pprev = new_type; +} + +static void hdhomerun_discover_recv_merge_device_types(struct hdhomerun_discover2_device_t *device, struct hdhomerun_discover2_device_type_t *type_list) +{ + while (type_list) { + struct hdhomerun_discover2_device_type_t *new_type = type_list; + type_list = new_type->next; + hdhomerun_discover_recv_merge_device_type(device, new_type); } } -static bool hdhomerun_discover_is_legacy(uint32_t device_id) +static void hdhomerun_discover_recv_merge_device_if(struct hdhomerun_discover2_device_t *device, struct hdhomerun_discover2_device_if_t *device_if) { - switch (device_id >> 20) { + struct hdhomerun_discover2_device_if_t **pprev = &device->if_list; + struct hdhomerun_discover2_device_if_t *p = device->if_list; + while (p) { + if (p->priority < device_if->priority) { + pprev = &p->next; + p = p->next; + continue; + } + if (p->priority > device_if->priority) { + break; + } + + char *p_base_url = p->base_url; + char *device_if_base_url = device_if->base_url; + if (!p_base_url) { + p_base_url = ""; + } + if (!device_if_base_url) { + device_if_base_url = ""; + } + + int cmp = strcmp(p_base_url, device_if_base_url); + if (cmp < 0) { + pprev = &p->next; + p = p->next; + continue; + } + if (cmp > 0) { + break; + } + + /* Duplicate */ + hdhomerun_discover_device_if_free(device_if); + return; + } + + device_if->next = *pprev; + *pprev = device_if; +} + +static void hdhomerun_discover_recv_merge_device(struct hdhomerun_discover_t *ds, struct hdhomerun_discover2_device_t *device) +{ + struct hdhomerun_discover2_device_t **pprev = &ds->device_list; + struct hdhomerun_discover2_device_t *p = ds->device_list; + while (p) { + if (p->device_id < device->device_id) { + pprev = &p->next; + p = p->next; + continue; + } + if (p->device_id > device->device_id) { + break; + } + + char *p_storage_id = p->storage_id; + char *device_storage_id = device->storage_id; + if (!p_storage_id) { + p_storage_id = ""; + } + if (!device_storage_id) { + device_storage_id = ""; + } + + int cmp = strcmp(p_storage_id, device_storage_id); + if (cmp < 0) { + pprev = &p->next; + p = p->next; + continue; + } + if (cmp > 0) { + break; + } + + /* Merge */ + struct hdhomerun_discover2_device_type_t *type_list = device->type_list; + device->type_list = NULL; + hdhomerun_discover_recv_merge_device_types(p, type_list); + + struct hdhomerun_discover2_device_if_t *device_if = device->if_list; + device->if_list = NULL; + hdhomerun_discover_recv_merge_device_if(p, device_if); + + hdhomerun_discover_device_free(device); + return; + } + + device->next = *pprev; + *pprev = device; +} + +static bool hdhomerun_discover_recvfrom_match_flags(const struct sockaddr *remote_addr, uint32_t flags) +{ + if (remote_addr->sa_family == AF_INET6) { + if (hdhomerun_discover_is_ipv6_localhost(remote_addr)) { + return (flags & HDHOMERUN_DISCOVER_FLAGS_IPV6_LOCALHOST) != 0; + } + + if (hdhomerun_discover_is_ipv6_linklocal(remote_addr)) { + return (flags & HDHOMERUN_DISCOVER_FLAGS_IPV6_LINKLOCAL) != 0; + } + + return (flags & HDHOMERUN_DISCOVER_FLAGS_IPV6_GENERAL) != 0; + } + + if (remote_addr->sa_family == AF_INET) { + if (hdhomerun_discover_is_ipv4_localhost(remote_addr)) { + return (flags & HDHOMERUN_DISCOVER_FLAGS_IPV4_LOCALHOST) != 0; + } + + return (flags & HDHOMERUN_DISCOVER_FLAGS_IPV4_GENERAL) != 0; + } + + return false; +} + +static bool hdhomerun_discover_recvfrom_match_device_type(struct hdhomerun_discover2_device_t *device, const uint32_t device_types_match[], size_t device_types_count) +{ + if (device_types_count == 0) { + return false; + } + + if (device_types_match[0] == HDHOMERUN_DEVICE_TYPE_WILDCARD) { + return true; + } + + while (device_types_count--) { + if (hdhomerun_discover2_device_is_type(device, *device_types_match++)) { + return true; + } + } + + return false; +} + +static bool hdhomerun_discover_recvfrom_match_device_id(struct hdhomerun_discover2_device_t *device, uint32_t device_id_match) +{ + if (device_id_match == HDHOMERUN_DEVICE_ID_WILDCARD) { + return true; + } + + return (device->device_id == device_id_match); +} + +static bool hdhomerun_discover_recvfrom(struct hdhomerun_discover_t *ds, struct hdhomerun_discover_sock_t *dss, uint32_t flags, const uint32_t device_types_match[], size_t device_types_count, uint32_t device_id_match) +{ + struct hdhomerun_pkt_t *rx_pkt = &ds->rx_pkt; + hdhomerun_pkt_reset(rx_pkt); + bool activity = false; + + struct sockaddr_storage remote_addr; + size_t length = rx_pkt->limit - rx_pkt->end; + if (!hdhomerun_sock_recvfrom_ex(dss->sock, &remote_addr, rx_pkt->end, &length, 0)) { + return activity; + } + + rx_pkt->end += length; + activity = true; + + if (!hdhomerun_discover_recvfrom_match_flags((const struct sockaddr *)&remote_addr, flags)) { + return activity; + } + + struct hdhomerun_discover2_device_t *device = (struct hdhomerun_discover2_device_t *)calloc(sizeof(struct hdhomerun_discover2_device_t), 1); + struct hdhomerun_discover2_device_if_t *device_if = (struct hdhomerun_discover2_device_if_t *)calloc(sizeof(struct hdhomerun_discover2_device_if_t), 1); + if (!device || !device_if) { + if (device) { + free(device); + } + if (device_if) { + free(device_if); + } + return activity; + } + + device->if_list = device_if; + device_if->ip_addr = remote_addr; + + if (!hdhomerun_discover_recv_internal(ds, rx_pkt, device)) { + hdhomerun_discover_device_free(device); + return activity; + } + + if (!hdhomerun_discover_recvfrom_match_device_type(device, device_types_match, device_types_count)) { + hdhomerun_discover_device_free(device); + return activity; + } + + if (!hdhomerun_discover_recvfrom_match_device_id(device, device_id_match)) { + hdhomerun_discover_device_free(device); + return activity; + } + + hdhomerun_discover_recv_merge_device(ds, device); + return activity; +} + +static uint32_t hdhomerun_discover2_find_devices_targeted_flags(const struct sockaddr *target_addr) +{ + if (target_addr->sa_family == AF_INET6) { + if (hdhomerun_discover_is_ipv6_localhost(target_addr)) { + return HDHOMERUN_DISCOVER_FLAGS_IPV6_LOCALHOST; + } + + if (hdhomerun_discover_is_ipv6_linklocal(target_addr)) { + return HDHOMERUN_DISCOVER_FLAGS_IPV6_LINKLOCAL; + } + + return HDHOMERUN_DISCOVER_FLAGS_IPV6_GENERAL; + } + + if (target_addr->sa_family == AF_INET) { + if (hdhomerun_discover_is_ipv4_localhost(target_addr)) { + return HDHOMERUN_DISCOVER_FLAGS_IPV4_LOCALHOST; + } + + return HDHOMERUN_DISCOVER_FLAGS_IPV4_GENERAL; + } + + return 0; +} + +static int hdhomerun_discover2_find_devices_targeted_internal(struct hdhomerun_discover_t *ds, const struct sockaddr *target_addr, const uint32_t device_types[], size_t device_types_count, uint32_t device_id) +{ + uint32_t flags = hdhomerun_discover2_find_devices_targeted_flags(target_addr); + if (flags == 0) { + return -1; + } + + hdhomerun_discover_free_device_list(ds); + hdhomerun_discover_sock_detect_all_from_flags(ds, flags); + + int attempt; + for (attempt = 0; attempt < 2; attempt++) { + struct hdhomerun_discover_sock_t *recv_list = NULL; + if (flags & HDHOMERUN_DISCOVER_FLAGS_IPV6_LOCALHOST) { + hdhomerun_discover_send_ipv6_localhost(ds, target_addr, flags, device_types, device_types_count, device_id, &recv_list); + } + if (flags & (HDHOMERUN_DISCOVER_FLAGS_IPV6_GENERAL | HDHOMERUN_DISCOVER_FLAGS_IPV6_LINKLOCAL)) { + hdhomerun_discover_send_ipv6_targeted(ds, target_addr, flags, device_types, device_types_count, device_id, &recv_list); + } + if (flags & HDHOMERUN_DISCOVER_FLAGS_IPV4_LOCALHOST) { + hdhomerun_discover_send_ipv4_localhost(ds, target_addr, flags, device_types, device_types_count, device_id, &recv_list); + } + if (flags & HDHOMERUN_DISCOVER_FLAGS_IPV4_GENERAL) { + hdhomerun_discover_send_ipv4_targeted(ds, target_addr, flags, device_types, device_types_count, device_id, &recv_list); + } + if (!recv_list) { + return -1; + } + + uint64_t timeout = getcurrenttime() + 200; + + while (1) { + bool activity = false; + + struct hdhomerun_discover_sock_t *dss = recv_list; + while (dss) { + activity |= hdhomerun_discover_recvfrom(ds, dss, flags, device_types, device_types_count, device_id); + dss = dss->recv_next; + } + + if (ds->device_list) { + return 1; + } + if (activity) { + continue; + } + if (getcurrenttime() >= timeout) { + break; + } + msleep_approx(16); + } + } + + return (ds->device_list) ? 1 : 0; +} + +static int hdhomerun_discover2_find_devices_broadcast_internal(struct hdhomerun_discover_t *ds, uint32_t flags, const uint32_t device_types[], size_t device_types_count, uint32_t device_id) +{ + hdhomerun_discover_free_device_list(ds); + hdhomerun_discover_sock_detect_all_from_flags(ds, flags); + + int attempt; + for (attempt = 0; attempt < 2; attempt++) { + struct hdhomerun_discover_sock_t *recv_list = NULL; + if (flags & HDHOMERUN_DISCOVER_FLAGS_IPV6_LOCALHOST) { + hdhomerun_discover_send_ipv6_localhost(ds, NULL, flags, device_types, device_types_count, device_id, &recv_list); + } + if (flags & (HDHOMERUN_DISCOVER_FLAGS_IPV6_GENERAL | HDHOMERUN_DISCOVER_FLAGS_IPV6_LINKLOCAL)) { + hdhomerun_discover_send_ipv6_multicast(ds, flags, device_types, device_types_count, device_id, &recv_list); + } + if (flags & HDHOMERUN_DISCOVER_FLAGS_IPV4_LOCALHOST) { + hdhomerun_discover_send_ipv4_localhost(ds, NULL, flags, device_types, device_types_count, device_id, &recv_list); + } + if (flags & HDHOMERUN_DISCOVER_FLAGS_IPV4_GENERAL) { + hdhomerun_discover_send_ipv4_broadcast(ds, flags, device_types, device_types_count, device_id, &recv_list); + } + if (!recv_list) { + return -1; + } + + uint64_t timeout = getcurrenttime() + 200; + + while (1) { + bool activity = false; + + struct hdhomerun_discover_sock_t *dss = recv_list; + while (dss) { + activity |= hdhomerun_discover_recvfrom(ds, dss, flags, device_types, device_types_count, device_id); + dss = dss->recv_next; + } + + if ((device_id != HDHOMERUN_DEVICE_ID_WILDCARD) && ds->device_list) { + return 1; + } + if (activity) { + continue; + } + if (getcurrenttime() >= timeout) { + break; + } + msleep_approx(16); + } + } + + return (ds->device_list) ? 1 : 0; +} + +int hdhomerun_discover2_find_devices_targeted(struct hdhomerun_discover_t *ds, const struct sockaddr *target_addr, const uint32_t device_types[], size_t device_types_count) +{ + return hdhomerun_discover2_find_devices_targeted_internal(ds, target_addr, device_types, device_types_count, HDHOMERUN_DEVICE_ID_WILDCARD); +} + +int hdhomerun_discover2_find_devices_broadcast(struct hdhomerun_discover_t *ds, uint32_t flags, const uint32_t device_types[], size_t device_types_count) +{ + return hdhomerun_discover2_find_devices_broadcast_internal(ds, flags, device_types, device_types_count, HDHOMERUN_DEVICE_ID_WILDCARD); +} + +int hdhomerun_discover2_find_device_id_targeted(struct hdhomerun_discover_t *ds, const struct sockaddr *target_addr, uint32_t device_id) +{ + if ((device_id == 0) || (device_id == HDHOMERUN_DEVICE_ID_WILDCARD)) { + return -1; + } + if (!hdhomerun_discover_validate_device_id(device_id)) { + return - 1; + } + + uint32_t device_types[1]; + device_types[0] = HDHOMERUN_DEVICE_TYPE_WILDCARD; + + return hdhomerun_discover2_find_devices_targeted_internal(ds, target_addr, device_types, 1, device_id); +} + +int hdhomerun_discover2_find_device_id_broadcast(struct hdhomerun_discover_t *ds, uint32_t flags, uint32_t device_id) +{ + if ((device_id == 0) || (device_id == HDHOMERUN_DEVICE_ID_WILDCARD)) { + return -1; + } + if (!hdhomerun_discover_validate_device_id(device_id)) { + return -1; + } + + uint32_t device_types[1]; + device_types[0] = HDHOMERUN_DEVICE_TYPE_WILDCARD; + + return hdhomerun_discover2_find_devices_broadcast_internal(ds, flags, device_types, 1, device_id); +} + +struct hdhomerun_discover2_device_t *hdhomerun_discover2_iter_device_first(struct hdhomerun_discover_t *ds) +{ + return ds->device_list; +} + +struct hdhomerun_discover2_device_t *hdhomerun_discover2_iter_device_next(struct hdhomerun_discover2_device_t *device) +{ + return device->next; +} + +struct hdhomerun_discover2_device_if_t *hdhomerun_discover2_iter_device_if_first(struct hdhomerun_discover2_device_t *device) +{ + return device->if_list; +} + +struct hdhomerun_discover2_device_if_t *hdhomerun_discover2_iter_device_if_next(struct hdhomerun_discover2_device_if_t *device_if) +{ + return device_if->next; +} + +bool hdhomerun_discover2_device_is_legacy(struct hdhomerun_discover2_device_t *device) +{ + switch (device->device_id >> 20) { case 0x100: /* TECH-US/TECH3-US */ - return (device_id < 0x10040000); + return (device->device_id < 0x10040000); case 0x120: /* TECH3-EU */ - return (device_id < 0x12030000); + return (device->device_id < 0x12030000); case 0x101: /* HDHR-US */ case 0x102: /* HDHR-T1-US */ @@ -280,268 +1407,231 @@ static bool hdhomerun_discover_is_legacy(uint32_t device_id) } } -static bool hdhomerun_discover_recv_internal(struct hdhomerun_discover_t *ds, struct hdhomerun_discover_sock_t *dss, size_t result_struct_size, struct hdhomerun_discover_device_t *result) +bool hdhomerun_discover2_device_is_type(struct hdhomerun_discover2_device_t *device, uint32_t device_type) { - static char hdhomerun_discover_recv_base64_encode_table[64 + 1] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; - struct hdhomerun_pkt_t *rx_pkt = &ds->rx_pkt; - hdhomerun_pkt_reset(rx_pkt); - - uint32_t remote_addr; - uint16_t remote_port; - size_t length = rx_pkt->limit - rx_pkt->end; - if (!hdhomerun_sock_recvfrom(dss->sock, &remote_addr, &remote_port, rx_pkt->end, &length, 0)) { - return false; - } - - rx_pkt->end += length; - - uint16_t type; - if (hdhomerun_pkt_open_frame(rx_pkt, &type) <= 0) { - return false; - } - if (type != HDHOMERUN_TYPE_DISCOVER_RPY) { - return false; - } - - struct hdhomerun_discover_device_v3_t *result_v3 = (result_struct_size >= sizeof(struct hdhomerun_discover_device_v3_t)) ? (struct hdhomerun_discover_device_v3_t *)(void *)result : NULL; - memset(result, 0, result_struct_size); - result->ip_addr = remote_addr; - - while (1) { - uint8_t tag; - size_t len; - uint8_t *next = hdhomerun_pkt_read_tlv(rx_pkt, &tag, &len); - if (!next) { - break; - } - - int i; - switch (tag) { - case HDHOMERUN_TAG_DEVICE_TYPE: - if (len != 4) { - break; - } - result->device_type = hdhomerun_pkt_read_u32(rx_pkt); - break; - - case HDHOMERUN_TAG_DEVICE_ID: - if (len != 4) { - break; - } - result->device_id = hdhomerun_pkt_read_u32(rx_pkt); - result->is_legacy = hdhomerun_discover_is_legacy(result->device_id); - break; - - case HDHOMERUN_TAG_TUNER_COUNT: - if (len != 1) { - break; - } - result->tuner_count = hdhomerun_pkt_read_u8(rx_pkt); - break; - - case HDHOMERUN_TAG_DEVICE_AUTH_STR: - if (len >= sizeof(result->device_auth)) { - break; - } - hdhomerun_pkt_read_mem(rx_pkt, result->device_auth, len); - result->device_auth[len] = 0; - break; - - case HDHOMERUN_TAG_DEVICE_AUTH_BIN_DEPRECATED: - if (len != 18) { - break; - } - for (i = 0; i < 24; i += 4) { - uint32_t raw24; - raw24 = (uint32_t)hdhomerun_pkt_read_u8(rx_pkt) << 16; - raw24 |= (uint32_t)hdhomerun_pkt_read_u8(rx_pkt) << 8; - raw24 |= (uint32_t)hdhomerun_pkt_read_u8(rx_pkt) << 0; - result->device_auth[i + 0] = hdhomerun_discover_recv_base64_encode_table[(raw24 >> 18) & 0x3F]; - result->device_auth[i + 1] = hdhomerun_discover_recv_base64_encode_table[(raw24 >> 12) & 0x3F]; - result->device_auth[i + 2] = hdhomerun_discover_recv_base64_encode_table[(raw24 >> 6) & 0x3F]; - result->device_auth[i + 3] = hdhomerun_discover_recv_base64_encode_table[(raw24 >> 0) & 0x3F]; - } - result->device_auth[24] = 0; - break; - - case HDHOMERUN_TAG_BASE_URL: - if (len >= sizeof(result->base_url)) { - break; - } - hdhomerun_pkt_read_mem(rx_pkt, result->base_url, len); - result->base_url[len] = 0; - break; - - case HDHOMERUN_TAG_STORAGE_ID: - if (!result_v3) { - break; - } - if (len >= sizeof(result_v3->storage_id)) { - break; - } - hdhomerun_pkt_read_mem(rx_pkt, result_v3->storage_id, len); - result_v3->storage_id[len] = 0; - break; - - case HDHOMERUN_TAG_LINEUP_URL: - if (!result_v3) { - break; - } - if (len >= sizeof(result_v3->lineup_url)) { - break; - } - hdhomerun_pkt_read_mem(rx_pkt, result_v3->lineup_url, len); - result_v3->lineup_url[len] = 0; - break; - - case HDHOMERUN_TAG_STORAGE_URL: - if (!result_v3) { - break; - } - if (len >= sizeof(result_v3->storage_url)) { - break; - } - hdhomerun_pkt_read_mem(rx_pkt, result_v3->storage_url, len); - result_v3->storage_url[len] = 0; - break; - - default: - break; - } - - rx_pkt->pos = next; - } - - /* - * Fixup for old firmware. - */ - if (result->device_type == HDHOMERUN_DEVICE_TYPE_TUNER) { - if (result->tuner_count == 0) { - switch (result->device_id >> 20) { - case 0x102: - result->tuner_count = 1; - break; - - case 0x100: - case 0x101: - case 0x121: - result->tuner_count = 2; - break; - - default: - break; - } - } - - if (!result->base_url[0]) { - hdhomerun_sprintf(result->base_url, result->base_url + sizeof(result->base_url), "http://%u.%u.%u.%u:80", - (remote_addr >> 24) & 0xFF, (remote_addr >> 16) & 0xFF, (remote_addr >> 8) & 0xFF, (remote_addr >> 0) & 0xFF - ); - } - } - - return true; -} - -static bool hdhomerun_discover_recv(struct hdhomerun_discover_t *ds, size_t result_struct_size, struct hdhomerun_discover_device_t *result) -{ - unsigned int i; - for (i = 0; i < ds->sock_count; i++) { - struct hdhomerun_discover_sock_t *dss = &ds->socks[i]; - - if (hdhomerun_discover_recv_internal(ds, dss, result_struct_size, result)) { + struct hdhomerun_discover2_device_type_t *p = device->type_list; + while (p) { + if (p->device_type == device_type) { return true; } + p = p->next; } return false; } -static struct hdhomerun_discover_device_t *hdhomerun_discover_result_by_index(size_t result_struct_size, struct hdhomerun_discover_device_t result_list[], int index) +uint32_t hdhomerun_discover2_device_get_device_id(struct hdhomerun_discover2_device_t *device) { - uint8_t *ptr = (uint8_t *)(void *)result_list; - ptr += result_struct_size * index; - return (struct hdhomerun_discover_device_t *)(void *)ptr; + return device->device_id; } -static struct hdhomerun_discover_device_t *hdhomerun_discover_find_in_list(size_t result_struct_size, struct hdhomerun_discover_device_t result_list[], int count, struct hdhomerun_discover_device_t *lookup) +const char *hdhomerun_discover2_device_get_storage_id(struct hdhomerun_discover2_device_t *device) { - int index; - for (index = 0; index < count; index++) { - struct hdhomerun_discover_device_t *entry = hdhomerun_discover_result_by_index(result_struct_size, result_list, index); - if (lookup->ip_addr != entry->ip_addr) { - continue; - } + return device->storage_id; +} - if (strcmp(lookup->base_url, entry->base_url) != 0) { - continue; - } +uint8_t hdhomerun_discover2_device_get_tuner_count(struct hdhomerun_discover2_device_t *device) +{ + return device->tuner_count; +} - return entry; +const char *hdhomerun_discover2_device_get_device_auth(struct hdhomerun_discover2_device_t *device) +{ + return device->device_auth; +} + +bool hdhomerun_discover2_device_if_addr_is_ipv4(struct hdhomerun_discover2_device_if_t *device_if) +{ + return (device_if->ip_addr.ss_family == AF_INET); +} + +bool hdhomerun_discover2_device_if_addr_is_ipv6_linklocal(struct hdhomerun_discover2_device_if_t *device_if) +{ + return hdhomerun_discover_is_ipv6_linklocal((const struct sockaddr *)&device_if->ip_addr); +} + +void hdhomerun_discover2_device_if_get_ip_addr(struct hdhomerun_discover2_device_if_t *device_if, struct sockaddr_storage *ip_addr) +{ + *ip_addr = device_if->ip_addr; +} + +const char *hdhomerun_discover2_device_if_get_base_url(struct hdhomerun_discover2_device_if_t *device_if) +{ + return device_if->base_url; +} + +const char *hdhomerun_discover2_device_if_get_lineup_url(struct hdhomerun_discover2_device_if_t *device_if) +{ + return device_if->lineup_url; +} + +const char *hdhomerun_discover2_device_if_get_storage_url(struct hdhomerun_discover2_device_if_t *device_if) +{ + return device_if->storage_url; +} + +static void hdhomerun_discover_v2v3_strcpy(char *output, size_t size, const char *src) +{ + if (!src) { + output[0] = 0; + return; } - return NULL; + size_t src_len = strlen(src); + if (src_len >= size) { + output[0] = 0; + return; + } + + memcpy(output, src, src_len + 1); } -static int hdhomerun_discover_find_devices(struct hdhomerun_discover_t *ds, uint32_t target_ip, uint32_t device_type_match, uint32_t device_id_match, size_t result_struct_size, struct hdhomerun_discover_device_t result_list[], int max_count) +static uint32_t hdhomerun_discover_v2v3_device_type(struct hdhomerun_discover2_device_t *device, uint32_t device_type_match) { - if (target_ip == 0x00000000) { + if (device_type_match == HDHOMERUN_DEVICE_TYPE_WILDCARD) { + return device->type_list->device_type; + } + + if (hdhomerun_discover2_device_is_type(device, device_type_match)) { + return device_type_match; + } + + return device->type_list->device_type; +} + +int hdhomerun_discover_find_devices_v3(struct hdhomerun_discover_t *ds, uint32_t target_ip, uint32_t device_type_match, uint32_t device_id_match, struct hdhomerun_discover_device_v3_t result_list[], int max_count) +{ + if (target_ip == 0) { target_ip = 0xFFFFFFFF; } + if (device_type_match == 0) { + device_type_match = HDHOMERUN_DEVICE_TYPE_WILDCARD; + } + if (device_id_match == 0) { + device_id_match = HDHOMERUN_DEVICE_ID_WILDCARD; + } + if (!hdhomerun_discover_validate_device_id(device_id_match)) { + return -1; + } - hdhomerun_discover_sock_detect(ds); + uint32_t device_types[1]; + device_types[0] = device_type_match; + int ret; + if (target_ip == 0xFFFFFFFF) { + ret = hdhomerun_discover2_find_devices_broadcast_internal(ds, HDHOMERUN_DISCOVER_FLAGS_IPV4_GENERAL, device_types, 1, device_id_match); + } else { + struct sockaddr_in sock_addr_in; + memset(&sock_addr_in, 0, sizeof(sock_addr_in)); + sock_addr_in.sin_family = AF_INET; + sock_addr_in.sin_addr.s_addr = htonl(target_ip); + ret = hdhomerun_discover2_find_devices_targeted_internal(ds, (const struct sockaddr *)&sock_addr_in, device_types, 1, device_id_match); + } + if (ret <= 0) { + return ret; + } + + struct hdhomerun_discover_device_v3_t *result = result_list; + struct hdhomerun_discover2_device_t *device = ds->device_list; int count = 0; - int attempt; - for (attempt = 0; attempt < 2; attempt++) { - if (!hdhomerun_discover_send(ds, target_ip, device_type_match, device_id_match)) { - return -1; + + while (device && (count < max_count)) { + struct hdhomerun_discover2_device_if_t *device_if = device->if_list; + while (device_if) { + if (device_if->ip_addr.ss_family == AF_INET) { + break; + } + device_if = device_if->next; + } + if (!device_if) { + device = device->next; + continue; } - uint64_t timeout = getcurrenttime() + 200; - while (1) { - struct hdhomerun_discover_device_t *result = hdhomerun_discover_result_by_index(result_struct_size, result_list, count); + struct sockaddr_in *sock_addr_in = (struct sockaddr_in *)&device_if->ip_addr; + result->ip_addr = ntohl(sock_addr_in->sin_addr.s_addr); + result->device_type = hdhomerun_discover_v2v3_device_type(device, device_type_match); + result->device_id = device->device_id; + result->tuner_count = device->tuner_count; + result->is_legacy = hdhomerun_discover2_device_is_legacy(device); + hdhomerun_discover_v2v3_strcpy(result->storage_id, sizeof(result->storage_id), device->storage_id); + hdhomerun_discover_v2v3_strcpy(result->device_auth, sizeof(result->device_auth), device->device_auth); + hdhomerun_discover_v2v3_strcpy(result->base_url, sizeof(result->base_url), device_if->base_url); + hdhomerun_discover_v2v3_strcpy(result->lineup_url, sizeof(result->lineup_url), device_if->lineup_url); + hdhomerun_discover_v2v3_strcpy(result->storage_url, sizeof(result->storage_url), device_if->storage_url); - if (!hdhomerun_discover_recv(ds, result_struct_size, result)) { - if (getcurrenttime() >= timeout) { - break; - } - msleep_approx(16); - continue; - } - - /* Filter */ - if ((device_type_match != HDHOMERUN_DEVICE_TYPE_WILDCARD) && (result->device_type != device_type_match)) { - continue; - } - if ((device_id_match != HDHOMERUN_DEVICE_ID_WILDCARD) && (result->device_id != device_id_match)) { - continue; - } - - /* Ensure not already in list. */ - if (hdhomerun_discover_find_in_list(result_struct_size, result_list, count, result)) { - continue; - } - - /* Add to list. */ - count++; - if (count >= max_count) { - return count; - } - } + count++; + result++; + device = device->next; } return count; } -int hdhomerun_discover_find_devices_v3(struct hdhomerun_discover_t *ds, uint32_t target_ip, uint32_t device_type_match, uint32_t device_id_match, struct hdhomerun_discover_device_v3_t result_list[], int max_count) -{ - return hdhomerun_discover_find_devices(ds, target_ip, device_type_match, device_id_match, sizeof(struct hdhomerun_discover_device_v3_t), (struct hdhomerun_discover_device_t *)(void *)result_list, max_count); -} - int hdhomerun_discover_find_devices_v2(struct hdhomerun_discover_t *ds, uint32_t target_ip, uint32_t device_type_match, uint32_t device_id_match, struct hdhomerun_discover_device_t result_list[], int max_count) { - return hdhomerun_discover_find_devices(ds, target_ip, device_type_match, device_id_match, sizeof(struct hdhomerun_discover_device_t), (struct hdhomerun_discover_device_t *)(void *)result_list, max_count); + if (target_ip == 0) { + target_ip = 0xFFFFFFFF; + } + if (device_type_match == 0) { + device_type_match = HDHOMERUN_DEVICE_TYPE_WILDCARD; + } + if (device_id_match == 0) { + device_id_match = HDHOMERUN_DEVICE_ID_WILDCARD; + } + if (!hdhomerun_discover_validate_device_id(device_id_match)) { + return -1; + } + + uint32_t device_types[1]; + device_types[0] = device_type_match; + + int ret; + if (target_ip == 0xFFFFFFFF) { + ret = hdhomerun_discover2_find_devices_broadcast_internal(ds, HDHOMERUN_DISCOVER_FLAGS_IPV4_GENERAL, device_types, 1, device_id_match); + } else { + struct sockaddr_in sock_addr_in; + memset(&sock_addr_in, 0, sizeof(sock_addr_in)); + sock_addr_in.sin_family = AF_INET; + sock_addr_in.sin_addr.s_addr = htonl(target_ip); + ret = hdhomerun_discover2_find_devices_targeted_internal(ds, (const struct sockaddr *)&sock_addr_in, device_types, 1, device_id_match); + } + if (ret <= 0) { + return ret; + } + + struct hdhomerun_discover_device_t *result = result_list; + struct hdhomerun_discover2_device_t *device = ds->device_list; + int count = 0; + + while (device && (count < max_count)) { + struct hdhomerun_discover2_device_if_t *device_if = device->if_list; + while (device_if) { + if (device_if->ip_addr.ss_family == AF_INET) { + break; + } + device_if = device_if->next; + } + if (!device_if) { + device = device->next; + continue; + } + + struct sockaddr_in *sock_addr_in = (struct sockaddr_in *)&device_if->ip_addr; + result->ip_addr = ntohl(sock_addr_in->sin_addr.s_addr); + result->device_type = hdhomerun_discover_v2v3_device_type(device, device_type_match); + result->device_id = device->device_id; + result->tuner_count = device->tuner_count; + result->is_legacy = hdhomerun_discover2_device_is_legacy(device); + hdhomerun_discover_v2v3_strcpy(result->device_auth, sizeof(result->device_auth), device->device_auth); + hdhomerun_discover_v2v3_strcpy(result->base_url, sizeof(result->base_url), device_if->base_url); + + count++; + result++; + device = device->next; + } + + return count; } int hdhomerun_discover_find_devices_custom_v3(uint32_t target_ip, uint32_t device_type_match, uint32_t device_id_match, struct hdhomerun_discover_device_v3_t result_list[], int max_count) @@ -551,7 +1641,7 @@ int hdhomerun_discover_find_devices_custom_v3(uint32_t target_ip, uint32_t devic return -1; } - int ret = hdhomerun_discover_find_devices(ds, target_ip, device_type_match, device_id_match, sizeof(struct hdhomerun_discover_device_v3_t), (struct hdhomerun_discover_device_t *)(void *)result_list, max_count); + int ret = hdhomerun_discover_find_devices_v3(ds, target_ip, device_type_match, device_id_match, result_list, max_count); hdhomerun_discover_destroy(ds); return ret; } @@ -563,7 +1653,7 @@ int hdhomerun_discover_find_devices_custom_v2(uint32_t target_ip, uint32_t devic return -1; } - int ret = hdhomerun_discover_find_devices(ds, target_ip, device_type_match, device_id_match, sizeof(struct hdhomerun_discover_device_t), (struct hdhomerun_discover_device_t *)(void *)result_list, max_count); + int ret = hdhomerun_discover_find_devices_v2(ds, target_ip, device_type_match, device_id_match, result_list, max_count); hdhomerun_discover_destroy(ds); return ret; } @@ -590,3 +1680,19 @@ bool hdhomerun_discover_is_ip_multicast(uint32_t ip_addr) { return (ip_addr >= 0xE0000000) && (ip_addr < 0xF0000000); } + +bool hdhomerun_discover_is_ip_multicast_ex(const struct sockaddr *ip_addr) +{ + if (ip_addr->sa_family == AF_INET6) { + const struct sockaddr_in6 *addr_in6 = (const struct sockaddr_in6 *)ip_addr; + return (addr_in6->sin6_addr.s6_addr[0] == 0xFF); + } + + if (ip_addr->sa_family == AF_INET) { + const struct sockaddr_in *addr_in = (const struct sockaddr_in *)ip_addr; + uint32_t v = ntohl(addr_in->sin_addr.s_addr); + return (v >= 0xE0000000) && (v < 0xF0000000); + } + + return false; +} diff --git a/hdhomerun_discover.h b/hdhomerun_discover.h index 265459e..d6508d9 100644 --- a/hdhomerun_discover.h +++ b/hdhomerun_discover.h @@ -1,7 +1,7 @@ /* * hdhomerun_discover.h * - * Copyright © 2006-2019 Silicondust USA Inc. . + * Copyright © 2006-2022 Silicondust USA Inc. . * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -21,6 +21,111 @@ extern "C" { #endif +#define HDHOMERUN_DISCOVER_FLAGS_IPV4_GENERAL (1 << 0) +#define HDHOMERUN_DISCOVER_FLAGS_IPV4_LOCALHOST (1 << 1) +#define HDHOMERUN_DISCOVER_FLAGS_IPV6_GENERAL (1 << 2) +#define HDHOMERUN_DISCOVER_FLAGS_IPV6_LINKLOCAL (1 << 3) +#define HDHOMERUN_DISCOVER_FLAGS_IPV6_LOCALHOST (1 << 4) + +struct hdhomerun_discover2_device_t; +struct hdhomerun_discover2_device_if_t; + +/* + * Discover object. + * + * May be maintained and reused across the lifespan of the app or may be created and destroyed for each discover. + * If the app polls discover the same discover instance should be reused to avoid burning through local IP ports. + */ +extern LIBHDHOMERUN_API struct hdhomerun_discover_t *hdhomerun_discover_create(struct hdhomerun_debug_t *dbg); +extern LIBHDHOMERUN_API void hdhomerun_discover_destroy(struct hdhomerun_discover_t *ds); + +/* + * Discover API: + * + * Use hdhomerun_discover2_find_devices_broadcast() to find all devices on the local subnet. + * flags: IPv4 only use HDHOMERUN_DISCOVER_FLAGS_IPV4_GENERAL + * flags: IPv4 and IPv6 use HDHOMERUN_DISCOVER_FLAGS_IPV4_GENERAL | HDHOMERUN_DISCOVER_FLAGS_IPV6_GENERAL + * flags: Linklocal IPv6 requires special handling (scope id) - only include HDHOMERUN_DISCOVER_FLAGS_IPV6_LINKLOCAL if the app tracks scope id needed for linklocal IPv6. + * device_types: do not use HDHOMERUN_DEVICE_TYPE_WILDCARD, instead provide an array of types the app wants to discover, for example: + * uint32_t device_types[2]; + * device_types[0] = HDHOMERUN_DEVICE_TYPE_TUNER; + * device_types[1] = HDHOMERUN_DEVICE_TYPE_STORAGE; + * Returns 1 when one or more devices are found. + * Results 0 when no devices are found. + * Returns -1 on error. + */ +extern LIBHDHOMERUN_API int hdhomerun_discover2_find_devices_targeted(struct hdhomerun_discover_t *ds, const struct sockaddr *target_addr, const uint32_t device_types[], size_t device_types_count); +extern LIBHDHOMERUN_API int hdhomerun_discover2_find_devices_broadcast(struct hdhomerun_discover_t *ds, uint32_t flags, uint32_t const device_types[], size_t device_types_count); +extern LIBHDHOMERUN_API int hdhomerun_discover2_find_device_id_targeted(struct hdhomerun_discover_t *ds, const struct sockaddr *target_addr, uint32_t device_id); +extern LIBHDHOMERUN_API int hdhomerun_discover2_find_device_id_broadcast(struct hdhomerun_discover_t *ds, uint32_t flags, uint32_t device_id); + +/* + * Discover result access API. + * + * Use hdhomerun_discover2_iter_device_first() and hdhomerun_discover2_iter_device_next() to iterate through discover results. + * Use hdhomerun_discover2_device_xxx() APIs to query properties of the device. + * + * Use hdhomerun_discover2_iter_device_if_first() to select the first (best) set of device URLs. + * Use hdhomerun_discover2_device_if_xxx() to query specific device URLs. + * + * In most cases hdhomerun_discover2_iter_device_if_next() will not be used as the app should use the first set of URLs. + * + * Results are available until a new discover is run on the same discover object or until the discover object is destroyed. Strings shoud be copied. + */ +extern LIBHDHOMERUN_API struct hdhomerun_discover2_device_t *hdhomerun_discover2_iter_device_first(struct hdhomerun_discover_t *ds); +extern LIBHDHOMERUN_API struct hdhomerun_discover2_device_t *hdhomerun_discover2_iter_device_next(struct hdhomerun_discover2_device_t *device); +extern LIBHDHOMERUN_API struct hdhomerun_discover2_device_if_t *hdhomerun_discover2_iter_device_if_first(struct hdhomerun_discover2_device_t *device); +extern LIBHDHOMERUN_API struct hdhomerun_discover2_device_if_t *hdhomerun_discover2_iter_device_if_next(struct hdhomerun_discover2_device_if_t *device_if); + +extern LIBHDHOMERUN_API bool hdhomerun_discover2_device_is_legacy(struct hdhomerun_discover2_device_t *device); +extern LIBHDHOMERUN_API bool hdhomerun_discover2_device_is_type(struct hdhomerun_discover2_device_t *device, uint32_t device_type); +extern LIBHDHOMERUN_API uint32_t hdhomerun_discover2_device_get_device_id(struct hdhomerun_discover2_device_t *device); +extern LIBHDHOMERUN_API const char *hdhomerun_discover2_device_get_storage_id(struct hdhomerun_discover2_device_t *device); +extern LIBHDHOMERUN_API uint8_t hdhomerun_discover2_device_get_tuner_count(struct hdhomerun_discover2_device_t *device); +extern LIBHDHOMERUN_API const char *hdhomerun_discover2_device_get_device_auth(struct hdhomerun_discover2_device_t *device); + +extern LIBHDHOMERUN_API bool hdhomerun_discover2_device_if_addr_is_ipv4(struct hdhomerun_discover2_device_if_t *device_if); +extern LIBHDHOMERUN_API bool hdhomerun_discover2_device_if_addr_is_ipv6_linklocal(struct hdhomerun_discover2_device_if_t *device_if); +extern LIBHDHOMERUN_API void hdhomerun_discover2_device_if_get_ip_addr(struct hdhomerun_discover2_device_if_t *device_if, struct sockaddr_storage *ip_addr); +extern LIBHDHOMERUN_API const char *hdhomerun_discover2_device_if_get_base_url(struct hdhomerun_discover2_device_if_t *device_if); +extern LIBHDHOMERUN_API const char *hdhomerun_discover2_device_if_get_lineup_url(struct hdhomerun_discover2_device_if_t *device_if); +extern LIBHDHOMERUN_API const char *hdhomerun_discover2_device_if_get_storage_url(struct hdhomerun_discover2_device_if_t *device_if); + +/* + * Verify that the device ID given is valid. + * + * The device ID contains a self-check sequence that detects common user input errors including + * single-digit errors and two digit transposition errors. + * + * Returns true if valid. + * Returns false if not valid. + */ +extern LIBHDHOMERUN_API bool hdhomerun_discover_validate_device_id(uint32_t device_id); + +/* + * Detect if an IP address is multicast. + * + * Returns true if multicast. + * Returns false if zero, unicast, expermental, or broadcast. + */ +extern LIBHDHOMERUN_API bool hdhomerun_discover_is_ip_multicast(uint32_t ip_addr); +extern LIBHDHOMERUN_API bool hdhomerun_discover_is_ip_multicast_ex(const struct sockaddr *ip_addr); + +/* + * Legacy API - not for new applications. + * + * The device information is stored in caller-supplied array of hdhomerun_discover_device_t vars. + * Multiple attempts are made to find devices. + * Execution time is typically 400ms unless max_count is reached. + * + * Set target_ip to zero to auto-detect the IP address. + * Set device_type to HDHOMERUN_DEVICE_TYPE_TUNER to detect HDHomeRun tuner devices. + * Set device_id to HDHOMERUN_DEVICE_ID_WILDCARD to detect all device ids. + * + * Returns the number of devices found. + * Retruns -1 on error. + */ + struct hdhomerun_discover_device_t { uint32_t ip_addr; uint32_t device_type; @@ -45,50 +150,12 @@ struct hdhomerun_discover_device_v3_t { char storage_url[128]; }; -/* - * Find devices. - * - * The device information is stored in caller-supplied array of hdhomerun_discover_device_t vars. - * Multiple attempts are made to find devices. - * Execution time is typically 400ms unless max_count is reached. - * - * Set target_ip to zero to auto-detect the IP address. - * Set device_type to HDHOMERUN_DEVICE_TYPE_TUNER to detect HDHomeRun tuner devices. - * Set device_id to HDHOMERUN_DEVICE_ID_WILDCARD to detect all device ids. - * - * Returns the number of devices found. - * Retruns -1 on error. - */ extern LIBHDHOMERUN_API int hdhomerun_discover_find_devices_custom_v2(uint32_t target_ip, uint32_t device_type_match, uint32_t device_id_match, struct hdhomerun_discover_device_t result_list[], int max_count); extern LIBHDHOMERUN_API int hdhomerun_discover_find_devices_custom_v3(uint32_t target_ip, uint32_t device_type_match, uint32_t device_id_match, struct hdhomerun_discover_device_v3_t result_list[], int max_count); -/* - * Optional: persistent discover instance available for discover polling use. - */ -extern LIBHDHOMERUN_API struct hdhomerun_discover_t *hdhomerun_discover_create(struct hdhomerun_debug_t *dbg); -extern LIBHDHOMERUN_API void hdhomerun_discover_destroy(struct hdhomerun_discover_t *ds); extern LIBHDHOMERUN_API int hdhomerun_discover_find_devices_v2(struct hdhomerun_discover_t *ds, uint32_t target_ip, uint32_t device_type_match, uint32_t device_id_match, struct hdhomerun_discover_device_t result_list[], int max_count); extern LIBHDHOMERUN_API int hdhomerun_discover_find_devices_v3(struct hdhomerun_discover_t *ds, uint32_t target_ip, uint32_t device_type_match, uint32_t device_id_match, struct hdhomerun_discover_device_v3_t result_list[], int max_count); -/* - * Verify that the device ID given is valid. - * - * The device ID contains a self-check sequence that detects common user input errors including - * single-digit errors and two digit transposition errors. - * - * Returns true if valid. - * Returns false if not valid. - */ -extern LIBHDHOMERUN_API bool hdhomerun_discover_validate_device_id(uint32_t device_id); - -/* - * Detect if an IP address is multicast. - * - * Returns true if multicast. - * Returns false if zero, unicast, expermental, or broadcast. - */ -extern LIBHDHOMERUN_API bool hdhomerun_discover_is_ip_multicast(uint32_t ip_addr); - #ifdef __cplusplus } #endif diff --git a/hdhomerun_pkt.h b/hdhomerun_pkt.h index 40fb20d..42af236 100644 --- a/hdhomerun_pkt.h +++ b/hdhomerun_pkt.h @@ -1,7 +1,7 @@ /* * hdhomerun_pkt.h * - * Copyright © 2006-2015 Silicondust USA Inc. . + * Copyright © 2006-2022 Silicondust USA Inc. . * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -133,6 +133,7 @@ extern "C" { #define HDHOMERUN_TAG_BASE_URL 0x2A #define HDHOMERUN_TAG_DEVICE_AUTH_STR 0x2B #define HDHOMERUN_TAG_STORAGE_ID 0x2C +#define HDHOMERUN_TAG_MULTI_TYPE 0x2D #define HDHOMERUN_DEVICE_TYPE_WILDCARD 0xFFFFFFFF #define HDHOMERUN_DEVICE_TYPE_TUNER 0x00000001 diff --git a/hdhomerun_sock.c b/hdhomerun_sock.c new file mode 100644 index 0000000..2180a61 --- /dev/null +++ b/hdhomerun_sock.c @@ -0,0 +1,380 @@ +/* + * hdhomerun_sock.c + * + * Copyright © 2022 Silicondust USA Inc. . + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "hdhomerun.h" + +#if defined(_WIN32) +#include +#else +#include +#endif + +#if defined(_WINRT) +static inline uint32_t if_nametoindex(const char *ifname) { return 0; } +#endif + +bool hdhomerun_sock_sockaddr_is_addr(const struct sockaddr *addr) +{ + if (addr->sa_family == AF_INET6) { + static uint8_t hdhomerun_sock_ipv6_zero_bytes[16] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + const struct sockaddr_in6 *addr_in6 = (const struct sockaddr_in6 *)addr; + return (memcmp(addr_in6->sin6_addr.s6_addr, hdhomerun_sock_ipv6_zero_bytes, 16) != 0); + } + + if (addr->sa_family == AF_INET) { + const struct sockaddr_in *addr_in = (const struct sockaddr_in *)addr; + return (addr_in->sin_addr.s_addr != 0); + } + + return false; +} + +uint16_t hdhomerun_sock_sockaddr_get_port(const struct sockaddr *addr) +{ + if (addr->sa_family == AF_INET6) { + const struct sockaddr_in6 *addr_in6 = (const struct sockaddr_in6 *)addr; + return ntohs(addr_in6->sin6_port); + } + + if (addr->sa_family == AF_INET) { + const struct sockaddr_in *addr_in = (const struct sockaddr_in *)addr; + return ntohs(addr_in->sin_port); + } + + return 0; +} + +void hdhomerun_sock_sockaddr_set_port(struct sockaddr *addr, uint16_t port) +{ + if (addr->sa_family == AF_INET6) { + struct sockaddr_in6 *addr_in6 = (struct sockaddr_in6 *)addr; + addr_in6->sin6_port = htons(port); + return; + } + + if (addr->sa_family == AF_INET) { + struct sockaddr_in *addr_in = (struct sockaddr_in *)addr; + addr_in->sin_port = htons(port); + return; + } +} + +void hdhomerun_sock_sockaddr_copy(struct sockaddr_storage *result, const struct sockaddr *addr) +{ + memset(result, 0, sizeof(struct sockaddr_storage)); + + if (addr->sa_family == AF_INET6) { + memcpy(result, addr, sizeof(struct sockaddr_in6)); + } + + if (addr->sa_family == AF_INET) { + memcpy(result, addr, sizeof(struct sockaddr_in)); + } +} + +void hdhomerun_sock_sockaddr_to_ip_str(char ip_str[64], const struct sockaddr *ip_addr, bool include_ipv6_scope_id) +{ + size_t ip_str_size = 64; + + if (ip_addr->sa_family == AF_INET6) { + const struct sockaddr_in6 *ip_addr_in6 = (const struct sockaddr_in6 *)ip_addr; + inet_ntop(AF_INET6, ip_addr_in6->sin6_addr.s6_addr, ip_str, ip_str_size); + if (include_ipv6_scope_id && (ip_addr_in6->sin6_scope_id != 0)) { + hdhomerun_sprintf(strchr(ip_str, 0), ip_str + ip_str_size, "%%%u", ip_addr_in6->sin6_scope_id); + } + return; + } + + if (ip_addr->sa_family == AF_INET) { + const struct sockaddr_in *ip_addr_in = (const struct sockaddr_in *)ip_addr; + inet_ntop(AF_INET, &ip_addr_in->sin_addr.s_addr, ip_str, ip_str_size); + return; + } + + ip_str[0] = 0; +} + +bool hdhomerun_sock_ip_str_to_sockaddr(const char *ip_str, struct sockaddr_storage *result) +{ + memset(result, 0, sizeof(struct sockaddr_storage)); + + struct sockaddr_in *result_in = (struct sockaddr_in *)result; + if (inet_pton(AF_INET, ip_str, &result_in->sin_addr) == 1) { + result_in->sin_family = AF_INET; + return true; + } + + bool framed = (*ip_str == '['); + bool ifname_present = (strchr(ip_str, '%') != NULL); + + if (!framed && !ifname_present) { + struct sockaddr_in6 *result_in6 = (struct sockaddr_in6 *)result; + if (inet_pton(AF_INET6, ip_str, &result_in6->sin6_addr) == 1) { + result_in6->sin6_family = AF_INET6; + return true; + } + return false; + } + + if (framed) { + ip_str++; + } + + char ip_str_tmp[64]; + if (!hdhomerun_sprintf(ip_str_tmp, ip_str_tmp + sizeof(ip_str_tmp), "%s", ip_str)) { + return false; + } + + if (framed) { + char *end = strchr(ip_str_tmp, ']'); + if (!end) { + return false; + } + + *end++ = 0; + if (*end) { + return false; + } + } + + char *ifname = NULL; + if (ifname_present) { + ifname = strchr(ip_str_tmp, '%'); + *ifname++ = 0; + } + + struct sockaddr_in6 *result_in6 = (struct sockaddr_in6 *)result; + if (inet_pton(AF_INET6, ip_str_tmp, &result_in6->sin6_addr) != 1) { + return false; + } + result_in6->sin6_family = AF_INET6; + + if (!ifname_present) { + return true; + } + + char *end; + result_in6->sin6_scope_id = (uint32_t)strtoul(ifname, &end, 10); + if ((result_in6->sin6_scope_id > 0) && (*end == 0)) { + return true; + } + + result_in6->sin6_scope_id = if_nametoindex(ifname); + return (result_in6->sin6_scope_id > 0); +} + +struct hdhomerun_local_ip_info_state_t { + struct hdhomerun_local_ip_info_t *ip_info; + int max_count; + int count; +}; + +static void hdhomerun_local_ip_info_callback(void *arg, uint32_t ifindex, const struct sockaddr *local_ip, uint8_t cidr) +{ + struct hdhomerun_local_ip_info_state_t *state = (struct hdhomerun_local_ip_info_state_t *)arg; + + if (state->count >= state->max_count) { + state->count++; + return; + } + + const struct sockaddr_in *local_ip_in = (const struct sockaddr_in *)local_ip; + state->ip_info->ip_addr = ntohl(local_ip_in->sin_addr.s_addr); + state->ip_info->subnet_mask = 0xFFFFFFFF << (32 - cidr); + state->ip_info++; + state->count++; +} + +int hdhomerun_local_ip_info(struct hdhomerun_local_ip_info_t ip_info_list[], int max_count) +{ + struct hdhomerun_local_ip_info_state_t state; + state.ip_info = ip_info_list; + state.max_count = max_count; + state.count = 0; + + if (!hdhomerun_local_ip_info2(AF_INET, hdhomerun_local_ip_info_callback, &state)) { + return -1; + } + + return state.count; +} + +struct hdhomerun_sock_t *hdhomerun_sock_create_udp(void) +{ + return hdhomerun_sock_create_udp_ex(AF_INET); +} + +struct hdhomerun_sock_t *hdhomerun_sock_create_tcp(void) +{ + return hdhomerun_sock_create_tcp_ex(AF_INET); +} + +uint32_t hdhomerun_sock_getsockname_addr(struct hdhomerun_sock_t *sock) +{ + struct sockaddr_storage sock_addr; + memset(&sock_addr, 0, sizeof(sock_addr)); + + if (!hdhomerun_sock_getsockname_addr_ex(sock, &sock_addr)) { + return 0; + } + if (sock_addr.ss_family != AF_INET) { + return 0; + } + + struct sockaddr_in *sock_addr_in = (struct sockaddr_in *)&sock_addr; + return ntohl(sock_addr_in->sin_addr.s_addr); +} + +uint32_t hdhomerun_sock_getpeername_addr(struct hdhomerun_sock_t *sock) +{ + struct sockaddr_storage sock_addr; + memset(&sock_addr, 0, sizeof(sock_addr)); + + if (!hdhomerun_sock_getpeername_addr_ex(sock, &sock_addr)) { + return 0; + } + if (sock_addr.ss_family != AF_INET) { + return 0; + } + + struct sockaddr_in *sock_addr_in = (struct sockaddr_in *)&sock_addr; + return ntohl(sock_addr_in->sin_addr.s_addr); +} + +uint32_t hdhomerun_sock_getaddrinfo_addr(struct hdhomerun_sock_t *sock, const char *name) +{ + struct sockaddr_storage sock_addr; + memset(&sock_addr, 0, sizeof(sock_addr)); + + if (!hdhomerun_sock_getaddrinfo_addr_ex(AF_INET, name, &sock_addr)) { + return 0; + } + if (sock_addr.ss_family != AF_INET) { + return 0; + } + + struct sockaddr_in *sock_addr_in = (struct sockaddr_in *)&sock_addr; + return ntohl(sock_addr_in->sin_addr.s_addr); +} + +bool hdhomerun_sock_getaddrinfo_addr_ex(int af, const char *name, struct sockaddr_storage *result) +{ + struct addrinfo hints; + memset(&hints, 0, sizeof(hints)); + hints.ai_family = af; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = IPPROTO_TCP; + + struct addrinfo *sock_info; + if (getaddrinfo(name, NULL, &hints, &sock_info) != 0) { + return false; + } + + if ((size_t)sock_info->ai_addrlen > sizeof(struct sockaddr_storage)) { + return false; + } + + memcpy(result, sock_info->ai_addr, sock_info->ai_addrlen); + freeaddrinfo(sock_info); + return true; +} + +bool hdhomerun_sock_join_multicast_group(struct hdhomerun_sock_t *sock, uint32_t multicast_ip, uint32_t local_ip) +{ + struct sockaddr_in multicast_addr; + memset(&multicast_addr, 0, sizeof(multicast_addr)); + multicast_addr.sin_family = AF_INET; + multicast_addr.sin_addr.s_addr = htonl(multicast_ip); + + struct sockaddr_in local_addr; + memset(&local_addr, 0, sizeof(local_addr)); + local_addr.sin_family = AF_INET; + local_addr.sin_addr.s_addr = htonl(local_ip); + + return hdhomerun_sock_join_multicast_group_ex(sock, (const struct sockaddr *)&multicast_addr, (const struct sockaddr *)&local_addr); +} + +bool hdhomerun_sock_leave_multicast_group(struct hdhomerun_sock_t *sock, uint32_t multicast_ip, uint32_t local_ip) +{ + struct sockaddr_in multicast_addr; + memset(&multicast_addr, 0, sizeof(multicast_addr)); + multicast_addr.sin_family = AF_INET; + multicast_addr.sin_addr.s_addr = htonl(multicast_ip); + + struct sockaddr_in local_addr; + memset(&local_addr, 0, sizeof(local_addr)); + local_addr.sin_family = AF_INET; + local_addr.sin_addr.s_addr = htonl(local_ip); + + return hdhomerun_sock_leave_multicast_group_ex(sock, (const struct sockaddr *)&multicast_addr, (const struct sockaddr *)&local_addr); +} + +bool hdhomerun_sock_bind(struct hdhomerun_sock_t *sock, uint32_t local_addr, uint16_t local_port, bool allow_reuse) +{ + struct sockaddr_in sock_addr; + memset(&sock_addr, 0, sizeof(sock_addr)); + sock_addr.sin_family = AF_INET; + sock_addr.sin_addr.s_addr = htonl(local_addr); + sock_addr.sin_port = htons(local_port); + + return hdhomerun_sock_bind_ex(sock, (const struct sockaddr *)&sock_addr, allow_reuse); +} + +bool hdhomerun_sock_connect(struct hdhomerun_sock_t *sock, uint32_t remote_addr, uint16_t remote_port, uint64_t timeout) +{ + struct sockaddr_in sock_addr_in; + memset(&sock_addr_in, 0, sizeof(sock_addr_in)); + + sock_addr_in.sin_family = AF_INET; + sock_addr_in.sin_addr.s_addr = htonl(remote_addr); + sock_addr_in.sin_port = htons(remote_port); + + return hdhomerun_sock_connect_ex(sock, (const struct sockaddr *)&sock_addr_in, timeout); +} + +bool hdhomerun_sock_sendto(struct hdhomerun_sock_t *sock, uint32_t remote_addr, uint16_t remote_port, const void *data, size_t length, uint64_t timeout) +{ + struct sockaddr_in sock_addr_in; + memset(&sock_addr_in, 0, sizeof(sock_addr_in)); + + sock_addr_in.sin_family = AF_INET; + sock_addr_in.sin_addr.s_addr = htonl(remote_addr); + sock_addr_in.sin_port = htons(remote_port); + + return hdhomerun_sock_sendto_ex(sock, (const struct sockaddr *)&sock_addr_in, data, length, timeout); +} + +bool hdhomerun_sock_recvfrom(struct hdhomerun_sock_t *sock, uint32_t *remote_addr, uint16_t *remote_port, void *data, size_t *length, uint64_t timeout) +{ + struct sockaddr_storage sock_addr; + memset(&sock_addr, 0, sizeof(sock_addr)); + + if (!hdhomerun_sock_recvfrom_ex(sock, &sock_addr, data, length, timeout)) { + return false; + } + if (sock_addr.ss_family != AF_INET) { + return false; + } + + struct sockaddr_in *sock_addr_in = (struct sockaddr_in *)&sock_addr; + *remote_addr = ntohl(sock_addr_in->sin_addr.s_addr); + *remote_port = ntohs(sock_addr_in->sin_port); + return true; +} diff --git a/hdhomerun_sock.h b/hdhomerun_sock.h index 5710a5f..cea4be3 100644 --- a/hdhomerun_sock.h +++ b/hdhomerun_sock.h @@ -1,7 +1,7 @@ /* * hdhomerun_sock.h * - * Copyright © 2010-2016 Silicondust USA Inc. . + * Copyright © 2010-2022 Silicondust USA Inc. . * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -21,42 +21,81 @@ extern "C" { #endif +/* + * Windows: + * hdhomerun_sock.c + * hdhomerun_sock_windows.c + * + * Linux/Android: + * hdhomerun_sock.c + * hdhomerun_sock_netlink.c + * hdhomerun_sock_posix.c + * + * Mac/BSD: + * hdhomerun_sock.c + * hdhomerun_sock_getifaddrs.c + * hdhomerun_sock_posix.c + */ + +extern LIBHDHOMERUN_API bool hdhomerun_sock_sockaddr_is_addr(const struct sockaddr *addr); +extern LIBHDHOMERUN_API uint16_t hdhomerun_sock_sockaddr_get_port(const struct sockaddr *addr); +extern LIBHDHOMERUN_API void hdhomerun_sock_sockaddr_set_port(struct sockaddr *addr, uint16_t port); +extern LIBHDHOMERUN_API void hdhomerun_sock_sockaddr_copy(struct sockaddr_storage *result, const struct sockaddr *addr); +extern LIBHDHOMERUN_API void hdhomerun_sock_sockaddr_to_ip_str(char ip_str[64], const struct sockaddr *ip_addr, bool include_ipv6_scope_id); +extern LIBHDHOMERUN_API bool hdhomerun_sock_ip_str_to_sockaddr(const char *ip_str, struct sockaddr_storage *result); + struct hdhomerun_local_ip_info_t { uint32_t ip_addr; uint32_t subnet_mask; }; +typedef void (*hdhomerun_local_ip_info2_callback_t)(void *arg, uint32_t ifindex, const struct sockaddr *local_ip, uint8_t cidr); + extern LIBHDHOMERUN_API int hdhomerun_local_ip_info(struct hdhomerun_local_ip_info_t ip_info_list[], int max_count); -extern LIBHDHOMERUN_API void hdhomerun_local_ip_info_set_str(const char *ip_info_str); /* WinRT only */ +extern LIBHDHOMERUN_API bool hdhomerun_local_ip_info2(int af, hdhomerun_local_ip_info2_callback_t callback, void *callback_arg); struct hdhomerun_sock_t; extern LIBHDHOMERUN_API struct hdhomerun_sock_t *hdhomerun_sock_create_udp(void); extern LIBHDHOMERUN_API struct hdhomerun_sock_t *hdhomerun_sock_create_tcp(void); +extern LIBHDHOMERUN_API struct hdhomerun_sock_t *hdhomerun_sock_create_udp_ex(int af); +extern LIBHDHOMERUN_API struct hdhomerun_sock_t *hdhomerun_sock_create_tcp_ex(int af); extern LIBHDHOMERUN_API void hdhomerun_sock_stop(struct hdhomerun_sock_t *sock); extern LIBHDHOMERUN_API void hdhomerun_sock_destroy(struct hdhomerun_sock_t *sock); extern LIBHDHOMERUN_API void hdhomerun_sock_set_send_buffer_size(struct hdhomerun_sock_t *sock, size_t size); extern LIBHDHOMERUN_API void hdhomerun_sock_set_recv_buffer_size(struct hdhomerun_sock_t *sock, size_t size); extern LIBHDHOMERUN_API void hdhomerun_sock_set_allow_reuse(struct hdhomerun_sock_t *sock); +extern LIBHDHOMERUN_API void hdhomerun_sock_set_ipv4_onesbcast(struct hdhomerun_sock_t *sock, int v); +extern LIBHDHOMERUN_API void hdhomerun_sock_set_ipv6_multicast_ifindex(struct hdhomerun_sock_t *sock, uint32_t ifindex); extern LIBHDHOMERUN_API int hdhomerun_sock_getlasterror(void); extern LIBHDHOMERUN_API uint32_t hdhomerun_sock_getsockname_addr(struct hdhomerun_sock_t *sock); +extern LIBHDHOMERUN_API bool hdhomerun_sock_getsockname_addr_ex(struct hdhomerun_sock_t *sock, struct sockaddr_storage *result); extern LIBHDHOMERUN_API uint16_t hdhomerun_sock_getsockname_port(struct hdhomerun_sock_t *sock); extern LIBHDHOMERUN_API uint32_t hdhomerun_sock_getpeername_addr(struct hdhomerun_sock_t *sock); +extern LIBHDHOMERUN_API bool hdhomerun_sock_getpeername_addr_ex(struct hdhomerun_sock_t *sock, struct sockaddr_storage *result); + extern LIBHDHOMERUN_API uint32_t hdhomerun_sock_getaddrinfo_addr(struct hdhomerun_sock_t *sock, const char *name); +extern LIBHDHOMERUN_API bool hdhomerun_sock_getaddrinfo_addr_ex(int af, const char *name, struct sockaddr_storage *result); extern LIBHDHOMERUN_API bool hdhomerun_sock_join_multicast_group(struct hdhomerun_sock_t *sock, uint32_t multicast_ip, uint32_t local_ip); +extern LIBHDHOMERUN_API bool hdhomerun_sock_join_multicast_group_ex(struct hdhomerun_sock_t *sock, const struct sockaddr *multicast_addr, const struct sockaddr *local_addr); extern LIBHDHOMERUN_API bool hdhomerun_sock_leave_multicast_group(struct hdhomerun_sock_t *sock, uint32_t multicast_ip, uint32_t local_ip); +extern LIBHDHOMERUN_API bool hdhomerun_sock_leave_multicast_group_ex(struct hdhomerun_sock_t *sock, const struct sockaddr *multicast_addr, const struct sockaddr *local_addr); extern LIBHDHOMERUN_API bool hdhomerun_sock_bind(struct hdhomerun_sock_t *sock, uint32_t local_addr, uint16_t local_port, bool allow_reuse); +extern LIBHDHOMERUN_API bool hdhomerun_sock_bind_ex(struct hdhomerun_sock_t *sock, const struct sockaddr *local_addr, bool allow_reuse); extern LIBHDHOMERUN_API bool hdhomerun_sock_connect(struct hdhomerun_sock_t *sock, uint32_t remote_addr, uint16_t remote_port, uint64_t timeout); +extern LIBHDHOMERUN_API bool hdhomerun_sock_connect_ex(struct hdhomerun_sock_t *sock, const struct sockaddr *remote_addr, uint64_t timeout); extern LIBHDHOMERUN_API bool hdhomerun_sock_send(struct hdhomerun_sock_t *sock, const void *data, size_t length, uint64_t timeout); extern LIBHDHOMERUN_API bool hdhomerun_sock_sendto(struct hdhomerun_sock_t *sock, uint32_t remote_addr, uint16_t remote_port, const void *data, size_t length, uint64_t timeout); +extern LIBHDHOMERUN_API bool hdhomerun_sock_sendto_ex(struct hdhomerun_sock_t *sock, const struct sockaddr *remote_addr, const void *data, size_t length, uint64_t timeout); extern LIBHDHOMERUN_API bool hdhomerun_sock_recv(struct hdhomerun_sock_t *sock, void *data, size_t *length, uint64_t timeout); extern LIBHDHOMERUN_API bool hdhomerun_sock_recvfrom(struct hdhomerun_sock_t *sock, uint32_t *remote_addr, uint16_t *remote_port, void *data, size_t *length, uint64_t timeout); +extern LIBHDHOMERUN_API bool hdhomerun_sock_recvfrom_ex(struct hdhomerun_sock_t *sock, struct sockaddr_storage *remote_addr, void *data, size_t *length, uint64_t timeout); #ifdef __cplusplus } diff --git a/hdhomerun_sock_getifaddrs.c b/hdhomerun_sock_getifaddrs.c new file mode 100644 index 0000000..f334712 --- /dev/null +++ b/hdhomerun_sock_getifaddrs.c @@ -0,0 +1,130 @@ +/* + * hdhomerun_sock_getifaddrs.c + * + * Copyright © 2010-2022 Silicondust USA Inc. . + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "hdhomerun.h" + +#include +#include +#include +#include + +static uint8_t hdhomerun_local_ip_netmask_to_cidr(uint8_t *subnet_mask, size_t len) +{ + uint8_t result = 0; + + uint8_t *ptr = subnet_mask; + uint8_t *end = subnet_mask + len; + while (ptr < end) { + uint8_t c = *ptr++; + if (c == 0xFF) { + result += 8; + continue; + } + + while (c & 0x80) { + result++; + c <<= 1; + } + + break; + } + + return result; +} + +bool hdhomerun_local_ip_info2(int af, hdhomerun_local_ip_info2_callback_t callback, void *callback_arg) +{ + int af6_sock = socket(AF_INET6, SOCK_DGRAM, 0); + if (af6_sock == -1) { + return -1; + } + + struct ifaddrs *ifaddrs; + if (getifaddrs(&ifaddrs) != 0) { + close(af6_sock); + return -1; + } + + struct ifaddrs *ifa = ifaddrs; + while (ifa) { + if (ifa->ifa_addr == NULL) { + ifa = ifa->ifa_next; + continue; + } + + if ((af != AF_UNSPEC) && (ifa->ifa_addr->sa_family != af)) { + ifa = ifa->ifa_next; + continue; + } + + unsigned int flags = ifa->ifa_flags & (IFF_LOOPBACK | IFF_POINTOPOINT | IFF_UP | IFF_RUNNING | IFF_MULTICAST); + if (flags != (IFF_UP | IFF_RUNNING | IFF_MULTICAST)) { + ifa = ifa->ifa_next; + continue; + } + + if (ifa->ifa_addr->sa_family == AF_INET) { + uint32_t ifindex = if_nametoindex(ifa->ifa_name); + + struct sockaddr_in *netmask_in = (struct sockaddr_in *)ifa->ifa_netmask; + uint8_t cidr = hdhomerun_local_ip_netmask_to_cidr((uint8_t *)&netmask_in->sin_addr.s_addr, 4); + + callback(callback_arg, ifindex, ifa->ifa_addr, cidr); + + ifa = ifa->ifa_next; + continue; + } + + if (ifa->ifa_addr->sa_family == AF_INET6) { + struct in6_ifreq ifr6; + memset(&ifr6, 0, sizeof(ifr6)); + + struct sockaddr_in6 *addr_in = (struct sockaddr_in6 *)ifa->ifa_addr; + strcpy(ifr6.ifr_name, ifa->ifa_name); + ifr6.ifr_addr = *addr_in; + + if (ioctl(af6_sock, SIOCGIFALIFETIME_IN6, &ifr6) < 0) { + ifa = ifa->ifa_next; + continue; + } + + if (ifr6.ifr_ifru.ifru_lifetime.ia6t_vltime != 0xFFFFFFFF) { + ifa = ifa->ifa_next; + continue; + } + + uint32_t ifindex = if_nametoindex(ifa->ifa_name); + + struct sockaddr_in6 *netmask_in = (struct sockaddr_in6 *)ifa->ifa_netmask; + uint8_t cidr = hdhomerun_local_ip_netmask_to_cidr(netmask_in->sin6_addr.s6_addr, 16); + + callback(callback_arg, ifindex, ifa->ifa_addr, cidr); + + ifa = ifa->ifa_next; + continue; + } + + ifa = ifa->ifa_next; + } + + close(af6_sock); + freeifaddrs(ifaddrs); + return true; +} diff --git a/hdhomerun_sock_netdevice.c b/hdhomerun_sock_netdevice.c new file mode 100644 index 0000000..4d9723a --- /dev/null +++ b/hdhomerun_sock_netdevice.c @@ -0,0 +1,130 @@ +/* + * hdhomerun_sock_getifaddrs.c + * + * Copyright © 2010-2022 Silicondust USA Inc. . + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "hdhomerun.h" + +#include +#include + +static uint8_t hdhomerun_local_ip_netmask_to_cidr(uint8_t *subnet_mask, size_t len) +{ + uint8_t result = 0; + + uint8_t *ptr = subnet_mask; + uint8_t *end = subnet_mask + len; + while (ptr < end) { + uint8_t c = *ptr++; + if (c == 0xFF) { + result += 8; + continue; + } + + while (c & 0x80) { + result++; + c <<= 1; + } + + break; + } + + return result; +} + +bool hdhomerun_local_ip_info2(int af, hdhomerun_local_ip_info2_callback_t callback, void *callback_arg) +{ + if (af != AF_INET) { + return false; + } + + int sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP); + if (sock == -1) { + return false; + } + + int ifreq_buffer_size = 128 * sizeof(struct ifreq); + char *ifreq_buffer = (char *)calloc(ifreq_buffer_size, 1); + if (!ifreq_buffer) { + close(sock); + return false; + } + + struct ifconf ifc; + ifc.ifc_len = ifreq_buffer_size; + ifc.ifc_buf = ifreq_buffer; + + if (ioctl(sock, SIOCGIFCONF, &ifc) != 0) { + free(ifreq_buffer); + close(sock); + return false; + } + + if (ifc.ifc_len > ifreq_buffer_size) { + ifc.ifc_len = ifreq_buffer_size; + } + + char *ptr = ifc.ifc_buf; + char *end = ifc.ifc_buf + ifc.ifc_len; + + while (ptr + sizeof(struct ifreq) <= end) { + struct ifreq *ifr = (struct ifreq *)ptr; + ptr += sizeof(struct ifreq); + + /* Local IP address. */ + struct sockaddr_in ip_addr_in; + memcpy(&ip_addr_in, &ifr->ifr_addr, sizeof(ip_addr_in)); + + uint32_t ip_addr = ntohl(ip_addr_in.sin_addr.s_addr); + if (ip_addr == 0) { + continue; + } + + /* Flags. */ + if (ioctl(sock, SIOCGIFFLAGS, ifr) != 0) { + continue; + } + + unsigned int flags = ifr->ifr_flags & (IFF_LOOPBACK | IFF_POINTOPOINT | IFF_UP | IFF_RUNNING | IFF_MULTICAST); + if (flags != (IFF_UP | IFF_RUNNING | IFF_MULTICAST)) { + continue; + } + + /* Subnet mask. */ + if (ioctl(sock, SIOCGIFNETMASK, ifr) != 0) { + continue; + } + + struct sockaddr_in *netmask_in = (struct sockaddr_in *)&ifr->ifr_addr; + uint8_t cidr = hdhomerun_local_ip_netmask_to_cidr((uint8_t *)&netmask_in->sin_addr.s_addr, 4); + + /* ifindex. */ + if (ioctl(sock, SIOCGIFINDEX, ifr) != 0) { + continue; + } + + uint32_t ifindex = ifr->ifr_ifindex; + + /* Result. */ + callback(callback_arg, ifindex, (const struct sockaddr *)&ip_addr_in, cidr); + } + + free(ifreq_buffer); + close(sock); + return true; +} diff --git a/hdhomerun_sock_netlink.c b/hdhomerun_sock_netlink.c new file mode 100644 index 0000000..c1a9da6 --- /dev/null +++ b/hdhomerun_sock_netlink.c @@ -0,0 +1,192 @@ +/* + * hdhomerun_sock_netlink.c + * + * Copyright © 2022 Silicondust USA Inc. . + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "hdhomerun.h" + +#include +#include +#include +#include + +#define HDHOMERUN_SOCK_NETLINK_BUFFER_SIZE 32768 + +struct nlmsghdr_ifaddrmsg { + struct nlmsghdr nlh; + struct ifaddrmsg msg; +}; + +static void hdhomerun_local_ip_info2_newaddr(int af_sock, struct nlmsghdr *hdr, hdhomerun_local_ip_info2_callback_t callback, void *callback_arg) +{ + struct ifaddrmsg *addrmsg = (struct ifaddrmsg *)NLMSG_DATA(hdr); + + if ((addrmsg->ifa_family != AF_INET) && (addrmsg->ifa_family != AF_INET6)) { + return; + } + + if ((addrmsg->ifa_family == AF_INET6) && (addrmsg->ifa_flags & IFA_F_TEMPORARY)) { + return; /* skip temporary IPv6 addresses */ + } + + /* ifindex */ + uint32_t ifindex = addrmsg->ifa_index; + + /* interface flags */ + struct ifreq ifr; + memset(&ifr, 0, sizeof(ifr)); + if (!if_indextoname(ifindex, ifr.ifr_name)) { + return; + } + + if (ioctl(af_sock, SIOCGIFFLAGS, &ifr) < 0) { + return; + } + + uint32_t flags = ifr.ifr_flags; + flags &= (IFF_LOOPBACK | IFF_POINTOPOINT | IFF_UP | IFF_RUNNING | IFF_MULTICAST); + if (flags != (IFF_UP | IFF_RUNNING | IFF_MULTICAST)) { + return; + } + + /* addresses */ + size_t ifa_payload_length = IFA_PAYLOAD(hdr); + struct rtattr *rta = IFA_RTA(addrmsg); + while (1) { + if (!RTA_OK(rta, ifa_payload_length)) { + break; + } + + if (rta->rta_type != IFA_ADDRESS) { + rta = RTA_NEXT(rta, ifa_payload_length); + continue; + } + + uint8_t cidr = (uint8_t)addrmsg->ifa_prefixlen; + + if (addrmsg->ifa_family == AF_INET) { + struct sockaddr_in local_ip; + memset(&local_ip, 0, sizeof(local_ip)); + + local_ip.sin_family = AF_INET; + memcpy(&local_ip.sin_addr.s_addr, RTA_DATA(rta), 4); + + callback(callback_arg, ifindex, (const struct sockaddr *)&local_ip, cidr); + } + + if (addrmsg->ifa_family == AF_INET6) { + struct sockaddr_in6 local_ip; + memset(&local_ip, 0, sizeof(local_ip)); + + local_ip.sin6_family = AF_INET6; + memcpy(local_ip.sin6_addr.s6_addr, RTA_DATA(rta), 16); + + if ((local_ip.sin6_addr.s6_addr[0] == 0xFE) && ((local_ip.sin6_addr.s6_addr[1] & 0xC0) == 0x80)) { + local_ip.sin6_scope_id = ifindex; + } + + callback(callback_arg, ifindex, (const struct sockaddr *)&local_ip, cidr); + } + + rta = RTA_NEXT(rta, ifa_payload_length); + } +} + +bool hdhomerun_local_ip_info2(int af, hdhomerun_local_ip_info2_callback_t callback, void *callback_arg) +{ + uint8_t *nl_buffer = (uint8_t *)malloc(HDHOMERUN_SOCK_NETLINK_BUFFER_SIZE); + if (!nl_buffer) { + return false; + } + + int nl_sock = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE); + int af_sock = socket(AF_INET, SOCK_DGRAM, 0); + if ((nl_sock == -1) || (af_sock == -1)) { + close(af_sock); + close(nl_sock); + free(nl_buffer); + return false; + } + + struct nlmsghdr_ifaddrmsg req; + memset(&req, 0, sizeof(req)); + req.nlh.nlmsg_len = NLMSG_ALIGN(NLMSG_LENGTH(sizeof(req))); + req.nlh.nlmsg_type = RTM_GETADDR; + req.nlh.nlmsg_flags = NLM_F_REQUEST | NLM_F_MATCH; + req.msg.ifa_family = af; + + if (send(nl_sock, &req, req.nlh.nlmsg_len, 0) != (ssize_t)req.nlh.nlmsg_len) { + close(af_sock); + close(nl_sock); + free(nl_buffer); + return false; + } + + bool again = true; + while (1) { + struct pollfd poll_fds[1]; + poll_fds[0].fd = nl_sock; + poll_fds[0].events = POLLIN; + poll_fds[0].revents = 0; + + int ret = poll(poll_fds, 1, 25); + if (ret <= 0) { + break; + } + if ((poll_fds[0].revents & POLLIN) == 0) { + break; + } + + int length = (int)recv(nl_sock, nl_buffer, HDHOMERUN_SOCK_NETLINK_BUFFER_SIZE, 0); + if (length <= 0) { + break; + } + + struct nlmsghdr *hdr = (struct nlmsghdr *)nl_buffer; + while (1) { + if (!NLMSG_OK(hdr, length)) { + break; + } + + if (hdr->nlmsg_type == NLMSG_DONE) { + again = false; + break; + } + + if (hdr->nlmsg_type == NLMSG_ERROR) { + again = false; + break; + } + + if (hdr->nlmsg_type == RTM_NEWADDR) { + hdhomerun_local_ip_info2_newaddr(af_sock, hdr, callback, callback_arg); + } + + hdr = NLMSG_NEXT(hdr, length); + } + + if (!again) { + break; + } + } + + close(af_sock); + close(nl_sock); + free(nl_buffer); + return true; +} diff --git a/hdhomerun_sock_posix.c b/hdhomerun_sock_posix.c index 1ef3eff..76088a3 100644 --- a/hdhomerun_sock_posix.c +++ b/hdhomerun_sock_posix.c @@ -1,7 +1,7 @@ /* * hdhomerun_sock_posix.c * - * Copyright © 2010-2019 Silicondust USA Inc. . + * Copyright © 2010-2022 Silicondust USA Inc. . * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -20,14 +20,6 @@ #include "hdhomerun.h" -#if defined(LIBHDHOMERUN_USE_SIOCGIFCONF) -#include -#else -#include -#endif - -#include - #ifndef MSG_NOSIGNAL #define MSG_NOSIGNAL 0 #endif @@ -36,136 +28,7 @@ struct hdhomerun_sock_t { int sock; }; -#if defined(LIBHDHOMERUN_USE_SIOCGIFCONF) -int hdhomerun_local_ip_info(struct hdhomerun_local_ip_info_t ip_info_list[], int max_count) -{ - int sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP); - if (sock == -1) { - return -1; - } - - int ifreq_buffer_size = 128 * sizeof(struct ifreq); - char *ifreq_buffer = (char *)calloc(ifreq_buffer_size, 1); - if (!ifreq_buffer) { - close(sock); - return -1; - } - - struct ifconf ifc; - ifc.ifc_len = ifreq_buffer_size; - ifc.ifc_buf = ifreq_buffer; - - if (ioctl(sock, SIOCGIFCONF, &ifc) != 0) { - free(ifreq_buffer); - close(sock); - return -1; - } - - if (ifc.ifc_len > ifreq_buffer_size) { - ifc.ifc_len = ifreq_buffer_size; - } - - struct hdhomerun_local_ip_info_t *ip_info = ip_info_list; - int count = 0; - - char *ptr = ifc.ifc_buf; - char *end = ifc.ifc_buf + ifc.ifc_len; - - while (ptr + sizeof(struct ifreq) <= end) { - struct ifreq *ifr = (struct ifreq *)ptr; - ptr += sizeof(struct ifreq); - - /* Local IP address. */ - struct sockaddr_in *ip_addr_in = (struct sockaddr_in *)&ifr->ifr_addr; - uint32_t ip_addr = ntohl(ip_addr_in->sin_addr.s_addr); - if (ip_addr == 0) { - continue; - } - - /* Flags. */ - if (ioctl(sock, SIOCGIFFLAGS, ifr) != 0) { - continue; - } - - unsigned int flags = ifr->ifr_flags & (IFF_LOOPBACK | IFF_POINTOPOINT | IFF_UP | IFF_RUNNING); - if (flags != (IFF_UP | IFF_RUNNING)) { - continue; - } - - /* Subnet mask. */ - if (ioctl(sock, SIOCGIFNETMASK, ifr) != 0) { - continue; - } - - struct sockaddr_in *subnet_mask_in = (struct sockaddr_in *)&ifr->ifr_addr; - uint32_t subnet_mask = ntohl(subnet_mask_in->sin_addr.s_addr); - - /* Result. */ - if (count < max_count) { - ip_info->ip_addr = ip_addr; - ip_info->subnet_mask = subnet_mask; - ip_info++; - } - - count++; - } - - free(ifreq_buffer); - close(sock); - return count; -} -#else -int hdhomerun_local_ip_info(struct hdhomerun_local_ip_info_t ip_info_list[], int max_count) -{ - struct ifaddrs *ifaddrs; - if (getifaddrs(&ifaddrs) != 0) { - return -1; - } - - struct hdhomerun_local_ip_info_t *ip_info = ip_info_list; - struct ifaddrs *ifa = ifaddrs; - int count = 0; - - while (ifa) { - if (ifa->ifa_addr == NULL) { - ifa = ifa->ifa_next; - continue; - } - - if (ifa->ifa_addr->sa_family != AF_INET) { - ifa = ifa->ifa_next; - continue; - } - - unsigned int flags = ifa->ifa_flags & (IFF_LOOPBACK | IFF_POINTOPOINT | IFF_UP | IFF_RUNNING); - if (flags != (IFF_UP | IFF_RUNNING)) { - ifa = ifa->ifa_next; - continue; - } - - struct sockaddr_in *addr_in = (struct sockaddr_in *)ifa->ifa_addr; - uint32_t ip_addr = ntohl(addr_in->sin_addr.s_addr); - - struct sockaddr_in *netmask_in = (struct sockaddr_in *)ifa->ifa_netmask; - uint32_t subnet_mask = ntohl(netmask_in->sin_addr.s_addr); - - ifa = ifa->ifa_next; - - if (count < max_count) { - ip_info->ip_addr = ip_addr; - ip_info->subnet_mask = subnet_mask; - ip_info++; - } - - count++; - } - - freeifaddrs(ifaddrs); - return count; -} -#endif - -static struct hdhomerun_sock_t *hdhomerun_sock_create_internal(int protocol) +static struct hdhomerun_sock_t *hdhomerun_sock_create_internal(int af, int protocol) { struct hdhomerun_sock_t *sock = (struct hdhomerun_sock_t *)calloc(1, sizeof(struct hdhomerun_sock_t)); if (!sock) { @@ -173,7 +36,7 @@ static struct hdhomerun_sock_t *hdhomerun_sock_create_internal(int protocol) } /* Create socket. */ - sock->sock = socket(AF_INET, protocol, 0); + sock->sock = socket(af, protocol, 0); if (sock->sock == -1) { free(sock); return NULL; @@ -191,13 +54,19 @@ static struct hdhomerun_sock_t *hdhomerun_sock_create_internal(int protocol) setsockopt(sock->sock, SOL_SOCKET, SO_NOSIGPIPE, (char *)&set, sizeof(set)); #endif + /* Set ipv6 */ + if (af == AF_INET6) { + int sock_opt_ipv6only = 1; + setsockopt(sock->sock, IPPROTO_IPV6, IPV6_V6ONLY, (char *)&sock_opt_ipv6only, sizeof(sock_opt_ipv6only)); + } + /* Success. */ return sock; } -struct hdhomerun_sock_t *hdhomerun_sock_create_udp(void) +struct hdhomerun_sock_t *hdhomerun_sock_create_udp_ex(int af) { - struct hdhomerun_sock_t *sock = hdhomerun_sock_create_internal(SOCK_DGRAM); + struct hdhomerun_sock_t *sock = hdhomerun_sock_create_internal(af, SOCK_DGRAM); if (!sock) { return NULL; } @@ -210,9 +79,9 @@ struct hdhomerun_sock_t *hdhomerun_sock_create_udp(void) return sock; } -struct hdhomerun_sock_t *hdhomerun_sock_create_tcp(void) +struct hdhomerun_sock_t *hdhomerun_sock_create_tcp_ex(int af) { - return hdhomerun_sock_create_internal(SOCK_STREAM); + return hdhomerun_sock_create_internal(af, SOCK_STREAM); } void hdhomerun_sock_destroy(struct hdhomerun_sock_t *sock) @@ -244,122 +113,166 @@ void hdhomerun_sock_set_allow_reuse(struct hdhomerun_sock_t *sock) setsockopt(sock->sock, SOL_SOCKET, SO_REUSEADDR, (char *)&sock_opt, sizeof(sock_opt)); } +void hdhomerun_sock_set_ipv4_onesbcast(struct hdhomerun_sock_t *sock, int v) +{ +#if defined(IP_ONESBCAST) + setsockopt(sock->sock, IPPROTO_IP, IP_ONESBCAST, (char *)&v, sizeof(v)); +#endif +} + +void hdhomerun_sock_set_ipv6_multicast_ifindex(struct hdhomerun_sock_t *sock, uint32_t ifindex) +{ + setsockopt(sock->sock, IPPROTO_IPV6, IPV6_MULTICAST_IF, (char *)&ifindex, sizeof(ifindex)); +} + int hdhomerun_sock_getlasterror(void) { return errno; } -uint32_t hdhomerun_sock_getsockname_addr(struct hdhomerun_sock_t *sock) +bool hdhomerun_sock_getsockname_addr_ex(struct hdhomerun_sock_t *sock, struct sockaddr_storage *result) { - struct sockaddr_in sock_addr; - socklen_t sockaddr_size = sizeof(sock_addr); - - if (getsockname(sock->sock, (struct sockaddr *)&sock_addr, &sockaddr_size) != 0) { - return 0; - } - - return ntohl(sock_addr.sin_addr.s_addr); + socklen_t sockaddr_size = sizeof(struct sockaddr_storage); + return (getsockname(sock->sock, (struct sockaddr *)result, &sockaddr_size) == 0); } uint16_t hdhomerun_sock_getsockname_port(struct hdhomerun_sock_t *sock) { - struct sockaddr_in sock_addr; + struct sockaddr_storage sock_addr; socklen_t sockaddr_size = sizeof(sock_addr); if (getsockname(sock->sock, (struct sockaddr *)&sock_addr, &sockaddr_size) != 0) { return 0; } - return ntohs(sock_addr.sin_port); + if (sock_addr.ss_family == AF_INET) { + struct sockaddr_in *sock_addr_in = (struct sockaddr_in *)&sock_addr; + return ntohs(sock_addr_in->sin_port); + } + if (sock_addr.ss_family == AF_INET6) { + struct sockaddr_in6 *sock_addr_in = (struct sockaddr_in6 *)&sock_addr; + return ntohs(sock_addr_in->sin6_port); + } + return 0; } -uint32_t hdhomerun_sock_getpeername_addr(struct hdhomerun_sock_t *sock) +bool hdhomerun_sock_getpeername_addr_ex(struct hdhomerun_sock_t *sock, struct sockaddr_storage *result) { - struct sockaddr_in sock_addr; - socklen_t sockaddr_size = sizeof(sock_addr); + socklen_t sockaddr_size = sizeof(struct sockaddr_storage); + return (getpeername(sock->sock, (struct sockaddr *)result, &sockaddr_size) == 0); +} - if (getpeername(sock->sock, (struct sockaddr *)&sock_addr, &sockaddr_size) != 0) { - return 0; +bool hdhomerun_sock_join_multicast_group_ex(struct hdhomerun_sock_t *sock, const struct sockaddr *multicast_addr, const struct sockaddr *local_addr) +{ + if (multicast_addr->sa_family == AF_INET6) { + const struct sockaddr_in6 *multicast_addr_in = (const struct sockaddr_in6 *)multicast_addr; + + struct ipv6_mreq imr; + memset(&imr, 0, sizeof(imr)); + memcpy(imr.ipv6mr_multiaddr.s6_addr, multicast_addr_in->sin6_addr.s6_addr, 16); + imr.ipv6mr_interface = multicast_addr_in->sin6_scope_id; + + if (setsockopt(sock->sock, IPPROTO_IPV6, IPV6_JOIN_GROUP, (const char *)&imr, sizeof(imr)) != 0) { + return false; + } + + return true; } - return ntohl(sock_addr.sin_addr.s_addr); -} + if (multicast_addr->sa_family == AF_INET) { + const struct sockaddr_in *multicast_addr_in = (const struct sockaddr_in *)multicast_addr; + const struct sockaddr_in *local_addr_in = (const struct sockaddr_in *)local_addr; -uint32_t hdhomerun_sock_getaddrinfo_addr(struct hdhomerun_sock_t *sock, const char *name) -{ - struct addrinfo hints; - memset(&hints, 0, sizeof(hints)); - hints.ai_family = AF_INET; - hints.ai_socktype = SOCK_STREAM; - hints.ai_protocol = IPPROTO_TCP; + struct ip_mreq imr; + memset(&imr, 0, sizeof(imr)); + imr.imr_multiaddr.s_addr = multicast_addr_in->sin_addr.s_addr; + imr.imr_interface.s_addr = (local_addr->sa_family == AF_INET) ? local_addr_in->sin_addr.s_addr : 0; - struct addrinfo *sock_info; - if (getaddrinfo(name, NULL, &hints, &sock_info) != 0) { - return 0; + if (setsockopt(sock->sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, (const char *)&imr, sizeof(imr)) != 0) { + return false; + } + + return true; } - struct sockaddr_in *sock_addr = (struct sockaddr_in *)sock_info->ai_addr; - uint32_t addr = ntohl(sock_addr->sin_addr.s_addr); - - freeaddrinfo(sock_info); - return addr; + return false; } -bool hdhomerun_sock_join_multicast_group(struct hdhomerun_sock_t *sock, uint32_t multicast_ip, uint32_t local_ip) +bool hdhomerun_sock_leave_multicast_group_ex(struct hdhomerun_sock_t *sock, const struct sockaddr *multicast_addr, const struct sockaddr *local_addr) { - struct ip_mreq imr; - memset(&imr, 0, sizeof(imr)); - imr.imr_multiaddr.s_addr = htonl(multicast_ip); - imr.imr_interface.s_addr = htonl(local_ip); + if (multicast_addr->sa_family == AF_INET6) { + const struct sockaddr_in6 *multicast_addr_in = (const struct sockaddr_in6 *)multicast_addr; - if (setsockopt(sock->sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, (const char *)&imr, sizeof(imr)) != 0) { + struct ipv6_mreq imr; + memset(&imr, 0, sizeof(imr)); + memcpy(imr.ipv6mr_multiaddr.s6_addr, multicast_addr_in->sin6_addr.s6_addr, 16); + imr.ipv6mr_interface = multicast_addr_in->sin6_scope_id; + + if (setsockopt(sock->sock, IPPROTO_IPV6, IPV6_LEAVE_GROUP, (const char *)&imr, sizeof(imr)) != 0) { + return false; + } + + return true; + } + + if (multicast_addr->sa_family == AF_INET) { + const struct sockaddr_in *multicast_addr_in = (const struct sockaddr_in *)multicast_addr; + const struct sockaddr_in *local_addr_in = (const struct sockaddr_in *)local_addr; + + struct ip_mreq imr; + memset(&imr, 0, sizeof(imr)); + imr.imr_multiaddr.s_addr = multicast_addr_in->sin_addr.s_addr; + imr.imr_interface.s_addr = (local_addr->sa_family == AF_INET) ? local_addr_in->sin_addr.s_addr : 0; + + if (setsockopt(sock->sock, IPPROTO_IP, IP_DROP_MEMBERSHIP, (const char *)&imr, sizeof(imr)) != 0) { + return false; + } + + return true; + } + + return false; +} + +bool hdhomerun_sock_bind_ex(struct hdhomerun_sock_t *sock, const struct sockaddr *local_addr, bool allow_reuse) +{ + socklen_t local_addr_size; + switch (local_addr->sa_family) { + case AF_INET6: + local_addr_size = (socklen_t)sizeof(struct sockaddr_in6); + break; + case AF_INET: + local_addr_size = (socklen_t)sizeof(struct sockaddr_in); + break; + default: return false; } - return true; -} - -bool hdhomerun_sock_leave_multicast_group(struct hdhomerun_sock_t *sock, uint32_t multicast_ip, uint32_t local_ip) -{ - struct ip_mreq imr; - memset(&imr, 0, sizeof(imr)); - imr.imr_multiaddr.s_addr = htonl(multicast_ip); - imr.imr_interface.s_addr = htonl(local_ip); - - if (setsockopt(sock->sock, IPPROTO_IP, IP_DROP_MEMBERSHIP, (const char *)&imr, sizeof(imr)) != 0) { - return false; - } - - return true; -} - -bool hdhomerun_sock_bind(struct hdhomerun_sock_t *sock, uint32_t local_addr, uint16_t local_port, bool allow_reuse) -{ int sock_opt = allow_reuse; setsockopt(sock->sock, SOL_SOCKET, SO_REUSEADDR, (char *)&sock_opt, sizeof(sock_opt)); - struct sockaddr_in sock_addr; - memset(&sock_addr, 0, sizeof(sock_addr)); - sock_addr.sin_family = AF_INET; - sock_addr.sin_addr.s_addr = htonl(local_addr); - sock_addr.sin_port = htons(local_port); - - if (bind(sock->sock, (struct sockaddr *)&sock_addr, sizeof(sock_addr)) != 0) { + if (bind(sock->sock, (const struct sockaddr *)local_addr, local_addr_size) != 0) { return false; } return true; } -bool hdhomerun_sock_connect(struct hdhomerun_sock_t *sock, uint32_t remote_addr, uint16_t remote_port, uint64_t timeout) +bool hdhomerun_sock_connect_ex(struct hdhomerun_sock_t *sock, const struct sockaddr *remote_addr, uint64_t timeout) { - struct sockaddr_in sock_addr; - memset(&sock_addr, 0, sizeof(sock_addr)); - sock_addr.sin_family = AF_INET; - sock_addr.sin_addr.s_addr = htonl(remote_addr); - sock_addr.sin_port = htons(remote_port); + socklen_t remote_addr_size; + switch (remote_addr->sa_family) { + case AF_INET6: + remote_addr_size = (socklen_t)sizeof(struct sockaddr_in6); + break; + case AF_INET: + remote_addr_size = (socklen_t)sizeof(struct sockaddr_in); + break; + default: + return false; + } - if (connect(sock->sock, (struct sockaddr *)&sock_addr, sizeof(sock_addr)) != 0) { + if (connect(sock->sock, remote_addr, remote_addr_size) != 0) { if ((errno != EAGAIN) && (errno != EWOULDBLOCK) && (errno != EINPROGRESS)) { return false; } @@ -437,16 +350,22 @@ bool hdhomerun_sock_send(struct hdhomerun_sock_t *sock, const void *data, size_t } } -bool hdhomerun_sock_sendto(struct hdhomerun_sock_t *sock, uint32_t remote_addr, uint16_t remote_port, const void *data, size_t length, uint64_t timeout) +bool hdhomerun_sock_sendto_ex(struct hdhomerun_sock_t *sock, const struct sockaddr *remote_addr, const void *data, size_t length, uint64_t timeout) { - struct sockaddr_in sock_addr; - memset(&sock_addr, 0, sizeof(sock_addr)); - sock_addr.sin_family = AF_INET; - sock_addr.sin_addr.s_addr = htonl(remote_addr); - sock_addr.sin_port = htons(remote_port); + socklen_t remote_addr_size; + switch (remote_addr->sa_family) { + case AF_INET6: + remote_addr_size = (socklen_t)sizeof(struct sockaddr_in6); + break; + case AF_INET: + remote_addr_size = (socklen_t)sizeof(struct sockaddr_in); + break; + default: + return false; + } const uint8_t *ptr = (const uint8_t *)data; - ssize_t ret = sendto(sock->sock, ptr, length, 0, (struct sockaddr *)&sock_addr, sizeof(sock_addr)); + ssize_t ret = sendto(sock->sock, ptr, length, 0, remote_addr, remote_addr_size); if (ret >= (ssize_t)length) { return true; } @@ -476,7 +395,7 @@ bool hdhomerun_sock_sendto(struct hdhomerun_sock_t *sock, uint32_t remote_addr, return false; } - ret = sendto(sock->sock, ptr, length, 0, (struct sockaddr *)&sock_addr, sizeof(sock_addr)); + ret = sendto(sock->sock, ptr, length, 0, remote_addr, remote_addr_size); if (ret >= (ssize_t)length) { return true; } @@ -536,16 +455,11 @@ bool hdhomerun_sock_recv(struct hdhomerun_sock_t *sock, void *data, size_t *leng return false; } -bool hdhomerun_sock_recvfrom(struct hdhomerun_sock_t *sock, uint32_t *remote_addr, uint16_t *remote_port, void *data, size_t *length, uint64_t timeout) +bool hdhomerun_sock_recvfrom_ex(struct hdhomerun_sock_t *sock, struct sockaddr_storage *remote_addr, void *data, size_t *length, uint64_t timeout) { - struct sockaddr_in sock_addr; - memset(&sock_addr, 0, sizeof(sock_addr)); - socklen_t sockaddr_size = sizeof(sock_addr); - - ssize_t ret = recvfrom(sock->sock, data, *length, 0, (struct sockaddr *)&sock_addr, &sockaddr_size); + socklen_t sockaddr_size = sizeof(struct sockaddr_storage); + ssize_t ret = recvfrom(sock->sock, data, *length, 0, (struct sockaddr *)remote_addr, &sockaddr_size); if (ret > 0) { - *remote_addr = ntohl(sock_addr.sin_addr.s_addr); - *remote_port = ntohs(sock_addr.sin_port); *length = (size_t)ret; return true; } @@ -570,10 +484,8 @@ bool hdhomerun_sock_recvfrom(struct hdhomerun_sock_t *sock, uint32_t *remote_add return false; } - ret = recvfrom(sock->sock, data, *length, 0, (struct sockaddr *)&sock_addr, &sockaddr_size); + ret = recvfrom(sock->sock, data, *length, 0, (struct sockaddr *)remote_addr, &sockaddr_size); if (ret > 0) { - *remote_addr = ntohl(sock_addr.sin_addr.s_addr); - *remote_port = ntohs(sock_addr.sin_port); *length = (size_t)ret; return true; } diff --git a/hdhomerun_sock_windows.c b/hdhomerun_sock_windows.c index ea5d232..e8b85b0 100644 --- a/hdhomerun_sock_windows.c +++ b/hdhomerun_sock_windows.c @@ -1,7 +1,7 @@ /* * hdhomerun_sock_windows.c * - * Copyright © 2010-2016 Silicondust USA Inc. . + * Copyright © 2010-2022 Silicondust USA Inc. . * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -27,123 +27,78 @@ struct hdhomerun_sock_t { long events_selected; }; -#if defined(_WINRT) -static char *hdhomerun_local_ip_info_str = NULL; - -/* - * String format: ip address '/' subnet mask bits ... - * Example: "192.168.0.100/24 169.254.0.100/16" - */ -void hdhomerun_local_ip_info_set_str(const char *ip_info_str) +bool hdhomerun_local_ip_info2(int af, hdhomerun_local_ip_info2_callback_t callback, void *callback_arg) { - if (hdhomerun_local_ip_info_str) { - free(hdhomerun_local_ip_info_str); - } - - hdhomerun_local_ip_info_str = strdup(ip_info_str); -} - -int hdhomerun_local_ip_info(struct hdhomerun_local_ip_info_t ip_info_list[], int max_count) -{ - const char *ptr = hdhomerun_local_ip_info_str; - if (!ptr) { - return 0; - } - - struct hdhomerun_local_ip_info_t *ip_info = ip_info_list; - int count = 0; - - while (count < max_count) { - unsigned int a[4]; - unsigned int mask_bitcount; - if (sscanf(ptr, "%u.%u.%u.%u/%u", &a[0], &a[1], &a[2], &a[3], &mask_bitcount) != 5) { - break; - } - - ip_info->ip_addr = (uint32_t)((a[0] << 24) | (a[1] << 16) | (a[2] << 8) | (a[3] << 0)); - ip_info->subnet_mask = 0xFFFFFFFF << (32 - mask_bitcount); - ip_info++; - - count++; - - ptr = strchr(ptr, ' '); - if (!ptr) { - break; - } - - ptr++; - } - - return count; -} -#endif - -#if !defined(_WINRT) -int hdhomerun_local_ip_info(struct hdhomerun_local_ip_info_t ip_info_list[], int max_count) -{ - PIP_ADAPTER_INFO AdapterInfo; - ULONG AdapterInfoLength = sizeof(IP_ADAPTER_INFO) * 16; + IP_ADAPTER_ADDRESSES *adapter_addresses; + ULONG adapter_addresses_length = sizeof(IP_ADAPTER_ADDRESSES) * 16; while (1) { - AdapterInfo = (IP_ADAPTER_INFO *)malloc(AdapterInfoLength); - if (!AdapterInfo) { - return -1; + adapter_addresses = (IP_ADAPTER_ADDRESSES *)malloc(adapter_addresses_length); + if (!adapter_addresses) { + return false; } - ULONG LengthNeeded = AdapterInfoLength; - DWORD Ret = GetAdaptersInfo(AdapterInfo, &LengthNeeded); - if (Ret == NO_ERROR) { + ULONG length_needed = adapter_addresses_length; + DWORD ret = GetAdaptersAddresses(af, GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_SKIP_DNS_SERVER | GAA_FLAG_SKIP_FRIENDLY_NAME, NULL, adapter_addresses, &length_needed); + if (ret == NO_ERROR) { break; } - free(AdapterInfo); + free(adapter_addresses); - if (Ret != ERROR_BUFFER_OVERFLOW) { - return -1; + if (ret != ERROR_BUFFER_OVERFLOW) { + return false; } - if (AdapterInfoLength >= LengthNeeded) { - return -1; + if (adapter_addresses_length >= length_needed) { + return false; } - AdapterInfoLength = LengthNeeded; + adapter_addresses_length = length_needed; } - int count = 0; - PIP_ADAPTER_INFO Adapter = AdapterInfo; - while (Adapter) { - IP_ADDR_STRING *IPAddr = &Adapter->IpAddressList; - while (IPAddr) { - uint32_t ip_addr = ntohl(inet_addr(IPAddr->IpAddress.String)); - uint32_t subnet_mask = ntohl(inet_addr(IPAddr->IpMask.String)); + IP_ADAPTER_ADDRESSES *adapter = adapter_addresses; - if (ip_addr == 0) { - IPAddr = IPAddr->Next; + while (adapter) { + if ((adapter->IfType != MIB_IF_TYPE_ETHERNET) && (adapter->IfType != IF_TYPE_IEEE80211)) { + adapter = adapter->Next; + continue; + } + + if (adapter->PhysicalAddressLength != 6) { + adapter = adapter->Next; + continue; + } + + uint32_t ifindex = adapter->IfIndex; + + IP_ADAPTER_UNICAST_ADDRESS *adapter_address = adapter->FirstUnicastAddress; + while (adapter_address) { + if (adapter_address->Flags & IP_ADAPTER_ADDRESS_TRANSIENT) { + adapter_address = adapter_address->Next; continue; } - if (count < max_count) { - struct hdhomerun_local_ip_info_t *ip_info = &ip_info_list[count]; - ip_info->ip_addr = ip_addr; - ip_info->subnet_mask = subnet_mask; + struct sockaddr *local_ip = adapter_address->Address.lpSockaddr; + + if ((local_ip->sa_family == AF_INET6) && (adapter_address->ValidLifetime != 0xFFFFFFFF)) { + adapter_address = adapter_address->Next; + continue; /* skip temporary addresses */ } - count++; - IPAddr = IPAddr->Next; + uint8_t cidr = adapter_address->OnLinkPrefixLength; + callback(callback_arg, ifindex, local_ip, cidr); + + adapter_address = adapter_address->Next; } - if (count >= max_count) { - break; - } - - Adapter = Adapter->Next; + adapter = adapter->Next; } - free(AdapterInfo); - return count; + free(adapter_addresses); + return true; } -#endif -static struct hdhomerun_sock_t *hdhomerun_sock_create_internal(int protocol) +static struct hdhomerun_sock_t *hdhomerun_sock_create_internal(int af, int protocol) { struct hdhomerun_sock_t *sock = (struct hdhomerun_sock_t *)calloc(1, sizeof(struct hdhomerun_sock_t)); if (!sock) { @@ -151,7 +106,7 @@ static struct hdhomerun_sock_t *hdhomerun_sock_create_internal(int protocol) } /* Create socket. */ - sock->sock = socket(AF_INET, protocol, 0); + sock->sock = socket(af, protocol, 0); if (sock->sock == INVALID_SOCKET) { free(sock); return NULL; @@ -164,6 +119,12 @@ static struct hdhomerun_sock_t *hdhomerun_sock_create_internal(int protocol) return NULL; } + /* Set ipv6 */ + if (af == AF_INET6) { + int sock_opt_ipv6only = 1; + setsockopt(sock->sock, IPPROTO_IPV6, IPV6_V6ONLY, (char *)&sock_opt_ipv6only, sizeof(sock_opt_ipv6only)); + } + /* Event */ sock->event = CreateEvent(NULL, false, false, NULL); if (!sock->event) { @@ -175,9 +136,9 @@ static struct hdhomerun_sock_t *hdhomerun_sock_create_internal(int protocol) return sock; } -struct hdhomerun_sock_t *hdhomerun_sock_create_udp(void) +struct hdhomerun_sock_t *hdhomerun_sock_create_udp_ex(int af) { - struct hdhomerun_sock_t *sock = hdhomerun_sock_create_internal(SOCK_DGRAM); + struct hdhomerun_sock_t *sock = hdhomerun_sock_create_internal(af, SOCK_DGRAM); if (!sock) { return NULL; } @@ -190,9 +151,9 @@ struct hdhomerun_sock_t *hdhomerun_sock_create_udp(void) return sock; } -struct hdhomerun_sock_t *hdhomerun_sock_create_tcp(void) +struct hdhomerun_sock_t *hdhomerun_sock_create_tcp_ex(int af) { - return hdhomerun_sock_create_internal(SOCK_STREAM); + return hdhomerun_sock_create_internal(af, SOCK_STREAM); } void hdhomerun_sock_destroy(struct hdhomerun_sock_t *sock) @@ -228,107 +189,142 @@ void hdhomerun_sock_set_allow_reuse(struct hdhomerun_sock_t *sock) setsockopt(sock->sock, SOL_SOCKET, SO_REUSEADDR, (char *)&sock_opt, sizeof(sock_opt)); } +void hdhomerun_sock_set_ipv4_onesbcast(struct hdhomerun_sock_t *sock, int v) +{ +} + +void hdhomerun_sock_set_ipv6_multicast_ifindex(struct hdhomerun_sock_t *sock, uint32_t ifindex) +{ + setsockopt(sock->sock, IPPROTO_IPV6, IPV6_MULTICAST_IF, (char *)&ifindex, sizeof(ifindex)); +} + int hdhomerun_sock_getlasterror(void) { return WSAGetLastError(); } -uint32_t hdhomerun_sock_getsockname_addr(struct hdhomerun_sock_t *sock) +bool hdhomerun_sock_getsockname_addr_ex(struct hdhomerun_sock_t *sock, struct sockaddr_storage *result) { - struct sockaddr_in sock_addr; - int sockaddr_size = sizeof(sock_addr); - - if (getsockname(sock->sock, (struct sockaddr *)&sock_addr, &sockaddr_size) != 0) { - return 0; - } - - return ntohl(sock_addr.sin_addr.s_addr); + socklen_t sockaddr_size = sizeof(struct sockaddr_storage); + return (getsockname(sock->sock, (struct sockaddr *)result, &sockaddr_size) == 0); } uint16_t hdhomerun_sock_getsockname_port(struct hdhomerun_sock_t *sock) { - struct sockaddr_in sock_addr; - int sockaddr_size = sizeof(sock_addr); + struct sockaddr_storage sock_addr; + socklen_t sockaddr_size = sizeof(sock_addr); if (getsockname(sock->sock, (struct sockaddr *)&sock_addr, &sockaddr_size) != 0) { return 0; } - return ntohs(sock_addr.sin_port); + if (sock_addr.ss_family == AF_INET) { + struct sockaddr_in *sock_addr_in = (struct sockaddr_in *)&sock_addr; + return ntohs(sock_addr_in->sin_port); + } + if (sock_addr.ss_family == AF_INET6) { + struct sockaddr_in6 *sock_addr_in = (struct sockaddr_in6 *)&sock_addr; + return ntohs(sock_addr_in->sin6_port); + } + return 0; } -uint32_t hdhomerun_sock_getpeername_addr(struct hdhomerun_sock_t *sock) +bool hdhomerun_sock_getpeername_addr_ex(struct hdhomerun_sock_t *sock, struct sockaddr_storage *result) { - struct sockaddr_in sock_addr; - int sockaddr_size = sizeof(sock_addr); + socklen_t sockaddr_size = sizeof(struct sockaddr_storage); + return (getpeername(sock->sock, (struct sockaddr *)result, &sockaddr_size) == 0); +} - if (getpeername(sock->sock, (struct sockaddr *)&sock_addr, &sockaddr_size) != 0) { - return 0; +bool hdhomerun_sock_join_multicast_group_ex(struct hdhomerun_sock_t *sock, const struct sockaddr *multicast_addr, const struct sockaddr *local_addr) +{ + if (multicast_addr->sa_family == AF_INET6) { + const struct sockaddr_in6 *multicast_addr_in = (const struct sockaddr_in6 *)multicast_addr; + + struct ipv6_mreq imr; + memset(&imr, 0, sizeof(imr)); + memcpy(imr.ipv6mr_multiaddr.s6_addr, multicast_addr_in->sin6_addr.s6_addr, 16); + imr.ipv6mr_interface = multicast_addr_in->sin6_scope_id; + + if (setsockopt(sock->sock, IPPROTO_IPV6, IPV6_JOIN_GROUP, (const char *)&imr, sizeof(imr)) != 0) { + return false; + } + + return true; } - return ntohl(sock_addr.sin_addr.s_addr); -} + if (multicast_addr->sa_family == AF_INET) { + const struct sockaddr_in *multicast_addr_in = (const struct sockaddr_in *)multicast_addr; + const struct sockaddr_in *local_addr_in = (const struct sockaddr_in *)local_addr; -uint32_t hdhomerun_sock_getaddrinfo_addr(struct hdhomerun_sock_t *sock, const char *name) -{ - struct addrinfo hints; - memset(&hints, 0, sizeof(hints)); - hints.ai_family = AF_INET; - hints.ai_socktype = SOCK_STREAM; - hints.ai_protocol = IPPROTO_TCP; + struct ip_mreq imr; + memset(&imr, 0, sizeof(imr)); + imr.imr_multiaddr.s_addr = multicast_addr_in->sin_addr.s_addr; + imr.imr_interface.s_addr = (local_addr->sa_family == AF_INET) ? local_addr_in->sin_addr.s_addr : 0; - struct addrinfo *sock_info; - if (getaddrinfo(name, NULL, &hints, &sock_info) != 0) { - return 0; + if (setsockopt(sock->sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, (const char *)&imr, sizeof(imr)) != 0) { + return false; + } + + return true; } - struct sockaddr_in *sock_addr = (struct sockaddr_in *)sock_info->ai_addr; - uint32_t addr = ntohl(sock_addr->sin_addr.s_addr); - - freeaddrinfo(sock_info); - return addr; + return false; } -bool hdhomerun_sock_join_multicast_group(struct hdhomerun_sock_t *sock, uint32_t multicast_ip, uint32_t local_ip) +bool hdhomerun_sock_leave_multicast_group_ex(struct hdhomerun_sock_t *sock, const struct sockaddr *multicast_addr, const struct sockaddr *local_addr) { - struct ip_mreq imr; - memset(&imr, 0, sizeof(imr)); - imr.imr_multiaddr.s_addr = htonl(multicast_ip); - imr.imr_interface.s_addr = htonl(local_ip); + if (multicast_addr->sa_family == AF_INET6) { + const struct sockaddr_in6 *multicast_addr_in = (const struct sockaddr_in6 *)multicast_addr; - if (setsockopt(sock->sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, (const char *)&imr, sizeof(imr)) != 0) { + struct ipv6_mreq imr; + memset(&imr, 0, sizeof(imr)); + memcpy(imr.ipv6mr_multiaddr.s6_addr, multicast_addr_in->sin6_addr.s6_addr, 16); + imr.ipv6mr_interface = multicast_addr_in->sin6_scope_id; + + if (setsockopt(sock->sock, IPPROTO_IPV6, IPV6_LEAVE_GROUP, (const char *)&imr, sizeof(imr)) != 0) { + return false; + } + + return true; + } + + if (multicast_addr->sa_family == AF_INET) { + const struct sockaddr_in *multicast_addr_in = (const struct sockaddr_in *)multicast_addr; + const struct sockaddr_in *local_addr_in = (const struct sockaddr_in *)local_addr; + + struct ip_mreq imr; + memset(&imr, 0, sizeof(imr)); + imr.imr_multiaddr.s_addr = multicast_addr_in->sin_addr.s_addr; + imr.imr_interface.s_addr = (local_addr->sa_family == AF_INET) ? local_addr_in->sin_addr.s_addr : 0; + + if (setsockopt(sock->sock, IPPROTO_IP, IP_DROP_MEMBERSHIP, (const char *)&imr, sizeof(imr)) != 0) { + return false; + } + + return true; + } + + return false; +} + +bool hdhomerun_sock_bind_ex(struct hdhomerun_sock_t *sock, const struct sockaddr *local_addr, bool allow_reuse) +{ + socklen_t local_addr_size; + switch (local_addr->sa_family) { + case AF_INET6: + local_addr_size = (socklen_t)sizeof(struct sockaddr_in6); + break; + case AF_INET: + local_addr_size = (socklen_t)sizeof(struct sockaddr_in); + break; + default: return false; } - return true; -} - -bool hdhomerun_sock_leave_multicast_group(struct hdhomerun_sock_t *sock, uint32_t multicast_ip, uint32_t local_ip) -{ - struct ip_mreq imr; - memset(&imr, 0, sizeof(imr)); - imr.imr_multiaddr.s_addr = htonl(multicast_ip); - imr.imr_interface.s_addr = htonl(local_ip); - - if (setsockopt(sock->sock, IPPROTO_IP, IP_DROP_MEMBERSHIP, (const char *)&imr, sizeof(imr)) != 0) { - return false; - } - - return true; -} - -bool hdhomerun_sock_bind(struct hdhomerun_sock_t *sock, uint32_t local_addr, uint16_t local_port, bool allow_reuse) -{ int sock_opt = allow_reuse; setsockopt(sock->sock, SOL_SOCKET, SO_REUSEADDR, (char *)&sock_opt, sizeof(sock_opt)); - struct sockaddr_in sock_addr; - memset(&sock_addr, 0, sizeof(sock_addr)); - sock_addr.sin_family = AF_INET; - sock_addr.sin_addr.s_addr = htonl(local_addr); - sock_addr.sin_port = htons(local_port); - - if (bind(sock->sock, (struct sockaddr *)&sock_addr, sizeof(sock_addr)) != 0) { + if (bind(sock->sock, (const struct sockaddr *)local_addr, local_addr_size) != 0) { return false; } @@ -348,19 +344,25 @@ static bool hdhomerun_sock_event_select(struct hdhomerun_sock_t *sock, long even return true; } -bool hdhomerun_sock_connect(struct hdhomerun_sock_t *sock, uint32_t remote_addr, uint16_t remote_port, uint64_t timeout) +bool hdhomerun_sock_connect_ex(struct hdhomerun_sock_t *sock, const struct sockaddr *remote_addr, uint64_t timeout) { + socklen_t remote_addr_size; + switch (remote_addr->sa_family) { + case AF_INET6: + remote_addr_size = (socklen_t)sizeof(struct sockaddr_in6); + break; + case AF_INET: + remote_addr_size = (socklen_t)sizeof(struct sockaddr_in); + break; + default: + return false; + } + if (!hdhomerun_sock_event_select(sock, FD_WRITE | FD_CLOSE)) { return false; } - struct sockaddr_in sock_addr; - memset(&sock_addr, 0, sizeof(sock_addr)); - sock_addr.sin_family = AF_INET; - sock_addr.sin_addr.s_addr = htonl(remote_addr); - sock_addr.sin_port = htons(remote_port); - - if (connect(sock->sock, (struct sockaddr *)&sock_addr, sizeof(sock_addr)) != 0) { + if (connect(sock->sock, remote_addr, remote_addr_size) != 0) { if (WSAGetLastError() != WSAEWOULDBLOCK) { return false; } @@ -420,19 +422,25 @@ bool hdhomerun_sock_send(struct hdhomerun_sock_t *sock, const void *data, size_t } } -bool hdhomerun_sock_sendto(struct hdhomerun_sock_t *sock, uint32_t remote_addr, uint16_t remote_port, const void *data, size_t length, uint64_t timeout) +bool hdhomerun_sock_sendto_ex(struct hdhomerun_sock_t *sock, const struct sockaddr *remote_addr, const void *data, size_t length, uint64_t timeout) { + socklen_t remote_addr_size; + switch (remote_addr->sa_family) { + case AF_INET6: + remote_addr_size = (socklen_t)sizeof(struct sockaddr_in6); + break; + case AF_INET: + remote_addr_size = (socklen_t)sizeof(struct sockaddr_in); + break; + default: + return false; + } + if (!hdhomerun_sock_event_select(sock, FD_WRITE | FD_CLOSE)) { return false; } - struct sockaddr_in sock_addr; - memset(&sock_addr, 0, sizeof(sock_addr)); - sock_addr.sin_family = AF_INET; - sock_addr.sin_addr.s_addr = htonl(remote_addr); - sock_addr.sin_port = htons(remote_port); - - int ret = sendto(sock->sock, (char *)data, (int)length, 0, (struct sockaddr *)&sock_addr, sizeof(sock_addr)); + int ret = sendto(sock->sock, (char *)data, (int)length, 0, remote_addr, remote_addr_size); if (ret >= (int)length) { return true; } @@ -448,7 +456,7 @@ bool hdhomerun_sock_sendto(struct hdhomerun_sock_t *sock, uint32_t remote_addr, return false; } - ret = sendto(sock->sock, (char *)data, (int)length, 0, (struct sockaddr *)&sock_addr, sizeof(sock_addr)); + ret = sendto(sock->sock, (char *)data, (int)length, 0, remote_addr, remote_addr_size); if (ret >= (int)length) { return true; } @@ -488,20 +496,15 @@ bool hdhomerun_sock_recv(struct hdhomerun_sock_t *sock, void *data, size_t *leng return false; } -bool hdhomerun_sock_recvfrom(struct hdhomerun_sock_t *sock, uint32_t *remote_addr, uint16_t *remote_port, void *data, size_t *length, uint64_t timeout) +bool hdhomerun_sock_recvfrom_ex(struct hdhomerun_sock_t *sock, struct sockaddr_storage *remote_addr, void *data, size_t *length, uint64_t timeout) { if (!hdhomerun_sock_event_select(sock, FD_READ | FD_CLOSE)) { return false; } - struct sockaddr_in sock_addr; - memset(&sock_addr, 0, sizeof(sock_addr)); - int sockaddr_size = sizeof(sock_addr); - - int ret = recvfrom(sock->sock, (char *)data, (int)(*length), 0, (struct sockaddr *)&sock_addr, &sockaddr_size); + socklen_t sockaddr_size = sizeof(struct sockaddr_storage); + int ret = recvfrom(sock->sock, (char *)data, (int)(*length), 0, (struct sockaddr *)remote_addr, &sockaddr_size); if (ret > 0) { - *remote_addr = ntohl(sock_addr.sin_addr.s_addr); - *remote_port = ntohs(sock_addr.sin_port); *length = ret; return true; } @@ -517,10 +520,8 @@ bool hdhomerun_sock_recvfrom(struct hdhomerun_sock_t *sock, uint32_t *remote_add return false; } - ret = recvfrom(sock->sock, (char *)data, (int)(*length), 0, (struct sockaddr *)&sock_addr, &sockaddr_size); + ret = recvfrom(sock->sock, (char *)data, (int)(*length), 0, (struct sockaddr *)remote_addr, &sockaddr_size); if (ret > 0) { - *remote_addr = ntohl(sock_addr.sin_addr.s_addr); - *remote_port = ntohs(sock_addr.sin_port); *length = ret; return true; } diff --git a/hdhomerun_video.c b/hdhomerun_video.c index a472634..75bbfda 100644 --- a/hdhomerun_video.c +++ b/hdhomerun_video.c @@ -1,7 +1,7 @@ /* * hdhomerun_video.c * - * Copyright © 2006-2016 Silicondust USA Inc. . + * Copyright © 2006-2022 Silicondust USA Inc. . * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -26,8 +26,7 @@ struct hdhomerun_video_sock_t { struct hdhomerun_sock_t *sock; uint32_t keepalive_lockkey; - uint32_t keepalive_addr; - uint16_t keepalive_port; + struct sockaddr_storage keepalive_addr; volatile bool keepalive_start; volatile size_t head; @@ -52,6 +51,16 @@ struct hdhomerun_video_sock_t { static void hdhomerun_video_thread_execute(void *arg); struct hdhomerun_video_sock_t *hdhomerun_video_create(uint16_t listen_port, bool allow_port_reuse, size_t buffer_size, struct hdhomerun_debug_t *dbg) +{ + struct sockaddr_in listen_addr_in; + memset(&listen_addr_in, 0, sizeof(listen_addr_in)); + listen_addr_in.sin_family = AF_INET; + listen_addr_in.sin_port = htons(listen_port); + + return hdhomerun_video_create_ex((const struct sockaddr *)&listen_addr_in, allow_port_reuse, buffer_size, dbg); +} + +struct hdhomerun_video_sock_t *hdhomerun_video_create_ex(const struct sockaddr *listen_addr, bool allow_port_reuse, size_t buffer_size, struct hdhomerun_debug_t *dbg) { /* Create object. */ struct hdhomerun_video_sock_t *vs = (struct hdhomerun_video_sock_t *)calloc(1, sizeof(struct hdhomerun_video_sock_t)); @@ -82,7 +91,7 @@ struct hdhomerun_video_sock_t *hdhomerun_video_create(uint16_t listen_port, bool } /* Create socket. */ - vs->sock = hdhomerun_sock_create_udp(); + vs->sock = hdhomerun_sock_create_udp_ex(listen_addr->sa_family); if (!vs->sock) { hdhomerun_debug_printf(dbg, "hdhomerun_video_create: failed to allocate socket\n"); goto error; @@ -92,8 +101,8 @@ struct hdhomerun_video_sock_t *hdhomerun_video_create(uint16_t listen_port, bool hdhomerun_sock_set_recv_buffer_size(vs->sock, 1024 * 1024); /* Bind socket. */ - if (!hdhomerun_sock_bind(vs->sock, INADDR_ANY, listen_port, allow_port_reuse)) { - hdhomerun_debug_printf(dbg, "hdhomerun_video_create: failed to bind socket (port %u)\n", listen_port); + if (!hdhomerun_sock_bind_ex(vs->sock, listen_addr, allow_port_reuse)) { + hdhomerun_debug_printf(dbg, "hdhomerun_video_create: failed to bind socket\n"); goto error; } @@ -134,14 +143,36 @@ void hdhomerun_video_destroy(struct hdhomerun_video_sock_t *vs) } void hdhomerun_video_set_keepalive(struct hdhomerun_video_sock_t *vs, uint32_t remote_addr, uint16_t remote_port, uint32_t lockkey) +{ + if ((remote_addr == 0) || (remote_port == 0)) { + hdhomerun_video_set_keepalive_ex(vs, NULL, lockkey); + return; + } + + struct sockaddr_in remote_addr_in; + memset(&remote_addr_in, 0, sizeof(remote_addr_in)); + remote_addr_in.sin_family = AF_INET; + remote_addr_in.sin_addr.s_addr = htonl(remote_addr); + remote_addr_in.sin_port = htons(remote_port); + + hdhomerun_video_set_keepalive_ex(vs, (struct sockaddr *)&remote_addr_in, lockkey); +} + +void hdhomerun_video_set_keepalive_ex(struct hdhomerun_video_sock_t *vs, const struct sockaddr *remote_addr, uint32_t lockkey) { thread_mutex_lock(&vs->lock); - vs->keepalive_addr = remote_addr; - vs->keepalive_port = remote_port; + memset(&vs->keepalive_addr, 0, sizeof(vs->keepalive_addr)); + if (remote_addr && (remote_addr->sa_family == AF_INET6)) { + memcpy(&vs->keepalive_addr, remote_addr, sizeof(struct sockaddr_in6)); + } + if (remote_addr && (remote_addr->sa_family == AF_INET)) { + memcpy(&vs->keepalive_addr, remote_addr, sizeof(struct sockaddr_in)); + } + vs->keepalive_lockkey = lockkey; - if ((remote_addr != 0) && (remote_port != 0)) { + if (vs->keepalive_addr.ss_family) { vs->keepalive_start = true; } @@ -166,7 +197,22 @@ uint16_t hdhomerun_video_get_local_port(struct hdhomerun_video_sock_t *vs) int hdhomerun_video_join_multicast_group(struct hdhomerun_video_sock_t *vs, uint32_t multicast_ip, uint32_t local_ip) { - if (!hdhomerun_sock_join_multicast_group(vs->sock, multicast_ip, local_ip)) { + struct sockaddr_in multicast_addr; + memset(&multicast_addr, 0, sizeof(multicast_addr)); + multicast_addr.sin_family = AF_INET; + multicast_addr.sin_addr.s_addr = htonl(multicast_ip); + + struct sockaddr_in local_addr; + memset(&local_addr, 0, sizeof(local_addr)); + local_addr.sin_family = AF_INET; + local_addr.sin_addr.s_addr = htonl(local_ip); + + return hdhomerun_video_join_multicast_group_ex(vs, (struct sockaddr *)&multicast_addr, (struct sockaddr *)&local_addr); +} + +int hdhomerun_video_join_multicast_group_ex(struct hdhomerun_video_sock_t *vs, const struct sockaddr *multicast_addr, const struct sockaddr *local_addr) +{ + if (!hdhomerun_sock_join_multicast_group_ex(vs->sock, multicast_addr, local_addr)) { hdhomerun_debug_printf(vs->dbg, "hdhomerun_video_join_multicast_group: setsockopt failed (%d)\n", hdhomerun_sock_getlasterror()); return -1; } @@ -176,7 +222,22 @@ int hdhomerun_video_join_multicast_group(struct hdhomerun_video_sock_t *vs, uint void hdhomerun_video_leave_multicast_group(struct hdhomerun_video_sock_t *vs, uint32_t multicast_ip, uint32_t local_ip) { - if (!hdhomerun_sock_leave_multicast_group(vs->sock, multicast_ip, local_ip)) { + struct sockaddr_in multicast_addr; + memset(&multicast_addr, 0, sizeof(multicast_addr)); + multicast_addr.sin_family = AF_INET; + multicast_addr.sin_addr.s_addr = htonl(multicast_ip); + + struct sockaddr_in local_addr; + memset(&local_addr, 0, sizeof(local_addr)); + local_addr.sin_family = AF_INET; + local_addr.sin_addr.s_addr = htonl(local_ip); + + hdhomerun_video_leave_multicast_group_ex(vs, (struct sockaddr *)&multicast_addr, (struct sockaddr *)&local_addr); +} + +void hdhomerun_video_leave_multicast_group_ex(struct hdhomerun_video_sock_t *vs, const struct sockaddr *multicast_addr, const struct sockaddr *local_addr) +{ + if (!hdhomerun_sock_leave_multicast_group_ex(vs->sock, multicast_addr, local_addr)) { hdhomerun_debug_printf(vs->dbg, "hdhomerun_video_leave_multicast_group: setsockopt failed (%d)\n", hdhomerun_sock_getlasterror()); } } @@ -251,19 +312,19 @@ static void hdhomerun_video_thread_send_keepalive(struct hdhomerun_video_sock_t { thread_mutex_lock(&vs->lock); uint32_t keepalive_lockkey = vs->keepalive_lockkey; - uint32_t keepalive_addr = vs->keepalive_addr; - uint16_t keepalive_port = vs->keepalive_port; + struct sockaddr_storage keepalive_addr; + keepalive_addr = vs->keepalive_addr; vs->keepalive_start = false; thread_mutex_unlock(&vs->lock); - if ((keepalive_addr == 0) || (keepalive_port == 0)) { + if (keepalive_addr.ss_family == 0) { return; } struct hdhomerun_pkt_t pkt; hdhomerun_pkt_reset(&pkt); hdhomerun_pkt_write_u32(&pkt, keepalive_lockkey); - hdhomerun_sock_sendto(vs->sock, keepalive_addr, keepalive_port, pkt.start, pkt.end - pkt.start, 25); + hdhomerun_sock_sendto_ex(vs->sock, (struct sockaddr *)&keepalive_addr, pkt.start, pkt.end - pkt.start, 25); } static void hdhomerun_video_thread_execute(void *arg) diff --git a/hdhomerun_video.h b/hdhomerun_video.h index 863547a..b379398 100644 --- a/hdhomerun_video.h +++ b/hdhomerun_video.h @@ -1,7 +1,7 @@ /* * hdhomerun_video.h * - * Copyright © 2006-2016 Silicondust USA Inc. . + * Copyright © 2006-2022 Silicondust USA Inc. . * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -49,12 +49,14 @@ struct hdhomerun_video_stats_t { * When no longer needed, the socket should be destroyed by calling hdhomerun_control_destroy. */ extern LIBHDHOMERUN_API struct hdhomerun_video_sock_t *hdhomerun_video_create(uint16_t listen_port, bool allow_port_reuse, size_t buffer_size, struct hdhomerun_debug_t *dbg); +extern LIBHDHOMERUN_API struct hdhomerun_video_sock_t *hdhomerun_video_create_ex(const struct sockaddr *listen_addr, bool allow_port_reuse, size_t buffer_size, struct hdhomerun_debug_t *dbg); extern LIBHDHOMERUN_API void hdhomerun_video_destroy(struct hdhomerun_video_sock_t *vs); /* * Configure to send a keepalive packet every second. */ extern LIBHDHOMERUN_API void hdhomerun_video_set_keepalive(struct hdhomerun_video_sock_t *vs, uint32_t remote_addr, uint16_t remote_port, uint32_t lockkey); +extern LIBHDHOMERUN_API void hdhomerun_video_set_keepalive_ex(struct hdhomerun_video_sock_t *vs, const struct sockaddr *remote_addr, uint32_t lockkey); /* * Get the port the socket is listening on. @@ -67,7 +69,9 @@ extern LIBHDHOMERUN_API uint16_t hdhomerun_video_get_local_port(struct hdhomerun * Join/leave multicast group. */ extern LIBHDHOMERUN_API int hdhomerun_video_join_multicast_group(struct hdhomerun_video_sock_t *vs, uint32_t multicast_ip, uint32_t local_ip); +extern LIBHDHOMERUN_API int hdhomerun_video_join_multicast_group_ex(struct hdhomerun_video_sock_t *vs, const struct sockaddr *multicast_addr, const struct sockaddr *local_addr); extern LIBHDHOMERUN_API void hdhomerun_video_leave_multicast_group(struct hdhomerun_video_sock_t *vs, uint32_t multicast_ip, uint32_t local_ip); +extern LIBHDHOMERUN_API void hdhomerun_video_leave_multicast_group_ex(struct hdhomerun_video_sock_t *vs, const struct sockaddr *multicast_addr, const struct sockaddr *local_addr); /* * Read data from buffer.