diff --git a/feeds/ucentral/ucode/Makefile b/feeds/ucentral/ucode/Makefile index 9c92505de..8af4d9a43 100644 --- a/feeds/ucentral/ucode/Makefile +++ b/feeds/ucentral/ucode/Makefile @@ -13,8 +13,8 @@ PKG_RELEASE:=1 PKG_SOURCE_PROTO:=git PKG_SOURCE_URL=https://github.com/jow-/ucode.git PKG_SOURCE_DATE:=2021-07-30 -PKG_SOURCE_VERSION:=0f022aae0c6008fe6f2219871d32dca8b9105066 -PKG_MIRROR_HASH:= +PKG_SOURCE_VERSION:=03b6a8efc1834a4114f0d11e1a7cecff3242b305 +PKG_MIRROR_HASH:=6af37293d6d7023f30728c76a02b4e48d323262a45058410760a30256e0dbe2b PKG_MAINTAINER:=Jo-Philipp Wich PKG_LICENSE:=ISC @@ -33,7 +33,7 @@ endef define Package/ucode $(Package/ucode/default) - DEPENDS:=+libucode + DEPENDS:=+libucode endef define Package/ucode/description @@ -110,6 +110,33 @@ define Package/ucode-mod-uci/description endef +define Package/ucode-mod-nl80211 + $(Package/ucode/default) + TITLE+= (nl80211 module) + DEPENDS:=ucode +libnl-tiny +kmod-mac80211 +endef + +define Package/ucode-mod-nl80211/description + The nl80211 module allows templates to send and receive nl80211 messages.. +endef + + +define Package/ucode-mod-struct + $(Package/ucode/default) + TITLE+= (struct module) + DEPENDS:=ucode +endef + +define Package/ucode-mod-struct/description + The struct module allows templates to unpack binary buffers. +endef + + +define Build/Prepare + $(Build/Prepare/Default) + $(CP) $(STAGING_DIR)/usr/include/mac80211/uapi/linux/nl80211.h $(PKG_BUILD_DIR)/nl80211_copy.h +endef + define Build/InstallDev $(INSTALL_DIR) $(1)/usr/lib $(1)/usr/include/ucode $(CP) $(PKG_INSTALL_DIR)/usr/include/ucode/*.h $(1)/usr/include/ucode/ @@ -152,6 +179,16 @@ define Package/ucode-mod-uci/install $(INSTALL_BIN) $(PKG_INSTALL_DIR)/usr/lib/ucode/uci.so $(1)/usr/lib/ucode/ endef +define Package/ucode-mod-nl80211/install + $(INSTALL_DIR) $(1)/usr/lib/ucode + $(INSTALL_BIN) $(PKG_INSTALL_DIR)/usr/lib/ucode/nl80211.so $(1)/usr/lib/ucode/ +endef + +define Package/ucode-mod-struct/install + $(INSTALL_DIR) $(1)/usr/lib/ucode + $(INSTALL_BIN) $(PKG_INSTALL_DIR)/usr/lib/ucode/struct.so $(1)/usr/lib/ucode/ +endef + $(eval $(call BuildPackage,ucode)) $(eval $(call BuildPackage,libucode)) @@ -160,3 +197,5 @@ $(eval $(call BuildPackage,ucode-mod-resolv)) $(eval $(call BuildPackage,ucode-mod-math)) $(eval $(call BuildPackage,ucode-mod-ubus)) $(eval $(call BuildPackage,ucode-mod-uci)) +$(eval $(call BuildPackage,ucode-mod-nl80211)) +$(eval $(call BuildPackage,ucode-mod-struct)) diff --git a/feeds/ucentral/ucode/patches/000-nl80211_copy.patch b/feeds/ucentral/ucode/patches/000-nl80211_copy.patch new file mode 100644 index 000000000..52812b28d --- /dev/null +++ b/feeds/ucentral/ucode/patches/000-nl80211_copy.patch @@ -0,0 +1,13 @@ +Index: ucode-2021-07-30-03b6a8ef/lib/nl80211.c +=================================================================== +--- ucode-2021-07-30-03b6a8ef.orig/lib/nl80211.c ++++ ucode-2021-07-30-03b6a8ef/lib/nl80211.c +@@ -38,7 +38,7 @@ limitations under the License. + #include + #include + +-#include ++#include "../nl80211_copy.h" + #include + + #include "ucode/module.h" diff --git a/feeds/ucentral/ucode/patches/0001-fixes.patch b/feeds/ucentral/ucode/patches/0001-fixes.patch new file mode 100644 index 000000000..a9d94556c --- /dev/null +++ b/feeds/ucentral/ucode/patches/0001-fixes.patch @@ -0,0 +1,166 @@ +From aaf6b8d2355044843a7f0e765fb295518d5c114b Mon Sep 17 00:00:00 2001 +From: John Crispin +Date: Thu, 20 Jan 2022 10:48:35 +0100 +Subject: [PATCH] fixes + +--- + lib/nl80211.c | 73 +++++++++++++++++++++++++++++++++++++++++++++++---- + 1 file changed, 68 insertions(+), 5 deletions(-) + +diff --git a/lib/nl80211.c b/lib/nl80211.c +index fc24fb8..e308ebc 100644 +--- a/lib/nl80211.c ++++ b/lib/nl80211.c +@@ -45,6 +45,8 @@ limitations under the License. + + #define err_return(code, ...) do { set_error(code, __VA_ARGS__); return NULL; } while(0) + ++#define NL80211_ATTR_NOT_IMPLEMENTED 0x10000 ++ + static struct { + int code; + char *msg; +@@ -257,6 +259,14 @@ static const uc_nl_nested_spec_t nl80211_keys_nla = { + } + }; + ++#ifndef NL80211_MESHCONF_NOLEARN ++#define NL80211_MESHCONF_NOLEARN NL80211_ATTR_NOT_IMPLEMENTED ++#endif ++ ++#ifndef NL80211_MESHCONF_CONNECTED_TO_AS ++#define NL80211_MESHCONF_CONNECTED_TO_AS NL80211_ATTR_NOT_IMPLEMENTED ++#endif ++ + static const uc_nl_nested_spec_t nl80211_mesh_params_nla = { + .headsize = 0, + .nattrs = 29, +@@ -348,6 +358,14 @@ static const uc_nl_nested_spec_t nl80211_nan_func_nla = { + } + }; + ++#ifndef NL80211_PMSR_FTM_REQ_ATTR_LMR_FEEDBACK ++#define NL80211_PMSR_FTM_REQ_ATTR_LMR_FEEDBACK NL80211_ATTR_NOT_IMPLEMENTED ++#endif ++ ++#ifndef NL80211_PMSR_FTM_REQ_ATTR_BSS_COLOR ++#define NL80211_PMSR_FTM_REQ_ATTR_BSS_COLOR NL80211_ATTR_NOT_IMPLEMENTED ++#endif ++ + static const uc_nl_nested_spec_t nl80211_peer_measurements_peers_req_data_ftm_nla = { + .headsize = 0, + .nattrs = 13, +@@ -497,6 +515,26 @@ static const uc_nl_nested_spec_t nl80211_wiphy_bands_freqs_wmm_nla = { + } + }; + ++#ifndef NL80211_FREQUENCY_ATTR_1MHZ ++#define NL80211_FREQUENCY_ATTR_1MHZ NL80211_ATTR_NOT_IMPLEMENTED ++#endif ++ ++#ifndef NL80211_FREQUENCY_ATTR_2MHZ ++#define NL80211_FREQUENCY_ATTR_2MHZ NL80211_ATTR_NOT_IMPLEMENTED ++#endif ++ ++#ifndef NL80211_FREQUENCY_ATTR_4MHZ ++#define NL80211_FREQUENCY_ATTR_4MHZ NL80211_ATTR_NOT_IMPLEMENTED ++#endif ++ ++#ifndef NL80211_FREQUENCY_ATTR_8MHZ ++#define NL80211_FREQUENCY_ATTR_8MHZ NL80211_ATTR_NOT_IMPLEMENTED ++#endif ++ ++#ifndef NL80211_FREQUENCY_ATTR_16MHZ ++#define NL80211_FREQUENCY_ATTR_16MHZ NL80211_ATTR_NOT_IMPLEMENTED ++#endif ++ + static const uc_nl_nested_spec_t nl80211_wiphy_bands_freqs_nla = { + .headsize = 0, + .nattrs = 25, +@@ -538,6 +576,10 @@ static const uc_nl_nested_spec_t nl80211_wiphy_bands_rates_nla = { + } + }; + ++#ifndef NL80211_BAND_IFTYPE_ATTR_VENDOR_ELEMS ++#define NL80211_BAND_IFTYPE_ATTR_VENDOR_ELEMS NL80211_ATTR_NOT_IMPLEMENTED ++#endif ++ + static const uc_nl_nested_spec_t nl80211_wiphy_bands_iftype_data_nla = { + .headsize = 0, + .nattrs = 7, +@@ -645,13 +687,26 @@ static const uc_nl_nested_spec_t nl80211_bss_nla = { + + static const uc_nl_nested_spec_t nl80211_sta_info_bitrate_nla = { + .headsize = 0, +- .nattrs = 5, ++ .nattrs = 18, + .attrs = { + { NL80211_RATE_INFO_BITRATE, "bitrate", DT_U16, 0, NULL }, + { NL80211_RATE_INFO_BITRATE32, "bitrate32", DT_U32, 0, NULL }, + { NL80211_RATE_INFO_MCS, "mcs", DT_U8, 0, NULL }, + { NL80211_RATE_INFO_40_MHZ_WIDTH, "40_mhz_width", DT_FLAG, 0, NULL }, + { NL80211_RATE_INFO_SHORT_GI, "short_gi", DT_FLAG, 0, NULL }, ++ { NL80211_RATE_INFO_VHT_MCS, "vht_mcs", DT_U8, 0, NULL }, ++ { NL80211_RATE_INFO_VHT_NSS, "vht_nss", DT_U8, 0, NULL }, ++ { NL80211_RATE_INFO_HE_MCS, "he_mcs", DT_U8, 0, NULL }, ++ { NL80211_RATE_INFO_HE_NSS, "he_nss", DT_U8, 0, NULL }, ++ { NL80211_RATE_INFO_HE_GI, "he_gi", DT_U8, 0, NULL }, ++ { NL80211_RATE_INFO_HE_DCM, "he_dcm", DT_U8, 0, NULL }, ++ { NL80211_RATE_INFO_HE_RU_ALLOC, "he_ru_alloc", DT_U8, 0, NULL }, ++ { NL80211_RATE_INFO_40_MHZ_WIDTH, "width_40", DT_FLAG, 0, NULL }, ++ { NL80211_RATE_INFO_80_MHZ_WIDTH, "width_80", DT_FLAG, 0, NULL }, ++ { NL80211_RATE_INFO_80P80_MHZ_WIDTH, "width_80p80", DT_FLAG, 0, NULL }, ++ { NL80211_RATE_INFO_160_MHZ_WIDTH, "width_160", DT_FLAG, 0, NULL }, ++ { NL80211_RATE_INFO_10_MHZ_WIDTH, "width_10", DT_FLAG, 0, NULL }, ++ { NL80211_RATE_INFO_5_MHZ_WIDTH, "width_5", DT_FLAG, 0, NULL }, + } + }; + +@@ -695,9 +750,13 @@ static const uc_nl_nested_spec_t nl80211_bss_param_nla = { + } + }; + ++#ifndef NL80211_STA_INFO_CONNECTED_TO_AS ++#define NL80211_STA_INFO_CONNECTED_TO_AS NL80211_ATTR_NOT_IMPLEMENTED ++#endif ++ + static const uc_nl_nested_spec_t nl80211_sta_info_nla = { + .headsize = 0, +- .nattrs = 34, ++ .nattrs = 35, + .attrs = { + { NL80211_STA_INFO_INACTIVE_TIME, "inactive_time", DT_U32, 0, NULL }, + { NL80211_STA_INFO_RX_BYTES, "rx_bytes", DT_U32, 0, NULL }, +@@ -724,15 +783,16 @@ static const uc_nl_nested_spec_t nl80211_sta_info_nla = { + { NL80211_STA_INFO_NONPEER_PM, "nonpeer_pm", DT_U32, 0, NULL }, + { NL80211_STA_INFO_CHAIN_SIGNAL, "chain_signal", DT_S8, DF_MULTIPLE|DF_AUTOIDX, NULL }, + { NL80211_STA_INFO_CHAIN_SIGNAL_AVG, "chain_signal_avg", DT_S8, DF_MULTIPLE|DF_AUTOIDX, NULL }, +- { NL80211_STA_INFO_TID_STATS, "tid_stats", DT_NESTED, 0, &nl80211_tid_stats_nla }, ++ { NL80211_STA_INFO_TID_STATS, "tid_stats", DT_NESTED, DF_MULTIPLE|DF_AUTOIDX, &nl80211_tid_stats_nla }, + { NL80211_STA_INFO_BSS_PARAM, "bss_param", DT_NESTED, 0, &nl80211_bss_param_nla }, + { NL80211_STA_INFO_RX_DURATION, "rx_duration", DT_U64, 0, NULL }, + { NL80211_STA_INFO_TX_DURATION, "tx_duration", DT_U64, 0, NULL }, +- { NL80211_STA_INFO_ACK_SIGNAL, "ack_signal", DT_U8, 0, NULL }, +- { NL80211_STA_INFO_ACK_SIGNAL_AVG, "ack_signal_avg", DT_U8, 0, NULL }, ++ { NL80211_STA_INFO_ACK_SIGNAL, "ack_signal", DT_S8, 0, NULL }, ++ { NL80211_STA_INFO_ACK_SIGNAL_AVG, "ack_signal_avg", DT_S8, 0, NULL }, + { NL80211_STA_INFO_AIRTIME_LINK_METRIC, "airtime_link_metric", DT_U32, 0, NULL }, + { NL80211_STA_INFO_CONNECTED_TO_AS, "connected_to_as", DT_BOOL, 0, NULL }, + { NL80211_STA_INFO_CONNECTED_TO_GATE, "connected_to_gate", DT_BOOL, 0, NULL }, ++ { NL80211_STA_INFO_CONNECTED_TIME, "connected_time", DT_U32, 0, NULL }, + } + }; + +@@ -1044,6 +1104,9 @@ uc_nl_parse_attrs(struct nl_msg *msg, char *base, const uc_nl_attr_spec_t *attrs + bool exists; + + for (i = 0; i < nattrs; i++) { ++ if (attrs[i].attr == NL80211_ATTR_NOT_IMPLEMENTED) ++ continue; ++ + v = ucv_object_get(obj, attrs[i].key, &exists); + + if (!exists) +-- +2.25.1 + diff --git a/feeds/ucentral/ucode/patches/0002-add-os-library.patch b/feeds/ucentral/ucode/patches/0002-add-os-library.patch new file mode 100644 index 000000000..a6d6aea6b --- /dev/null +++ b/feeds/ucentral/ucode/patches/0002-add-os-library.patch @@ -0,0 +1,213 @@ +From 9c77d85f8e121bf0994eb4bc572eea5cf093f0c6 Mon Sep 17 00:00:00 2001 +From: John Crispin +Date: Tue, 25 Jan 2022 16:54:48 +0100 +Subject: [PATCH] add os library + +Signed-off-by: John Crispin +--- + CMakeLists.txt | 7 +++ + lib/os.c | 167 +++++++++++++++++++++++++++++++++++++++++++++++++ + 2 files changed, 174 insertions(+) + create mode 100644 lib/os.c + +diff --git a/CMakeLists.txt b/CMakeLists.txt +index 7c84fc3..f7d4f83 100644 +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -27,6 +27,7 @@ OPTION(RTNL_SUPPORT "Route Netlink plugin support" ON) + OPTION(NL80211_SUPPORT "Wireless Netlink plugin support" ON) + OPTION(RESOLV_SUPPORT "NS resolve plugin support" ON) + OPTION(STRUCT_SUPPORT "Struct plugin support" ON) ++OPTION(OS_SUPPORT "OS plugin support" ON) + + SET(LIB_SEARCH_PATH "${CMAKE_INSTALL_PREFIX}/lib/ucode/*.so:${CMAKE_INSTALL_PREFIX}/share/ucode/*.uc:./*.so:./*.uc" CACHE STRING "Default library search path") + STRING(REPLACE ":" "\", \"" LIB_SEARCH_DEFINE "${LIB_SEARCH_PATH}") +@@ -143,6 +144,12 @@ IF(NL80211_SUPPORT) + TARGET_LINK_LIBRARIES(nl80211_lib ${nl}) + ENDIF() + ++IF(OS_SUPPORT) ++ SET(LIBRARIES ${LIBRARIES} os_lib) ++ ADD_LIBRARY(os_lib MODULE lib/os.c) ++ SET_TARGET_PROPERTIES(os_lib PROPERTIES OUTPUT_NAME os PREFIX "") ++ENDIF() ++ + IF(RESOLV_SUPPORT) + SET(LIBRARIES ${LIBRARIES} resolv_lib) + ADD_LIBRARY(resolv_lib MODULE lib/resolv.c) +diff --git a/lib/os.c b/lib/os.c +new file mode 100644 +index 0000000..cebad15 +--- /dev/null ++++ b/lib/os.c +@@ -0,0 +1,167 @@ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++ ++#include "ucode/module.h" ++ ++static regex_t pat_vmdata, pat_vmstk; ++static struct sysinfo info; ++static long systick; ++ ++static void __attribute__((constructor)) ++measure_init() ++{ ++ regcomp(&pat_vmdata, "VmData:[ \t]*([0-9]*) kB", REG_EXTENDED); ++ regcomp(&pat_vmstk, "VmStk:[ \t]*([0-9]*) kB", REG_EXTENDED); ++ sysinfo(&info); ++ systick = sysconf(_SC_CLK_TCK); ++} ++ ++static void __attribute__((destructor)) ++measure_fini() ++{ ++ regfree(&pat_vmdata); ++ regfree(&pat_vmstk); ++} ++ ++static char * ++strnchr(char *buf, int c, int i) ++{ ++ while (*buf && i) { ++ buf = strchr(buf, c); ++ buf++; ++ i--; ++ } ++ return buf; ++} ++ ++static int ++measure_process(uc_value_t *obj, pid_t pid) ++{ ++ int fd; ++ char buffer[512] = ""; ++ ssize_t rxed; ++ regmatch_t matches[2]; ++ glob_t gl; ++ size_t i; ++ char *ch; ++ ++ uint32_t fdcount = 0; ++ uint32_t mem = 0; ++ ++ snprintf(buffer, sizeof(buffer), "/proc/%i/fd/*", (int)pid); ++ ++ if (glob(buffer, GLOB_NOESCAPE | GLOB_MARK, NULL, &gl)) ++ return -1; ++ ++ for (i = 0; i < gl.gl_pathc; i++) ++ if (isdigit(basename(gl.gl_pathv[i])[0])) ++ fdcount = fdcount + 1; ++ globfree(&gl); ++ ucv_object_add(obj, "fd", ucv_int64_new(fdcount)); ++ ++ snprintf(buffer, sizeof(buffer), "/proc/%i/stat", (int)pid); ++ fd = open(buffer, O_RDONLY); ++ if (fd == -1) ++ return -1; ++ ++ rxed = read(fd, buffer, sizeof(buffer) - 1); ++ close(fd); ++ if (rxed == -1) ++ return -1; ++ ++ buffer[rxed] = 0; ++ ++ ch = strnchr(buffer, ' ', 14); ++ if (ch) ++ ucv_object_add(obj, "load", ucv_int64_new(atoll(ch))); ++ ++ ch = strnchr(buffer, ' ', 21); ++ if (ch) ++ ucv_object_add(obj, "age", ucv_int64_new(info.uptime - atol(ch) / systick)); ++ ++ snprintf(buffer, sizeof(buffer), "/proc/%i/status", (int)pid); ++ fd = open(buffer, O_RDONLY); ++ if (fd == -1) ++ return -1; ++ ++ rxed = read(fd, buffer, sizeof(buffer) - 1); ++ close(fd); ++ if (rxed == -1) ++ return -1; ++ ++ buffer[rxed] = 0; ++ ++ if (!regexec(&pat_vmdata, buffer, 2, matches, 0)) ++ mem += atoi(buffer + matches[1].rm_so) * 1024; ++ ++ if (!regexec(&pat_vmstk, buffer, 2, matches, 0)) ++ mem += atoi(buffer + matches[1].rm_so) * 1024; ++ ++ ucv_object_add(obj, "memory", ucv_int64_new(mem)); ++ ++ return 0; ++} ++ ++static uc_value_t * ++uc_system_hostname(uc_vm_t *vm, size_t nargs) ++{ ++ char buf[255] = {}; ++ ++ if (gethostname(buf, sizeof(buf)) < 0) ++ return NULL; ++ ++ return ucv_string_new(buf); ++} ++ ++static uc_value_t * ++uc_system_loadavg(uc_vm_t *vm, size_t nargs) ++{ ++ uc_value_t *res_obj = NULL; ++ int i; ++ ++ sysinfo(&info); ++ ++ res_obj = ucv_array_new(vm); ++ for (i = 0; i < 3; i++) ++ ucv_array_push(res_obj, ucv_double_new(((double) info.loads[i]) / 65535.0f)); ++ ++ return res_obj; ++} ++ ++static uc_value_t * ++uc_system_process(uc_vm_t *vm, size_t nargs) ++{ ++ uc_value_t *pid = uc_fn_arg(0); ++ uc_value_t *res_obj = NULL; ++ ++ if (ucv_type(pid) != UC_INTEGER) ++ return NULL; ++ ++ res_obj = ucv_object_new(vm); ++ ++ measure_process(res_obj, ucv_uint64_get(pid)); ++ ++ return res_obj; ++} ++ ++static const uc_function_list_t system_fns[] = { ++ { "hostname", uc_system_hostname }, ++ { "loadavg", uc_system_loadavg }, ++ { "process", uc_system_process }, ++}; ++ ++void uc_module_init(uc_vm_t *vm, uc_value_t *scope) ++{ ++ uc_function_list_register(scope, system_fns); ++} +-- +2.25.1 + diff --git a/feeds/ucentral/ucode/patches/100-resolv.patch b/feeds/ucentral/ucode/patches/100-resolv.patch deleted file mode 100644 index ad7da4d6d..000000000 --- a/feeds/ucentral/ucode/patches/100-resolv.patch +++ /dev/null @@ -1,1133 +0,0 @@ -From c9e68bb8f6830b053537b2fc62b78172bd815ff2 Mon Sep 17 00:00:00 2001 -From: Jo-Philipp Wich -Date: Thu, 21 Oct 2021 00:18:55 +0200 -Subject: [PATCH] lib: introduce resolver library - -This adds a simple, UDP-only DNS resolver library mimicking the operation of -the extended busybox nslookup applet. - -Simply querying a domain name will perform A + AAAA resolving by default: - - # ucode -mresolv -Rs 'printf("%.J\n", resolv.query("example.com"))' - { - "example.com": { - "A": [ - "93.184.216.34" - ], - "AAAA": [ - "2606:2800:220:1:248:1893:25c8:1946" - ] - } - } - -Passing IP addresses will automatically perform PTR requests: - - # ucode -mresolv -Rs 'printf("%.J\n", resolv.query("8.8.8.8"))' - { - "8.8.8.8.in-addr.arpa": { - "PTR": [ - "dns.google" - ] - } - } - - # ucode -mresolv -Rs 'printf("%.J\n", resolv.query("2001:4860:4860::8888"))' - { - "8.8.8.8.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.6.8.4.0.6.8.4.1.0.0.2.ip6.arpa": { - "PTR": [ - "dns.google" - ] - } - } - -Additional options for query type and nameserver selection can be passed via -a second optional options dictionary: - - # ucode -mresolv -Rs 'printf("%.J\n", - resolv.query([ "openwrt.org", "example.org", "doesnotexist.tld" ], { - type: [ "A", "AAAA", "MX" ], - nameserver: [ "1.1.1.1", "8.8.4.4" ], - timeout: 5000, - retries: 2, - edns_maxsize: 4096 - }))' - { - "openwrt.org": { - "A": [ - "139.59.209.225" - ], - "MX": [ - [ - 10, - "util-01.infra.openwrt.org" - ] - ], - "AAAA": [ - "2a03:b0c0:3:d0::1af1:1" - ] - }, - "example.org": { - "A": [ - "93.184.216.34" - ], - "AAAA": [ - "2606:2800:220:1:248:1893:25c8:1946" - ], - "MX": [ - [ - 0, - "." - ] - ] - }, - "doesnotexist.tld": { - "rcode": "NXDOMAIN" - } - } - -Signed-off-by: Jo-Philipp Wich ---- - CMakeLists.txt | 15 + - lib/resolv.c | 1000 ++++++++++++++++++++++++++++++++++++++++++++++++ - 2 files changed, 1015 insertions(+) - create mode 100644 lib/resolv.c - -diff --git a/CMakeLists.txt b/CMakeLists.txt -index f993019..e5fb58a 100644 ---- a/CMakeLists.txt -+++ b/CMakeLists.txt -@@ -19,6 +19,7 @@ OPTION(UBUS_SUPPORT "Ubus plugin support" ON) - OPTION(UCI_SUPPORT "UCI plugin support" ON) - OPTION(RTNL_SUPPORT "Route Netlink plugin support" ON) - OPTION(NL80211_SUPPORT "Wireless Netlink plugin support" ON) -+OPTION(RESOLV_SUPPORT "NS resolve plugin support" ON) - - OPTION(LEGACY_SUPPORT "Support deprecated syntax features" ON) - -@@ -136,6 +137,20 @@ IF(NL80211_SUPPORT) - TARGET_LINK_LIBRARIES(nl80211_lib ${nl}) - ENDIF() - -+IF(RESOLV_SUPPORT) -+ SET(LIBRARIES ${LIBRARIES} resolv_lib) -+ ADD_LIBRARY(resolv_lib MODULE lib/resolv.c) -+ SET_TARGET_PROPERTIES(resolv_lib PROPERTIES OUTPUT_NAME resolv PREFIX "") -+ CHECK_FUNCTION_EXISTS(res_mkquery RES_MKQUERY_FUNCTION_EXISTS) -+ CHECK_FUNCTION_EXISTS(clock_gettime CLOCK_GETTIME_FUNCTION_EXISTS) -+ IF (NOT RES_MKQUERY_FUNCTION_EXISTS) -+ TARGET_LINK_LIBRARIES(resolv_lib resolv) -+ ENDIF() -+ IF (NOT CLOCK_GETTIME_FUNCTION_EXISTS) -+ TARGET_LINK_LIBRARIES(resolv_lib rt) -+ ENDIF() -+ENDIF() -+ - IF(UNIT_TESTING) - ENABLE_TESTING() - ADD_DEFINITIONS(-DUNIT_TESTING) -diff --git a/lib/resolv.c b/lib/resolv.c -new file mode 100644 -index 0000000..fe704fa ---- /dev/null -+++ b/lib/resolv.c -@@ -0,0 +1,1000 @@ -+/* -+ * nslookup_lede - musl compatible replacement for busybox nslookup -+ * -+ * Copyright (C) 2017 Jo-Philipp Wich -+ * -+ * Permission to use, copy, modify, and/or distribute this software for any -+ * purpose with or without fee is hereby granted, provided that the above -+ * copyright notice and this permission notice appear in all copies. -+ * -+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#include "ucode/module.h" -+ -+#define for_each_item(arr, item) \ -+ for (uc_value_t *_idx = NULL, *item = (ucv_type(arr) == UC_ARRAY) ? ucv_array_get(arr, 0) : arr; \ -+ (uintptr_t)_idx < (ucv_type(arr) == UC_ARRAY ? ucv_array_length(arr) : (arr != NULL)); \ -+ _idx = (void *)((uintptr_t)_idx + 1), item = ucv_array_get(arr, (uintptr_t)_idx)) -+ -+#define err_return(code, ...) do { set_error(code, __VA_ARGS__); return NULL; } while(0) -+ -+static struct { -+ int code; -+ char *msg; -+} last_error; -+ -+__attribute__((format(printf, 2, 3))) static void -+set_error(int errcode, const char *fmt, ...) { -+ va_list ap; -+ -+ free(last_error.msg); -+ -+ last_error.code = errcode; -+ last_error.msg = NULL; -+ -+ if (fmt) { -+ va_start(ap, fmt); -+ vasprintf(&last_error.msg, fmt, ap); -+ va_end(ap); -+ } -+} -+ -+typedef struct { -+ socklen_t len; -+ union { -+ struct sockaddr sa; -+ struct sockaddr_in sin; -+ struct sockaddr_in6 sin6; -+ } u; -+} addr_t; -+ -+typedef struct { -+ const char *name; -+ addr_t addr; -+} ns_t; -+ -+typedef struct { -+ char *name; -+ size_t qlen, rlen; -+ unsigned char query[512]; -+ int rcode; -+} query_t; -+ -+typedef struct __attribute__((packed)) { -+ uint8_t root_domain; -+ uint16_t type; -+ uint16_t edns_maxsize; -+ uint8_t extended_rcode; -+ uint8_t edns_version; -+ uint16_t z; -+ uint16_t data_length; -+} opt_rr_t; -+ -+typedef struct { -+ uint32_t qtypes; -+ size_t n_ns; -+ ns_t *ns; -+ size_t n_queries; -+ query_t *queries; -+ uint32_t retries; -+ uint32_t timeout; -+ uint16_t edns_maxsize; -+} resolve_ctx_t; -+ -+ -+static struct { -+ int type; -+ const char *name; -+} qtypes[] = { -+ { ns_t_soa, "SOA" }, -+ { ns_t_ns, "NS" }, -+ { ns_t_a, "A" }, -+ { ns_t_aaaa, "AAAA" }, -+ { ns_t_cname, "CNAME" }, -+ { ns_t_mx, "MX" }, -+ { ns_t_txt, "TXT" }, -+ { ns_t_srv, "SRV" }, -+ { ns_t_ptr, "PTR" }, -+ { ns_t_any, "ANY" }, -+ { } -+}; -+ -+static const char *rcodes[] = { -+ "NOERROR", -+ "FORMERR", -+ "SERVFAIL", -+ "NXDOMAIN", -+ "NOTIMP", -+ "REFUSED", -+ "YXDOMAIN", -+ "YXRRSET", -+ "NXRRSET", -+ "NOTAUTH", -+ "NOTZONE", -+ "RESERVED11", -+ "RESERVED12", -+ "RESERVED13", -+ "RESERVED14", -+ "RESERVED15", -+ "BADVERS" -+}; -+ -+static unsigned int default_port = 53; -+ -+ -+static uc_value_t * -+init_obj(uc_vm_t *vm, uc_value_t *obj, const char *key, uc_type_t type) -+{ -+ uc_value_t *existing; -+ -+ existing = ucv_object_get(obj, key, NULL); -+ -+ if (existing == NULL) { -+ switch (type) { -+ case UC_ARRAY: -+ existing = ucv_array_new(vm); -+ break; -+ -+ case UC_OBJECT: -+ existing = ucv_object_new(vm); -+ break; -+ -+ default: -+ return NULL; -+ } -+ -+ ucv_object_add(obj, key, existing); -+ } -+ -+ return existing; -+} -+ -+static int -+parse_reply(uc_vm_t *vm, uc_value_t *res_obj, const unsigned char *msg, size_t len) -+{ -+ ns_msg handle; -+ ns_rr rr; -+ int i, n, rdlen; -+ const char *key = NULL; -+ char astr[INET6_ADDRSTRLEN], dname[MAXDNAME]; -+ const unsigned char *cp; -+ uc_value_t *name_obj, *type_arr, *item; -+ -+ if (ns_initparse(msg, len, &handle) != 0) { -+ set_error(errno, "Unable to parse reply packet"); -+ -+ return -1; -+ } -+ -+ for (i = 0; i < ns_msg_count(handle, ns_s_an); i++) { -+ if (ns_parserr(&handle, ns_s_an, i, &rr) != 0) { -+ set_error(errno, "Unable to parse resource record"); -+ -+ return -1; -+ } -+ -+ name_obj = init_obj(vm, res_obj, ns_rr_name(rr), UC_OBJECT); -+ -+ rdlen = ns_rr_rdlen(rr); -+ -+ switch (ns_rr_type(rr)) -+ { -+ case ns_t_a: -+ if (rdlen != 4) { -+ set_error(EBADMSG, "Invalid A record length"); -+ -+ return -1; -+ } -+ -+ type_arr = init_obj(vm, name_obj, "A", UC_ARRAY); -+ -+ inet_ntop(AF_INET, ns_rr_rdata(rr), astr, sizeof(astr)); -+ ucv_array_push(type_arr, ucv_string_new(astr)); -+ break; -+ -+ case ns_t_aaaa: -+ if (rdlen != 16) { -+ set_error(EBADMSG, "Invalid AAAA record length"); -+ -+ return -1; -+ } -+ -+ type_arr = init_obj(vm, name_obj, "AAAA", UC_ARRAY); -+ -+ inet_ntop(AF_INET6, ns_rr_rdata(rr), astr, sizeof(astr)); -+ ucv_array_push(type_arr, ucv_string_new(astr)); -+ break; -+ -+ case ns_t_ns: -+ if (!key) -+ key = "NS"; -+ /* fall through */ -+ -+ case ns_t_cname: -+ if (!key) -+ key = "CNAME"; -+ /* fall through */ -+ -+ case ns_t_ptr: -+ if (!key) -+ key = "PTR"; -+ -+ if (ns_name_uncompress(ns_msg_base(handle), ns_msg_end(handle), -+ ns_rr_rdata(rr), dname, sizeof(dname)) < 0) { -+ set_error(errno, "Unable to uncompress domain name"); -+ -+ return -1; -+ } -+ -+ type_arr = init_obj(vm, name_obj, key, UC_ARRAY); -+ n = ucv_array_length(type_arr); -+ item = n ? ucv_array_get(type_arr, n - 1) : NULL; -+ -+ if (!n || strcmp(ucv_string_get(item), dname)) -+ ucv_array_push(type_arr, ucv_string_new(dname)); -+ -+ break; -+ -+ case ns_t_mx: -+ if (rdlen < 2) { -+ set_error(EBADMSG, "MX record too short"); -+ -+ return -1; -+ } -+ -+ n = ns_get16(ns_rr_rdata(rr)); -+ -+ if (ns_name_uncompress(ns_msg_base(handle), ns_msg_end(handle), -+ ns_rr_rdata(rr) + 2, dname, sizeof(dname)) < 0) { -+ set_error(errno, "Unable to uncompress MX domain"); -+ -+ return -1; -+ } -+ -+ type_arr = init_obj(vm, name_obj, "MX", UC_ARRAY); -+ item = ucv_array_new_length(vm, 2); -+ ucv_array_push(item, ucv_int64_new(n)); -+ ucv_array_push(item, ucv_string_new(dname)); -+ ucv_array_push(type_arr, item); -+ break; -+ -+ case ns_t_txt: -+ if (rdlen < 1) { -+ set_error(EBADMSG, "TXT record too short"); -+ -+ return -1; -+ } -+ -+ n = *(unsigned char *)ns_rr_rdata(rr); -+ -+ if (n > 0) { -+ memset(dname, 0, sizeof(dname)); -+ memcpy(dname, ns_rr_rdata(rr) + 1, n); -+ -+ type_arr = init_obj(vm, name_obj, "TXT", UC_ARRAY); -+ ucv_array_push(type_arr, ucv_string_new(dname)); -+ } -+ break; -+ -+ case ns_t_srv: -+ if (rdlen < 6) { -+ set_error(EBADMSG, "SRV record too short"); -+ -+ return -1; -+ } -+ -+ cp = ns_rr_rdata(rr); -+ n = ns_name_uncompress(ns_msg_base(handle), ns_msg_end(handle), -+ cp + 6, dname, sizeof(dname)); -+ -+ if (n < 0) { -+ set_error(errno, "Unable to uncompress domain name"); -+ -+ return -1; -+ } -+ -+ type_arr = init_obj(vm, name_obj, "SRV", UC_ARRAY); -+ item = ucv_array_new_length(vm, 4); -+ ucv_array_push(item, ucv_int64_new(ns_get16(cp))); -+ ucv_array_push(item, ucv_int64_new(ns_get16(cp + 2))); -+ ucv_array_push(item, ucv_int64_new(ns_get16(cp + 4))); -+ ucv_array_push(item, ucv_string_new(dname)); -+ ucv_array_push(type_arr, item); -+ break; -+ -+ case ns_t_soa: -+ if (rdlen < 20) { -+ set_error(EBADMSG, "SOA record too short"); -+ -+ return -1; -+ } -+ -+ type_arr = init_obj(vm, name_obj, "SOA", UC_ARRAY); -+ item = ucv_array_new_length(vm, 7); -+ -+ cp = ns_rr_rdata(rr); -+ n = ns_name_uncompress(ns_msg_base(handle), ns_msg_end(handle), -+ cp, dname, sizeof(dname)); -+ -+ if (n < 0) { -+ set_error(errno, "Unable to uncompress domain name"); -+ ucv_put(item); -+ -+ return -1; -+ } -+ -+ ucv_array_push(item, ucv_string_new(dname)); /* origin */ -+ cp += n; -+ -+ n = ns_name_uncompress(ns_msg_base(handle), ns_msg_end(handle), -+ cp, dname, sizeof(dname)); -+ -+ if (n < 0) { -+ set_error(errno, "Unable to uncompress domain name"); -+ ucv_put(item); -+ -+ return -1; -+ } -+ -+ ucv_array_push(item, ucv_string_new(dname)); /* mail addr */ -+ cp += n; -+ -+ ucv_array_push(item, ucv_int64_new(ns_get32(cp))); /* serial */ -+ cp += 4; -+ -+ ucv_array_push(item, ucv_int64_new(ns_get32(cp))); /* refresh */ -+ cp += 4; -+ -+ ucv_array_push(item, ucv_int64_new(ns_get32(cp))); /* retry */ -+ cp += 4; -+ -+ ucv_array_push(item, ucv_int64_new(ns_get32(cp))); /* expire */ -+ cp += 4; -+ -+ ucv_array_push(item, ucv_int64_new(ns_get32(cp))); /* minimum */ -+ -+ ucv_array_push(type_arr, item); -+ break; -+ -+ default: -+ break; -+ } -+ } -+ -+ return i; -+} -+ -+static int -+parse_nsaddr(const char *addrstr, addr_t *lsa) -+{ -+ char *eptr, *hash, ifname[IFNAMSIZ]; -+ unsigned int port = default_port; -+ unsigned int scope = 0; -+ -+ hash = strchr(addrstr, '#'); -+ -+ if (hash) { -+ *hash++ = '\0'; -+ port = strtoul(hash, &eptr, 10); -+ -+ if (eptr == hash || *eptr != '\0' || port > 65535) { -+ errno = EINVAL; -+ return -1; -+ } -+ } -+ -+ hash = strchr(addrstr, '%'); -+ -+ if (hash) { -+ for (eptr = ++hash; *eptr != '\0' && *eptr != '#'; eptr++) { -+ if ((eptr - hash) >= IFNAMSIZ) { -+ errno = ENODEV; -+ return -1; -+ } -+ -+ ifname[eptr - hash] = *eptr; -+ } -+ -+ ifname[eptr - hash] = '\0'; -+ scope = if_nametoindex(ifname); -+ -+ if (scope == 0) { -+ errno = ENODEV; -+ return -1; -+ } -+ } -+ -+ if (inet_pton(AF_INET6, addrstr, &lsa->u.sin6.sin6_addr)) { -+ lsa->u.sin6.sin6_family = AF_INET6; -+ lsa->u.sin6.sin6_port = htons(port); -+ lsa->u.sin6.sin6_scope_id = scope; -+ lsa->len = sizeof(lsa->u.sin6); -+ return 0; -+ } -+ -+ if (!scope && inet_pton(AF_INET, addrstr, &lsa->u.sin.sin_addr)) { -+ lsa->u.sin.sin_family = AF_INET; -+ lsa->u.sin.sin_port = htons(port); -+ lsa->len = sizeof(lsa->u.sin); -+ return 0; -+ } -+ -+ errno = EINVAL; -+ return -1; -+} -+ -+static char * -+make_ptr(const char *addrstr) -+{ -+ const char *hexdigit = "0123456789abcdef"; -+ static char ptrstr[73]; -+ unsigned char addr[16]; -+ char *ptr = ptrstr; -+ int i; -+ -+ if (inet_pton(AF_INET6, addrstr, addr)) { -+ if (memcmp(addr, "\0\0\0\0\0\0\0\0\0\0\xff\xff", 12) != 0) { -+ for (i = 0; i < 16; i++) { -+ *ptr++ = hexdigit[(unsigned char)addr[15 - i] & 0xf]; -+ *ptr++ = '.'; -+ *ptr++ = hexdigit[(unsigned char)addr[15 - i] >> 4]; -+ *ptr++ = '.'; -+ } -+ strcpy(ptr, "ip6.arpa"); -+ } -+ else { -+ sprintf(ptr, "%u.%u.%u.%u.in-addr.arpa", -+ addr[15], addr[14], addr[13], addr[12]); -+ } -+ -+ return ptrstr; -+ } -+ -+ if (inet_pton(AF_INET, addrstr, addr)) { -+ sprintf(ptr, "%u.%u.%u.%u.in-addr.arpa", -+ addr[3], addr[2], addr[1], addr[0]); -+ return ptrstr; -+ } -+ -+ return NULL; -+} -+ -+static unsigned long -+mtime(void) -+{ -+ struct timespec ts; -+ clock_gettime(CLOCK_REALTIME, &ts); -+ -+ return (unsigned long)ts.tv_sec * 1000 + ts.tv_nsec / 1000000; -+} -+ -+static void -+to_v4_mapped(addr_t *a) -+{ -+ if (a->u.sa.sa_family != AF_INET) -+ return; -+ -+ memcpy(a->u.sin6.sin6_addr.s6_addr + 12, -+ &a->u.sin.sin_addr, 4); -+ -+ memcpy(a->u.sin6.sin6_addr.s6_addr, -+ "\0\0\0\0\0\0\0\0\0\0\xff\xff", 12); -+ -+ a->u.sin6.sin6_family = AF_INET6; -+ a->u.sin6.sin6_flowinfo = 0; -+ a->u.sin6.sin6_scope_id = 0; -+ a->len = sizeof(a->u.sin6); -+} -+ -+static void -+add_status(uc_vm_t *vm, uc_value_t *res_obj, const char *name, const char *rcode) -+{ -+ uc_value_t *name_obj = init_obj(vm, res_obj, name, UC_OBJECT); -+ -+ ucv_object_add(name_obj, "rcode", ucv_string_new(rcode)); -+} -+ -+/* -+ * Function logic borrowed & modified from musl libc, res_msend.c -+ */ -+ -+static int -+send_queries(resolve_ctx_t *ctx, uc_vm_t *vm, uc_value_t *res_obj) -+{ -+ int fd; -+ int servfail_retry = 0; -+ addr_t from = { }; -+ int one = 1; -+ int recvlen = 0; -+ int n_replies = 0; -+ struct pollfd pfd; -+ unsigned long t0, t1, t2, timeout = ctx->timeout, retry_interval; -+ unsigned int nn, qn, next_query = 0; -+ struct { unsigned char *buf; size_t len; } reply_buf = { 0 }; -+ -+ from.u.sa.sa_family = AF_INET; -+ from.len = sizeof(from.u.sin); -+ -+ for (nn = 0; nn < ctx->n_ns; nn++) { -+ if (ctx->ns[nn].addr.u.sa.sa_family == AF_INET6) { -+ from.u.sa.sa_family = AF_INET6; -+ from.len = sizeof(from.u.sin6); -+ break; -+ } -+ } -+ -+ /* Get local address and open/bind a socket */ -+ fd = socket(from.u.sa.sa_family, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); -+ -+ /* Handle case where system lacks IPv6 support */ -+ if (fd < 0 && from.u.sa.sa_family == AF_INET6 && errno == EAFNOSUPPORT) { -+ fd = socket(AF_INET, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); -+ from.u.sa.sa_family = AF_INET; -+ } -+ -+ if (fd < 0) { -+ set_error(errno, "Unable to open UDP socket"); -+ -+ return -1; -+ } -+ -+ if (bind(fd, &from.u.sa, from.len) < 0) { -+ set_error(errno, "Unable to bind UDP socket"); -+ close(fd); -+ -+ return -1; -+ } -+ -+ /* Convert any IPv4 addresses in a mixed environment to v4-mapped */ -+ if (from.u.sa.sa_family == AF_INET6) { -+ setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &one, sizeof(one)); -+ -+ for (nn = 0; nn < ctx->n_ns; nn++) -+ to_v4_mapped(&ctx->ns[nn].addr); -+ } -+ -+ pfd.fd = fd; -+ pfd.events = POLLIN; -+ retry_interval = timeout / ctx->retries; -+ t0 = t2 = mtime(); -+ t1 = t2 - retry_interval; -+ -+ for (; t2 - t0 < timeout; t2 = mtime()) { -+ if (t2 - t1 >= retry_interval) { -+ for (qn = 0; qn < ctx->n_queries; qn++) { -+ if (ctx->queries[qn].rcode == 0 || ctx->queries[qn].rcode == 3) -+ continue; -+ -+ for (nn = 0; nn < ctx->n_ns; nn++) { -+ sendto(fd, ctx->queries[qn].query, ctx->queries[qn].qlen, -+ MSG_NOSIGNAL, &ctx->ns[nn].addr.u.sa, ctx->ns[nn].addr.len); -+ } -+ } -+ -+ t1 = t2; -+ servfail_retry = 2 * ctx->n_queries; -+ } -+ -+ /* Wait for a response, or until time to retry */ -+ switch (poll(&pfd, 1, t1+retry_interval-t2)) { -+ case 0: -+ /* timeout */ -+ for (qn = 0; qn < ctx->n_queries; qn++) { -+ if (ctx->queries[qn].rcode != -1) -+ continue; -+ -+ for (nn = 0; nn < ctx->n_ns; nn++) -+ add_status(vm, res_obj, ctx->queries[qn].name, "TIMEOUT"); -+ } -+ -+ continue; -+ -+ case -1: -+ /* error */ -+ continue; -+ } -+ -+ while (1) { -+ recvlen = recvfrom(fd, NULL, 0, MSG_PEEK|MSG_TRUNC, &from.u.sa, &from.len); -+ -+ /* read error */ -+ if (recvlen < 0) -+ break; -+ -+ if ((size_t)recvlen > reply_buf.len) { -+ reply_buf.buf = xrealloc(reply_buf.buf, recvlen); -+ reply_buf.len = recvlen; -+ } -+ -+ recvlen = recvfrom(fd, reply_buf.buf, recvlen, 0, &from.u.sa, &from.len); -+ -+ /* Ignore non-identifiable packets */ -+ if (recvlen < 4) -+ continue; -+ -+ /* Ignore replies from addresses we didn't send to */ -+ for (nn = 0; nn < ctx->n_ns; nn++) -+ if (memcmp(&from.u.sa, &ctx->ns[nn].addr.u.sa, from.len) == 0) -+ break; -+ -+ if (nn >= ctx->n_ns) -+ continue; -+ -+ /* Find which query this answer goes with, if any */ -+ for (qn = next_query; qn < ctx->n_queries; qn++) -+ if (!memcmp(reply_buf.buf, ctx->queries[qn].query, 2)) -+ break; -+ -+ /* Do not overwrite previous replies from other servers -+ * but allow overwriting preexisting NXDOMAIN reply */ -+ if (qn >= ctx->n_queries || -+ ctx->queries[qn].rcode == 0 || -+ (ctx->queries[qn].rcode == 3 && (reply_buf.buf[3] & 15) != 0)) -+ continue; -+ -+ ctx->queries[qn].rcode = reply_buf.buf[3] & 15; -+ -+ switch (ctx->queries[qn].rcode) { -+ case 0: -+ ucv_object_delete( -+ ucv_object_get(res_obj, ctx->queries[qn].name, NULL), -+ "rcodes"); -+ -+ break; -+ -+ case 2: -+ /* Retry immediately on server failure. */ -+ if (servfail_retry && servfail_retry--) -+ sendto(fd, ctx->queries[qn].query, ctx->queries[qn].qlen, -+ MSG_NOSIGNAL, &ctx->ns[nn].addr.u.sa, ctx->ns[nn].addr.len); -+ -+ /* fall through */ -+ -+ default: -+ add_status(vm, res_obj, ctx->queries[qn].name, -+ rcodes[ctx->queries[qn].rcode]); -+ } -+ -+ /* Store answer */ -+ n_replies++; -+ -+ ctx->queries[qn].rlen = recvlen; -+ -+ parse_reply(vm, res_obj, reply_buf.buf, recvlen); -+ -+ if (qn == next_query) { -+ while (next_query < ctx->n_queries) { -+ if (ctx->queries[next_query].rcode == -1) -+ break; -+ -+ next_query++; -+ } -+ } -+ -+ if (next_query >= ctx->n_queries) -+ goto out; -+ } -+ } -+ -+out: -+ free(reply_buf.buf); -+ -+ return n_replies; -+} -+ -+static ns_t * -+add_ns(resolve_ctx_t *ctx, const char *addr) -+{ -+ char portstr[sizeof("65535")], *p; -+ addr_t a = { }; -+ struct addrinfo *ai, *aip, hints = { -+ .ai_flags = AI_NUMERICSERV, -+ .ai_socktype = SOCK_DGRAM -+ }; -+ -+ if (parse_nsaddr(addr, &a)) { -+ /* Maybe we got a domain name, attempt to resolve it using the standard -+ * resolver routines */ -+ -+ p = strchr(addr, '#'); -+ snprintf(portstr, sizeof(portstr), "%hu", -+ (unsigned short)(p ? strtoul(p, NULL, 10) : default_port)); -+ -+ if (!getaddrinfo(addr, portstr, &hints, &ai)) { -+ for (aip = ai; aip; aip = aip->ai_next) { -+ if (aip->ai_addr->sa_family != AF_INET && -+ aip->ai_addr->sa_family != AF_INET6) -+ continue; -+ -+ ctx->ns = xrealloc(ctx->ns, sizeof(*ctx->ns) * (ctx->n_ns + 1)); -+ ctx->ns[ctx->n_ns].name = addr; -+ ctx->ns[ctx->n_ns].addr.len = aip->ai_addrlen; -+ -+ memcpy(&ctx->ns[ctx->n_ns].addr.u.sa, aip->ai_addr, aip->ai_addrlen); -+ -+ ctx->n_ns++; -+ } -+ -+ freeaddrinfo(ai); -+ -+ return &ctx->ns[ctx->n_ns]; -+ } -+ -+ return NULL; -+ } -+ -+ ctx->ns = xrealloc(ctx->ns, sizeof(*ctx->ns) * (ctx->n_ns + 1)); -+ ctx->ns[ctx->n_ns].addr = a; -+ ctx->ns[ctx->n_ns].name = addr; -+ -+ return &ctx->ns[ctx->n_ns++]; -+} -+ -+static int -+parse_resolvconf(resolve_ctx_t *ctx) -+{ -+ int prev_n_ns = ctx->n_ns; -+ char line[128], *p; -+ FILE *resolv; -+ bool ok; -+ -+ if ((resolv = fopen("/etc/resolv.conf", "r")) != NULL) { -+ while (fgets(line, sizeof(line), resolv)) { -+ p = strtok(line, " \t\n"); -+ -+ if (!p || strcmp(p, "nameserver")) -+ continue; -+ -+ p = strtok(NULL, " \t\n"); -+ -+ if (!p) -+ continue; -+ -+ p = xstrdup(p); -+ ok = add_ns(ctx, p); -+ -+ free(p); -+ -+ if (!ok) -+ break; -+ } -+ -+ fclose(resolv); -+ } -+ -+ return ctx->n_ns - prev_n_ns; -+} -+ -+static query_t * -+add_query(resolve_ctx_t *ctx, int type, const char *dname) -+{ -+ opt_rr_t *opt; -+ ssize_t qlen; -+ -+ ctx->queries = xrealloc(ctx->queries, sizeof(*ctx->queries) * (ctx->n_queries + 1)); -+ -+ memset(&ctx->queries[ctx->n_queries], 0, sizeof(*ctx->queries)); -+ -+ qlen = res_mkquery(QUERY, dname, C_IN, type, NULL, 0, NULL, -+ ctx->queries[ctx->n_queries].query, -+ sizeof(ctx->queries[ctx->n_queries].query)); -+ -+ /* add OPT record */ -+ if (ctx->edns_maxsize != 0 && qlen + sizeof(opt_rr_t) <= sizeof(ctx->queries[ctx->n_queries].query)) { -+ ctx->queries[ctx->n_queries].query[11] = 1; -+ -+ opt = (opt_rr_t *)&ctx->queries[ctx->n_queries].query[qlen]; -+ opt->root_domain = 0; -+ opt->type = htons(41); -+ opt->edns_maxsize = htons(ctx->edns_maxsize); -+ opt->extended_rcode = 0; -+ opt->edns_version = 0; -+ opt->z = htons(0); -+ opt->data_length = htons(0); -+ -+ qlen += sizeof(opt_rr_t); -+ } -+ -+ ctx->queries[ctx->n_queries].qlen = qlen; -+ ctx->queries[ctx->n_queries].name = xstrdup(dname); -+ ctx->queries[ctx->n_queries].rcode = -1; -+ -+ return &ctx->queries[ctx->n_queries++]; -+} -+ -+static bool -+check_types(uc_value_t *typenames, uint32_t *types) -+{ -+ size_t i; -+ -+ *types = 0; -+ -+ for_each_item(typenames, typename) { -+ if (ucv_type(typename) != UC_STRING) -+ err_return(EINVAL, "Query type value not a string"); -+ -+ for (i = 0; qtypes[i].name; i++) { -+ if (!strcasecmp(ucv_string_get(typename), qtypes[i].name)) { -+ *types |= (1 << i); -+ break; -+ } -+ } -+ -+ if (!qtypes[i].name) -+ err_return(EINVAL, "Unrecognized query type '%s'", -+ ucv_string_get(typename)); -+ } -+ -+ return true; -+} -+ -+static void -+add_queries(resolve_ctx_t *ctx, uc_value_t *name) -+{ -+ char *s = ucv_string_get(name); -+ char *ptr; -+ size_t i; -+ -+ if (ctx->qtypes == 0) { -+ ptr = make_ptr(s); -+ -+ if (ptr) { -+ add_query(ctx, ns_t_ptr, ptr); -+ } -+ else { -+ add_query(ctx, ns_t_a, s); -+ add_query(ctx, ns_t_aaaa, s); -+ } -+ } -+ else { -+ for (i = 0; qtypes[i].name; i++) { -+ if (ctx->qtypes & (1 << i)) { -+ if (qtypes[i].type == ns_t_ptr) { -+ ptr = make_ptr(s); -+ add_query(ctx, ns_t_ptr, ptr ? ptr : s); -+ } -+ else { -+ add_query(ctx, qtypes[i].type, s); -+ } -+ } -+ } -+ } -+} -+ -+static bool -+parse_options(resolve_ctx_t *ctx, uc_value_t *opts) -+{ -+ uc_value_t *v; -+ -+ if (!check_types(ucv_object_get(opts, "type", NULL), &ctx->qtypes)) -+ return false; -+ -+ for_each_item(ucv_object_get(opts, "nameserver", NULL), server) { -+ if (ucv_type(server) != UC_STRING) -+ err_return(EINVAL, "Nameserver value not a string"); -+ -+ if (!add_ns(ctx, ucv_string_get(server))) -+ err_return(EINVAL, "Unable to resolve nameserver address '%s'", -+ ucv_string_get(server)); -+ } -+ -+ /* Find NS servers in resolv.conf if none provided */ -+ if (ctx->n_ns == 0) -+ parse_resolvconf(ctx); -+ -+ /* Fall back to localhost if we could not find NS in resolv.conf */ -+ if (ctx->n_ns == 0) -+ add_ns(ctx, "127.0.0.1"); -+ -+ v = ucv_object_get(opts, "retries", NULL); -+ -+ if (ucv_type(v) == UC_INTEGER) -+ ctx->retries = ucv_uint64_get(v); -+ else if (v) -+ err_return(EINVAL, "Retries value not an integer"); -+ -+ v = ucv_object_get(opts, "timeout", NULL); -+ -+ if (ucv_type(v) == UC_INTEGER) -+ ctx->timeout = ucv_uint64_get(v); -+ else if (v) -+ err_return(EINVAL, "Timeout value not an integer"); -+ -+ v = ucv_object_get(opts, "edns_maxsize", NULL); -+ -+ if (ucv_type(v) == UC_INTEGER) -+ ctx->edns_maxsize = ucv_uint64_get(v); -+ else if (v) -+ err_return(EINVAL, "EDNS max size not an integer"); -+ -+ return true; -+} -+ -+static uc_value_t * -+uc_resolv_query(uc_vm_t *vm, size_t nargs) -+{ -+ resolve_ctx_t ctx = { .retries = 2, .timeout = 5000, .edns_maxsize = 4096 }; -+ uc_value_t *names = uc_fn_arg(0); -+ uc_value_t *opts = uc_fn_arg(1); -+ uc_value_t *res_obj = NULL; -+ -+ if (!parse_options(&ctx, opts)) -+ goto err; -+ -+ for_each_item(names, name) { -+ if (ucv_type(name) != UC_STRING) { -+ set_error(EINVAL, "Domain name value not a string"); -+ goto err; -+ } -+ -+ add_queries(&ctx, name); -+ } -+ -+ res_obj = ucv_object_new(vm); -+ -+ if (send_queries(&ctx, vm, res_obj) == 0) -+ set_error(ETIMEDOUT, "Server did not respond"); -+ -+err: -+ while (ctx.n_queries) -+ free(ctx.queries[--ctx.n_queries].name); -+ -+ free(ctx.queries); -+ free(ctx.ns); -+ -+ return res_obj; -+} -+ -+static uc_value_t * -+uc_resolv_error(uc_vm_t *vm, size_t nargs) -+{ -+ uc_stringbuf_t *buf; -+ const char *s; -+ -+ if (last_error.code == 0) -+ return NULL; -+ -+ buf = ucv_stringbuf_new(); -+ -+ s = strerror(last_error.code); -+ -+ ucv_stringbuf_addstr(buf, s, strlen(s)); -+ -+ if (last_error.msg) -+ ucv_stringbuf_printf(buf, ": %s", last_error.msg); -+ -+ set_error(0, NULL); -+ -+ return ucv_stringbuf_finish(buf); -+} -+ -+ -+static const uc_function_list_t resolv_fns[] = { -+ { "query", uc_resolv_query }, -+ { "error", uc_resolv_error }, -+}; -+ -+void uc_module_init(uc_vm_t *vm, uc_value_t *scope) -+{ -+ uc_function_list_register(scope, resolv_fns); -+}