From f06bfd51611e6a3d231acedacf8052ef3da10cfa Mon Sep 17 00:00:00 2001 From: Anjali Nijhara Date: Tue, 5 Dec 2023 09:00:23 +0530 Subject: [PATCH] Imported Upstream version 1.42 Change-Id: Ib8320153c783db22181c568b14353d2b9651c1bc --- .gitignore | 1 + AUTHORS | 7 + ChangeLog | 16 + Makefile.am | 17 + Makefile.plugins | 1 + bootstrap-configure | 3 +- configure.ac | 46 +- doc/connman.8.in | 11 +- doc/connman.conf.5.in | 20 +- gdhcp/client.c | 18 +- gsupplicant/supplicant.c | 42 +- gweb/gresolv.c | 1 + gweb/gweb.c | 2 +- include/agent.h | 1 + include/network.h | 1 + include/timeserver.h | 7 + plugins/iwd.c | 189 +++- plugins/ofono.c | 5 + plugins/wifi.c | 106 ++- scripts/libppp-compat.h | 127 +++ scripts/libppp-plugin.c | 15 +- src/agent-connman.c | 14 + src/agent.c | 22 + src/clock.c | 9 +- src/connman.h | 7 +- src/dhcp.c | 3 +- src/dnsproxy.c | 1990 ++++++++++++++++++++++--------------------- src/main.c | 57 +- src/network.c | 35 +- src/ntp.c | 2 +- src/resolver.c | 33 +- src/rtnl.c | 17 +- src/service.c | 75 +- src/timeserver.c | 116 ++- src/timezone.c | 105 ++- src/wispr.c | 218 +++-- tools/dnsproxy-simple-test | 195 +++++ tools/dnsproxy-standalone.c | 155 ++++ tools/dnsproxy-test.c | 11 +- vpn/plugins/vpnc.c | 2 +- vpn/vpn-polkit.policy | 4 +- vpn/vpn-util.c | 4 +- 42 files changed, 2446 insertions(+), 1264 deletions(-) create mode 100644 scripts/libppp-compat.h create mode 100755 tools/dnsproxy-simple-test create mode 100644 tools/dnsproxy-standalone.c diff --git a/.gitignore b/.gitignore index cd7d881..9e8d1a4 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ *.a *.lo *.la +*~ .deps .libs .dirstamp diff --git a/AUTHORS b/AUTHORS index 3ed0985..70cb07c 100644 --- a/AUTHORS +++ b/AUTHORS @@ -176,3 +176,10 @@ Lukáš Karas Michael Nazzareno Trimarchi Christian Taedcke Matthias Gerstner +Sebastian Pipping +Daniel Linjama +Nathan Crandall +Ben Kohler +Polina Smirnova +Eivind Næss +Oskar Roesler diff --git a/ChangeLog b/ChangeLog index 11566a5..d3819e6 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,19 @@ +ver 1.42: + Fix issue with iwd and signal strength calculation. + Fix issue with iwd and handling service removal. + Fix issue with iwd and handling new connections. + Fix issue with handling default online check URL. + Fix issue with handling nameservers refresh. + Fix issue with handling proxy from DHCP lease. + Fix issue with handling multiple proxies from PAC. + Fix issue with handling manual time update changes. + Fix issue with handling invalid gateway routes. + Fix issue with handling hidden WiFi agent requests. + Fix issue with handling WiFi SAE authentication failure. + Fix issue with handling DNS Proxy and TCP server replies. + Add support for regulatory domain following timezone. + Add support for localtime configuration option. + ver 1.41: Fix issue with RTNL netlink message alignment. Fix issue with dnsproxy and timeout for TCP feature. diff --git a/Makefile.am b/Makefile.am index e5718b1..1a3dbe3 100644 --- a/Makefile.am +++ b/Makefile.am @@ -243,6 +243,7 @@ AM_CFLAGS = @DBUS_CFLAGS@ @GLIB_CFLAGS@ \ -DSCRIPTDIR=\""$(build_scriptdir)"\" \ -DSTORAGEDIR=\""$(storagedir)\"" \ -DVPN_STORAGEDIR=\""$(vpn_storagedir)\"" \ + -DRUNSTATEDIR=\""$(runstatedir)"\" \ -DCONFIGDIR=\""$(configdir)\"" if VPN @@ -275,6 +276,7 @@ vpn_connman_vpnd_CFLAGS = @DBUS_CFLAGS@ @GLIB_CFLAGS@ \ -DSCRIPTDIR=\""$(build_scriptdir)"\" \ -DSTORAGEDIR=\""$(storagedir)\"" \ -DVPN_STORAGEDIR=\""$(vpn_storagedir)\"" \ + -DRUNSTATEDIR=\""$(runstatedir)"\" \ -DCONFIGDIR=\""$(configdir)\"" \ -I$(builddir)/vpn @@ -434,6 +436,21 @@ test_scripts += test/vpn-connect test/vpn-disconnect test/vpn-get \ if TEST testdir = $(pkglibdir)/test test_SCRIPTS = $(test_scripts) + +if INTERNAL_DNS_BACKEND +tools_dnsproxy_standalone_CFLAGS = $(src_connmand_CFLAGS) -I$(srcdir)/src -DDNSPROXY_DEBUG +tools_dnsproxy_standalone_SOURCES = tools/dnsproxy-standalone.c $(src_connmand_SOURCES) +# for EXTRA_PROGRAMS the BUILT_SOURCES aren't automatically added as +# dependency, so let's do it explicitly +tools/dnsproxy-standalone.c: $(BUILT_SOURCES) +tools_dnsproxy_standalone_LDADD = $(src_connmand_LDADD) +# pass -zmuldefs to let the linker tolerate the duplicate definition of +# main(), the first definition from dnsproxy-standalone should be used +tools_dnsproxy_standalone_LDFLAGS = $(src_connmand_LDFLAGS) -Wl,-zmuldefs + +noinst_PROGRAMS += tools/dnsproxy-standalone +endif + endif EXTRA_DIST += $(test_scripts) diff --git a/Makefile.plugins b/Makefile.plugins index 8e32361..bd5049e 100644 --- a/Makefile.plugins +++ b/Makefile.plugins @@ -138,6 +138,7 @@ vpn_plugin_objects += $(plugins_vpnc_la_OBJECTS) vpn_plugins_vpnc_la_SOURCES = vpn/plugins/vpnc.c vpn_plugins_vpnc_la_CFLAGS = $(plugin_cflags) -DVPNC=\"@VPNC@\" \ -DVPN_STATEDIR=\""$(vpn_statedir)"\" \ + -DRUNSTATEDIR=\""$(runstatedir)"\" \ -DSCRIPTDIR=\""$(build_scriptdir)"\" vpn_plugins_vpnc_la_LDFLAGS = $(plugin_ldflags) endif diff --git a/bootstrap-configure b/bootstrap-configure index 3f69798..ed5ecd7 100755 --- a/bootstrap-configure +++ b/bootstrap-configure @@ -18,4 +18,5 @@ fi --enable-vpnc=builtin \ --enable-session-policy-local=builtin \ --enable-nmcompat \ - --enable-polkit $* + --enable-polkit $* \ + --enable-test diff --git a/configure.ac b/configure.ac index a573cef..f224bcc 100644 --- a/configure.ac +++ b/configure.ac @@ -1,5 +1,5 @@ AC_PREREQ(2.60) -AC_INIT(connman, 1.41) +AC_INIT(connman, 1.42) AC_CONFIG_MACRO_DIR([m4]) @@ -22,7 +22,7 @@ AC_SUBST(abs_top_builddir) AC_LANG_C AC_USE_SYSTEM_EXTENSIONS -AC_PROG_CC +AC_PROG_CC_C99 AM_PROG_CC_C_O AC_PROG_CC_PIE AC_PROG_INSTALL @@ -135,14 +135,6 @@ AC_ARG_ENABLE(l2tp, AC_HELP_STRING([--enable-l2tp], [enable l2tp support]), [enable_l2tp=${enableval}], [enable_l2tp="no"]) if (test "${enable_l2tp}" != "no"); then - if (test -z "${path_pppd}"); then - AC_PATH_PROG(PPPD, [pppd], [/usr/sbin/pppd], $PATH:/sbin:/usr/sbin) - else - PPPD="${path_pppd}" - AC_SUBST(PPPD) - fi - AC_CHECK_HEADERS(pppd/pppd.h, dummy=yes, - AC_MSG_ERROR(ppp header files are required)) if (test -z "${path_l2tp}"); then AC_PATH_PROG(L2TP, [xl2tpd], [/usr/sbin/xl2tpd], $PATH:/sbin:/usr/sbin) else @@ -160,6 +152,18 @@ AC_ARG_ENABLE(pptp, AC_HELP_STRING([--enable-pptp], [enable pptp support]), [enable_pptp=${enableval}], [enable_pptp="no"]) if (test "${enable_pptp}" != "no"); then + if (test -z "${path_pptp}"); then + AC_PATH_PROG(PPTP, [pptp], [/usr/sbin/pptp], $PATH:/sbin:/usr/sbin) + else + PPTP="${path_pptp}" + AC_SUBST(PPTP) + fi +fi +AM_CONDITIONAL(PPTP, test "${enable_pptp}" != "no") +AM_CONDITIONAL(PPTP_BUILTIN, test "${enable_pptp}" = "builtin") + +if (test "${enable_pptp}" != "no" || test "${enable_l2tp}" != "no"); then + if (test -z "${path_pppd}"); then AC_PATH_PROG(PPPD, [pppd], [/usr/sbin/pppd], $PATH:/sbin:/usr/sbin) else @@ -168,15 +172,23 @@ if (test "${enable_pptp}" != "no"); then fi AC_CHECK_HEADERS(pppd/pppd.h, dummy=yes, AC_MSG_ERROR(ppp header files are required)) - if (test -z "${path_pptp}"); then - AC_PATH_PROG(PPTP, [pptp], [/usr/sbin/pptp], $PATH:/sbin:/usr/sbin) - else - PPTP="${path_pptp}" - AC_SUBST(PPTP) + AC_CHECK_HEADERS([pppd/chap.h pppd/chap-new.h pppd/chap_ms.h]) + + PKG_CHECK_EXISTS([pppd], + [AS_VAR_SET([pppd_pkgconfig_support],[yes])]) + + PPPD_VERSION=2.4.9 + if test x"$pppd_pkgconfig_support" = xyes; then + PPPD_VERSION=`$PKG_CONFIG --modversion pppd` fi + + AC_DEFINE_UNQUOTED([PPP_VERSION(x,y,z)], + [((x & 0xFF) << 16 | (y & 0xFF) << 8 | (z & 0xFF) << 0)], + [Macro to help determine the particular version of pppd]) + PPP_VERSION=$(echo $PPPD_VERSION | sed -e "s/\./\,/g") + AC_DEFINE_UNQUOTED(WITH_PPP_VERSION, PPP_VERSION($PPP_VERSION), + [The real version of pppd represented as an int]) fi -AM_CONDITIONAL(PPTP, test "${enable_pptp}" != "no") -AM_CONDITIONAL(PPTP_BUILTIN, test "${enable_pptp}" = "builtin") AC_CHECK_HEADERS(resolv.h, dummy=yes, AC_MSG_ERROR(resolver header files are required)) diff --git a/doc/connman.8.in b/doc/connman.8.in index 85e7c5e..ffee8d3 100644 --- a/doc/connman.8.in +++ b/doc/connman.8.in @@ -66,7 +66,8 @@ Only manage these network interfaces. By default all network interfaces are managed. .TP .BR \-I\ \fIinterface \fR[,...],\ \-\-nodevice= \fIinterface \fR[,...] -Never manage these network interfaces. +Never manage these network interfaces. The option can be a pattern +containing "*" and "?" characters. .TP .BI \-p\ plugin \fR[,...],\ \fB\-\-plugin= plugin \fR[,...] Load these plugins only. The option can be a pattern containing @@ -94,9 +95,11 @@ to itself by setting nameserver to 127.0.0.1 in \fBresolv.conf\fP(5) file or leave DNS management to an external entity, such as systemd-resolved. If this is not desired and you want that all programs call directly some DNS server, then you can use the \fB--nodnsproxy\fP -option. If this option is used, then ConnMan is not able to cache the -DNS queries because the DNS traffic is not going through ConnMan and that -can cause some extra network traffic. +option. ConnMan then figures out the DNS server and search domain +on startup and sets them in \fBresolv.conf\fP(5). If this option is +used, then ConnMan is not able to cache the DNS queries because the DNS +traffic is not going through ConnMan and that can cause some extra +network traffic. .SH SEE ALSO .BR connmanctl (1), \ connman.conf (5), \ connman-service.config (5), \c .BR \ connman-vpn (8) diff --git a/doc/connman.conf.5.in b/doc/connman.conf.5.in index 82cceb7..1f9b290 100644 --- a/doc/connman.conf.5.in +++ b/doc/connman.conf.5.in @@ -129,7 +129,8 @@ are in 'online' or 'ready' states, the newly connected service is the only one that will be kept connected. A service connected by the user will be used until going out of network coverage. With this setting enabled applications will notice more network breaks than -normal. Default value is false. +normal. Note this options can't be used with VPNs. +Default value is false. .TP .BI TetheringTechnologies= technology\fR[,...] List of technologies that are allowed to enable tethering separated by ",". @@ -205,6 +206,23 @@ address will be chosen instead (according to RFC3927). If an address conflict occurs for an address offered via DHCP, ConnMan send a DHCP DECLINE once and for the second conflict resort to finding an IPv4LL address. Default value is false. +.TP +.BI Localtime= string +Path to localtime file. Defaults to /etc/localtime. +.TP +.BI RegdomFollowsTimezone= true\ \fR|\fB\ false +Enable regdomain to be changed along timezone changes. With this option set to +true each time the timezone changes the first present ISO3166 country code is +being read from /usr/share/zoneinfo/zone1970.tab and set as regdom value. +Default value is false. +.TP +.BI ResolvConf= string +Path to resolv.conf file. If the file does not exist, but intermediate +directories exist, it will be created. +If this option is not set, it tries to write into +@runstatedir@/connman/resolv.conf and fallbacks to @sysconfdir@/resolv.conf if +it fails (@runstatedir@/connman does not exist or is not writeable). +If you do not want to update resolv.conf, you can set /dev/null. .SH "EXAMPLE" The following example configuration disables hostname updates and enables ethernet tethering. diff --git a/gdhcp/client.c b/gdhcp/client.c index 3016dfc..8201769 100644 --- a/gdhcp/client.c +++ b/gdhcp/client.c @@ -1319,9 +1319,9 @@ static bool sanity_check(struct ip_udp_dhcp_packet *packet, int bytes) static int dhcp_recv_l2_packet(struct dhcp_packet *dhcp_pkt, int fd, struct sockaddr_in *dst_addr) { - int bytes; struct ip_udp_dhcp_packet packet; uint16_t check; + int bytes, tot_len; memset(&packet, 0, sizeof(packet)); @@ -1329,15 +1329,17 @@ static int dhcp_recv_l2_packet(struct dhcp_packet *dhcp_pkt, int fd, if (bytes < 0) return -1; - if (bytes < (int) (sizeof(packet.ip) + sizeof(packet.udp))) - return -1; - - if (bytes < ntohs(packet.ip.tot_len)) + tot_len = ntohs(packet.ip.tot_len); + if (bytes > tot_len) { + /* ignore any extra garbage bytes */ + bytes = tot_len; + } else if (bytes < tot_len) { /* packet is bigger than sizeof(packet), we did partial read */ return -1; + } - /* ignore any extra garbage bytes */ - bytes = ntohs(packet.ip.tot_len); + if (bytes < (int) (sizeof(packet.ip) + sizeof(packet.udp))) + return -1; if (!sanity_check(&packet, bytes)) return -1; @@ -3036,7 +3038,7 @@ char *g_dhcp_client_get_server_address(GDHCPClient *dhcp_client) if (!dhcp_client) return NULL; - return get_ip(dhcp_client->server_ip); + return get_ip(htonl(dhcp_client->server_ip)); } char *g_dhcp_client_get_address(GDHCPClient *dhcp_client) diff --git a/gsupplicant/supplicant.c b/gsupplicant/supplicant.c index 470d99e..1b92ec4 100644 --- a/gsupplicant/supplicant.c +++ b/gsupplicant/supplicant.c @@ -810,6 +810,33 @@ static void remove_peer(gpointer data) g_free(peer); } +static void remove_ssid(gpointer data) +{ + GSupplicantSSID *ssid = data; + + if (!ssid) + return; + + g_free((void *) ssid->ssid); + g_free((char *) ssid->eap); + g_free((char *) ssid->passphrase); + g_free((char *) ssid->identity); + g_free((char *) ssid->anonymous_identity); + g_free((char *) ssid->ca_cert_path); + g_free((char *) ssid->subject_match); + g_free((char *) ssid->altsubject_match); + g_free((char *) ssid->domain_suffix_match); + g_free((char *) ssid->domain_match); + g_free((char *) ssid->client_cert_path); + g_free((char *) ssid->private_key_path); + g_free((char *) ssid->private_key_passphrase); + g_free((char *) ssid->phase2_auth); + g_free((char *) ssid->pin_wps); + g_free((char *) ssid->bgscan); + + g_free(ssid); +} + static void debug_strvalmap(const char *label, struct strvalmap *map, unsigned int val) { @@ -4151,6 +4178,7 @@ done: if (data->callback) data->callback(err, NULL, data->user_data); + remove_ssid(data->ssid); dbus_free(data); } @@ -4469,7 +4497,7 @@ static void interface_select_network_result(const char *error, if (data->callback) data->callback(err, data->interface, data->user_data); - g_free(data->ssid); + remove_ssid(data->ssid); dbus_free(data); } @@ -4525,7 +4553,7 @@ error: } g_free(data->path); - g_free(data->ssid); + remove_ssid(data->ssid); g_free(data); } @@ -5030,7 +5058,7 @@ static void interface_wps_start_result(const char *error, data->callback(err, data->interface, data->user_data); g_free(data->path); - g_free(data->ssid); + remove_ssid(data->ssid); dbus_free(data); } @@ -5070,7 +5098,7 @@ static void wps_start(const char *error, DBusMessageIter *iter, void *user_data) if (error) { SUPPLICANT_DBG("error: %s", error); g_free(data->path); - g_free(data->ssid); + remove_ssid(data->ssid); dbus_free(data); return; } @@ -5159,6 +5187,7 @@ int g_supplicant_interface_connect(GSupplicantInterface *interface, * type is 802.11x). */ if (compare_network_parameters(interface, ssid)) { + remove_ssid(ssid); return -EALREADY; } @@ -5184,6 +5213,7 @@ int g_supplicant_interface_connect(GSupplicantInterface *interface, if (ret < 0) { g_free(data->path); + remove_ssid(data->ssid); dbus_free(data); return ret; } @@ -5234,6 +5264,7 @@ static void network_remove_result(const char *error, } else { if (data->callback) data->callback(result, data->interface, data->user_data); + remove_ssid(data->ssid); } g_free(data->path); dbus_free(data); @@ -5287,6 +5318,7 @@ static void interface_disconnect_result(const char *error, data->user_data); g_free(data->path); + remove_ssid(data->ssid); dbus_free(data); return; } @@ -5299,10 +5331,12 @@ static void interface_disconnect_result(const char *error, if (result != -ECONNABORTED) { if (network_remove(data) < 0) { g_free(data->path); + remove_ssid(data->ssid); dbus_free(data); } } else { g_free(data->path); + remove_ssid(data->ssid); dbus_free(data); } } diff --git a/gweb/gresolv.c b/gweb/gresolv.c index 954e7cf..8101d71 100644 --- a/gweb/gresolv.c +++ b/gweb/gresolv.c @@ -36,6 +36,7 @@ #include #include #include +#include #include "gresolv.h" diff --git a/gweb/gweb.c b/gweb/gweb.c index 12fcb1d..13c6c5f 100644 --- a/gweb/gweb.c +++ b/gweb/gweb.c @@ -918,7 +918,7 @@ static gboolean received_data(GIOChannel *channel, GIOCondition cond, } *pos = '\0'; - count = strlen((char *) ptr); + count = pos - ptr; if (count > 0 && ptr[count - 1] == '\r') { ptr[--count] = '\0'; bytes_read--; diff --git a/include/agent.h b/include/agent.h index 6961f7a..2702020 100644 --- a/include/agent.h +++ b/include/agent.h @@ -71,6 +71,7 @@ int connman_agent_queue_message(void *user_context, DBusMessage *msg, int timeout, agent_queue_cb callback, void *user_data, void *agent_data); +bool connman_agent_queue_search(void *user_context, void *agent_data); void *connman_agent_get_info(const char *dbus_sender, const char **sender, const char **path); diff --git a/include/network.h b/include/network.h index 8f9dd94..5bca62a 100644 --- a/include/network.h +++ b/include/network.h @@ -163,6 +163,7 @@ struct connman_network_driver { void (*remove) (struct connman_network *network); int (*connect) (struct connman_network *network); int (*disconnect) (struct connman_network *network); + int (*forget) (struct connman_network *network); int (*set_autoconnect) (struct connman_network *network, bool autoconnect); }; diff --git a/include/timeserver.h b/include/timeserver.h index 48ea194..3177d4e 100644 --- a/include/timeserver.h +++ b/include/timeserver.h @@ -26,6 +26,13 @@ extern "C" { #endif +enum connman_timeserver_sync_reason { + CONNMAN_TIMESERVER_SYNC_REASON_START = 0, + CONNMAN_TIMESERVER_SYNC_REASON_ADDRESS_UPDATE = 1, + CONNMAN_TIMESERVER_SYNC_REASON_STATE_UPDATE = 2, + CONNMAN_TIMESERVER_SYNC_REASON_TS_CHANGE = 3, +}; + int __connman_timeserver_system_set(char **server); #ifdef __cplusplus diff --git a/plugins/iwd.c b/plugins/iwd.c index ac3d1e1..2fe49a2 100644 --- a/plugins/iwd.c +++ b/plugins/iwd.c @@ -41,7 +41,15 @@ static GDBusClient *client; static GDBusProxy *agent_proxy; static GHashTable *adapters; static GHashTable *devices; +/* + * Mapping from dbus path -> struct iwd_network, tracking the set of Network + * objects seen by iwd. + */ static GHashTable *networks; +/* + * Mapping from dbus path -> struct iwd_network, tracking the set of iwd + * KnownNetwork objects. + */ static GHashTable *known_networks; static GHashTable *stations; static GHashTable *access_points; @@ -84,6 +92,11 @@ struct iwd_device { struct connman_device *device; }; +/* + * Structure tracking an net.connman.iwd.Network D-Bus object. + * + * This is mapped one-to-one to a connman_network object. + */ struct iwd_network { GDBusProxy *proxy; char *path; @@ -95,10 +108,17 @@ struct iwd_network { struct iwd_device *iwdd; struct connman_network *network; - /* service's autoconnect */ - bool autoconnect; + /* + * connman_service's autoconnect. + * + * See Note [Managing autoconnect state] for more details. + */ + bool cm_autoconnect; }; +/* + * Structure tracking a net.connman.iwd.KnownNetwork D-Bus object. + */ struct iwd_known_network { GDBusProxy *proxy; char *path; @@ -106,11 +126,15 @@ struct iwd_known_network { char *type; bool hidden; char *last_connected_time; - bool auto_connect; + bool iwd_auto_connect; int auto_connect_id; - /* service's autoconnect */ - bool autoconnect; + /* + * connman_service's autoconnect. + * + * See Note [Managing autoconnect state] for more details. + */ + bool cm_autoconnect; }; struct iwd_station { @@ -241,10 +265,12 @@ static void cm_network_connect_cb(DBusMessage *message, void *user_data) return; DBG("%s connect failed: %s", path, dbus_error); - if (!strcmp(dbus_error, "net.connman.iwd.Failed")) + if (!strcmp(dbus_error, "net.connman.iwd.Failed") || + !strcmp(dbus_error, + "net.connman.iwd.InvalidFormat")) connman_network_set_error(iwdn->network, CONNMAN_NETWORK_ERROR_INVALID_KEY); - else if (!iwdn->autoconnect) + else if (!iwdn->cm_autoconnect) connman_network_set_error(iwdn->network, CONNMAN_NETWORK_ERROR_CONNECT_FAIL); return; @@ -270,6 +296,46 @@ static int cm_network_connect(struct connman_network *network) return -EINPROGRESS; } +static void cm_network_forget_cb(DBusMessage *message, void *user_data) +{ + struct iwd_known_network *iwdkn; + const char *path = user_data; + + iwdkn = g_hash_table_lookup(known_networks, path); + if (!iwdkn) + return; + + if (dbus_message_get_type(message) == DBUS_MESSAGE_TYPE_ERROR) { + const char *dbus_error = dbus_message_get_error_name(message); + + DBG("%s failed: %s", path, dbus_error); + } +} + +static int cm_network_forget(struct connman_network *network) +{ + struct iwd_network *iwdn = connman_network_get_data(network); + struct iwd_known_network *iwdkn; + + if (!iwdn) + return -EINVAL; + + if (!iwdn->known_network) + return 0; + + iwdkn = g_hash_table_lookup(known_networks, + iwdn->known_network); + if (!iwdkn) + return 0; + + if (!g_dbus_proxy_method_call(iwdkn->proxy, "Forget", + NULL, cm_network_forget_cb, + g_strdup(iwdkn->path), g_free)) + return -EIO; + + return 0; +} + static void cm_network_disconnect_cb(DBusMessage *message, void *user_data) { const char *path = user_data; @@ -432,25 +498,25 @@ static int enable_auto_connect(struct iwd_known_network *iwdkn) static int update_auto_connect(struct iwd_known_network *iwdkn) { - DBG("auto_connect %d autoconnect %d", iwdkn->auto_connect, iwdkn->autoconnect); + DBG("iwd_auto_connect %d cm_autoconnect %d", iwdkn->iwd_auto_connect, iwdkn->cm_autoconnect); - if (iwdkn->auto_connect == iwdkn->autoconnect) + if (iwdkn->iwd_auto_connect == iwdkn->cm_autoconnect) return -EALREADY; - if (iwdkn->autoconnect) + if (iwdkn->cm_autoconnect) return enable_auto_connect(iwdkn); return disable_auto_connect(iwdkn); } static int cm_network_set_autoconnect(struct connman_network *network, - bool autoconnect) + bool cm_autoconnect) { struct iwd_network *iwdn = connman_network_get_data(network); struct iwd_known_network *iwdkn; - DBG("autoconnect %d", autoconnect); + DBG("cm_autoconnect %d", cm_autoconnect); - iwdn->autoconnect = autoconnect; + iwdn->cm_autoconnect = cm_autoconnect; if (!iwdn->known_network) return -ENOENT; @@ -459,7 +525,7 @@ static int cm_network_set_autoconnect(struct connman_network *network, if (!iwdkn) return -ENOENT; - iwdkn->autoconnect = autoconnect; + iwdkn->cm_autoconnect = cm_autoconnect; return update_auto_connect(iwdkn); } @@ -470,6 +536,7 @@ static struct connman_network_driver network_driver = { .probe = cm_network_probe, .connect = cm_network_connect, .disconnect = cm_network_disconnect, + .forget = cm_network_forget, .set_autoconnect = cm_network_set_autoconnect, }; @@ -693,7 +760,7 @@ static void tech_enable_tethering_cb(const DBusError *error, void *user_data) } if (dbus_error_is_set(error)) { - connman_warn("iwd device %s could not enable AcessPoint mode: %s", + connman_warn("iwd device %s could not enable AccessPoint mode: %s", cbd->path, error->message); goto out; } @@ -745,6 +812,7 @@ static void tech_disable_tethering_cb(const DBusError *error, void *user_data) goto out; } + connman_technology_tethering_notify(cbd->tech, false); iwdap = g_hash_table_lookup(access_points, iwdd->path); if (!iwdap) { DBG("%s no ap object found", iwdd->path); @@ -756,11 +824,6 @@ static void tech_disable_tethering_cb(const DBusError *error, void *user_data) iwdap->bridge = NULL; iwdap->tech = NULL; - if (!connman_inet_remove_from_bridge(cbd->index, cbd->bridge)) - goto out; - - connman_technology_tethering_notify(cbd->tech, false); - if (!g_dbus_proxy_method_call(iwdap->proxy, "Stop", NULL, tech_ap_stop_cb, cbd, NULL)) { connman_warn("iwd ap %s could not stop AccessPoint mode: %s", @@ -787,6 +850,9 @@ static int cm_change_tethering(struct iwd_device *iwdd, if (index < 0) return -ENODEV; + if (!enabled && connman_inet_remove_from_bridge(index, bridge)) + return -EIO; + cbd = g_new(struct tech_cb_data, 1); cbd->iwdd = iwdd; cbd->path = g_strdup(iwdd->path); @@ -1111,11 +1177,58 @@ static void network_property_change(GDBusProxy *proxy, const char *name, iwdkn = g_hash_table_lookup(known_networks, iwdn->known_network); - if (iwdkn) + if (iwdkn) { + /* See Note [Managing autoconnect state] */ + iwdkn->cm_autoconnect = iwdn->cm_autoconnect; update_auto_connect(iwdkn); + } } } +/* + * Note [Managing autoconnect state]: + * + * We need to set the iwd_known_network's cm_autoconnect status from the + * iwd_network, which has in turn been set to the corresponding + * connman_service's state when it first appeared (due to + * __connman_service_create_from_network). + * + * The management of the autoconnect state between ConnMan and its plugins and + * iwd is rather subtle and prone to bugs: + * - ConnMan itself determines the autoconnect state in struct connman_service, + * which we cannot directly see; we only see cm_network_set_autoconnect + * callbacks. + * + * - The iwd plugin maintains an independent state machine tracking iwd's view of + * the world, which processes events in a non atomic fashion; for + * instance, a iwd.KnownNetwork created event will appear before the + * corresponding PropertyChanged setting the KnownNetwork property of the + * iwd.Network corresponding to the created iwd.KnownNetwork. + * + * A typical flow of a network being newly connected to looks like so: + * - An iwd.Network appears, and add_network registers a connman_network + * structure with ConnMan. ConnMan then in turn creates a service via + * __connman_service_create_from_network. + * + * - The iwd plugin receives a callback from ConnMan to set the autoconnect + * state, setting the cm_autoconnect state of the iwd_network. At this point, + * there is no iwd_known_network yet. + * + * - ConnMan receives a Connect() request on the connman.Service, which is + * forwarded to the iwd plugin via cm_network_connect. The iwd plugin calls + * Connect() on the corresponding iwd.Network (possibly using the iwd + * plugin's agent to get credentials if necessary). + * + * - Around the time that the connection completes, a iwd.KnownNetwork created + * event appears, followed by a PropertyChanged event noting the change in + * the iwd.Network's KnownNetwork property. + * + * This is the first time that we can associate the iwd.KnownNetwork with the + * corresponding iwd.Network and iwd_network. We have the ConnMan-side + * autoconnect status in the iwd_network structure at this point, so we + * synchronize the autoconnect state with iwd here. + */ + static unsigned char calculate_strength(int strength) { unsigned char res; @@ -1128,7 +1241,9 @@ static unsigned char calculate_strength(int strength) * ConnMan expects it in the range from 100 (strongest) to 0 * (weakest). */ - res = (unsigned char)((strength + 10000) / 100); + res = (unsigned char)(120 + strength / 100); + if (res > 100) + res = 100; return res; } @@ -1635,12 +1750,12 @@ static void known_network_property_change(GDBusProxy *proxy, const char *name, return; if (!strcmp(name, "AutoConnect")) { - dbus_bool_t auto_connect; + dbus_bool_t iwd_auto_connect; - dbus_message_iter_get_basic(iter, &auto_connect); - iwdkn->auto_connect = auto_connect; + dbus_message_iter_get_basic(iter, &iwd_auto_connect); + iwdkn->iwd_auto_connect = iwd_auto_connect; - DBG("%p auto_connect %d", path, iwdkn->auto_connect); + DBG("%s iwd_auto_connect %d", path, iwdkn->iwd_auto_connect); update_auto_connect(iwdkn); } @@ -1664,13 +1779,13 @@ static void init_auto_connect(struct iwd_known_network *iwdkn) if (iwdkn != kn) continue; - iwdkn->autoconnect = iwdn->autoconnect; + iwdkn->cm_autoconnect = iwdn->cm_autoconnect; update_auto_connect(iwdkn); return; } } -static void create_know_network(GDBusProxy *proxy) +static void create_known_network(GDBusProxy *proxy) { const char *path = g_dbus_proxy_get_path(proxy); struct iwd_known_network *iwdkn; @@ -1692,12 +1807,22 @@ static void create_know_network(GDBusProxy *proxy) iwdkn->hidden = proxy_get_bool(proxy, "Hidden"); iwdkn->last_connected_time = g_strdup(proxy_get_string(proxy, "LastConnectedTime")); - iwdkn->auto_connect = proxy_get_bool(proxy, "AutoConnect"); + iwdkn->iwd_auto_connect = proxy_get_bool(proxy, "AutoConnect"); - DBG("name '%s' type %s hidden %d, last_connection_time %s auto_connect %d", + DBG("name '%s' type %s hidden %d, last_connection_time %s iwd_auto_connect %d", iwdkn->name, iwdkn->type, iwdkn->hidden, - iwdkn->last_connected_time, iwdkn->auto_connect); + iwdkn->last_connected_time, iwdkn->iwd_auto_connect); + /* + * Although we initialize the autoconnect state of this + * iwd_known_network here, it is only initialized in the case of + * networks that already existed prior to startup: in the + * case of a new iwd.KnownNetwork appearing, we are called before the + * iwd_network.known_network field is initialized by a subsequent + * PropertyChanged event. + * + * See Note [Managing autoconnect state]. + */ init_auto_connect(iwdkn); g_dbus_proxy_set_property_watch(iwdkn->proxy, @@ -1781,7 +1906,7 @@ static void object_added(GDBusProxy *proxy, void *user_data) else if (!strcmp(interface, IWD_NETWORK_INTERFACE)) create_network(proxy); else if (!strcmp(interface, IWD_KNOWN_NETWORK_INTERFACE)) - create_know_network(proxy); + create_known_network(proxy); else if (!strcmp(interface, IWD_STATION_INTERFACE)) create_station(proxy); else if (!strcmp(interface, IWD_AP_INTERFACE)) diff --git a/plugins/ofono.c b/plugins/ofono.c index f0bd3c5..8bb5394 100644 --- a/plugins/ofono.c +++ b/plugins/ofono.c @@ -40,6 +40,7 @@ #include #include #include +#include #include "mcc.h" @@ -1710,6 +1711,10 @@ static void netreg_update_regdom(struct modem_data *modem, char *alpha2; int mcc; + /* Do not change regdom here if it is set to follow timezone. */ + if (connman_setting_get_bool("RegdomFollowsTimezone")) + return; + dbus_message_iter_get_basic(value, &mobile_country_code); DBG("%s MobileContryCode %s", modem->path, mobile_country_code); diff --git a/plugins/wifi.c b/plugins/wifi.c index e947b16..ed7437f 100644 --- a/plugins/wifi.c +++ b/plugins/wifi.c @@ -2163,21 +2163,29 @@ static GSupplicantSecurity network_security(const char *security) static void ssid_init(GSupplicantSSID *ssid, struct connman_network *network) { struct wifi_network *network_data = connman_network_get_data(network); + const char *ssid_blob; const char *security; memset(ssid, 0, sizeof(*ssid)); ssid->mode = G_SUPPLICANT_MODE_INFRA; - ssid->ssid = connman_network_get_blob(network, "WiFi.SSID", - &ssid->ssid_len); + ssid->ssid_len = 0; + ssid_blob = + connman_network_get_blob(network, "WiFi.SSID", &ssid->ssid_len); + ssid->ssid = g_try_malloc(ssid->ssid_len); + if (ssid->ssid) + memcpy((void *)ssid->ssid, ssid_blob, ssid->ssid_len); + else + ssid->ssid_len = 0; + ssid->scan_ssid = 1; security = connman_network_get_string(network, "WiFi.Security"); ssid->security = network_security(security); ssid->keymgmt = network_data->keymgmt; ssid->ieee80211w = G_SUPPLICANT_MFP_OPTIONAL; - ssid->passphrase = connman_network_get_string(network, - "WiFi.Passphrase"); + ssid->passphrase = g_strdup( + connman_network_get_string(network, "WiFi.Passphrase")); - ssid->eap = connman_network_get_string(network, "WiFi.EAP"); + ssid->eap = g_strdup(connman_network_get_string(network, "WiFi.EAP")); /* * If our private key password is unset, @@ -2186,42 +2194,44 @@ static void ssid_init(GSupplicantSSID *ssid, struct connman_network *network) * cert may have to be provided. */ if (!connman_network_get_string(network, "WiFi.PrivateKeyPassphrase")) - connman_network_set_string(network, - "WiFi.PrivateKeyPassphrase", - ssid->passphrase); + connman_network_set_string(network, "WiFi.PrivateKeyPassphrase", + ssid->passphrase); /* We must have an identity for both PEAP and TLS */ - ssid->identity = connman_network_get_string(network, "WiFi.Identity"); + ssid->identity = + g_strdup(connman_network_get_string(network, "WiFi.Identity")); /* Use agent provided identity as a fallback */ if (!ssid->identity || strlen(ssid->identity) == 0) - ssid->identity = connman_network_get_string(network, - "WiFi.AgentIdentity"); - - ssid->anonymous_identity = connman_network_get_string(network, - "WiFi.AnonymousIdentity"); - ssid->ca_cert_path = connman_network_get_string(network, - "WiFi.CACertFile"); - ssid->subject_match = connman_network_get_string(network, - "WiFi.SubjectMatch"); - ssid->altsubject_match = connman_network_get_string(network, - "WiFi.AltSubjectMatch"); - ssid->domain_suffix_match = connman_network_get_string(network, - "WiFi.DomainSuffixMatch"); - ssid->domain_match = connman_network_get_string(network, - "WiFi.DomainMatch"); - ssid->client_cert_path = connman_network_get_string(network, - "WiFi.ClientCertFile"); - ssid->private_key_path = connman_network_get_string(network, - "WiFi.PrivateKeyFile"); - ssid->private_key_passphrase = connman_network_get_string(network, - "WiFi.PrivateKeyPassphrase"); - ssid->phase2_auth = connman_network_get_string(network, "WiFi.Phase2"); + ssid->identity = g_strdup(connman_network_get_string( + network, "WiFi.AgentIdentity")); + + ssid->anonymous_identity = g_strdup( + connman_network_get_string(network, "WiFi.AnonymousIdentity")); + ssid->ca_cert_path = g_strdup( + connman_network_get_string(network, "WiFi.CACertFile")); + ssid->subject_match = g_strdup( + connman_network_get_string(network, "WiFi.SubjectMatch")); + ssid->altsubject_match = g_strdup( + connman_network_get_string(network, "WiFi.AltSubjectMatch")); + ssid->domain_suffix_match = g_strdup( + connman_network_get_string(network, "WiFi.DomainSuffixMatch")); + ssid->domain_match = g_strdup( + connman_network_get_string(network, "WiFi.DomainMatch")); + ssid->client_cert_path = g_strdup( + connman_network_get_string(network, "WiFi.ClientCertFile")); + ssid->private_key_path = g_strdup( + connman_network_get_string(network, "WiFi.PrivateKeyFile")); + ssid->private_key_passphrase = g_strdup(connman_network_get_string( + network, "WiFi.PrivateKeyPassphrase")); + ssid->phase2_auth = + g_strdup(connman_network_get_string(network, "WiFi.Phase2")); ssid->use_wps = connman_network_get_bool(network, "WiFi.UseWPS"); - ssid->pin_wps = connman_network_get_string(network, "WiFi.PinWPS"); + ssid->pin_wps = + g_strdup(connman_network_get_string(network, "WiFi.PinWPS")); if (connman_setting_get_bool("BackgroundScanning")) - ssid->bgscan = BGSCAN_DEFAULT; + ssid->bgscan = g_strdup(BGSCAN_DEFAULT); } static int network_connect(struct connman_network *network) @@ -2246,12 +2256,12 @@ static int network_connect(struct connman_network *network) interface = wifi->interface; - ssid_init(ssid, network); - if (wifi->disconnecting) { wifi->pending_network = network; g_free(ssid); } else { + ssid_init(ssid, network); + wifi->network = connman_network_ref(network); wifi->retries = 0; @@ -2518,6 +2528,25 @@ static bool handle_4way_handshake_failure(GSupplicantInterface *interface, return false; } +static bool handle_sae_authentication_failure(struct connman_network *network, + struct wifi_data *wifi) +{ + struct wifi_network *network_data = connman_network_get_data(network); + + if (!(network_data->keymgmt & G_SUPPLICANT_KEYMGMT_SAE)) + return false; + + if (wifi->state != G_SUPPLICANT_STATE_AUTHENTICATING) + return false; + + if (wifi->connected) + return false; + + connman_network_set_error(network, CONNMAN_NETWORK_ERROR_INVALID_KEY); + + return true; +} + static void interface_state(GSupplicantInterface *interface) { struct connman_network *network; @@ -2615,6 +2644,13 @@ static void interface_state(GSupplicantInterface *interface) network, wifi)) break; + /* + * On WPA3-SAE authentication, wpa_supplicant goes directly from + * authenticating to disconnected state if the key was invalid. + */ + if (handle_sae_authentication_failure(network, wifi)) + break; + /* See table 8-36 Reason codes in IEEE Std 802.11 */ switch (wifi->disconnect_code) { case 6: /* Class 2 frame received from nonauthenticated STA */ diff --git a/scripts/libppp-compat.h b/scripts/libppp-compat.h new file mode 100644 index 0000000..eee1d09 --- /dev/null +++ b/scripts/libppp-compat.h @@ -0,0 +1,127 @@ +/* Copyright (C) Eivind Naess, eivnaes@yahoo.com */ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#ifndef __LIBPPP_COMPAT_H__ +#define __LIBPPP_COMPAT_H__ + +/* Define USE_EAPTLS compile with EAP TLS support against older pppd headers, + * pppd >= 2.5.0 use PPP_WITH_EAPTLS and is defined in pppdconf.h */ +#define USE_EAPTLS 1 + +/* Define INET6 to compile with IPv6 support against older pppd headers, + * pppd >= 2.5.0 use PPP_WITH_IPV6CP and is defined in pppdconf.h */ +#define INET6 1 + +/* PPP < 2.5.0 defines and exports VERSION which overlaps with current package VERSION define. + * this silly macro magic is to work around that. */ +#undef VERSION +#include + +#ifndef PPPD_VERSION +#define PPPD_VERSION VERSION +#endif + +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_PPPD_CHAP_H +#include +#endif + +#ifdef HAVE_PPPD_CHAP_NEW_H +#include +#endif + +#ifdef HAVE_PPPD_CHAP_MS_H +#include +#endif + +#ifndef PPP_PROTO_CHAP +#define PPP_PROTO_CHAP 0xc223 +#endif + +#ifndef PPP_PROTO_EAP +#define PPP_PROTO_EAP 0xc227 +#endif + + +#if WITH_PPP_VERSION < PPP_VERSION(2,5,0) + +static inline bool +debug_on (void) +{ + return debug; +} + +static inline const char +*ppp_ipparam (void) +{ + return ipparam; +} + +static inline int +ppp_ifunit (void) +{ + return ifunit; +} + +static inline const char * +ppp_ifname (void) +{ + return ifname; +} + +static inline int +ppp_get_mtu (int idx) +{ + return netif_get_mtu(idx); +} + +typedef enum ppp_notify +{ + NF_PID_CHANGE, + NF_PHASE_CHANGE, + NF_EXIT, + NF_SIGNALED, + NF_IP_UP, + NF_IP_DOWN, + NF_IPV6_UP, + NF_IPV6_DOWN, + NF_AUTH_UP, + NF_LINK_DOWN, + NF_FORK, + NF_MAX_NOTIFY +} ppp_notify_t; + +typedef void (ppp_notify_fn) (void *ctx, int arg); + +static inline void +ppp_add_notify (ppp_notify_t type, ppp_notify_fn *func, void *ctx) +{ + struct notifier **list[NF_MAX_NOTIFY] = { + [NF_PID_CHANGE ] = &pidchange, + [NF_PHASE_CHANGE] = &phasechange, + [NF_EXIT ] = &exitnotify, + [NF_SIGNALED ] = &sigreceived, + [NF_IP_UP ] = &ip_up_notifier, + [NF_IP_DOWN ] = &ip_down_notifier, + [NF_IPV6_UP ] = &ipv6_up_notifier, + [NF_IPV6_DOWN ] = &ipv6_down_notifier, + [NF_AUTH_UP ] = &auth_up_notifier, + [NF_LINK_DOWN ] = &link_down_notifier, + [NF_FORK ] = &fork_notifier, + }; + + struct notifier **notify = list[type]; + if (notify) { + add_notifier(notify, func, ctx); + } +} + +#endif /* #if WITH_PPP_VERSION < PPP_VERSION(2,5,0) */ +#endif /* #if__LIBPPP_COMPAT_H__ */ diff --git a/scripts/libppp-plugin.c b/scripts/libppp-plugin.c index 0dd8b47..61641b5 100644 --- a/scripts/libppp-plugin.c +++ b/scripts/libppp-plugin.c @@ -29,14 +29,13 @@ #include #include #include -#include -#include -#include #include #include #include +#include "libppp-compat.h" + #define INET_ADDRES_LEN (INET_ADDRSTRLEN + 5) #define INET_DNS_LEN (2*INET_ADDRSTRLEN + 9) @@ -47,7 +46,7 @@ static char *path; static DBusConnection *connection; static int prev_phase; -char pppd_version[] = VERSION; +char pppd_version[] = PPPD_VERSION; int plugin_init(void); @@ -170,7 +169,7 @@ static void ppp_up(void *data, int arg) DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_STRING_AS_STRING DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict); - append(&dict, "INTERNAL_IFNAME", ifname); + append(&dict, "INTERNAL_IFNAME", ppp_ifname()); inet_ntop(AF_INET, &ipcp_gotoptions[0].ouraddr, buf, INET_ADDRSTRLEN); append(&dict, "INTERNAL_IP4_ADDRESS", buf); @@ -309,9 +308,9 @@ int plugin_init(void) chap_check_hook = ppp_have_secret; pap_check_hook = ppp_have_secret; - add_notifier(&ip_up_notifier, ppp_up, NULL); - add_notifier(&phasechange, ppp_phase_change, NULL); - add_notifier(&exitnotify, ppp_exit, connection); + ppp_add_notify(NF_IP_UP, ppp_up, NULL); + ppp_add_notify(NF_PHASE_CHANGE, ppp_phase_change, NULL); + ppp_add_notify(NF_EXIT, ppp_exit, connection); return 0; } diff --git a/src/agent-connman.c b/src/agent-connman.c index fca7cc1..2bd33e0 100644 --- a/src/agent-connman.c +++ b/src/agent-connman.c @@ -865,3 +865,17 @@ int __connman_agent_request_peer_authorization(struct connman_peer *peer, return -EINPROGRESS; } + +bool __connman_agent_is_request_pending(struct connman_service *service, + const char *dbus_sender) +{ + void *agent; + + /* Default agent will be returned if no dbus_sender */ + agent = connman_agent_get_info(dbus_sender, NULL, NULL); + + if (!service || !agent) + return false; + + return connman_agent_queue_search(service, agent); +} diff --git a/src/agent.c b/src/agent.c index d4f9add..23517d9 100644 --- a/src/agent.c +++ b/src/agent.c @@ -257,6 +257,28 @@ int connman_agent_queue_message(void *user_context, return err; } +bool connman_agent_queue_search(void *user_context, void *agent_data) +{ + struct connman_agent *agent = agent_data; + struct connman_agent_request *queue_data; + GList *iter; + + if (!agent || !user_context) + return false; + + if (agent->pending && agent->pending->user_context == user_context) + return true; + + for (iter = agent->queue; iter; iter = iter->next) { + queue_data = iter->data; + + if (queue_data && queue_data->user_context == user_context) + return true; + } + + return false; +} + static void set_default_agent(void) { struct connman_agent *agent = NULL; diff --git a/src/clock.c b/src/clock.c index 906538a..54ac274 100644 --- a/src/clock.c +++ b/src/clock.c @@ -270,6 +270,7 @@ static DBusMessage *set_property(DBusConnection *conn, } else if (g_str_equal(name, "TimeUpdates")) { const char *strval; enum time_updates newval; + struct connman_service *service; if (type != DBUS_TYPE_STRING) return __connman_error_invalid_arguments(msg); @@ -290,12 +291,8 @@ static DBusMessage *set_property(DBusConnection *conn, CONNMAN_CLOCK_INTERFACE, "TimeUpdates", DBUS_TYPE_STRING, &strval); - if (newval == TIME_UPDATES_AUTO) { - struct connman_service *service; - - service = connman_service_get_default(); - __connman_timeserver_conf_update(service); - } + service = connman_service_get_default(); + __connman_timeserver_conf_update(service); } else if (g_str_equal(name, "Timezone")) { const char *strval; diff --git a/src/connman.h b/src/connman.h index 6817608..b955d98 100644 --- a/src/connman.h +++ b/src/connman.h @@ -126,6 +126,8 @@ int __connman_agent_request_peer_authorization(struct connman_peer *peer, bool wps_requested, const char *dbus_sender, void *user_data); +bool __connman_agent_is_request_pending(struct connman_service *service, + const char *dbus_sender); #include @@ -449,7 +451,8 @@ char **__connman_timeserver_system_get(); GSList *__connman_timeserver_add_list(GSList *server_list, const char *timeserver); GSList *__connman_timeserver_get_all(struct connman_service *service); -void __connman_timeserver_sync(struct connman_service *service); +void __connman_timeserver_sync(struct connman_service *service, + enum connman_timeserver_sync_reason reason); void __connman_timeserver_conf_update(struct connman_service *service); bool __connman_timeserver_is_synced(void); @@ -605,6 +608,7 @@ void __connman_network_set_device(struct connman_network *network, int __connman_network_connect(struct connman_network *network); int __connman_network_disconnect(struct connman_network *network); +int __connman_network_forget(struct connman_network *network); int __connman_network_clear_ipconfig(struct connman_network *network, struct connman_ipconfig *ipconfig); int __connman_network_enable_ipconfig(struct connman_network *network, @@ -983,6 +987,7 @@ void __connman_dnsproxy_remove_listener(int index); int __connman_dnsproxy_append(int index, const char *domain, const char *server); int __connman_dnsproxy_remove(int index, const char *domain, const char *server); int __connman_dnsproxy_set_mdns(int index, bool enabled); +void __connman_dnsproxy_set_listen_port(unsigned int port); int __connman_6to4_probe(struct connman_service *service); void __connman_6to4_remove(struct connman_ipconfig *ipconfig); diff --git a/src/dhcp.c b/src/dhcp.c index 2d96c43..18dbab2 100644 --- a/src/dhcp.c +++ b/src/dhcp.c @@ -422,8 +422,7 @@ static bool apply_lease_available_on_network(GDHCPClient *dhcp_client, g_free(dhcp->pac); dhcp->pac = pac; - __connman_ipconfig_set_proxy_autoconfig(dhcp->ipconfig, - dhcp->pac); + __connman_service_set_proxy_autoconfig(service, dhcp->pac); } if (connman_setting_get_bool("Enable6to4")) diff --git a/src/dnsproxy.c b/src/dnsproxy.c index cf1d36c..7ebffbc 100644 --- a/src/dnsproxy.c +++ b/src/dnsproxy.c @@ -3,6 +3,7 @@ * Connection Manager * * Copyright (C) 2007-2014 Intel Corporation. All rights reserved. + * Copyright (C) 2022 Matthias Gerstner of SUSE. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as @@ -41,7 +42,13 @@ #include "connman.h" -#define debug(fmt...) do { } while (0) +#ifdef DNSPROXY_DEBUG +# define debug(fmt...) do { fprintf(stderr, fmt); fprintf(stderr, "\n"); } while (0) +#else +# define debug(fmt...) do { } while (0) +#endif + +#define NUM_ARRAY_ELEMENTS(a) sizeof(a) / sizeof(a[0]) #if __BYTE_ORDER == __LITTLE_ENDIAN struct domain_hdr { @@ -172,11 +179,17 @@ struct cache_data { struct cache_entry { char *key; bool want_refresh; - int hits; + size_t hits; struct cache_data *ipv4; struct cache_data *ipv6; }; +struct cache_timeout { + time_t current_time; + time_t max_timeout; + bool try_harder; +}; + struct domain_question { uint16_t type; uint16_t class; @@ -214,38 +227,91 @@ struct domain_rr { */ #define MAX_CACHE_SIZE 256 +#define DNS_HEADER_SIZE sizeof(struct domain_hdr) +#define DNS_HEADER_TCP_EXTRA_BYTES 2 +#define DNS_TCP_HEADER_SIZE DNS_HEADER_SIZE + DNS_HEADER_TCP_EXTRA_BYTES +#define DNS_QUESTION_SIZE sizeof(struct domain_question) +#define DNS_RR_SIZE sizeof(struct domain_rr) +#define DNS_QTYPE_QCLASS_SIZE sizeof(struct qtype_qclass) + +enum dns_type { + /* IPv4 address 32-bit */ + DNS_TYPE_A = ns_t_a, + /* IPv6 address 128-bit */ + DNS_TYPE_AAAA = ns_t_aaaa, + /* alias to another name */ + DNS_TYPE_CNAME = ns_t_cname, + /* start of a zone of authority */ + DNS_TYPE_SOA = ns_t_soa +}; + +enum dns_class { + DNS_CLASS_IN = ns_c_in, + DNS_CLASS_ANY = ns_c_any /* only valid for QCLASS fields */ +}; + static int cache_size; static GHashTable *cache; static int cache_refcount; -static GSList *server_list = NULL; -static GSList *request_list = NULL; -static GHashTable *listener_table = NULL; +static GSList *server_list; +static GSList *request_list; +static GHashTable *listener_table; static time_t next_refresh; static GHashTable *partial_tcp_req_table; -static guint cache_timer = 0; +static guint cache_timer; +static in_port_t dns_listen_port = 53; +/* we can keep using the same resolve's */ +static GResolv *ipv4_resolve; +static GResolv *ipv6_resolve; static guint16 get_id(void) { uint64_t rand; + /* TODO: return code is ignored, should we rather abort() on error? */ __connman_util_get_random(&rand); return rand; } -static int protocol_offset(int protocol) +static size_t protocol_offset(int protocol) { switch (protocol) { case IPPROTO_UDP: return 0; case IPPROTO_TCP: - return 2; + return DNS_HEADER_TCP_EXTRA_BYTES; default: - return -EINVAL; + /* this should never happen */ + abort(); } +} +static const char* protocol_label(int protocol) +{ + switch(protocol) { + case IPPROTO_UDP: + return "UDP"; + case IPPROTO_TCP: + return "TCP"; + default: + return "BAD_PROTOCOL"; + } +} + +static int socket_type(int protocol, int extra_flags) +{ + switch (protocol) { + case IPPROTO_UDP: + return SOCK_DGRAM | extra_flags; + case IPPROTO_TCP: + return SOCK_STREAM | extra_flags; + default: + /* this should never happen */ + abort(); + } } /* @@ -271,9 +337,7 @@ static time_t round_down_ttl(time_t end_time, int ttl) static struct request_data *find_request(guint16 id) { - GSList *list; - - for (list = request_list; list; list = list->next) { + for (GSList *list = request_list; list; list = list->next) { struct request_data *req = list->data; if (req->dstid == id || req->altid == id) @@ -287,11 +351,9 @@ static struct server_data *find_server(int index, const char *server, int protocol) { - GSList *list; - debug("index %d server %s proto %d", index, server, protocol); - for (list = server_list; list; list = list->next) { + for (GSList *list = server_list; list; list = list->next) { struct server_data *data = list->data; if (index < 0 && data->index < 0 && @@ -312,10 +374,6 @@ static struct server_data *find_server(int index, return NULL; } -/* we can keep using the same resolve's */ -static GResolv *ipv4_resolve; -static GResolv *ipv6_resolve; - static void dummy_resolve_func(GResolvResultStatus status, char **results, gpointer user_data) { @@ -353,95 +411,86 @@ static void refresh_dns_entry(struct cache_entry *entry, char *name) age = 4; } - entry->hits -= age; - if (entry->hits < 0) + if (entry->hits > age) + entry->hits -= age; + else entry->hits = 0; } -static int dns_name_length(unsigned char *buf) +static size_t dns_name_length(const unsigned char *buf) { if ((buf[0] & NS_CMPRSFLGS) == NS_CMPRSFLGS) /* compressed name */ return 2; - return strlen((char *)buf) + 1; + return strlen((const char *)buf) + 1; } -static void update_cached_ttl(unsigned char *buf, int len, int new_ttl) +static void update_cached_ttl(unsigned char *ptr, int len, int new_ttl) { - unsigned char *c; - uint16_t w; - int l; + size_t name_len; + const uint32_t raw_ttl = ntohl((uint32_t)new_ttl); + + if (new_ttl < 0) + return; /* skip the header */ - c = buf + 12; - len -= 12; + ptr += DNS_HEADER_SIZE; + len -= DNS_HEADER_SIZE; - /* skip the query, which is a name and 2 16 bit words */ - l = dns_name_length(c); - c += l; - len -= l; - c += 4; - len -= 4; + if (len < DNS_QUESTION_SIZE + 1) + return; + + /* skip the query, which is a name and a struct domain_question */ + name_len = dns_name_length(ptr); + + ptr += name_len + DNS_QUESTION_SIZE; + len -= name_len + DNS_QUESTION_SIZE; /* now we get the answer records */ while (len > 0) { + struct domain_rr *rr = NULL; + size_t rr_len; + /* first a name */ - l = dns_name_length(c); - c += l; - len -= l; - if (len < 0) - break; - /* then type + class, 2 bytes each */ - c += 4; - len -= 4; + name_len = dns_name_length(ptr); + ptr += name_len; + len -= name_len; if (len < 0) break; - /* now the 4 byte TTL field */ - c[0] = new_ttl >> 24 & 0xff; - c[1] = new_ttl >> 16 & 0xff; - c[2] = new_ttl >> 8 & 0xff; - c[3] = new_ttl & 0xff; - c += 4; - len -= 4; - if (len < 0) + rr = (void*)ptr; + if (len < sizeof(*rr)) + /* incomplete record */ break; - /* now the 2 byte rdlen field */ - w = c[0] << 8 | c[1]; - c += w + 2; - len -= w + 2; + /* update the TTL field */ + memcpy(&rr->ttl, &raw_ttl, sizeof(raw_ttl)); + + /* skip to the next record */ + rr_len = sizeof(*rr) + ntohs(rr->rdlen); + ptr += rr_len; + len -= rr_len; } } -static void send_cached_response(int sk, unsigned char *buf, int len, +static void send_cached_response(int sk, const unsigned char *ptr, size_t len, const struct sockaddr *to, socklen_t tolen, int protocol, int id, uint16_t answers, int ttl) { - struct domain_hdr *hdr; - unsigned char *ptr = buf; - int err, offset, dns_len, adj_len = len - 2; - + struct domain_hdr *hdr = NULL; + int err; + const size_t offset = protocol_offset(protocol); /* * The cached packet contains always the TCP offset (two bytes) * so skip them for UDP. */ - switch (protocol) { - case IPPROTO_UDP: - ptr += 2; - len -= 2; - dns_len = len; - offset = 0; - break; - case IPPROTO_TCP: - offset = 2; - dns_len = ptr[0] * 256 + ptr[1]; - break; - default: - return; - } + const size_t skip_bytes = offset ? 0 : DNS_HEADER_TCP_EXTRA_BYTES; + ptr += skip_bytes; + len -= skip_bytes; + const size_t dns_len = protocol == IPPROTO_UDP ? len : ntohs(*((uint16_t*)ptr)); - if (len < 12) + + if (len < DNS_HEADER_SIZE) return; hdr = (void *) (ptr + offset); @@ -456,22 +505,21 @@ static void send_cached_response(int sk, unsigned char *buf, int len, /* if this is a negative reply, we are authoritative */ if (answers == 0) hdr->aa = 1; - else + else { + const int adj_len = len - 2; update_cached_ttl((unsigned char *)hdr, adj_len, ttl); + } - debug("sk %d id 0x%04x answers %d ptr %p length %d dns %d", + debug("sk %d id 0x%04x answers %d ptr %p length %zd dns %zd", sk, hdr->id, answers, ptr, len, dns_len); err = sendto(sk, ptr, len, MSG_NOSIGNAL, to, tolen); if (err < 0) { connman_error("Cannot send cached DNS response: %s", strerror(errno)); - return; } - - if (err != len || (dns_len != (len - 2) && protocol == IPPROTO_TCP) || - (dns_len != len && protocol == IPPROTO_UDP)) - debug("Packet length mismatch, sent %d wanted %d dns %d", + else if (err != len || dns_len != (len - offset)) + debug("Packet length mismatch, sent %d wanted %zd dns %zd", err, len, dns_len); } @@ -480,20 +528,19 @@ static void send_response(int sk, unsigned char *buf, size_t len, int protocol) { struct domain_hdr *hdr; - int err, offset = protocol_offset(protocol); + int err; + const size_t offset = protocol_offset(protocol); + const size_t send_size = DNS_HEADER_SIZE + offset; debug("sk %d", sk); - if (offset < 0) - return; - - if (len < sizeof(*hdr) + offset) + if (len < send_size) return; hdr = (void *) (buf + offset); if (offset) { buf[0] = 0; - buf[1] = sizeof(*hdr); + buf[1] = DNS_HEADER_SIZE; } debug("id 0x%04x qr %d opcode %d", hdr->id, hdr->qr, hdr->opcode); @@ -506,11 +553,10 @@ static void send_response(int sk, unsigned char *buf, size_t len, hdr->nscount = 0; hdr->arcount = 0; - err = sendto(sk, buf, sizeof(*hdr) + offset, MSG_NOSIGNAL, to, tolen); + err = sendto(sk, buf, send_size, MSG_NOSIGNAL, to, tolen); if (err < 0) { connman_error("Failed to send DNS response to %d: %s", sk, strerror(errno)); - return; } } @@ -544,7 +590,7 @@ static gboolean request_timeout(gpointer user_data) { struct request_data *req = user_data; struct sockaddr *sa; - int sk; + int sk = -1; if (!req) return FALSE; @@ -559,7 +605,9 @@ static gboolean request_timeout(gpointer user_data) } else if (req->protocol == IPPROTO_TCP) { sk = req->client_sk; sa = NULL; - } else + } + + if (sk < 0) goto out; if (req->resplen > 0 && req->resp) { @@ -568,22 +616,18 @@ static gboolean request_timeout(gpointer user_data) * "not found" result), so send that back to client instead * of more fatal server failed error. */ - if (sk >= 0) - sendto(sk, req->resp, req->resplen, MSG_NOSIGNAL, - sa, req->sa_len); + sendto(sk, req->resp, req->resplen, MSG_NOSIGNAL, + sa, req->sa_len); } else if (req->request) { /* * There was not reply from server at all. */ - struct domain_hdr *hdr; - - hdr = (void *)(req->request + protocol_offset(req->protocol)); + struct domain_hdr *hdr = (void *)(req->request + protocol_offset(req->protocol)); hdr->id = req->srcid; - if (sk >= 0) - send_response(sk, req->request, req->request_len, - sa, req->sa_len, req->protocol); + send_response(sk, req->request, req->request_len, + sa, req->sa_len, req->protocol); } /* @@ -603,73 +647,92 @@ out: return FALSE; } -static int append_query(unsigned char *buf, unsigned int size, - const char *query, const char *domain) +static int append_data(unsigned char *buf, size_t size, const char *data) { unsigned char *ptr = buf; - int len; + size_t len; - debug("query %s domain %s", query, domain); - - while (query) { - const char *tmp; + while (true) { + const char *dot = strchr(data, '.'); + len = dot ? dot - data : strlen(data); - tmp = strchr(query, '.'); - if (!tmp) { - len = strlen(query); - if (len == 0) - break; - *ptr = len; - memcpy(ptr + 1, query, len); - ptr += len + 1; + if (len == 0) break; - } + else if (size < len + 1) + return -1; + + *ptr = len; + memcpy(ptr + 1, data, len); + ptr += len + 1; + size -= len + 1; - *ptr = tmp - query; - memcpy(ptr + 1, query, tmp - query); - ptr += tmp - query + 1; + if (!dot) + break; - query = tmp + 1; + data = dot + 1; } - while (domain) { - const char *tmp; + return ptr - buf; +} - tmp = strchr(domain, '.'); - if (!tmp) { - len = strlen(domain); - if (len == 0) - break; - *ptr = len; - memcpy(ptr + 1, domain, len); - ptr += len + 1; - break; - } +static int append_query(unsigned char *buf, size_t size, + const char *query, const char *domain) +{ + size_t added; + size_t left_size = size; + int res; - *ptr = tmp - domain; - memcpy(ptr + 1, domain, tmp - domain); - ptr += tmp - domain + 1; + debug("query %s domain %s", query, domain); - domain = tmp + 1; - } + res = append_data(buf, left_size, query); + if (res < 0) + return -1; + left_size -= res; - *ptr++ = 0x00; + res = append_data(buf + res, left_size, domain); + if (res < 0) + return -1; + left_size -= res; - return ptr - buf; + if (left_size == 0) + return -1; + + added = size - left_size; + *(buf + added) = 0x00; + + return added; } -static bool cache_check_is_valid(struct cache_data *data, - time_t current_time) +static bool cache_check_is_valid(struct cache_data *data, time_t current_time) { if (!data) return false; - - if (data->cache_until < current_time) + else if (data->cache_until < current_time) return false; return true; } +static void cache_free_ipv4(struct cache_entry *entry) +{ + if (!entry->ipv4) + return; + + g_free(entry->ipv4->data); + g_free(entry->ipv4); + entry->ipv4 = NULL; +} + +static void cache_free_ipv6(struct cache_entry *entry) +{ + if (!entry->ipv6) + return; + + g_free(entry->ipv6->data); + g_free(entry->ipv6); + entry->ipv6 = NULL; +} + /* * remove stale cached entries so that they can be refreshed */ @@ -677,76 +740,65 @@ static void cache_enforce_validity(struct cache_entry *entry) { time_t current_time = time(NULL); - if (!cache_check_is_valid(entry->ipv4, current_time) - && entry->ipv4) { + if (entry->ipv4 && !cache_check_is_valid(entry->ipv4, current_time)) { debug("cache timeout \"%s\" type A", entry->key); - g_free(entry->ipv4->data); - g_free(entry->ipv4); - entry->ipv4 = NULL; - + cache_free_ipv4(entry); } - if (!cache_check_is_valid(entry->ipv6, current_time) - && entry->ipv6) { + if (entry->ipv6 && !cache_check_is_valid(entry->ipv6, current_time)) { debug("cache timeout \"%s\" type AAAA", entry->key); - g_free(entry->ipv6->data); - g_free(entry->ipv6); - entry->ipv6 = NULL; + cache_free_ipv6(entry); } } -static uint16_t cache_check_validity(char *question, uint16_t type, +static bool cache_check_validity(const char *question, uint16_t type, struct cache_entry *entry) { - time_t current_time = time(NULL); - bool want_refresh = false; - - /* - * if we have a popular entry, we want a refresh instead of - * total destruction of the entry. - */ - if (entry->hits > 2) - want_refresh = true; + struct cache_data *cached_ip = NULL, *other_ip = NULL; + const time_t current_time = time(NULL); + bool want_refresh; cache_enforce_validity(entry); switch (type) { - case 1: /* IPv4 */ - if (!cache_check_is_valid(entry->ipv4, current_time)) { - debug("cache %s \"%s\" type A", entry->ipv4 ? - "timeout" : "entry missing", question); - - if (want_refresh) - entry->want_refresh = true; + case DNS_TYPE_A: /* IPv4 */ + cached_ip = entry->ipv4; + other_ip = entry->ipv6; + break; - /* - * We do not remove cache entry if there is still - * valid IPv6 entry found in the cache. - */ - if (!cache_check_is_valid(entry->ipv6, current_time) && !want_refresh) { - g_hash_table_remove(cache, question); - type = 0; - } - } + case DNS_TYPE_AAAA: /* IPv6 */ + cached_ip = entry->ipv6; + other_ip = entry->ipv4; break; + default: + return false; + } - case 28: /* IPv6 */ - if (!cache_check_is_valid(entry->ipv6, current_time)) { - debug("cache %s \"%s\" type AAAA", entry->ipv6 ? - "timeout" : "entry missing", question); + /* + * if we have a popular entry, we want a refresh instead of + * total destruction of the entry. + */ + want_refresh = entry->hits > 2 ? true : false; - if (want_refresh) - entry->want_refresh = true; + if (!cache_check_is_valid(cached_ip, current_time)) { + debug("cache %s \"%s\" type %s", + cached_ip ? "timeout" : "entry missing", + question, + cached_ip == entry->ipv4 ? "A" : "AAAA"); - if (!cache_check_is_valid(entry->ipv4, current_time) && !want_refresh) { - g_hash_table_remove(cache, question); - type = 0; - } + if (want_refresh) + entry->want_refresh = true; + /* + * We do not remove cache entry if there is still a + * valid entry for another IP version found in the cache. + */ + else if (!cache_check_is_valid(other_ip, current_time)) { + g_hash_table_remove(cache, question); + return false; } - break; } - return type; + return true; } static void cache_element_destroy(gpointer value) @@ -756,19 +808,13 @@ static void cache_element_destroy(gpointer value) if (!entry) return; - if (entry->ipv4) { - g_free(entry->ipv4->data); - g_free(entry->ipv4); - } - - if (entry->ipv6) { - g_free(entry->ipv6->data); - g_free(entry->ipv6); - } + cache_free_ipv4(entry); + cache_free_ipv6(entry); g_free(entry->key); g_free(entry); + /* TODO: this would be a worrying condition. Does this ever happen? */ if (--cache_size < 0) cache_size = 0; } @@ -782,6 +828,7 @@ static gboolean try_remove_cache(gpointer user_data) g_hash_table_destroy(cache); cache = NULL; + cache_size = 0; } return FALSE; @@ -789,36 +836,28 @@ static gboolean try_remove_cache(gpointer user_data) static void create_cache(void) { - if (__sync_fetch_and_add(&cache_refcount, 1) == 0) + if (__sync_fetch_and_add(&cache_refcount, 1) == 0) { cache = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, cache_element_destroy); + cache_size = 0; + } } -static struct cache_entry *cache_check(gpointer request, int *qtype, int proto) +static struct cache_entry *cache_check(gpointer request, uint16_t *qtype, int proto) { - char *question; - struct cache_entry *entry; - struct domain_question *q; - uint16_t type; - int offset, proto_offset; - if (!request) return NULL; - proto_offset = protocol_offset(proto); - if (proto_offset < 0) - return NULL; - - question = request + proto_offset + 12; - - offset = strlen(question) + 1; - q = (void *) (question + offset); - type = ntohs(q->type); + const char *question = request + protocol_offset(proto) + DNS_HEADER_SIZE; + const size_t offset = strlen(question) + 1; + const struct domain_question *q = (void *) (question + offset); + const uint16_t type = ntohs(q->type); + struct cache_entry *entry; /* We only cache either A (1) or AAAA (28) requests */ - if (type != 1 && type != 28) + if (type != DNS_TYPE_A && type != DNS_TYPE_AAAA) return NULL; if (!cache) { @@ -830,8 +869,7 @@ static struct cache_entry *cache_check(gpointer request, int *qtype, int proto) if (!entry) return NULL; - type = cache_check_validity(question, type, entry); - if (type == 0) + if (!cache_check_validity(question, type, entry)) return NULL; *qtype = type; @@ -846,20 +884,19 @@ static struct cache_entry *cache_check(gpointer request, int *qtype, int proto) * format so that we can cache the wire format string directly. */ static int get_name(int counter, - unsigned char *pkt, unsigned char *start, unsigned char *max, + const unsigned char *pkt, const unsigned char *start, const unsigned char *max, unsigned char *output, int output_max, int *output_len, - unsigned char **end, char *name, size_t max_name, int *name_len) + const unsigned char **end, char *name, size_t max_name, int *name_len) { - unsigned char *p; + const unsigned char *p = start; /* Limit recursion to 10 (this means up to 10 labels in domain name) */ if (counter > 10) return -EINVAL; - p = start; while (*p) { if ((*p & NS_CMPRSFLGS) == NS_CMPRSFLGS) { - uint16_t offset = (*p & 0x3F) * 256 + *(p + 1); + const uint16_t offset = (*p & 0x3F) * 256 + *(p + 1); if (offset >= max - pkt) return -ENOBUFS; @@ -875,11 +912,9 @@ static int get_name(int counter, if (pkt + label_len > max) return -ENOBUFS; - - if (*output_len > output_max) + else if (*output_len > output_max) return -ENOBUFS; - - if ((*name_len + 1 + label_len + 1) > max_name) + else if ((*name_len + 1 + label_len + 1) > max_name) return -ENOBUFS; /* @@ -908,25 +943,25 @@ static int get_name(int counter, return 0; } -static int parse_rr(unsigned char *buf, unsigned char *start, - unsigned char *max, - unsigned char *response, unsigned int *response_size, - uint16_t *type, uint16_t *class, int *ttl, int *rdlen, - unsigned char **end, +static int parse_rr(const unsigned char *buf, const unsigned char *start, + const unsigned char *max, + unsigned char *response, size_t *response_size, + uint16_t *type, uint16_t *class, int *ttl, uint16_t *rdlen, + const unsigned char **end, char *name, size_t max_name) { struct domain_rr *rr; - int err, offset; + size_t offset; int name_len = 0, output_len = 0, max_rsp = *response_size; + int err = get_name(0, buf, start, max, response, max_rsp, + &output_len, end, name, max_name, &name_len); - err = get_name(0, buf, start, max, response, max_rsp, - &output_len, end, name, max_name, &name_len); if (err < 0) return err; offset = output_len; - if ((unsigned int) offset > *response_size) + if (offset > *response_size) return -ENOBUFS; rr = (void *) (*end); @@ -942,31 +977,28 @@ static int parse_rr(unsigned char *buf, unsigned char *start, if (*ttl < 0) return -EINVAL; - memcpy(response + offset, *end, sizeof(struct domain_rr)); + memcpy(response + offset, *end, DNS_RR_SIZE); - offset += sizeof(struct domain_rr); - *end += sizeof(struct domain_rr); + offset += DNS_RR_SIZE; + *end += DNS_RR_SIZE; - if ((unsigned int) (offset + *rdlen) > *response_size) + if ((offset + *rdlen) > *response_size) return -ENOBUFS; memcpy(response + offset, *end, *rdlen); *end += *rdlen; - *response_size = offset + *rdlen; return 0; } -static bool check_alias(GSList *aliases, char *name) +static bool check_alias(GSList *aliases, const char *name) { - GSList *list; - if (aliases) { - for (list = aliases; list; list = list->next) { - int len = strlen((char *)list->data); - if (strncmp((char *)list->data, name, len) == 0) + for (GSList *list = aliases; list; list = list->next) { + const char *cmpname = (const char*)list->data; + if (strncmp(cmpname, name, NS_MAXDNAME) == 0) return true; } } @@ -974,55 +1006,67 @@ static bool check_alias(GSList *aliases, char *name) return false; } -static int parse_response(unsigned char *buf, int buflen, - char *question, int qlen, +/* + * Parses the DNS response packet found in 'buf' consisting of 'buflen' bytes. + * + * The parsed question label, response type and class, ttl and number of + * answer sections are output parameters. The response output buffer will + * receive all matching resource records to be cached. + * + * Return value is < 0 on error (negative errno) or zero on success. + */ +static int parse_response(const unsigned char *buf, size_t buflen, + char *question, size_t qlen, uint16_t *type, uint16_t *class, int *ttl, - unsigned char *response, unsigned int *response_len, + unsigned char *response, size_t *response_len, uint16_t *answers) { struct domain_hdr *hdr = (void *) buf; struct domain_question *q; - unsigned char *ptr; - uint16_t qdcount = ntohs(hdr->qdcount); - uint16_t ancount = ntohs(hdr->ancount); - int err, i; - uint16_t qtype, qclass; - unsigned char *next = NULL; - unsigned int maxlen = *response_len; - GSList *aliases = NULL, *list; - char name[NS_MAXDNAME + 1]; - - if (buflen < 12) + uint16_t qtype; + int err = -ENOMSG; + uint16_t ancount, qclass; + GSList *aliases = NULL; + const size_t maxlen = *response_len; + + *response_len = 0; + *answers = 0; + + if (buflen < DNS_HEADER_SIZE) return -EINVAL; + const uint16_t qdcount = ntohs(hdr->qdcount); + const unsigned char *ptr = buf + DNS_HEADER_SIZE; + const unsigned char *eptr = buf + buflen; + debug("qr %d qdcount %d", hdr->qr, qdcount); /* We currently only cache responses where question count is 1 */ if (hdr->qr != 1 || qdcount != 1) return -EINVAL; - ptr = buf + sizeof(struct domain_hdr); - - strncpy(question, (char *) ptr, qlen); + /* + * NOTE: currently the *caller* ensures that the `question' buffer is + * always zero terminated. + */ + strncpy(question, (const char *) ptr, MIN(qlen, buflen - DNS_HEADER_SIZE)); qlen = strlen(question); ptr += qlen + 1; /* skip \0 */ + if ((eptr - ptr) < DNS_QUESTION_SIZE) + return -EINVAL; + q = (void *) ptr; qtype = ntohs(q->type); /* We cache only A and AAAA records */ - if (qtype != 1 && qtype != 28) + if (qtype != DNS_TYPE_A && qtype != DNS_TYPE_AAAA) return -ENOMSG; - qclass = ntohs(q->class); - - ptr += 2 + 2; /* ptr points now to answers */ + ptr += DNS_QUESTION_SIZE; /* advance to answers section */ - err = -ENOMSG; - *response_len = 0; - *answers = 0; - - memset(name, 0, sizeof(name)); + ancount = ntohs(hdr->ancount); + qclass = ntohs(q->class); /* * We have a bunch of answers (like A, AAAA, CNAME etc) to @@ -1030,7 +1074,8 @@ static int parse_response(unsigned char *buf, int buflen, * resource records. Only A and AAAA records are cached, all * the other records in answers are skipped. */ - for (i = 0; i < ancount; i++) { + for (uint16_t i = 0; i < ancount; i++) { + char name[NS_MAXDNAME + 1] = {0}; /* * Get one address at a time to this buffer. * The max size of the answer is @@ -1038,23 +1083,27 @@ static int parse_response(unsigned char *buf, int buflen, * 4 (ttl) + 2 (rdlen) + addr (16 or 4) = 28 * for A or AAAA record. * For CNAME the size can be bigger. + * TODO: why are we using the MAXCDNAME constant as buffer + * size then? */ - unsigned char rsp[NS_MAXCDNAME]; - unsigned int rsp_len = sizeof(rsp) - 1; - int ret, rdlen; - - memset(rsp, 0, sizeof(rsp)); + unsigned char rsp[NS_MAXCDNAME] = {0}; + size_t rsp_len = sizeof(rsp) - 1; + const unsigned char *next = NULL; + uint16_t rdlen; - ret = parse_rr(buf, ptr, buf + buflen, rsp, &rsp_len, + int ret = parse_rr(buf, ptr, buf + buflen, rsp, &rsp_len, type, class, ttl, &rdlen, &next, name, sizeof(name) - 1); if (ret != 0) { err = ret; - goto out; + break; } + /* set pointer to the next RR for the next iteration */ + ptr = next; + /* - * Now rsp contains compressed or uncompressed resource + * Now rsp contains a compressed or an uncompressed resource * record. Next we check if this record answers the question. * The name var contains the uncompressed label. * One tricky bit is the CNAME records as they alias @@ -1066,8 +1115,6 @@ static int parse_response(unsigned char *buf, int buflen, * looking for. */ if (*class != qclass) { - ptr = next; - next = NULL; continue; } @@ -1092,7 +1139,7 @@ static int parse_response(unsigned char *buf, int buflen, * address of ipv6.l.google.com. For caching purposes this * should not cause any issues. */ - if (*type == 5 && strncmp(question, name, qlen) == 0) { + if (*type == DNS_TYPE_CNAME && strncmp(question, name, qlen) == 0) { /* * So now the alias answered the question. This is * not very useful from caching point of view as @@ -1100,7 +1147,7 @@ static int parse_response(unsigned char *buf, int buflen, * question. We need to find the real A/AAAA record * of the alias and cache that. */ - unsigned char *end = NULL; + const unsigned char *end = NULL; int name_len = 0, output_len = 0; memset(rsp, 0, sizeof(rsp)); @@ -1116,8 +1163,6 @@ static int parse_response(unsigned char *buf, int buflen, name, sizeof(name) - 1, &name_len); if (ret != 0) { /* just ignore the error at this point */ - ptr = next; - next = NULL; continue; } @@ -1129,12 +1174,8 @@ static int parse_response(unsigned char *buf, int buflen, */ aliases = g_slist_prepend(aliases, g_strdup(name)); - ptr = next; - next = NULL; continue; - } - - if (*type == qtype) { + } else if (*type == qtype) { /* * We found correct type (A or AAAA) */ @@ -1151,7 +1192,7 @@ static int parse_response(unsigned char *buf, int buflen, */ if (*response_len + rsp_len > maxlen) { err = -ENOBUFS; - goto out; + break; } memcpy(response + *response_len, rsp, rsp_len); *response_len += rsp_len; @@ -1159,31 +1200,21 @@ static int parse_response(unsigned char *buf, int buflen, err = 0; } } - - ptr = next; - next = NULL; } -out: - for (list = aliases; list; list = list->next) + for (GSList *list = aliases; list; list = list->next) g_free(list->data); g_slist_free(aliases); return err; } -struct cache_timeout { - time_t current_time; - int max_timeout; - int try_harder; -}; - static gboolean cache_check_entry(gpointer key, gpointer value, gpointer user_data) { struct cache_timeout *data = user_data; struct cache_entry *entry = value; - int max_timeout; + time_t max_timeout; /* Scale the number of hits by half as part of cache aging */ @@ -1224,14 +1255,14 @@ static gboolean cache_check_entry(gpointer key, gpointer value, static void cache_cleanup(void) { - static int max_timeout; - struct cache_timeout data; + static time_t max_timeout; + struct cache_timeout data = { + .current_time = time(NULL), + .max_timeout = 0, + .try_harder = false + }; int count = 0; - data.current_time = time(NULL); - data.max_timeout = 0; - data.try_harder = 0; - /* * In the first pass, we only remove entries that have timed out. * We use a cache of the first time to expire to do this only @@ -1248,7 +1279,7 @@ static void cache_cleanup(void) * we also expire entries with a low hit count, * while aging the hit count at the same time. */ - data.try_harder = 1; + data.try_harder = true; if (count == 0) count = g_hash_table_foreach_remove(cache, cache_check_entry, &data); @@ -1278,23 +1309,11 @@ static gboolean cache_invalidate_entry(gpointer key, gpointer value, entry->want_refresh = true; /* delete the cached data */ - if (entry->ipv4) { - g_free(entry->ipv4->data); - g_free(entry->ipv4); - entry->ipv4 = NULL; - } - - if (entry->ipv6) { - g_free(entry->ipv6->data); - g_free(entry->ipv6); - entry->ipv6 = NULL; - } + cache_free_ipv4(entry); + cache_free_ipv6(entry); /* keep the entry if we want it refreshed, delete it otherwise */ - if (entry->want_refresh) - return FALSE; - else - return TRUE; + return entry->want_refresh ? FALSE : TRUE; } /* @@ -1315,25 +1334,24 @@ static void cache_invalidate(void) static void cache_refresh_entry(struct cache_entry *entry) { - cache_enforce_validity(entry); - if (entry->hits > 2 && !entry->ipv4) - entry->want_refresh = true; - if (entry->hits > 2 && !entry->ipv6) + if (entry->hits > 2 && (!entry->ipv4 || !entry->ipv6)) entry->want_refresh = true; if (entry->want_refresh) { - char *c; char dns_name[NS_MAXDNAME + 1]; + char *c; + entry->want_refresh = false; /* turn a DNS name into a hostname with dots */ strncpy(dns_name, entry->key, NS_MAXDNAME); c = dns_name; - while (c && *c) { - int jump; - jump = *c; + while (*c) { + /* fetch the size of the current component and replace + it by a dot */ + int jump = *c; *c = '.'; c += jump + 1; } @@ -1359,43 +1377,44 @@ static void cache_refresh(void) g_hash_table_foreach(cache, cache_refresh_iterator, NULL); } -static int reply_query_type(unsigned char *msg, int len) +static int reply_query_type(const unsigned char *msg, int len) { - unsigned char *c; - int l; - int type; - /* skip the header */ - c = msg + sizeof(struct domain_hdr); - len -= sizeof(struct domain_hdr); + const unsigned char *c = msg + DNS_HEADER_SIZE; + int type; + len -= DNS_HEADER_SIZE; if (len < 0) return 0; - /* now the query, which is a name and 2 16 bit words */ - l = dns_name_length(c); - c += l; + /* now the query, which is a name and 2 16 bit words for type and class */ + c += dns_name_length(c); + type = c[0] << 8 | c[1]; return type; } -static int cache_update(struct server_data *srv, unsigned char *msg, - unsigned int msg_len) +/* + * update the cache with the DNS reply found in msg + */ +static int cache_update(struct server_data *srv, const unsigned char *msg, size_t msg_len) { - int offset = protocol_offset(srv->protocol); - int err, qlen, ttl = 0; + const size_t offset = protocol_offset(srv->protocol); + int err, ttl = 0; + uint16_t *lenhdr; + size_t qlen; + bool is_new_entry = false; uint16_t answers = 0, type = 0, class = 0; struct domain_hdr *hdr = (void *)(msg + offset); - struct domain_question *q; + struct domain_question *q = NULL; struct cache_entry *entry; struct cache_data *data; char question[NS_MAXDNAME + 1]; unsigned char response[NS_MAXDNAME + 1]; - unsigned char *ptr; - unsigned int rsplen; - bool new_entry = true; - time_t current_time; + unsigned char *ptr = NULL; + size_t rsplen = sizeof(response) - 1; + const time_t current_time = time(NULL); if (cache_size >= MAX_CACHE_SIZE) { cache_cleanup(); @@ -1403,18 +1422,13 @@ static int cache_update(struct server_data *srv, unsigned char *msg, return 0; } - current_time = time(NULL); - /* don't do a cache refresh more than twice a minute */ if (next_refresh < current_time) { cache_refresh(); next_refresh = current_time + 30; } - if (offset < 0) - return 0; - - debug("offset %d hdr %p msg %p rcode %d", offset, hdr, msg, hdr->rcode); + debug("offset %zd hdr %p msg %p rcode %d", offset, hdr, msg, hdr->rcode); /* Continue only if response code is 0 (=ok) */ if (hdr->rcode != ns_r_noerror) @@ -1423,9 +1437,7 @@ static int cache_update(struct server_data *srv, unsigned char *msg, if (!cache) create_cache(); - rsplen = sizeof(response) - 1; question[sizeof(question) - 1] = '\0'; - err = parse_response(msg + offset, msg_len - offset, question, sizeof(question) - 1, &type, &class, &ttl, @@ -1438,26 +1450,29 @@ static int cache_update(struct server_data *srv, unsigned char *msg, */ if ((err == -ENOMSG || err == -ENOBUFS) && reply_query_type(msg + offset, - msg_len - offset) == 28) { + msg_len - offset) == DNS_TYPE_AAAA) { entry = g_hash_table_lookup(cache, question); if (entry && entry->ipv4 && !entry->ipv6) { - int cache_offset = 0; + struct cache_data *data = g_try_new(struct cache_data, 1); - data = g_try_new(struct cache_data, 1); if (!data) return -ENOMEM; data->inserted = entry->ipv4->inserted; data->type = type; data->answers = ntohs(hdr->ancount); data->timeout = entry->ipv4->timeout; - if (srv->protocol == IPPROTO_UDP) - cache_offset = 2; - data->data_len = msg_len + cache_offset; - data->data = ptr = g_malloc(data->data_len); - ptr[0] = (data->data_len - 2) / 256; - ptr[1] = (data->data_len - 2) - ptr[0] * 256; - if (srv->protocol == IPPROTO_UDP) - ptr += 2; + data->data_len = msg_len + + (offset ? 0 : DNS_HEADER_TCP_EXTRA_BYTES); + data->data = g_malloc(data->data_len); + ptr = data->data; + if (srv->protocol == IPPROTO_UDP) { + /* add the two bytes length header also for + * UDP responses */ + lenhdr = (void*)ptr; + *lenhdr = htons(data->data_len - + DNS_HEADER_TCP_EXTRA_BYTES); + ptr += DNS_HEADER_TCP_EXTRA_BYTES; + } data->valid_until = entry->ipv4->valid_until; data->cache_until = entry->ipv4->cache_until; memcpy(ptr, msg, msg_len); @@ -1466,9 +1481,7 @@ static int cache_update(struct server_data *srv, unsigned char *msg, * we will get a "hit" when we serve the response * out of the cache */ - entry->hits--; - if (entry->hits < 0) - entry->hits = 0; + entry->hits = entry->hits ? entry->hits - 1 : 0; return 0; } } @@ -1476,8 +1489,6 @@ static int cache_update(struct server_data *srv, unsigned char *msg, if (err < 0 || ttl == 0) return 0; - qlen = strlen(question); - /* * If the cache contains already data, check if the * type of the cached data is the same and do not add @@ -1485,7 +1496,11 @@ static int cache_update(struct server_data *srv, unsigned char *msg, * This is needed so that we can cache both A and AAAA * records for the same name. */ + entry = g_hash_table_lookup(cache, question); + data = NULL; + is_new_entry = !entry; + if (!entry) { entry = g_try_new(struct cache_entry, 1); if (!entry) @@ -1502,37 +1517,28 @@ static int cache_update(struct server_data *srv, unsigned char *msg, entry->want_refresh = false; entry->hits = 0; - if (type == 1) - entry->ipv4 = data; - else - entry->ipv6 = data; } else { - if (type == 1 && entry->ipv4) + if (type == DNS_TYPE_A && entry->ipv4) return 0; - - if (type == 28 && entry->ipv6) + else if (type == DNS_TYPE_AAAA && entry->ipv6) return 0; data = g_try_new(struct cache_data, 1); if (!data) return -ENOMEM; - if (type == 1) - entry->ipv4 = data; - else - entry->ipv6 = data; - /* * compensate for the hit we'll get for serving * the response out of the cache */ - entry->hits--; - if (entry->hits < 0) - entry->hits = 0; - - new_entry = false; + entry->hits = entry->hits ? entry->hits - 1 : 0; } + if (type == DNS_TYPE_A) + entry->ipv4 = data; + else + entry->ipv6 = data; + if (ttl < MIN_CACHE_TTL) ttl = MIN_CACHE_TTL; @@ -1540,14 +1546,21 @@ static int cache_update(struct server_data *srv, unsigned char *msg, data->type = type; data->answers = answers; data->timeout = ttl; + data->valid_until = current_time + ttl; + + qlen = strlen(question); /* - * The "2" in start of the length is the TCP offset. We allocate it - * here even for UDP packet because it simplifies the sending - * of cached packet. + * We allocate the extra TCP header bytes here even for UDP packet + * because it simplifies the sending of cached packet. */ - data->data_len = 2 + 12 + qlen + 1 + 2 + 2 + rsplen; - data->data = ptr = g_malloc(data->data_len); - data->valid_until = current_time + ttl; + data->data_len = DNS_TCP_HEADER_SIZE + qlen + 1 + 2 + 2 + rsplen; + data->data = g_malloc(data->data_len); + if (!data->data) { + g_free(entry->key); + g_free(data); + g_free(entry); + return -ENOMEM; + } /* * Restrict the cached DNS record TTL to some sane value @@ -1558,45 +1571,39 @@ static int cache_update(struct server_data *srv, unsigned char *msg, data->cache_until = round_down_ttl(current_time + ttl, ttl); - if (!data->data) { - g_free(entry->key); - g_free(data); - g_free(entry); - return -ENOMEM; - } + ptr = data->data; /* * We cache the two extra bytes at the start of the message - * in a TCP packet. When sending UDP packet, we skip the first + * in a TCP packet. When sending UDP packet, we pad the first * two bytes. This way we do not need to know the format * (UDP/TCP) of the cached message. */ - if (srv->protocol == IPPROTO_UDP) - memcpy(ptr + 2, msg, offset + 12); - else - memcpy(ptr, msg, offset + 12); + lenhdr = (void*)ptr; + *lenhdr = htons(data->data_len - DNS_HEADER_TCP_EXTRA_BYTES); + ptr += DNS_HEADER_TCP_EXTRA_BYTES; - ptr[0] = (data->data_len - 2) / 256; - ptr[1] = (data->data_len - 2) - ptr[0] * 256; - if (srv->protocol == IPPROTO_UDP) - ptr += 2; + memcpy(ptr, hdr, DNS_HEADER_SIZE); + ptr += DNS_HEADER_SIZE; - memcpy(ptr + offset + 12, question, qlen + 1); /* copy also the \0 */ + memcpy(ptr, question, qlen + 1); /* copy also the \0 */ + ptr += qlen + 1; - q = (void *) (ptr + offset + 12 + qlen + 1); + q = (void *)ptr; q->type = htons(type); q->class = htons(class); - memcpy(ptr + offset + 12 + qlen + 1 + sizeof(struct domain_question), - response, rsplen); + ptr += DNS_QUESTION_SIZE; + + memcpy(ptr, response, rsplen); - if (new_entry) { + if (is_new_entry) { g_hash_table_replace(cache, entry->key, entry); cache_size++; } debug("cache %d %squestion \"%s\" type %d ttl %d size %zd packet %u " "dns len %u", - cache_size, new_entry ? "new " : "old ", + cache_size, is_new_entry ? "new " : "old ", question, type, ttl, sizeof(*entry) + sizeof(*data) + data->data_len + qlen, data->data_len, @@ -1607,38 +1614,41 @@ static int cache_update(struct server_data *srv, unsigned char *msg, return 0; } -static int ns_resolv(struct server_data *server, struct request_data *req, - gpointer request, gpointer name) +/* + * attempts to answer the given request from cached replies. + * + * returns: + * > 0 on cache hit (answer is already sent out to client) + * == 0 on cache miss + * < 0 on error condition (errno) + */ +static int ns_try_resolv_from_cache( + struct request_data *req, gpointer request, const char *lookup) { - GList *list; - int sk, err, type = 0; - char *dot, *lookup = (char *) name; - struct cache_entry *entry; + uint16_t type = 0; + int ttl_left; + struct cache_data *data; + struct cache_entry *entry = cache_check(request, &type, req->protocol); + if (!entry) + return 0; - entry = cache_check(request, &type, req->protocol); - if (entry) { - int ttl_left = 0; - struct cache_data *data; + debug("cache hit %s type %s", lookup, type == 1 ? "A" : "AAAA"); - debug("cache hit %s type %s", lookup, type == 1 ? "A" : "AAAA"); - if (type == 1) - data = entry->ipv4; - else - data = entry->ipv6; + data = type == DNS_TYPE_A ? entry->ipv4 : entry->ipv6; - if (data) { - ttl_left = data->valid_until - time(NULL); - entry->hits++; - } + if (!data) + return 0; + + ttl_left = data->valid_until - time(NULL); + entry->hits++; - if (data && req->protocol == IPPROTO_TCP) { + switch(req->protocol) { + case IPPROTO_TCP: send_cached_response(req->client_sk, data->data, data->data_len, NULL, 0, IPPROTO_TCP, req->srcid, data->answers, ttl_left); return 1; - } - - if (data && req->protocol == IPPROTO_UDP) { + case IPPROTO_UDP: { int udp_sk = get_req_udp_socket(req); if (udp_sk < 0) @@ -1652,6 +1662,24 @@ static int ns_resolv(struct server_data *server, struct request_data *req, } } + return -EINVAL; +} + +static int ns_resolv(struct server_data *server, struct request_data *req, + gpointer request, gpointer name) +{ + int sk = -1; + const char *lookup = (const char *)name; + int err = ns_try_resolv_from_cache(req, request, lookup); + + if (err > 0) + /* cache hit */ + return 1; + else if (err != 0) + /* error other than cache miss, don't continue */ + return err; + + /* forward request to real DNS server */ sk = g_io_channel_unix_get_fd(server->channel); err = sendto(sk, request, req->request_len, MSG_NOSIGNAL, @@ -1667,54 +1695,51 @@ static int ns_resolv(struct server_data *server, struct request_data *req, req->numserv++; /* If we have more than one dot, we don't add domains */ - dot = strchr(lookup, '.'); - if (dot && dot != lookup + strlen(lookup) - 1) - return 0; + { + const char *dot = strchr(lookup, '.'); + if (dot && dot != lookup + strlen(lookup) - 1) + return 0; + } if (server->domains && server->domains->data) req->append_domain = true; - for (list = server->domains; list; list = list->next) { - char *domain; + for (GList *list = server->domains; list; list = list->next) { + int domlen, altlen; unsigned char alt[1024]; - struct domain_hdr *hdr = (void *) &alt; - int altlen, domlen, offset; - - domain = list->data; + const char *domain = list->data; + const size_t offset = protocol_offset(server->protocol); + struct domain_hdr *hdr = (void *) (&alt[0] + offset); if (!domain) continue; - offset = protocol_offset(server->protocol); - if (offset < 0) - return offset; - domlen = strlen(domain) + 1; + if (domlen < 5) return -EINVAL; - alt[offset] = req->altid & 0xff; - alt[offset + 1] = req->altid >> 8; + memcpy(alt + offset, &req->altid, sizeof(req->altid)); - memcpy(alt + offset + 2, request + offset + 2, 10); + memcpy(alt + offset + 2, request + offset + 2, DNS_HEADER_SIZE - 2); hdr->qdcount = htons(1); - altlen = append_query(alt + offset + 12, sizeof(alt) - 12, + altlen = append_query(alt + offset + DNS_HEADER_SIZE, sizeof(alt) - DNS_HEADER_SIZE - offset, name, domain); if (altlen < 0) return -EINVAL; - altlen += 12; + altlen += DNS_HEADER_SIZE; + altlen += offset; - memcpy(alt + offset + altlen, - request + offset + altlen - domlen, - req->request_len - altlen - offset + domlen); + memcpy(alt + altlen, + request + altlen - domlen, + req->request_len - altlen + domlen); if (server->protocol == IPPROTO_TCP) { - int req_len = req->request_len + domlen - 2; - - alt[0] = (req_len >> 8) & 0xff; - alt[1] = req_len & 0xff; + uint16_t req_len = req->request_len + domlen - DNS_HEADER_TCP_EXTRA_BYTES; + uint16_t *len_hdr = (void*)alt; + *len_hdr = htons(req_len); } debug("req %p dstid 0x%04x altid 0x%04x", req, req->dstid, @@ -1730,17 +1755,17 @@ static int ns_resolv(struct server_data *server, struct request_data *req, return 0; } -static char *convert_label(char *start, char *end, char *ptr, char *uptr, +static bool convert_label(const char *start, const char *end, const char *ptr, char *uptr, int remaining_len, int *used_comp, int *used_uncomp) { - int pos, comp_pos; + int comp_pos; char name[NS_MAXLABEL]; - pos = dn_expand((u_char *)start, (u_char *)end, (u_char *)ptr, + const int pos = dn_expand((const u_char *)start, (const u_char *)end, (const u_char *)ptr, name, NS_MAXLABEL); if (pos < 0) { debug("uncompress error [%d/%s]", errno, strerror(errno)); - goto out; + return false; } /* @@ -1750,20 +1775,17 @@ static char *convert_label(char *start, char *end, char *ptr, char *uptr, comp_pos = dn_comp(name, (u_char *)uptr, remaining_len, NULL, NULL); if (comp_pos < 0) { debug("compress error [%d/%s]", errno, strerror(errno)); - goto out; + return false; } *used_comp = pos; *used_uncomp = comp_pos; - return ptr; - -out: - return NULL; + return true; } -static char *uncompress(int16_t field_count, char *start, char *end, - char *ptr, char *uncompressed, int uncomp_len, +static const char* uncompress(int16_t field_count, const char *start, const char *end, + const char *ptr, char *uncompressed, int uncomp_len, char **uncompressed_ptr) { char *uptr = *uncompressed_ptr; /* position in result buffer */ @@ -1781,7 +1803,7 @@ static char *uncompress(int16_t field_count, char *start, char *end, if (!convert_label(start, end, ptr, name, NS_MAXLABEL, &pos, &comp_pos)) - goto out; + return NULL; /* * Copy the uncompressed resource record, type, class and \0 to @@ -1790,7 +1812,7 @@ static char *uncompress(int16_t field_count, char *start, char *end, ulen = strlen(name) + 1; if ((uptr + ulen) > uncomp_end) - goto out; + return NULL; memcpy(uptr, name, ulen); debug("pos %d ulen %d left %d name %s", pos, ulen, @@ -1806,15 +1828,15 @@ static char *uncompress(int16_t field_count, char *start, char *end, */ if ((uptr + NS_RRFIXEDSZ) > uncomp_end) { debug("uncompressed data too large for buffer"); - goto out; + return NULL; } memcpy(uptr, ptr, NS_RRFIXEDSZ); dns_type = uptr[0] << 8 | uptr[1]; dns_class = uptr[2] << 8 | uptr[3]; - if (dns_class != ns_c_in) - goto out; + if (dns_class != DNS_CLASS_IN) + return NULL; ptr += NS_RRFIXEDSZ; uptr += NS_RRFIXEDSZ; @@ -1824,11 +1846,11 @@ static char *uncompress(int16_t field_count, char *start, char *end, * Typically this portion is also compressed * so we need to uncompress it also when necessary. */ - if (dns_type == ns_t_cname) { + if (dns_type == DNS_TYPE_CNAME) { if (!convert_label(start, end, ptr, uptr, uncomp_len - (uptr - uncompressed), &pos, &comp_pos)) - goto out; + return NULL; uptr[-2] = comp_pos << 8; uptr[-1] = comp_pos & 0xff; @@ -1836,19 +1858,19 @@ static char *uncompress(int16_t field_count, char *start, char *end, uptr += comp_pos; ptr += pos; - } else if (dns_type == ns_t_a || dns_type == ns_t_aaaa) { + } else if (dns_type == DNS_TYPE_A || dns_type == DNS_TYPE_AAAA) { dlen = uptr[-2] << 8 | uptr[-1]; if ((ptr + dlen) > end || (uptr + dlen) > uncomp_end) { debug("data len %d too long", dlen); - goto out; + return NULL; } memcpy(uptr, ptr, dlen); uptr += dlen; ptr += dlen; - } else if (dns_type == ns_t_soa) { + } else if (dns_type == DNS_TYPE_SOA) { int total_len = 0; char *len_ptr; @@ -1856,7 +1878,7 @@ static char *uncompress(int16_t field_count, char *start, char *end, if (!convert_label(start, end, ptr, uptr, uncomp_len - (uptr - uncompressed), &pos, &comp_pos)) - goto out; + return NULL; total_len += comp_pos; len_ptr = &uptr[-2]; @@ -1867,7 +1889,7 @@ static char *uncompress(int16_t field_count, char *start, char *end, if (!convert_label(start, end, ptr, uptr, uncomp_len - (uptr - uncompressed), &pos, &comp_pos)) - goto out; + return NULL; total_len += comp_pos; ptr += pos; @@ -1880,7 +1902,7 @@ static char *uncompress(int16_t field_count, char *start, char *end, */ if ((uptr + 20) > uncomp_end || (ptr + 20) > end) { debug("soa record too long"); - goto out; + return NULL; } memcpy(uptr, ptr, 20); uptr += 20; @@ -1898,261 +1920,300 @@ static char *uncompress(int16_t field_count, char *start, char *end, } return ptr; - -out: - return NULL; } -static int strip_domains(char *name, char *answers, int maxlen) +/* + * removes the qualified domain name part from the given answer sections + * starting at 'answers', consisting of 'length' bytes. + * + * 'name' points the start of the unqualified host label including the leading + * length octet. + * + * returns the new (possibly shorter) length of remaining payload in the + * answers buffer, or a negative (errno) value to indicate error conditions. + */ +static int strip_domains(const char *name, char *answers, size_t length) { uint16_t data_len; - int name_len = strlen(name); - char *ptr, *start = answers, *end = answers + maxlen; + struct domain_rr *rr; + /* length of the name label including the length header octet */ + const size_t name_len = strlen(name); + const char *end = answers + length; - while (maxlen > 0) { - ptr = strstr(answers, name); + while (answers < end) { + char *ptr = strstr(answers, name); if (ptr) { char *domain = ptr + name_len; + /* this now points to the domain part length octet. */ if (*domain) { - int domain_len = strlen(domain); + /* + * length of the rest of the labels up to the + * null label (zero byte). + */ + const size_t domain_len = strlen(domain); + char *remaining = domain + domain_len; - memmove(answers + name_len, - domain + domain_len, - end - (domain + domain_len)); + /* + * now shift the rest of the answer sections + * to the left to get rid of the domain label + * part + */ + memmove(ptr + name_len, + remaining, + end - remaining); end -= domain_len; - maxlen -= domain_len; + length -= domain_len; } } + /* skip to the next answer section */ + + /* the labels up to the root null label */ answers += strlen(answers) + 1; - answers += 2 + 2 + 4; /* skip type, class and ttl fields */ + /* the fixed part of the RR */ + rr = (void*)answers; + if (answers + sizeof(*rr) > end) + return -EINVAL; + data_len = htons(rr->rdlen); + /* skip the rest of the RR */ + answers += sizeof(*rr); + answers += data_len; + } + + if (answers > end) + return -EINVAL; + + return length; +} + +/* + * Removes domain names from replies, if one has been appended during + * forwarding to the real DNS server. + * + * Returns: + * < 0 on error (abort processing reply) + * == 0 if the reply should be forwarded unmodified + * > 0 returns a new reply buffer in *new_reply on success. The return value + * indicates the new length of the data in *new_reply. + */ +static int dns_reply_fixup_domains( + const char *reply, size_t reply_len, + const size_t offset, + struct request_data *req, + char **new_reply) +{ + char uncompressed[NS_MAXDNAME]; + char *uptr, *answers; + size_t fixed_len; + int new_an_len; + const struct domain_hdr *hdr = (void *)(reply + offset); + const char *eom = reply + reply_len; + const uint16_t header_len = offset + DNS_HEADER_SIZE; + /* full header plus at least one byte for the hostname length */ + if (reply_len < header_len + 1) + return -EINVAL; + + const uint16_t section_counts[] = { + hdr->ancount, + hdr->nscount, + hdr->arcount + }; + + /* + * length octet of the hostname. + * ->hostname.domain.net + */ + const char *ptr = reply + header_len; + const uint8_t host_len = *ptr; + const char *domain = ptr + host_len + 1; + if (domain >= eom) + return -EINVAL; + + const uint16_t domain_len = host_len ? strnlen(domain, eom - domain) : 0; + + /* + * If the query type is anything other than A or AAAA, then bail out + * and pass the message as is. We only want to deal with IPv4 or IPv6 + * addresses. + */ + const struct qtype_qclass *qtc = (void*)(domain + domain_len + 1); + if (((const char*)(qtc + 1)) > eom) + return -EINVAL; + + const uint16_t dns_type = ntohs(qtc->qtype); + const uint16_t dns_class = ntohs(qtc->qclass); + + if (domain_len == 0) { + /* nothing to do */ + return 0; + } + + /* TODO: This condition looks wrong. It should probably be + * + * (dns_type != A && dns_type != AAAA) || dns_class != IN + * + * doing so, however, changes the behaviour of dnsproxy, e.g. MX + * records will be passed back to the client, but without the + * adjustment of the appended domain name. + */ + if (dns_type != DNS_TYPE_A && dns_type != DNS_TYPE_AAAA && + dns_class != DNS_CLASS_IN) { + debug("Pass msg dns type %d class %d", dns_type, dns_class); + return 0; + } + + /* + * Remove the domain name and replace it by the end of reply. Check if + * the domain is really there before trying to copy the data. We also + * need to uncompress the answers if necessary. The domain_len can be + * 0 because if the original query did not contain a domain name, then + * we are sending two packets, first without the domain name and the + * second packet with domain name. The append_domain is set to true + * even if we sent the first packet without domain name. In this case + * we end up in this branch. + */ + + /* NOTE: length checks up and including to qtype_qclass have already + been done above */ + + /* + * First copy host (without domain name) into tmp buffer. + */ + uptr = &uncompressed[0]; + memcpy(uptr, ptr, host_len + 1); + + uptr[host_len + 1] = '\0'; /* host termination */ + uptr += host_len + 2; + + /* + * Copy type and class fields of the question. + */ + memcpy(uptr, qtc, sizeof(*qtc)); + + /* + * ptr points to answers after this + */ + ptr = (void*)(qtc + 1); + uptr += sizeof(*qtc); + answers = uptr; + fixed_len = answers - uncompressed; + + /* + * We then uncompress the result to buffer so that we can rip off the + * domain name part from the question. First answers, then name server + * (authority) information, and finally additional record info. + */ + + for (size_t i = 0; i < NUM_ARRAY_ELEMENTS(section_counts); i++) { + ptr = uncompress(ntohs(section_counts[i]), reply + offset, eom, + ptr, uncompressed, NS_MAXDNAME, &uptr); + if (!ptr) { + /* failed to uncompress, pass on as is + * (TODO: good idea?) */ + return 0; + } + } + + /* + * The uncompressed buffer now contains an almost valid response. + * Final step is to get rid of the domain name because at least glibc + * gethostbyname() implementation does extra checks and expects to + * find an answer without domain name if we asked a query without + * domain part. Note that glibc getaddrinfo() works differently and + * accepts FQDN in answer + */ + new_an_len = strip_domains(uncompressed, answers, uptr - answers); + if (new_an_len < 0) { + debug("Corrupted packet"); + return -EINVAL; + } + + /* + * Because we have now uncompressed the answers we might have to + * create a bigger buffer to hold all that data. + * + * TODO: only create a bigger buffer if actually necessary, pass + * allocation size of input buffer via additional parameter. + */ - data_len = answers[0] << 8 | answers[1]; - answers += 2; /* skip the length field */ + reply_len = header_len + new_an_len + fixed_len; - if (answers + data_len > end) - return -EINVAL; + *new_reply = g_try_malloc(reply_len); + if (!*new_reply) + return -ENOMEM; - answers += data_len; - maxlen -= answers - ptr; - } + memcpy(*new_reply, reply, header_len); + memcpy(*new_reply + header_len, uncompressed, new_an_len + fixed_len); - return end - start; + return reply_len; } -static int forward_dns_reply(unsigned char *reply, int reply_len, int protocol, - struct server_data *data) +static struct request_data* lookup_request( + const unsigned char *reply, size_t len, int protocol) { - struct domain_hdr *hdr; + const size_t offset = protocol_offset(protocol); struct request_data *req; - int dns_id, sk, err, offset = protocol_offset(protocol); + struct domain_hdr *hdr = (void *)(reply + offset); - if (offset < 0) - return offset; - if (reply_len < 0) - return -EINVAL; - if (reply_len < offset + 1) - return -EINVAL; - if ((size_t)reply_len < sizeof(struct domain_hdr)) - return -EINVAL; + debug("Received %zd bytes (id 0x%04x)", len, hdr->id); - hdr = (void *)(reply + offset); - dns_id = reply[offset] | reply[offset + 1] << 8; + if (len < DNS_HEADER_SIZE + offset) + return NULL; - debug("Received %d bytes (id 0x%04x)", reply_len, dns_id); + req = find_request(hdr->id); - req = find_request(dns_id); if (!req) - return -EINVAL; + return NULL; debug("req %p dstid 0x%04x altid 0x%04x rcode %d", req, req->dstid, req->altid, hdr->rcode); - reply[offset] = req->srcid & 0xff; - reply[offset + 1] = req->srcid >> 8; - req->numresp++; - if (hdr->rcode == ns_r_noerror || !req->resp) { - unsigned char *new_reply = NULL; + return req; +} + +static int forward_dns_reply(char *reply, size_t reply_len, int protocol, + struct server_data *data, struct request_data *req) +{ + const size_t offset = protocol_offset(protocol); + struct domain_hdr *hdr = (void *)(reply + offset); + int err, sk; + + /* replace with original request ID from our client */ + hdr->id = req->srcid; + if (hdr->rcode == ns_r_noerror || !req->resp) { /* - * If the domain name was append - * remove it before forwarding the reply. - * If there were more than one question, then this - * domain name ripping can be hairy so avoid that - * and bail out in that that case. + * If the domain name was appended remove it before forwarding + * the reply. If there were more than one question, then this + * domain name ripping can be hairy so avoid that and bail out + * in that that case. * - * The reason we are doing this magic is that if the - * user's DNS client tries to resolv hostname without - * domain part, it also expects to get the result without - * a domain name part. + * The reason we are doing this magic is that if the user's + * DNS client tries to resolv hostname without domain part, it + * also expects to get the result without a domain name part. */ - if (req->append_domain && ntohs(hdr->qdcount) == 1) { - uint16_t domain_len = 0; - uint16_t header_len, payload_len; - uint16_t dns_type, dns_class; - uint8_t host_len, dns_type_pos; - char uncompressed[NS_MAXDNAME], *uptr; - char *ptr, *eom = (char *)reply + reply_len; - char *domain; - - /* - * ptr points to the first char of the hostname. - * ->hostname.domain.net - */ - header_len = offset + sizeof(struct domain_hdr); - if (reply_len < header_len) - return -EINVAL; - payload_len = reply_len - header_len; - - ptr = (char *)reply + header_len; - - host_len = *ptr; - domain = ptr + 1 + host_len; - if (domain > eom) - return -EINVAL; - - if (host_len > 0) - domain_len = strnlen(domain, eom - domain); - - /* - * If the query type is anything other than A or AAAA, - * then bail out and pass the message as is. - * We only want to deal with IPv4 or IPv6 addresses. - */ - dns_type_pos = host_len + 1 + domain_len + 1; - - if (ptr + (dns_type_pos + 3) > eom) - return -EINVAL; - dns_type = ptr[dns_type_pos] << 8 | - ptr[dns_type_pos + 1]; - dns_class = ptr[dns_type_pos + 2] << 8 | - ptr[dns_type_pos + 3]; - if (dns_type != ns_t_a && dns_type != ns_t_aaaa && - dns_class != ns_c_in) { - debug("Pass msg dns type %d class %d", - dns_type, dns_class); - goto pass; - } - - /* - * Remove the domain name and replace it by the end - * of reply. Check if the domain is really there - * before trying to copy the data. We also need to - * uncompress the answers if necessary. - * The domain_len can be 0 because if the original - * query did not contain a domain name, then we are - * sending two packets, first without the domain name - * and the second packet with domain name. - * The append_domain is set to true even if we sent - * the first packet without domain name. In this - * case we end up in this branch. - */ - if (domain_len > 0) { - int len = host_len + 1; - int new_len, fixed_len; - char *answers; - - if (len > payload_len) - return -EINVAL; - /* - * First copy host (without domain name) into - * tmp buffer. - */ - uptr = &uncompressed[0]; - memcpy(uptr, ptr, len); - - uptr[len] = '\0'; /* host termination */ - uptr += len + 1; - - /* - * Copy type and class fields of the question. - */ - ptr += len + domain_len + 1; - if (ptr + NS_QFIXEDSZ > eom) - return -EINVAL; - memcpy(uptr, ptr, NS_QFIXEDSZ); - - /* - * ptr points to answers after this - */ - ptr += NS_QFIXEDSZ; - uptr += NS_QFIXEDSZ; - answers = uptr; - fixed_len = answers - uncompressed; - if (ptr + offset > eom) - return -EINVAL; - - /* - * We then uncompress the result to buffer - * so that we can rip off the domain name - * part from the question. First answers, - * then name server (authority) information, - * and finally additional record info. - */ - - ptr = uncompress(ntohs(hdr->ancount), - (char *)reply + offset, eom, - ptr, uncompressed, NS_MAXDNAME, - &uptr); - if (!ptr) - goto out; - - ptr = uncompress(ntohs(hdr->nscount), - (char *)reply + offset, eom, - ptr, uncompressed, NS_MAXDNAME, - &uptr); - if (!ptr) - goto out; - - ptr = uncompress(ntohs(hdr->arcount), - (char *)reply + offset, eom, - ptr, uncompressed, NS_MAXDNAME, - &uptr); - if (!ptr) - goto out; - - /* - * The uncompressed buffer now contains almost - * valid response. Final step is to get rid of - * the domain name because at least glibc - * gethostbyname() implementation does extra - * checks and expects to find an answer without - * domain name if we asked a query without - * domain part. Note that glibc getaddrinfo() - * works differently and accepts FQDN in answer - */ - new_len = strip_domains(uncompressed, answers, - uptr - answers); - if (new_len < 0) { - debug("Corrupted packet"); - return -EINVAL; - } - - /* - * Because we have now uncompressed the answers - * we might have to create a bigger buffer to - * hold all that data. - */ - - reply_len = header_len + new_len + fixed_len; - - new_reply = g_try_malloc(reply_len); - if (!new_reply) - return -ENOMEM; - - memcpy(new_reply, reply, header_len); - memcpy(new_reply + header_len, uncompressed, - new_len + fixed_len); + char *new_reply = NULL; + if (req->append_domain && ntohs(hdr->qdcount) == 1) { + const int fixup_res = dns_reply_fixup_domains( + reply, reply_len, + offset, req, &new_reply); + if (fixup_res < 0) { + /* error occured */ + return fixup_res; + } else if (fixup_res > 0 && new_reply) { + /* new reply length */ + reply_len = fixup_res; reply = new_reply; + } else { + /* keep message as is */ } } - pass: g_free(req->resp); req->resplen = 0; @@ -2163,12 +2224,11 @@ static int forward_dns_reply(unsigned char *reply, int reply_len, int protocol, memcpy(req->resp, reply, reply_len); req->resplen = reply_len; - cache_update(data, reply, reply_len); + cache_update(data, (unsigned char*)reply, reply_len); g_free(new_reply); } -out: if (req->numresp < req->numserv) { if (hdr->rcode > ns_r_noerror) { return -EINVAL; @@ -2188,7 +2248,7 @@ out: err = sendto(sk, req->resp, req->resplen, 0, &req->sa, req->sa_len); } else { - uint16_t tcp_len = htons(req->resplen - 2); + const uint16_t tcp_len = htons(req->resplen - DNS_HEADER_TCP_EXTRA_BYTES); /* correct TCP message length */ memcpy(req->resp, &tcp_len, sizeof(tcp_len)); sk = req->client_sk; @@ -2201,8 +2261,6 @@ out: else debug("proto %d sent %d bytes to %d", protocol, err, sk); - destroy_request_data(req); - return err; } @@ -2266,8 +2324,10 @@ static gboolean udp_server_event(GIOChannel *channel, GIOCondition condition, gpointer user_data) { unsigned char buf[4096]; - int sk, len; + int sk, res; + ssize_t len; struct server_data *data = user_data; + struct request_data *req; if (condition & (G_IO_NVAL | G_IO_ERR | G_IO_HUP)) { connman_error("Error with UDP server %s", data->server); @@ -2276,10 +2336,22 @@ static gboolean udp_server_event(GIOChannel *channel, GIOCondition condition, } sk = g_io_channel_unix_get_fd(channel); - len = recv(sk, buf, sizeof(buf), 0); - forward_dns_reply(buf, len, IPPROTO_UDP, data); + if (len <= 0) + return TRUE; + + req = lookup_request(buf, len, IPPROTO_UDP); + + if (!req) + /* invalid / corrupt request */ + return TRUE; + + res = forward_dns_reply((char*)buf, len, IPPROTO_UDP, data, req); + + /* on success or no further responses are expected, destroy the req */ + if (res == 0 || req->numresp >= req->numserv) + destroy_request_data(req); return TRUE; } @@ -2287,10 +2359,9 @@ static gboolean udp_server_event(GIOChannel *channel, GIOCondition condition, static gboolean tcp_server_event(GIOChannel *channel, GIOCondition condition, gpointer user_data) { - int sk; + struct request_data *req; struct server_data *server = user_data; - - sk = g_io_channel_unix_get_fd(channel); + int sk = g_io_channel_unix_get_fd(channel); if (sk == 0) return FALSE; @@ -2308,14 +2379,13 @@ hangup: list = request_list; while (list) { - struct request_data *req = list->data; struct domain_hdr *hdr; + req = list->data; list = list->next; if (req->protocol == IPPROTO_UDP) continue; - - if (!req->request) + else if (!req->request) continue; /* @@ -2326,7 +2396,7 @@ hangup: if (req->numserv && --(req->numserv)) continue; - hdr = (void *) (req->request + 2); + hdr = (void *)(req->request + DNS_HEADER_TCP_EXTRA_BYTES); hdr->id = req->srcid; send_response(req->client_sk, req->request, req->request_len, NULL, 0, IPPROTO_TCP); @@ -2340,17 +2410,14 @@ hangup: } if ((condition & G_IO_OUT) && !server->connected) { - GSList *list; - GList *domains; bool no_request_sent = true; - struct server_data *udp_server; - - udp_server = find_server(server->index, server->server, - IPPROTO_UDP); + struct server_data *udp_server = find_server( + server->index, server->server, + IPPROTO_UDP); if (udp_server) { - for (domains = udp_server->domains; domains; + for (GList *domains = udp_server->domains; domains; domains = domains->next) { - char *dom = domains->data; + const char *dom = domains->data; debug("Adding domain %s to %s", dom, server->server); @@ -2375,9 +2442,11 @@ hangup: server->connected = true; server_list = g_slist_append(server_list, server); - for (list = request_list; list; ) { - struct request_data *req = list->data; + /* don't advance the list in the for loop, because we might + * need to delete elements while iterating through it */ + for (GSList *list = request_list; list; ) { int status; + req = list->data; if (req->protocol == IPPROTO_UDP) { list = list->next; @@ -2397,9 +2466,7 @@ hangup: request_list = g_slist_remove(request_list, req); destroy_request_data(req); continue; - } - - if (status < 0) { + } else if (status < 0) { list = list->next; continue; } @@ -2422,12 +2489,12 @@ hangup: } else if (condition & G_IO_IN) { struct partial_reply *reply = server->incoming_reply; int bytes_recv; + int res; if (!reply) { - unsigned char reply_len_buf[2]; uint16_t reply_len; - bytes_recv = recv(sk, reply_len_buf, 2, MSG_PEEK); + bytes_recv = recv(sk, &reply_len, sizeof(reply_len), MSG_PEEK); if (!bytes_recv) { goto hangup; } else if (bytes_recv < 0) { @@ -2437,11 +2504,12 @@ hangup: connman_error("DNS proxy error %s", strerror(errno)); goto hangup; - } else if (bytes_recv < 2) + } else if (bytes_recv < sizeof(reply_len)) return TRUE; - reply_len = reply_len_buf[1] | reply_len_buf[0] << 8; - reply_len += 2; + /* the header contains the length of the message + * excluding the two length bytes */ + reply_len = ntohs(reply_len) + DNS_HEADER_TCP_EXTRA_BYTES; debug("TCP reply %d bytes from %d", reply_len, sk); @@ -2450,6 +2518,8 @@ hangup: return TRUE; reply->len = reply_len; + /* we only peeked the two length bytes, so we have to + receive the complete message below proper. */ reply->received = 0; server->incoming_reply = reply; @@ -2472,15 +2542,30 @@ hangup: reply->received += bytes_recv; } - forward_dns_reply(reply->buf, reply->received, IPPROTO_TCP, - server); + req = lookup_request(reply->buf, reply->received, IPPROTO_TCP); + + if (!req) + /* invalid / corrupt request */ + return TRUE; + + res = forward_dns_reply((char*)reply->buf, reply->received, IPPROTO_TCP, server, req); g_free(reply); server->incoming_reply = NULL; - destroy_server(server); + /* on success or if no further responses are expected close + * connection */ + if (res == 0 || req->numresp >= req->numserv) { + destroy_request_data(req); + destroy_server(server); + return FALSE; + } - return FALSE; + /* + * keep the TCP connection open, there are more + * requests to be answered + */ + return TRUE; } return TRUE; @@ -2490,7 +2575,7 @@ static gboolean tcp_idle_timeout(gpointer user_data) { struct server_data *server = user_data; - debug(""); + debug("\n"); if (!server) return FALSE; @@ -2502,15 +2587,15 @@ static gboolean tcp_idle_timeout(gpointer user_data) static int server_create_socket(struct server_data *data) { - int sk, err; + int err; char *interface; + int sk = socket(data->server_addr->sa_family, + data->protocol == IPPROTO_TCP ? SOCK_STREAM : SOCK_DGRAM, + data->protocol); debug("index %d server %s proto %d", data->index, data->server, data->protocol); - sk = socket(data->server_addr->sa_family, - data->protocol == IPPROTO_TCP ? SOCK_STREAM : SOCK_DGRAM, - data->protocol); if (sk < 0) { err = errno; connman_error("Failed to create server %s socket", @@ -2581,9 +2666,7 @@ static int server_create_socket(struct server_data *data) static void enable_fallback(bool enable) { - GSList *list; - - for (list = server_list; list; list = list->next) { + for (GSList *list = server_list; list; list = list->next) { struct server_data *data = list->data; if (data->index != -1) @@ -2598,17 +2681,30 @@ static void enable_fallback(bool enable) } } +static unsigned int get_enabled_server_number(void) +{ + GSList *list; + unsigned int result = 0; + + for (list = server_list; list; list = list->next) { + struct server_data *data = list->data; + + if (data->index != -1 && data->enabled == true) + result++; + } + return result; +} + static struct server_data *create_server(int index, const char *domain, const char *server, int protocol) { - struct server_data *data; + struct server_data *data = g_try_new0(struct server_data, 1); struct addrinfo hints, *rp; int ret; DBG("index %d server %s", index, server); - data = g_try_new0(struct server_data, 1); if (!data) { connman_error("Failed to allocate server %s data", server); return NULL; @@ -2621,20 +2717,7 @@ static struct server_data *create_server(int index, data->protocol = protocol; memset(&hints, 0, sizeof(hints)); - - switch (protocol) { - case IPPROTO_UDP: - hints.ai_socktype = SOCK_DGRAM; - break; - - case IPPROTO_TCP: - hints.ai_socktype = SOCK_STREAM; - break; - - default: - destroy_server(data); - return NULL; - } + hints.ai_socktype = socket_type(protocol, 0); hints.ai_family = AF_UNSPEC; hints.ai_flags = AI_NUMERICSERV | AI_NUMERICHOST; @@ -2686,6 +2769,9 @@ static struct server_data *create_server(int index, DBG("Adding DNS server %s", data->server); enable_fallback(false); + } else if (data->index == -1 && get_enabled_server_number() == 0) { + data->enabled = true; + DBG("Adding fallback DNS server %s", data->server); } server_list = g_slist_append(server_list, data); @@ -2697,9 +2783,7 @@ static struct server_data *create_server(int index, static bool resolv(struct request_data *req, gpointer request, gpointer name) { - GSList *list; - - for (list = server_list; list; list = list->next) { + for (GSList *list = server_list; list; list = list->next) { struct server_data *data = list->data; if (data->protocol == IPPROTO_TCP) { @@ -2728,26 +2812,22 @@ static bool resolv(struct request_data *req, static void update_domain(int index, const char *domain, bool append) { - GSList *list; - DBG("index %d domain %s", index, domain); if (!domain) return; - for (list = server_list; list; list = list->next) { + for (GSList *list = server_list; list; list = list->next) { struct server_data *data = list->data; - GList *dom_list; char *dom = NULL; bool dom_found = false; if (data->index < 0) continue; - - if (data->index != index) + else if (data->index != index) continue; - for (dom_list = data->domains; dom_list; + for (GList *dom_list = data->domains; dom_list; dom_list = dom_list->next) { dom = dom_list->data; @@ -2780,9 +2860,7 @@ static void remove_domain(int index, const char *domain) static void flush_requests(struct server_data *server) { - GSList *list; - - list = request_list; + GSList *list = request_list; while (list) { struct request_data *req = list->data; @@ -2810,22 +2888,20 @@ int __connman_dnsproxy_append(int index, const char *domain, const char *server) { struct server_data *data; - DBG("index %d server %s", index, server); - if (!server && !domain) - return -EINVAL; - if (!server) { - append_domain(index, domain); - - return 0; + if (!domain) { + return -EINVAL; + } else { + append_domain(index, domain); + return 0; + } } if (g_str_equal(server, "127.0.0.1")) return -ENODEV; - - if (g_str_equal(server, "::1")) + else if (g_str_equal(server, "::1")) return -ENODEV; data = find_server(index, server, IPPROTO_UDP); @@ -2843,11 +2919,9 @@ int __connman_dnsproxy_append(int index, const char *domain, return 0; } -static void remove_server(int index, const char *domain, - const char *server, int protocol) +static void remove_server(int index, const char *server, int protocol) { struct server_data *data; - GSList *list; data = find_server(index, server, protocol); if (!data) @@ -2855,14 +2929,8 @@ static void remove_server(int index, const char *domain, destroy_server(data); - for (list = server_list; list; list = list->next) { - struct server_data *data = list->data; - - if (data->index != -1 && data->enabled == true) - return; - } - - enable_fallback(true); + if (get_enabled_server_number() == 0) + enable_fallback(true); } int __connman_dnsproxy_remove(int index, const char *domain, @@ -2870,34 +2938,31 @@ int __connman_dnsproxy_remove(int index, const char *domain, { DBG("index %d server %s", index, server); - if (!server && !domain) - return -EINVAL; - if (!server) { - remove_domain(index, domain); - - return 0; + if (!domain) { + return -EINVAL; + } else { + remove_domain(index, domain); + return 0; + } } if (g_str_equal(server, "127.0.0.1")) return -ENODEV; - - if (g_str_equal(server, "::1")) + else if (g_str_equal(server, "::1")) return -ENODEV; - remove_server(index, domain, server, IPPROTO_UDP); - remove_server(index, domain, server, IPPROTO_TCP); + remove_server(index, server, IPPROTO_UDP); + remove_server(index, server, IPPROTO_TCP); return 0; } static void dnsproxy_offline_mode(bool enabled) { - GSList *list; - DBG("enabled %d", enabled); - for (list = server_list; list; list = list->next) { + for (GSList *list = server_list; list; list = list->next) { struct server_data *data = list->data; if (!enabled) { @@ -2915,10 +2980,8 @@ static void dnsproxy_offline_mode(bool enabled) static void dnsproxy_default_changed(struct connman_service *service) { - bool server_enabled = false; - GSList *list; - int index; - int vpn_index; + bool any_server_enabled = false; + int index, vpn_index; DBG("service %p", service); @@ -2942,13 +3005,13 @@ static void dnsproxy_default_changed(struct connman_service *service) */ vpn_index = __connman_connection_get_vpn_index(index); - for (list = server_list; list; list = list->next) { + for (GSList *list = server_list; list; list = list->next) { struct server_data *data = list->data; if (data->index == index) { DBG("Enabling DNS server %s", data->server); data->enabled = true; - server_enabled = true; + any_server_enabled = true; } else if (data->index == vpn_index) { DBG("Enabling DNS server of VPN %s", data->server); data->enabled = true; @@ -2958,7 +3021,7 @@ static void dnsproxy_default_changed(struct connman_service *service) } } - if (!server_enabled) + if (!any_server_enabled) enable_fallback(true); cache_refresh(); @@ -3006,47 +3069,59 @@ static const struct connman_notifier dnsproxy_notifier = { .service_state_changed = dnsproxy_service_state_changed, }; -static const unsigned char opt_edns0_type[2] = { 0x00, 0x29 }; - +/* + * Parses the given request buffer. `buf´ is expected to be the start of the + * domain_hdr structure i.e. the TCP length header is not handled by this + * function. + * Returns the ascii string dot representation of the query in `name´, which + * must be able to hold `size´ bytes. + * + * Returns < 0 on error (errno) or zero on success. + */ static int parse_request(unsigned char *buf, size_t len, - char *name, unsigned int size) + char *name, size_t size) { + static const unsigned char OPT_EDNS0_TYPE[2] = { 0x00, 0x29 }; struct domain_hdr *hdr = (void *) buf; - uint16_t qdcount = ntohs(hdr->qdcount); - uint16_t ancount = ntohs(hdr->ancount); - uint16_t nscount = ntohs(hdr->nscount); - uint16_t arcount = ntohs(hdr->arcount); - unsigned char *ptr; - unsigned int remain, used = 0; - - if (len < sizeof(*hdr) + sizeof(struct qtype_qclass) || - hdr->qr || qdcount != 1 || ancount || nscount) { - DBG("Dropped DNS request qr %d with len %zd qdcount %d " - "ancount %d nscount %d", hdr->qr, len, qdcount, ancount, - nscount); + uint16_t qdcount, ancount, nscount, arcount; + unsigned char *ptr = buf + DNS_HEADER_SIZE; + size_t remain = len - DNS_HEADER_SIZE; + size_t used = 0; + if (len < DNS_HEADER_SIZE + DNS_QTYPE_QCLASS_SIZE) { + DBG("Dropped DNS request with short length %zd", len); return -EINVAL; } if (!name || !size) return -EINVAL; + qdcount = ntohs(hdr->qdcount); + ancount = ntohs(hdr->ancount); + nscount = ntohs(hdr->nscount); + arcount = ntohs(hdr->arcount); + + if (hdr->qr || qdcount != 1 || ancount || nscount) { + DBG("Dropped DNS request with bad flags/counts qr %d " + "with len %zd qdcount %d ancount %d nscount %d", + hdr->qr, len, qdcount, ancount, nscount); + + return -EINVAL; + } + debug("id 0x%04x qr %d opcode %d qdcount %d arcount %d", hdr->id, hdr->qr, hdr->opcode, qdcount, arcount); name[0] = '\0'; - ptr = buf + sizeof(struct domain_hdr); - remain = len - sizeof(struct domain_hdr); - + /* parse DNS query string into `name' out parameter */ while (remain > 0) { uint8_t label_len = *ptr; if (label_len == 0x00) { - uint8_t class; - struct qtype_qclass *q = - (struct qtype_qclass *)(ptr + 1); + struct qtype_qclass *q = (struct qtype_qclass *)(ptr + 1); + uint16_t class; if (remain < sizeof(*q)) { DBG("Dropped malformed DNS query"); @@ -3054,7 +3129,7 @@ static int parse_request(unsigned char *buf, size_t len, } class = ntohs(q->qclass); - if (class != 1 && class != 255) { + if (class != DNS_CLASS_IN && class != DNS_CLASS_ANY) { DBG("Dropped non-IN DNS class %d", class); return -EINVAL; } @@ -3071,18 +3146,17 @@ static int parse_request(unsigned char *buf, size_t len, strcat(name, "."); used += label_len + 1; - ptr += label_len + 1; remain -= label_len + 1; } - if (arcount && remain >= sizeof(struct domain_rr) + 1 && !ptr[0] && - ptr[1] == opt_edns0_type[0] && ptr[2] == opt_edns0_type[1]) { + if (arcount && remain >= DNS_RR_SIZE + 1 && !ptr[0] && + ptr[1] == OPT_EDNS0_TYPE[0] && ptr[2] == OPT_EDNS0_TYPE[1]) { struct domain_rr *edns0 = (struct domain_rr *)(ptr + 1); DBG("EDNS0 buffer size %u", ntohs(edns0->class)); } else if (!arcount && remain) { - DBG("DNS request with %d garbage bytes", remain); + DBG("DNS request with %zd garbage bytes", remain); } debug("query %s", name); @@ -3119,7 +3193,7 @@ static void client_reset(struct tcp_partial_client_data *client) client->buf_end = 0; } -static unsigned int get_msg_len(unsigned char *buf) +static size_t get_msg_len(const unsigned char *buf) { return buf[0]<<8 | buf[1]; } @@ -3130,15 +3204,14 @@ static bool read_tcp_data(struct tcp_partial_client_data *client, { char query[TCP_MAX_BUF_LEN]; struct request_data *req; - int client_sk, err; - unsigned int msg_len; - GSList *list; + struct domain_hdr *hdr; + int client_sk = g_io_channel_unix_get_fd(client->channel); + int err; + size_t msg_len; bool waiting_for_connect = false; - int qtype = 0; + uint16_t qtype = 0; struct cache_entry *entry; - client_sk = g_io_channel_unix_get_fd(client->channel); - if (read_len == 0) { debug("client %d closed, pending %d bytes", client_sk, client->buf_end); @@ -3151,34 +3224,36 @@ static bool read_tcp_data(struct tcp_partial_client_data *client, client->buf_end += read_len; - if (client->buf_end < 2) + /* we need at least the message length header */ + if (client->buf_end < DNS_HEADER_TCP_EXTRA_BYTES) return true; msg_len = get_msg_len(client->buf); if (msg_len > TCP_MAX_BUF_LEN) { - debug("client %d sent too much data %d", client_sk, msg_len); + debug("client %d sent too much data %zd", client_sk, msg_len); g_hash_table_remove(partial_tcp_req_table, GINT_TO_POINTER(client_sk)); return false; } read_another: - debug("client %d msg len %d end %d past end %d", client_sk, msg_len, + debug("client %d msg len %zd end %d past end %zd", client_sk, msg_len, client->buf_end, client->buf_end - (msg_len + 2)); if (client->buf_end < (msg_len + 2)) { - debug("client %d still missing %d bytes", + debug("client %d still missing %zd bytes", client_sk, msg_len + 2 - client->buf_end); return true; } - debug("client %d all data %d received", client_sk, msg_len); + debug("client %d all data %zd received", client_sk, msg_len); - err = parse_request(client->buf + 2, msg_len, - query, sizeof(query)); + err = parse_request(client->buf + DNS_HEADER_TCP_EXTRA_BYTES, + msg_len, query, sizeof(query)); if (err < 0 || (g_slist_length(server_list) == 0)) { - send_response(client_sk, client->buf, msg_len + 2, + send_response(client_sk, client->buf, + msg_len + DNS_HEADER_TCP_EXTRA_BYTES, NULL, 0, IPPROTO_TCP); return true; } @@ -3193,13 +3268,15 @@ read_another: req->protocol = IPPROTO_TCP; req->family = client->family; - req->srcid = client->buf[2] | (client->buf[3] << 8); + hdr = (void*)(client->buf + DNS_HEADER_TCP_EXTRA_BYTES); + + memcpy(&req->srcid, &hdr->id, sizeof(req->srcid)); req->dstid = get_id(); req->altid = get_id(); - req->request_len = msg_len + 2; + req->request_len = msg_len + DNS_HEADER_TCP_EXTRA_BYTES; - client->buf[2] = req->dstid & 0xff; - client->buf[3] = req->dstid >> 8; + /* replace ID the request for forwarding */ + memcpy(&hdr->id, &req->dstid, sizeof(hdr->id)); req->numserv = 0; req->ifdata = client->ifdata; @@ -3211,17 +3288,12 @@ read_another: */ entry = cache_check(client->buf, &qtype, IPPROTO_TCP); if (entry) { - int ttl_left = 0; - struct cache_data *data; - - debug("cache hit %s type %s", query, qtype == 1 ? "A" : "AAAA"); - if (qtype == 1) - data = entry->ipv4; - else - data = entry->ipv6; + debug("cache hit %s type %s", query, qtype == DNS_TYPE_A ? "A" : "AAAA"); + struct cache_data *data = qtype == DNS_TYPE_A ? + entry->ipv4 : entry->ipv6; if (data) { - ttl_left = data->valid_until - time(NULL); + int ttl_left = data->valid_until - time(NULL); entry->hits++; send_cached_response(client_sk, data->data, @@ -3234,7 +3306,7 @@ read_another: debug("data missing, ignoring cache for this query"); } - for (list = server_list; list; list = list->next) { + for (GSList *list = server_list; list; list = list->next) { struct server_data *data = list->data; if (data->protocol != IPPROTO_UDP || !data->enabled) @@ -3285,8 +3357,8 @@ read_another: request_list = g_slist_append(request_list, req); out: - if (client->buf_end > (msg_len + 2)) { - debug("client %d buf %p -> %p end %d len %d new %d", + if (client->buf_end > (msg_len + DNS_HEADER_TCP_EXTRA_BYTES)) { + debug("client %d buf %p -> %p end %d len %d new %zd", client_sk, client->buf + msg_len + 2, client->buf, client->buf_end, @@ -3302,7 +3374,7 @@ out: */ msg_len = get_msg_len(client->buf); if ((msg_len + 2) == client->buf_end) { - debug("client %d reading another %d bytes", client_sk, + debug("client %d reading another %zd bytes", client_sk, msg_len + 2); goto read_another; } @@ -3328,15 +3400,7 @@ static gboolean tcp_client_event(GIOChannel *channel, GIOCondition condition, gpointer user_data) { struct tcp_partial_client_data *client = user_data; - struct sockaddr_in6 client_addr6; - socklen_t client_addr6_len = sizeof(client_addr6); - struct sockaddr_in client_addr4; - socklen_t client_addr4_len = sizeof(client_addr4); - void *client_addr; - socklen_t *client_addr_len; - int len, client_sk; - - client_sk = g_io_channel_unix_get_fd(channel); + int client_sk = g_io_channel_unix_get_fd(channel); if (condition & (G_IO_NVAL | G_IO_ERR | G_IO_HUP)) { g_hash_table_remove(partial_tcp_req_table, @@ -3346,6 +3410,13 @@ static gboolean tcp_client_event(GIOChannel *channel, GIOCondition condition, return FALSE; } + struct sockaddr_in6 client_addr6; + socklen_t client_addr6_len = sizeof(client_addr6); + struct sockaddr_in client_addr4; + socklen_t client_addr4_len = sizeof(client_addr4); + void *client_addr; + socklen_t *client_addr_len; + switch (client->family) { case AF_INET: client_addr = &client_addr4; @@ -3362,7 +3433,7 @@ static gboolean tcp_client_event(GIOChannel *channel, GIOCondition condition, return FALSE; } - len = recvfrom(client_sk, client->buf + client->buf_end, + const int len = recvfrom(client_sk, client->buf + client->buf_end, TCP_MAX_BUF_LEN - client->buf_end, 0, client_addr, client_addr_len); if (len < 0) { @@ -3382,9 +3453,7 @@ static gboolean tcp_client_event(GIOChannel *channel, GIOCondition condition, static gboolean client_timeout(gpointer user_data) { struct tcp_partial_client_data *client = user_data; - int sock; - - sock = g_io_channel_unix_get_fd(client->channel); + int sock = g_io_channel_unix_get_fd(client->channel); debug("client %d timeout pending %d bytes", sock, client->buf_end); @@ -3397,8 +3466,12 @@ static bool tcp_listener_event(GIOChannel *channel, GIOCondition condition, struct listener_data *ifdata, int family, guint *listener_watch) { - int sk, client_sk, len; - unsigned int msg_len; + int sk = -1, client_sk = -1; + int recv_len; + size_t msg_len; + fd_set readfds; + struct timeval tv = {.tv_sec = 0, .tv_usec = 0}; + struct tcp_partial_client_data *client; struct sockaddr_in6 client_addr6; socklen_t client_addr6_len = sizeof(client_addr6); @@ -3406,8 +3479,6 @@ static bool tcp_listener_event(GIOChannel *channel, GIOCondition condition, socklen_t client_addr4_len = sizeof(client_addr4); void *client_addr; socklen_t *client_addr_len; - struct timeval tv; - fd_set readfds; debug("condition 0x%02x channel %p ifdata %p family %d", condition, channel, ifdata, family); @@ -3432,29 +3503,27 @@ static bool tcp_listener_event(GIOChannel *channel, GIOCondition condition, client_addr_len = &client_addr6_len; } - tv.tv_sec = tv.tv_usec = 0; FD_ZERO(&readfds); FD_SET(sk, &readfds); + /* TODO: check select return code */ select(sk + 1, &readfds, NULL, NULL, &tv); - if (FD_ISSET(sk, &readfds)) { - client_sk = accept(sk, client_addr, client_addr_len); - debug("client %d accepted", client_sk); - } else { + if (!FD_ISSET(sk, &readfds)) { debug("No data to read from master %d, waiting.", sk); return true; } + client_sk = accept(sk, client_addr, client_addr_len); if (client_sk < 0) { connman_error("Accept failure on TCP listener"); *listener_watch = 0; return false; } + debug("client %d accepted", client_sk); fcntl(client_sk, F_SETFL, O_NONBLOCK); - client = g_hash_table_lookup(partial_tcp_req_table, - GINT_TO_POINTER(client_sk)); + client = g_hash_table_lookup(partial_tcp_req_table, GINT_TO_POINTER(client_sk)); if (!client) { client = g_try_new0(struct tcp_partial_client_data, 1); if (!client) { @@ -3498,8 +3567,8 @@ static bool tcp_listener_event(GIOChannel *channel, GIOCondition condition, * proceed normally, otherwise read the bits until everything * is received or timeout occurs. */ - len = recv(client_sk, client->buf, TCP_MAX_BUF_LEN, 0); - if (len < 0) { + recv_len = recv(client_sk, client->buf, TCP_MAX_BUF_LEN, 0); + if (recv_len < 0) { if (errno == EAGAIN || errno == EWOULDBLOCK) { debug("client %d no data to read, waiting", client_sk); return true; @@ -3512,15 +3581,15 @@ static bool tcp_listener_event(GIOChannel *channel, GIOCondition condition, return true; } - if (len < 2) { + if (recv_len < DNS_HEADER_TCP_EXTRA_BYTES) { debug("client %d not enough data to read, waiting", client_sk); - client->buf_end += len; + client->buf_end += recv_len; return true; } msg_len = get_msg_len(client->buf); if (msg_len > TCP_MAX_BUF_LEN) { - debug("client %d invalid message length %u ignoring packet", + debug("client %d invalid message length %zd ignoring packet", client_sk, msg_len); g_hash_table_remove(partial_tcp_req_table, GINT_TO_POINTER(client_sk)); @@ -3531,15 +3600,15 @@ static bool tcp_listener_event(GIOChannel *channel, GIOCondition condition, * The packet length bytes do not contain the total message length, * that is the reason to -2 below. */ - if (msg_len != (unsigned int)(len - 2)) { - debug("client %d sent %d bytes but expecting %u pending %d", - client_sk, len, msg_len + 2, msg_len + 2 - len); + if (msg_len != (size_t)(recv_len - DNS_HEADER_TCP_EXTRA_BYTES)) { + debug("client %d sent %d bytes but expecting %zd pending %zd", + client_sk, recv_len, msg_len + 2, msg_len + 2 - recv_len); - client->buf_end += len; + client->buf_end += recv_len; return true; } - return read_tcp_data(client, client_addr, *client_addr_len, len); + return read_tcp_data(client, client_addr, *client_addr_len, recv_len); } static gboolean tcp4_listener_event(GIOChannel *channel, GIOCondition condition, @@ -3566,14 +3635,16 @@ static bool udp_listener_event(GIOChannel *channel, GIOCondition condition, { unsigned char buf[768]; char query[512]; - struct request_data *req; + struct request_data *req = NULL; + struct domain_hdr *hdr = NULL; + int sk = -1, err, len; + struct sockaddr_in6 client_addr6; socklen_t client_addr6_len = sizeof(client_addr6); struct sockaddr_in client_addr4; socklen_t client_addr4_len = sizeof(client_addr4); void *client_addr; socklen_t *client_addr_len; - int sk, err, len; if (condition & (G_IO_NVAL | G_IO_ERR | G_IO_HUP)) { connman_error("Error with UDP listener channel"); @@ -3581,8 +3652,6 @@ static bool udp_listener_event(GIOChannel *channel, GIOCondition condition, return false; } - sk = g_io_channel_unix_get_fd(channel); - if (family == AF_INET) { client_addr = &client_addr4; client_addr_len = &client_addr4_len; @@ -3592,6 +3661,7 @@ static bool udp_listener_event(GIOChannel *channel, GIOCondition condition, } memset(client_addr, 0, *client_addr_len); + sk = g_io_channel_unix_get_fd(channel); len = recvfrom(sk, buf, sizeof(buf), 0, client_addr, client_addr_len); if (len < 2) return true; @@ -3615,13 +3685,14 @@ static bool udp_listener_event(GIOChannel *channel, GIOCondition condition, req->protocol = IPPROTO_UDP; req->family = family; - req->srcid = buf[0] | (buf[1] << 8); + hdr = (void*)buf; + + req->srcid = hdr->id; req->dstid = get_id(); req->altid = get_id(); req->request_len = len; - buf[0] = req->dstid & 0xff; - buf[1] = req->dstid >> 8; + hdr->id = req->dstid; req->numserv = 0; req->ifdata = ifdata; @@ -3662,42 +3733,26 @@ static gboolean udp6_listener_event(GIOChannel *channel, GIOCondition condition, static GIOChannel *get_listener(int family, int protocol, int index) { - GIOChannel *channel; - const char *proto; + GIOChannel *channel = NULL; union { struct sockaddr sa; struct sockaddr_in6 sin6; struct sockaddr_in sin; } s; socklen_t slen; - int sk, type; + const char *proto = protocol_label(protocol); + const int type = socket_type(protocol, SOCK_CLOEXEC); char *interface; + int sk = socket(family, type, protocol); debug("family %d protocol %d index %d", family, protocol, index); - switch (protocol) { - case IPPROTO_UDP: - proto = "UDP"; - type = SOCK_DGRAM | SOCK_CLOEXEC; - break; - - case IPPROTO_TCP: - proto = "TCP"; - type = SOCK_STREAM | SOCK_CLOEXEC; - break; - - default: - return NULL; - } - - sk = socket(family, type, protocol); - if (sk < 0 && family == AF_INET6 && errno == EAFNOSUPPORT) { - connman_error("No IPv6 support"); - return NULL; - } - if (sk < 0) { - connman_error("Failed to create %s listener socket", proto); + if (family == AF_INET6 && errno == EAFNOSUPPORT) { + connman_error("No IPv6 support"); + } else { + connman_error("Failed to create %s listener socket", proto); + } return NULL; } @@ -3718,7 +3773,7 @@ static GIOChannel *get_listener(int family, int protocol, int index) if (family == AF_INET6) { memset(&s.sin6, 0, sizeof(s.sin6)); s.sin6.sin6_family = AF_INET6; - s.sin6.sin6_port = htons(53); + s.sin6.sin6_port = htons(dns_listen_port); slen = sizeof(s.sin6); if (__connman_inet_get_interface_address(index, @@ -3735,7 +3790,7 @@ static GIOChannel *get_listener(int family, int protocol, int index) } else if (family == AF_INET) { memset(&s.sin, 0, sizeof(s.sin)); s.sin.sin_family = AF_INET; - s.sin.sin_port = htons(53); + s.sin.sin_port = htons(dns_listen_port); slen = sizeof(s.sin); if (__connman_inet_get_interface_address(index, @@ -3756,7 +3811,6 @@ static GIOChannel *get_listener(int family, int protocol, int index) } if (protocol == IPPROTO_TCP) { - if (listen(sk, 10) < 0) { connman_error("Failed to listen on TCP socket %d/%s", -errno, strerror(errno)); @@ -3870,9 +3924,7 @@ static void destroy_tcp_listener(struct listener_data *ifdata) static int create_listener(struct listener_data *ifdata) { - int err, index; - - err = create_dns_listener(IPPROTO_UDP, ifdata); + int err = create_dns_listener(IPPROTO_UDP, ifdata); if ((err & UDP_FAILED) == UDP_FAILED) return -EIO; @@ -3882,7 +3934,7 @@ static int create_listener(struct listener_data *ifdata) return -EIO; } - index = connman_inet_ifindex("lo"); + int index = connman_inet_ifindex("lo"); if (ifdata->index == index) { if ((err & IPv6_FAILED) != IPv6_FAILED) __connman_resolvfile_append(index, NULL, "::1"); @@ -3896,16 +3948,14 @@ static int create_listener(struct listener_data *ifdata) static void destroy_listener(struct listener_data *ifdata) { - int index; - GSList *list; + int index = connman_inet_ifindex("lo"); - index = connman_inet_ifindex("lo"); if (ifdata->index == index) { __connman_resolvfile_remove(index, NULL, "127.0.0.1"); __connman_resolvfile_remove(index, NULL, "::1"); } - for (list = request_list; list; list = list->next) { + for (GSList *list = request_list; list; list = list->next) { struct request_data *req = list->data; debug("Dropping request (id 0x%04x -> 0x%04x)", @@ -3966,7 +4016,6 @@ int __connman_dnsproxy_add_listener(int index) void __connman_dnsproxy_remove_listener(int index) { struct listener_data *ifdata; - DBG("index %d", index); if (!listener_table) @@ -4019,17 +4068,15 @@ int __connman_dnsproxy_init(void) return err; err = connman_notifier_register(&dnsproxy_notifier); - if (err < 0) - goto destroy; - - return 0; + if (err < 0) { + __connman_dnsproxy_remove_listener(index); + g_hash_table_destroy(listener_table); + g_hash_table_destroy(partial_tcp_req_table); -destroy: - __connman_dnsproxy_remove_listener(index); - g_hash_table_destroy(listener_table); - g_hash_table_destroy(partial_tcp_req_table); + return err; + } - return err; + return 0; } int __connman_dnsproxy_set_mdns(int index, bool enabled) @@ -4064,3 +4111,8 @@ void __connman_dnsproxy_cleanup(void) if (ipv6_resolve) g_resolv_unref(ipv6_resolve); } + +void __connman_dnsproxy_set_listen_port(unsigned int port) +{ + dns_listen_port = port; +} diff --git a/src/main.c b/src/main.c index e209cf2..ae4a450 100644 --- a/src/main.c +++ b/src/main.c @@ -54,6 +54,7 @@ */ #define DEFAULT_ONLINE_CHECK_INITIAL_INTERVAL 1 #define DEFAULT_ONLINE_CHECK_MAX_INTERVAL 12 +#define DEFAULT_LOCALTIME "/etc/localtime" #define MAINFILE "main.conf" #define CONFIGMAINFILE CONFIGDIR "/" MAINFILE @@ -77,6 +78,7 @@ static char *default_blacklist[] = { "ifb", "ve-", "vb-", + "ham", NULL }; @@ -107,6 +109,9 @@ static struct { bool auto_connect_roaming_services; bool acd; bool use_gateways_as_timeservers; + char *localtime; + bool regdom_follows_timezone; + char *resolv_conf; } connman_settings = { .bg_scan = true, .pref_timeservers = NULL, @@ -134,6 +139,8 @@ static struct { .auto_connect_roaming_services = false, .acd = false, .use_gateways_as_timeservers = false, + .localtime = NULL, + .resolv_conf = NULL, }; #define CONF_BG_SCAN "BackgroundScanning" @@ -162,6 +169,9 @@ static struct { #define CONF_AUTO_CONNECT_ROAMING_SERVICES "AutoConnectRoamingServices" #define CONF_ACD "AddressConflictDetection" #define CONF_USE_GATEWAYS_AS_TIMESERVERS "UseGatewaysAsTimeservers" +#define CONF_LOCALTIME "Localtime" +#define CONF_REGDOM_FOLLOWS_TIMEZONE "RegdomFollowsTimezone" +#define CONF_RESOLV_CONF "ResolvConf" static const char *supported_options[] = { CONF_BG_SCAN, @@ -190,6 +200,9 @@ static const char *supported_options[] = { CONF_AUTO_CONNECT_ROAMING_SERVICES, CONF_ACD, CONF_USE_GATEWAYS_AS_TIMESERVERS, + CONF_LOCALTIME, + CONF_REGDOM_FOLLOWS_TIMEZONE, + CONF_RESOLV_CONF, NULL }; @@ -318,11 +331,17 @@ static void parse_config(GKeyFile *config) if (!config) { connman_settings.auto_connect = - parse_service_types(default_auto_connect, CONF_ARRAY_SIZE(default_auto_connect)); + parse_service_types(default_auto_connect, + CONF_ARRAY_SIZE(default_auto_connect)); connman_settings.favorite_techs = - parse_service_types(default_favorite_techs, CONF_ARRAY_SIZE(default_favorite_techs)); + parse_service_types(default_favorite_techs, + CONF_ARRAY_SIZE(default_favorite_techs)); connman_settings.blacklisted_interfaces = g_strdupv(default_blacklist); + connman_settings.online_check_ipv4_url = + g_strdup(DEFAULT_ONLINE_CHECK_IPV4_URL); + connman_settings.online_check_ipv6_url = + g_strdup(DEFAULT_ONLINE_CHECK_IPV6_URL); return; } @@ -565,6 +584,29 @@ static void parse_config(GKeyFile *config) connman_settings.use_gateways_as_timeservers = boolean; g_clear_error(&error); + + string = __connman_config_get_string(config, "General", + CONF_LOCALTIME, &error); + if (!error) + connman_settings.localtime = string; + else + g_free(string); + + g_clear_error(&error); + + boolean = __connman_config_get_bool(config, "General", + CONF_REGDOM_FOLLOWS_TIMEZONE, &error); + if (!error) + connman_settings.regdom_follows_timezone = boolean; + + string = __connman_config_get_string(config, "General", + CONF_RESOLV_CONF, &error); + if (!error) + connman_settings.resolv_conf = string; + else + g_free(string); + + g_clear_error(&error); } static int config_init(const char *file) @@ -755,6 +797,10 @@ char *connman_setting_get_string(const char *key) return option_wifi; } + if (g_str_equal(key, CONF_LOCALTIME)) + return connman_settings.localtime ? + connman_settings.localtime : DEFAULT_LOCALTIME; + return NULL; } @@ -793,6 +839,12 @@ bool connman_setting_get_bool(const char *key) if (g_str_equal(key, CONF_USE_GATEWAYS_AS_TIMESERVERS)) return connman_settings.use_gateways_as_timeservers; + if (g_str_equal(key, CONF_REGDOM_FOLLOWS_TIMEZONE)) + return connman_settings.regdom_follows_timezone; + + if (g_str_equal(key, CONF_RESOLV_CONF)) + return connman_settings.resolv_conf; + return false; } @@ -1031,6 +1083,7 @@ int main(int argc, char *argv[]) g_free(connman_settings.vendor_class_id); g_free(connman_settings.online_check_ipv4_url); g_free(connman_settings.online_check_ipv6_url); + g_free(connman_settings.localtime); g_free(option_debug); g_free(option_wifi); diff --git a/src/network.c b/src/network.c index 1cbdf9c..e3e02d1 100644 --- a/src/network.c +++ b/src/network.c @@ -940,7 +940,7 @@ static void set_disconnected(struct connman_network *network) { struct connman_ipconfig *ipconfig_ipv4, *ipconfig_ipv6; enum connman_ipconfig_method ipv4_method, ipv6_method; - enum connman_service_state state; + enum connman_service_state state_ipv4, state_ipv6; struct connman_service *service; service = connman_service_lookup_from_network(network); @@ -1006,18 +1006,18 @@ static void set_disconnected(struct connman_network *network) * or in failure. It does not make sense to go to disconnect * state if we were not connected. */ - state = __connman_service_ipconfig_get_state(service, + state_ipv4 = __connman_service_ipconfig_get_state(service, CONNMAN_IPCONFIG_TYPE_IPV4); - if (state != CONNMAN_SERVICE_STATE_IDLE && - state != CONNMAN_SERVICE_STATE_FAILURE) + if (state_ipv4 != CONNMAN_SERVICE_STATE_IDLE && + state_ipv4 != CONNMAN_SERVICE_STATE_FAILURE) __connman_service_ipconfig_indicate_state(service, CONNMAN_SERVICE_STATE_DISCONNECT, CONNMAN_IPCONFIG_TYPE_IPV4); - state = __connman_service_ipconfig_get_state(service, + state_ipv6 = __connman_service_ipconfig_get_state(service, CONNMAN_IPCONFIG_TYPE_IPV6); - if (state != CONNMAN_SERVICE_STATE_IDLE && - state != CONNMAN_SERVICE_STATE_FAILURE) + if (state_ipv6 != CONNMAN_SERVICE_STATE_IDLE && + state_ipv6 != CONNMAN_SERVICE_STATE_FAILURE) __connman_service_ipconfig_indicate_state(service, CONNMAN_SERVICE_STATE_DISCONNECT, CONNMAN_IPCONFIG_TYPE_IPV6); @@ -1041,11 +1041,15 @@ static void set_disconnected(struct connman_network *network) } } - __connman_service_ipconfig_indicate_state(service, + if (state_ipv4 != CONNMAN_SERVICE_STATE_IDLE && + state_ipv4 != CONNMAN_SERVICE_STATE_FAILURE) + __connman_service_ipconfig_indicate_state(service, CONNMAN_SERVICE_STATE_IDLE, CONNMAN_IPCONFIG_TYPE_IPV4); - __connman_service_ipconfig_indicate_state(service, + if (state_ipv6 != CONNMAN_SERVICE_STATE_IDLE && + state_ipv6 != CONNMAN_SERVICE_STATE_FAILURE) + __connman_service_ipconfig_indicate_state(service, CONNMAN_SERVICE_STATE_IDLE, CONNMAN_IPCONFIG_TYPE_IPV6); @@ -1848,6 +1852,19 @@ int __connman_network_disconnect(struct connman_network *network) return err; } +int __connman_network_forget(struct connman_network *network) +{ + DBG("network %p", network); + + if (!network->driver) + return -EUNATCH; + + if (network->driver->forget) + return network->driver->forget(network); + + return 0; +} + int __connman_network_clear_ipconfig(struct connman_network *network, struct connman_ipconfig *ipconfig) { diff --git a/src/ntp.c b/src/ntp.c index e7fee22..5d0df9e 100644 --- a/src/ntp.c +++ b/src/ntp.c @@ -577,7 +577,7 @@ int __connman_ntp_start(char *server, __connman_ntp_cb_t callback, return -EINVAL; if (ntp_data) { - connman_warn("ntp_data is not NULL (timerserver %s)", + connman_warn("ntp_data is not NULL (timeserver %s)", ntp_data->timeserver); free_ntp_data(ntp_data); } diff --git a/src/resolver.c b/src/resolver.c index 618353f..4ab51d6 100644 --- a/src/resolver.c +++ b/src/resolver.c @@ -103,6 +103,7 @@ static int resolvfile_export(void) int fd, err; unsigned int count; mode_t old_umask; + const char *resolv_conf; content = g_string_new("# Generated by Connection Manager\n"); @@ -161,15 +162,33 @@ static int resolvfile_export(void) old_umask = umask(022); - fd = open(RESOLV_CONF_STATEDIR, O_RDWR | O_CREAT | O_CLOEXEC, - S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); - if (fd < 0) { - connman_warn_once("Cannot create "RESOLV_CONF_STATEDIR" " - "falling back to "RESOLV_CONF_ETC); + resolv_conf = connman_setting_get_string("ResolvConf"); + /* + * TODO: This is mainly for backward compatibility. In some future version, + * "ResolvConf" setting should default to RESOLV_CONF_STATEDIR or + * RESOLV_CONF_ETC and this branch can be removed. + */ + if (resolv_conf == NULL) { + fd = open(RESOLV_CONF_STATEDIR, O_RDWR | O_CREAT | O_CLOEXEC, + S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + if (fd < 0) { + connman_warn_once("Cannot create "RESOLV_CONF_STATEDIR" " + "falling back to "RESOLV_CONF_ETC); - fd = open(RESOLV_CONF_ETC, O_RDWR | O_CREAT | O_CLOEXEC, - S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + fd = open(RESOLV_CONF_ETC, O_RDWR | O_CREAT | O_CLOEXEC, + S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + if (fd < 0) { + err = -errno; + goto done; + } + } + } else if (resolv_conf[0] == '\0' || strcmp(resolv_conf, "/dev/null") == 0) { + err = 0; + goto done; + } else { + fd = open(resolv_conf, O_RDWR | O_CREAT | O_CLOEXEC, + S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); if (fd < 0) { err = -errno; goto done; diff --git a/src/rtnl.c b/src/rtnl.c index a87296f..e8a8325 100644 --- a/src/rtnl.c +++ b/src/rtnl.c @@ -886,7 +886,7 @@ static inline void print_attr(struct rtattr *attr, const char *name) print(" attr %d (len %d)\n", attr->rta_type, len); } -static void rtnl_link(struct nlmsghdr *hdr) +static void rtnl_link(struct nlmsghdr *hdr, bool *has_master) { struct ifinfomsg *msg; struct rtattr *attr; @@ -928,6 +928,7 @@ static void rtnl_link(struct nlmsghdr *hdr) print_attr(attr, "priority"); break; case IFLA_MASTER: + *has_master = true; print_attr(attr, "master"); break; case IFLA_WIRELESS: @@ -960,22 +961,32 @@ static void rtnl_link(struct nlmsghdr *hdr) static void rtnl_newlink(struct nlmsghdr *hdr) { + bool has_master = false; struct ifinfomsg *msg = (struct ifinfomsg *) NLMSG_DATA(hdr); - rtnl_link(hdr); + rtnl_link(hdr, &has_master); if (hdr->nlmsg_type == IFLA_WIRELESS) connman_warn_once("Obsolete WEXT WiFi driver detected"); + /* ignore RTM_NEWLINK when adding interface to bridge */ + if (has_master) + return; + process_newlink(msg->ifi_type, msg->ifi_index, msg->ifi_flags, msg->ifi_change, msg, IFA_PAYLOAD(hdr)); } static void rtnl_dellink(struct nlmsghdr *hdr) { + bool has_master = false; struct ifinfomsg *msg = (struct ifinfomsg *) NLMSG_DATA(hdr); - rtnl_link(hdr); + rtnl_link(hdr, &has_master); + + /* ignore RTM_DELLINK when removing interface from bridge */ + if (has_master) + return; process_dellink(msg->ifi_type, msg->ifi_index, msg->ifi_flags, msg->ifi_change, msg, IFA_PAYLOAD(hdr)); diff --git a/src/service.c b/src/service.c index 1d2b78a..06d0232 100644 --- a/src/service.c +++ b/src/service.c @@ -137,7 +137,8 @@ struct connman_service { char *pac; bool wps; bool wps_advertizing; - guint online_timeout; + guint online_timeout_ipv4; + guint online_timeout_ipv6; unsigned int online_check_interval_ipv4; unsigned int online_check_interval_ipv6; bool do_split_routing; @@ -155,6 +156,7 @@ static struct connman_ipconfig *create_ip6config(struct connman_service *service int index); static void dns_changed(struct connman_service *service); static void vpn_auto_connect(void); +static void trigger_autoconnect(struct connman_service *service); struct find_data { const char *path; @@ -1438,12 +1440,16 @@ static bool check_proxy_setup(struct connman_service *service) static void cancel_online_check(struct connman_service *service) { - if (service->online_timeout == 0) - return; - - g_source_remove(service->online_timeout); - service->online_timeout = 0; - connman_service_unref(service); + if (service->online_timeout_ipv4) { + g_source_remove(service->online_timeout_ipv4); + service->online_timeout_ipv4 = 0; + connman_service_unref(service); + } + if (service->online_timeout_ipv6) { + g_source_remove(service->online_timeout_ipv6); + service->online_timeout_ipv6 = 0; + connman_service_unref(service); + } } static void start_online_check(struct connman_service *service, @@ -1461,7 +1467,7 @@ static void start_online_check(struct connman_service *service, online_check_max_interval = connman_setting_get_uint("OnlineCheckMaxInterval"); - if (type != CONNMAN_IPCONFIG_TYPE_IPV4 || check_proxy_setup(service)) { + if (type == CONNMAN_IPCONFIG_TYPE_IPV6 || check_proxy_setup(service)) { cancel_online_check(service); __connman_service_wispr_start(service, type); } @@ -1476,7 +1482,8 @@ static void address_updated(struct connman_service *service, nameserver_add_all(service, type); start_online_check(service, type); - __connman_timeserver_sync(service); + __connman_timeserver_sync(service, + CONNMAN_TIMESERVER_SYNC_REASON_ADDRESS_UPDATE); } } @@ -2592,7 +2599,6 @@ static void append_properties(DBusMessageIter *dict, dbus_bool_t limited, case CONNMAN_SERVICE_TYPE_UNKNOWN: case CONNMAN_SERVICE_TYPE_SYSTEM: case CONNMAN_SERVICE_TYPE_GPS: - case CONNMAN_SERVICE_TYPE_VPN: case CONNMAN_SERVICE_TYPE_P2P: break; case CONNMAN_SERVICE_TYPE_CELLULAR: @@ -2603,6 +2609,7 @@ static void append_properties(DBusMessageIter *dict, dbus_bool_t limited, connman_dbus_dict_append_dict(dict, "Ethernet", append_ethernet, service); break; + case CONNMAN_SERVICE_TYPE_VPN: case CONNMAN_SERVICE_TYPE_WIFI: case CONNMAN_SERVICE_TYPE_ETHERNET: case CONNMAN_SERVICE_TYPE_BLUETOOTH: @@ -3234,6 +3241,9 @@ int __connman_service_check_passphrase(enum connman_service_security security, return 0; } +static void set_error(struct connman_service *service, + enum connman_service_error error); + int __connman_service_set_passphrase(struct connman_service *service, const char *passphrase) { @@ -3258,6 +3268,10 @@ int __connman_service_set_passphrase(struct connman_service *service, connman_network_set_string(service->network, "WiFi.Passphrase", service->passphrase); + if (service->hidden_service && + service->error == CONNMAN_SERVICE_ERROR_INVALID_KEY) + set_error(service, CONNMAN_SERVICE_ERROR_UNKNOWN); + return 0; } @@ -3610,9 +3624,6 @@ void __connman_service_wispr_start(struct connman_service *service, __connman_wispr_start(service, type); } -static void set_error(struct connman_service *service, - enum connman_service_error error); - static DBusMessage *set_property(DBusConnection *conn, DBusMessage *msg, void *user_data) { @@ -4562,7 +4573,10 @@ static DBusMessage *connect_service(DBusConnection *conn, DBG("service %p", service); - if (service->pending) + /* Hidden services do not keep the pending msg, check it from agent */ + if (service->pending || (service->hidden && + __connman_agent_is_request_pending(service, + dbus_message_get_sender(msg)))) return __connman_error_in_progress(msg); index = __connman_service_get_index(service); @@ -4631,6 +4645,8 @@ bool __connman_service_remove(struct connman_service *service) return false; __connman_service_disconnect(service); + if (service->network) + __connman_network_forget(service->network); g_free(service->passphrase); service->passphrase = NULL; @@ -5613,6 +5629,9 @@ int __connman_service_set_favorite_delayed(struct connman_service *service, service->favorite = favorite; favorite_changed(service); + /* If native autoconnect is in use, the favorite state may affect the + * autoconnect state, so it needs to be rerun. */ + trigger_autoconnect(service); if (!delay_ordering) { @@ -6324,20 +6343,20 @@ static void service_rp_filter(struct connman_service *service, static void redo_wispr(struct connman_service *service, enum connman_ipconfig_type type) { - service->online_timeout = 0; - connman_service_unref(service); - DBG("Retrying %s WISPr for %p %s", __connman_ipconfig_type2string(type), service, service->name); __connman_wispr_start(service, type); + connman_service_unref(service); } static gboolean redo_wispr_ipv4(gpointer user_data) { struct connman_service *service = user_data; + service->online_timeout_ipv4 = 0; + redo_wispr(service, CONNMAN_IPCONFIG_TYPE_IPV4); return FALSE; @@ -6347,6 +6366,8 @@ static gboolean redo_wispr_ipv6(gpointer user_data) { struct connman_service *service = user_data; + service->online_timeout_ipv6 = 0; + redo_wispr(service, CONNMAN_IPCONFIG_TYPE_IPV6); return FALSE; @@ -6359,6 +6380,10 @@ void __connman_service_online_check(struct connman_service *service, GSourceFunc redo_func; unsigned int *interval; enum connman_service_state current_state; + int timeout; + + DBG("service %p type %s success %d\n", + service, __connman_ipconfig_type2string(type), success); if (type == CONNMAN_IPCONFIG_TYPE_IPV4) { interval = &service->online_check_interval_ipv4; @@ -6387,8 +6412,12 @@ redo_func: DBG("service %p type %s interval %d", service, __connman_ipconfig_type2string(type), *interval); - service->online_timeout = g_timeout_add_seconds(*interval * *interval, + timeout = g_timeout_add_seconds(*interval * *interval, redo_func, connman_service_ref(service)); + if (type == CONNMAN_IPCONFIG_TYPE_IPV4) + service->online_timeout_ipv4 = timeout; + else + service->online_timeout_ipv6 = timeout; /* Increment the interval for the next time, set a maximum timeout of * online_check_max_interval seconds * online_check_max_interval seconds. @@ -6501,7 +6530,8 @@ int __connman_service_ipconfig_indicate_state(struct connman_service *service, if (!is_connected(old_state) && is_connected(new_state)) nameserver_add_all(service, type); - __connman_timeserver_sync(service); + __connman_timeserver_sync(service, + CONNMAN_TIMESERVER_SYNC_REASON_STATE_UPDATE); return service_indicate_state(service); } @@ -6798,6 +6828,13 @@ int __connman_service_connect(struct connman_service *service, service->pending = NULL; } + if (service->hidden_service && + service->error == CONNMAN_SERVICE_ERROR_INVALID_KEY) { + __connman_service_indicate_error(service, + CONNMAN_SERVICE_ERROR_INVALID_KEY); + return err; + } + err = __connman_agent_request_passphrase_input(service, request_input_cb, dbus_sender, diff --git a/src/timeserver.c b/src/timeserver.c index feef8e8..d23776f 100644 --- a/src/timeserver.c +++ b/src/timeserver.c @@ -47,6 +47,7 @@ static GResolv *resolv = NULL; static int resolv_id = 0; static void sync_next(void); +static void ts_set_nameservers(struct connman_service *service); static void resolv_debug(const char *str, void *data) { @@ -183,6 +184,7 @@ static void sync_next(void) } __connman_ntp_stop(); + ts_set_nameservers(ts_service); while (ts_list) { ts_current = ts_list->data; @@ -307,7 +309,8 @@ static gboolean ts_recheck(gpointer user_data) g_slist_free_full(ts, g_free); service = connman_service_get_default(); - __connman_timeserver_sync(service); + __connman_timeserver_sync(service, + CONNMAN_TIMESERVER_SYNC_REASON_TS_CHANGE); return FALSE; } @@ -347,24 +350,34 @@ static void ts_recheck_enable(void) NULL); } -static void ts_reset(struct connman_service *service) +static int ts_setup_resolv(struct connman_service *service) { - char **nameservers; int i; + i = __connman_service_get_index(service); + if (i < 0) + return -EINVAL; + + if (resolv) { + g_resolv_unref(resolv); + resolv = NULL; + } + + resolv = g_resolv_new(i); if (!resolv) - return; + return -ENOMEM; - __connman_timeserver_set_synced(false); + if (getenv("CONNMAN_RESOLV_DEBUG")) + g_resolv_set_debug(resolv, resolv_debug, "RESOLV"); - /* - * Before we start creating the new timeserver list we must stop - * any ongoing ntp query and server resolution. - */ + return 0; +} - __connman_ntp_stop(); - ts_recheck_disable(); +static void ts_set_nameservers(struct connman_service *service) +{ + char **nameservers; + int i; if (resolv_id > 0) g_resolv_cancel_lookup(resolv, resolv_id); @@ -378,9 +391,31 @@ static void ts_reset(struct connman_service *service) g_strfreev(nameservers); } +} + +static void ts_reset(struct connman_service *service) +{ + if (!resolv) + return; + + __connman_timeserver_set_synced(false); + + /* + * Before we start creating the new timeserver list we must stop + * any ongoing ntp query and server resolution. + */ + + __connman_ntp_stop(); + + ts_recheck_disable(); + + ts_set_nameservers(service); g_slist_free_full(timeservers_list, g_free); + g_slist_free_full(ts_list, g_free); + ts_list = NULL; + timeservers_list = __connman_timeserver_get_all(service); __connman_service_timeserver_changed(service, timeservers_list); @@ -396,11 +431,27 @@ static void ts_reset(struct connman_service *service) timeserver_sync_start(); } -void __connman_timeserver_sync(struct connman_service *service) +void __connman_timeserver_sync(struct connman_service *service, + enum connman_timeserver_sync_reason reason) { - if (!service || ts_service == service) + if (!service) return; + switch (reason) { + case CONNMAN_TIMESERVER_SYNC_REASON_START: + case CONNMAN_TIMESERVER_SYNC_REASON_STATE_UPDATE: + if (ts_service == service) + return; + break; + case CONNMAN_TIMESERVER_SYNC_REASON_ADDRESS_UPDATE: + case CONNMAN_TIMESERVER_SYNC_REASON_TS_CHANGE: + if (ts_service != service) + return; + break; + default: + return; + } + ts_reset(service); } @@ -434,44 +485,19 @@ void __connman_timeserver_set_synced(bool status) static int timeserver_start(struct connman_service *service) { - char **nameservers; - int i; + int rv; DBG("service %p", service); - i = __connman_service_get_index(service); - if (i < 0) - return -EINVAL; - - nameservers = connman_service_get_nameservers(service); - - /* Stop an already ongoing resolution, if there is one */ - if (resolv && resolv_id > 0) - g_resolv_cancel_lookup(resolv, resolv_id); - /* get rid of the old resolver */ - if (resolv) { - g_resolv_unref(resolv); - resolv = NULL; - } - - resolv = g_resolv_new(i); - if (!resolv) { - g_strfreev(nameservers); - return -ENOMEM; - } - - if (getenv("CONNMAN_RESOLV_DEBUG")) - g_resolv_set_debug(resolv, resolv_debug, "RESOLV"); - - if (nameservers) { - for (i = 0; nameservers[i]; i++) - g_resolv_add_nameserver(resolv, nameservers[i], 53, 0); + rv = ts_setup_resolv(service); + if (rv) + return rv; - g_strfreev(nameservers); - } + ts_set_nameservers(service); - __connman_timeserver_sync(service); + __connman_timeserver_sync(service, + CONNMAN_TIMESERVER_SYNC_REASON_START); return 0; } diff --git a/src/timezone.c b/src/timezone.c index cc49909..f8d192d 100644 --- a/src/timezone.c +++ b/src/timezone.c @@ -38,9 +38,9 @@ #include "connman.h" -#define ETC_LOCALTIME "/etc/localtime" #define ETC_SYSCONFIG_CLOCK "/etc/sysconfig/clock" #define USR_SHARE_ZONEINFO "/usr/share/zoneinfo" +#define USR_SHARE_ZONEINFO_MAP USR_SHARE_ZONEINFO "/zone1970.tab" static char *read_key_file(const char *pathname, const char *key) { @@ -228,18 +228,104 @@ static char *find_origin(void *src_map, struct stat *src_st, return NULL; } +static char *get_timezone_alpha2(const char *zone) +{ + GIOChannel *channel; + struct stat st; + char **tokens; + char *line; + char *alpha2 = NULL; + gsize len; + int fd; + + if (!zone) + return NULL; + + fd = open(USR_SHARE_ZONEINFO_MAP, O_RDONLY | O_CLOEXEC); + if (fd < 0) { + connman_warn("failed to open zoneinfo map %s", + USR_SHARE_ZONEINFO_MAP); + return NULL; + } + + if (fstat(fd, &st) < 0 || !S_ISREG(st.st_mode)) { + connman_warn("zoneinfo map does not exist/not regular file"); + close(fd); + return NULL; + } + + channel = g_io_channel_unix_new(fd); + if (!channel) { + connman_warn("failed to create io channel for %s", + USR_SHARE_ZONEINFO_MAP); + close(fd); + return NULL; + } + + DBG("read %s for %s", USR_SHARE_ZONEINFO_MAP, zone); + g_io_channel_set_encoding(channel, "UTF-8", NULL); + + while (g_io_channel_read_line(channel, &line, &len, NULL, NULL) == + G_IO_STATUS_NORMAL) { + if (!line || !*line || *line == '#' || *line == '\n') { + g_free(line); + continue; + } + + /* File format: Countrycodes Coordinates TZ Comments */ + tokens = g_strsplit_set(line, " \t", 4); + if (!tokens) { + connman_warn("line %s failed to parse", line); + g_free(line); + continue; + } + + if (g_strv_length(tokens) >= 3 && !g_strcmp0( + g_strstrip(tokens[2]), zone)) { + /* + * Multiple country codes can be listed, use the first + * 2 chars. + */ + alpha2 = g_strndup(g_strstrip(tokens[0]), 2); + } + + g_strfreev(tokens); + g_free(line); + + if (alpha2) { + if (strlen(alpha2) != 2) { + connman_warn("Invalid ISO3166 code %s", alpha2); + g_free(alpha2); + alpha2 = NULL; + } else { + DBG("Zone %s ISO3166 country code %s", zone, + alpha2); + } + + break; + } + } + + g_io_channel_unref(channel); + close(fd); + + return alpha2; +} + char *__connman_timezone_lookup(void) { struct stat st; void *map; int fd; char *zone; + char *alpha2; zone = read_key_file(ETC_SYSCONFIG_CLOCK, "ZONE"); DBG("sysconfig zone %s", zone); - fd = open(ETC_LOCALTIME, O_RDONLY | O_CLOEXEC); + fd = open(connman_setting_get_string("Localtime"), + O_RDONLY | O_CLOEXEC); if (fd < 0) { g_free(zone); return NULL; @@ -283,6 +369,15 @@ done: DBG("localtime zone %s", zone); + if (connman_setting_get_bool("RegdomFollowsTimezone")) { + alpha2 = get_timezone_alpha2(zone); + if (alpha2) { + DBG("change regdom to %s", alpha2); + connman_technology_set_regdom(alpha2); + g_free(alpha2); + } + } + return zone; } @@ -338,7 +433,7 @@ int __connman_timezone_change(const char *zone) return -EIO; } - err = write_file(map, &st, ETC_LOCALTIME); + err = write_file(map, &st, connman_setting_get_string("Localtime")); munmap(map, st.st_size); @@ -432,9 +527,9 @@ int __connman_timezone_init(void) g_io_channel_unref(channel); - dirname = g_path_get_dirname(ETC_LOCALTIME); + dirname = g_path_get_dirname(connman_setting_get_string("Localtime")); - wd = inotify_add_watch(fd, dirname, IN_DONT_FOLLOW | + wd = inotify_add_watch(fd, dirname, IN_CREATE | IN_DONT_FOLLOW | IN_CLOSE_WRITE | IN_MOVED_TO); g_free(dirname); diff --git a/src/wispr.c b/src/wispr.c index 56007a3..a437201 100644 --- a/src/wispr.c +++ b/src/wispr.c @@ -56,6 +56,7 @@ struct wispr_route { }; struct connman_wispr_portal_context { + int refcount; struct connman_service *service; enum connman_ipconfig_type type; struct connman_wispr_portal *wispr_portal; @@ -91,16 +92,19 @@ struct connman_wispr_portal { static bool wispr_portal_web_result(GWebResult *result, gpointer user_data); -static GHashTable *wispr_portal_list = NULL; +static GHashTable *wispr_portal_hash = NULL; static char *online_check_ipv4_url = NULL; static char *online_check_ipv6_url = NULL; static bool enable_online_to_ready_transition = false; +#define wispr_portal_context_ref(wp_context) \ + wispr_portal_context_ref_debug(wp_context, __FILE__, __LINE__, __func__) +#define wispr_portal_context_unref(wp_context) \ + wispr_portal_context_unref_debug(wp_context, __FILE__, __LINE__, __func__) + static void connman_wispr_message_init(struct connman_wispr_message *msg) { - DBG(""); - msg->has_error = false; msg->current_element = NULL; @@ -160,11 +164,6 @@ static void free_wispr_routes(struct connman_wispr_portal_context *wp_context) static void free_connman_wispr_portal_context( struct connman_wispr_portal_context *wp_context) { - DBG("context %p", wp_context); - - if (!wp_context) - return; - if (wp_context->wispr_portal) { if (wp_context->wispr_portal->ipv4_context == wp_context) wp_context->wispr_portal->ipv4_context = NULL; @@ -201,9 +200,38 @@ static void free_connman_wispr_portal_context( g_free(wp_context); } +static struct connman_wispr_portal_context * +wispr_portal_context_ref_debug(struct connman_wispr_portal_context *wp_context, + const char *file, int line, const char *caller) +{ + DBG("%p ref %d by %s:%d:%s()", wp_context, + wp_context->refcount + 1, file, line, caller); + + __sync_fetch_and_add(&wp_context->refcount, 1); + + return wp_context; +} + +static void wispr_portal_context_unref_debug( + struct connman_wispr_portal_context *wp_context, + const char *file, int line, const char *caller) +{ + if (!wp_context) + return; + + DBG("%p ref %d by %s:%d:%s()", wp_context, + wp_context->refcount - 1, file, line, caller); + + if (__sync_fetch_and_sub(&wp_context->refcount, 1) != 1) + return; + + free_connman_wispr_portal_context(wp_context); +} + static struct connman_wispr_portal_context *create_wispr_portal_context(void) { - return g_try_new0(struct connman_wispr_portal_context, 1); + return wispr_portal_context_ref( + g_new0(struct connman_wispr_portal_context, 1)); } static void free_connman_wispr_portal(gpointer data) @@ -215,8 +243,8 @@ static void free_connman_wispr_portal(gpointer data) if (!wispr_portal) return; - free_connman_wispr_portal_context(wispr_portal->ipv4_context); - free_connman_wispr_portal_context(wispr_portal->ipv6_context); + wispr_portal_context_unref(wispr_portal->ipv4_context); + wispr_portal_context_unref(wispr_portal->ipv6_context); g_free(wispr_portal); } @@ -451,9 +479,6 @@ static void portal_manage_status(GWebResult *result, &str)) connman_info("Client-Timezone: %s", str); - if (!enable_online_to_ready_transition) - free_connman_wispr_portal_context(wp_context); - __connman_service_ipconfig_indicate_state(service, CONNMAN_SERVICE_STATE_ONLINE, type); @@ -512,16 +537,20 @@ static bool wispr_route_request(const char *address, int ai_family, static void wispr_portal_request_portal( struct connman_wispr_portal_context *wp_context) { - DBG(""); + DBG("wp_context %p %s", wp_context, + __connman_ipconfig_type2string(wp_context->type)); + wispr_portal_context_ref(wp_context); wp_context->request_id = g_web_request_get(wp_context->web, wp_context->status_url, wispr_portal_web_result, wispr_route_request, wp_context); - if (wp_context->request_id == 0) + if (wp_context->request_id == 0) { wispr_portal_error(wp_context); + wispr_portal_context_unref(wp_context); + } } static bool wispr_input(const guint8 **data, gsize *length, @@ -576,7 +605,7 @@ static void wispr_portal_browser_reply_cb(struct connman_service *service, if (index < 0) return; - wispr_portal = g_hash_table_lookup(wispr_portal_list, + wispr_portal = g_hash_table_lookup(wispr_portal_hash, GINT_TO_POINTER(index)); if (!wispr_portal) return; @@ -586,13 +615,15 @@ static void wispr_portal_browser_reply_cb(struct connman_service *service, return; if (!authentication_done) { - wispr_portal_error(wp_context); free_wispr_routes(wp_context); + wispr_portal_error(wp_context); + wispr_portal_context_unref(wp_context); return; } /* Restarting the test */ __connman_service_wispr_start(service, wp_context->type); + wispr_portal_context_unref(wp_context); } static void wispr_portal_request_wispr_login(struct connman_service *service, @@ -616,7 +647,7 @@ static void wispr_portal_request_wispr_login(struct connman_service *service, return; } - free_connman_wispr_portal_context(wp_context); + wispr_portal_context_unref(wp_context); return; } @@ -668,11 +699,13 @@ static bool wispr_manage_message(GWebResult *result, wp_context->wispr_result = CONNMAN_WISPR_RESULT_LOGIN; + wispr_portal_context_ref(wp_context); if (__connman_agent_request_login_input(wp_context->service, wispr_portal_request_wispr_login, - wp_context) != -EINPROGRESS) + wp_context) != -EINPROGRESS) { wispr_portal_error(wp_context); - else + wispr_portal_context_unref(wp_context); + } else return true; break; @@ -721,6 +754,7 @@ static bool wispr_portal_web_result(GWebResult *result, gpointer user_data) if (length > 0) { g_web_parser_feed_data(wp_context->wispr_parser, chunk, length); + /* read more data */ return true; } @@ -738,6 +772,7 @@ static bool wispr_portal_web_result(GWebResult *result, gpointer user_data) switch (status) { case 000: + wispr_portal_context_ref(wp_context); __connman_agent_request_browser(wp_context->service, wispr_portal_browser_reply_cb, wp_context->status_url, wp_context); @@ -749,11 +784,12 @@ static bool wispr_portal_web_result(GWebResult *result, gpointer user_data) if (g_web_result_get_header(result, "X-ConnMan-Status", &str)) { portal_manage_status(result, wp_context); - return false; - } else + } else { + wispr_portal_context_ref(wp_context); __connman_agent_request_browser(wp_context->service, wispr_portal_browser_reply_cb, wp_context->redirect_url, wp_context); + } break; case 300: @@ -766,6 +802,7 @@ static bool wispr_portal_web_result(GWebResult *result, gpointer user_data) !g_web_result_get_header(result, "Location", &redirect)) { + wispr_portal_context_ref(wp_context); __connman_agent_request_browser(wp_context->service, wispr_portal_browser_reply_cb, wp_context->status_url, wp_context); @@ -776,6 +813,7 @@ static bool wispr_portal_web_result(GWebResult *result, gpointer user_data) wp_context->redirect_url = g_strdup(redirect); + wispr_portal_context_ref(wp_context); wp_context->request_id = g_web_request_get(wp_context->web, redirect, wispr_portal_web_result, wispr_route_request, wp_context); @@ -788,6 +826,7 @@ static bool wispr_portal_web_result(GWebResult *result, gpointer user_data) break; case 505: + wispr_portal_context_ref(wp_context); __connman_agent_request_browser(wp_context->service, wispr_portal_browser_reply_cb, wp_context->status_url, wp_context); @@ -800,26 +839,51 @@ static bool wispr_portal_web_result(GWebResult *result, gpointer user_data) wp_context->request_id = 0; done: wp_context->wispr_msg.message_type = -1; + wispr_portal_context_unref(wp_context); return false; } +static char *parse_proxy(const char *proxy) +{ + char *proxy_server; + + if (!g_strcmp0(proxy, "DIRECT")) + return NULL; + + if (!g_str_has_prefix(proxy, "PROXY ")) + return NULL; + + proxy_server = g_strdup(proxy + 6); + + /* Use first proxy server */ + for (char *c = proxy_server; *c != '\0'; ++c) { + if (*c == ';') { + *c = '\0'; + break; + } + } + + g_strstrip(proxy_server); + + return proxy_server; +} + static void proxy_callback(const char *proxy, void *user_data) { struct connman_wispr_portal_context *wp_context = user_data; + char *proxy_server; DBG("proxy %s", proxy); - if (!wp_context) + if (!wp_context || !proxy) return; wp_context->token = 0; - if (proxy && g_strcmp0(proxy, "DIRECT") != 0) { - if (g_str_has_prefix(proxy, "PROXY")) { - proxy += 5; - for (; *proxy == ' ' && *proxy != '\0'; proxy++); - } - g_web_set_proxy(wp_context->web, proxy); + proxy_server = parse_proxy(proxy); + if (proxy_server) { + g_web_set_proxy(wp_context->web, proxy_server); + g_free(proxy_server); } g_web_set_accept(wp_context->web, NULL); @@ -834,6 +898,7 @@ static void proxy_callback(const char *proxy, void *user_data) xml_wispr_parser_callback, wp_context); wispr_portal_request_portal(wp_context); + wispr_portal_context_unref(wp_context); } static gboolean no_proxy_callback(gpointer user_data) @@ -850,7 +915,6 @@ static gboolean no_proxy_callback(gpointer user_data) static int wispr_portal_detect(struct connman_wispr_portal_context *wp_context) { enum connman_service_proxy_method proxy_method; - enum connman_service_type service_type; char *interface = NULL; char **nameservers = NULL; int if_index; @@ -860,23 +924,6 @@ static int wispr_portal_detect(struct connman_wispr_portal_context *wp_context) DBG("wispr/portal context %p service %p", wp_context, wp_context->service); - service_type = connman_service_get_type(wp_context->service); - - switch (service_type) { - case CONNMAN_SERVICE_TYPE_ETHERNET: - case CONNMAN_SERVICE_TYPE_WIFI: - case CONNMAN_SERVICE_TYPE_BLUETOOTH: - case CONNMAN_SERVICE_TYPE_CELLULAR: - case CONNMAN_SERVICE_TYPE_GADGET: - break; - case CONNMAN_SERVICE_TYPE_UNKNOWN: - case CONNMAN_SERVICE_TYPE_SYSTEM: - case CONNMAN_SERVICE_TYPE_GPS: - case CONNMAN_SERVICE_TYPE_VPN: - case CONNMAN_SERVICE_TYPE_P2P: - return -EOPNOTSUPP; - } - interface = connman_service_get_interface(wp_context->service); if (!interface) return -EINVAL; @@ -928,7 +975,7 @@ static int wispr_portal_detect(struct connman_wispr_portal_context *wp_context) if (wp_context->token == 0) { err = -EINVAL; - free_connman_wispr_portal_context(wp_context); + wispr_portal_context_unref(wp_context); } } else if (wp_context->timeout == 0) { wp_context->timeout = g_idle_add(no_proxy_callback, wp_context); @@ -946,42 +993,58 @@ int __connman_wispr_start(struct connman_service *service, { struct connman_wispr_portal_context *wp_context = NULL; struct connman_wispr_portal *wispr_portal = NULL; - int index; + int index, err; - DBG("service %p", service); + DBG("service %p %s", service, + __connman_ipconfig_type2string(type)); - if (!wispr_portal_list) + if (!wispr_portal_hash) return -EINVAL; + switch (connman_service_get_type(service)) { + case CONNMAN_SERVICE_TYPE_ETHERNET: + case CONNMAN_SERVICE_TYPE_WIFI: + case CONNMAN_SERVICE_TYPE_BLUETOOTH: + case CONNMAN_SERVICE_TYPE_CELLULAR: + case CONNMAN_SERVICE_TYPE_GADGET: + break; + case CONNMAN_SERVICE_TYPE_UNKNOWN: + case CONNMAN_SERVICE_TYPE_SYSTEM: + case CONNMAN_SERVICE_TYPE_GPS: + case CONNMAN_SERVICE_TYPE_VPN: + case CONNMAN_SERVICE_TYPE_P2P: + return -EOPNOTSUPP; + } + index = __connman_service_get_index(service); if (index < 0) return -EINVAL; - wispr_portal = g_hash_table_lookup(wispr_portal_list, + wispr_portal = g_hash_table_lookup(wispr_portal_hash, GINT_TO_POINTER(index)); if (!wispr_portal) { wispr_portal = g_try_new0(struct connman_wispr_portal, 1); if (!wispr_portal) return -ENOMEM; - g_hash_table_replace(wispr_portal_list, + g_hash_table_replace(wispr_portal_hash, GINT_TO_POINTER(index), wispr_portal); } if (type == CONNMAN_IPCONFIG_TYPE_IPV4) wp_context = wispr_portal->ipv4_context; - else if (type == CONNMAN_IPCONFIG_TYPE_IPV6) - wp_context = wispr_portal->ipv6_context; else - return -EINVAL; + wp_context = wispr_portal->ipv6_context; /* If there is already an existing context, we wipe it */ if (wp_context) - free_connman_wispr_portal_context(wp_context); + wispr_portal_context_unref(wp_context); wp_context = create_wispr_portal_context(); - if (!wp_context) - return -ENOMEM; + if (!wp_context) { + err = -ENOMEM; + goto free_wp; + } wp_context->service = service; wp_context->type = type; @@ -992,7 +1055,14 @@ int __connman_wispr_start(struct connman_service *service, else wispr_portal->ipv6_context = wp_context; - return wispr_portal_detect(wp_context); + err = wispr_portal_detect(wp_context); + if (err) + goto free_wp; + return 0; + +free_wp: + g_hash_table_remove(wispr_portal_hash, GINT_TO_POINTER(index)); + return err; } void __connman_wispr_stop(struct connman_service *service) @@ -1002,36 +1072,30 @@ void __connman_wispr_stop(struct connman_service *service) DBG("service %p", service); - if (!wispr_portal_list) + if (!wispr_portal_hash) return; index = __connman_service_get_index(service); if (index < 0) return; - wispr_portal = g_hash_table_lookup(wispr_portal_list, + wispr_portal = g_hash_table_lookup(wispr_portal_hash, GINT_TO_POINTER(index)); if (!wispr_portal) return; - if (wispr_portal->ipv4_context) { - if (service == wispr_portal->ipv4_context->service) - g_hash_table_remove(wispr_portal_list, - GINT_TO_POINTER(index)); - } - - if (wispr_portal->ipv6_context) { - if (service == wispr_portal->ipv6_context->service) - g_hash_table_remove(wispr_portal_list, - GINT_TO_POINTER(index)); - } + if ((wispr_portal->ipv4_context && + service == wispr_portal->ipv4_context->service) || + (wispr_portal->ipv6_context && + service == wispr_portal->ipv6_context->service)) + g_hash_table_remove(wispr_portal_hash, GINT_TO_POINTER(index)); } int __connman_wispr_init(void) { DBG(""); - wispr_portal_list = g_hash_table_new_full(g_direct_hash, + wispr_portal_hash = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, free_connman_wispr_portal); @@ -1050,6 +1114,6 @@ void __connman_wispr_cleanup(void) { DBG(""); - g_hash_table_destroy(wispr_portal_list); - wispr_portal_list = NULL; + g_hash_table_destroy(wispr_portal_hash); + wispr_portal_hash = NULL; } diff --git a/tools/dnsproxy-simple-test b/tools/dnsproxy-simple-test new file mode 100755 index 0000000..5c2f729 --- /dev/null +++ b/tools/dnsproxy-simple-test @@ -0,0 +1,195 @@ +#!/bin/bash + +# this script runs the dnsproxy-standalone test program and runs a couple of +# standard DNS queries against it, using the currently configured DNS server +# in the system as dnsproxy configuration. + +echoerr() { + echo $@ 1>&2 + echo -e "\n >>> ERROR OCCURED <<< \n" 1>&2 + exit 1 +} + + +showlog() { + if [ -z "$SHOW_LOG" -o -z "$logfile" ]; then + return + fi + + echo + echo "======== debug log ===========" + cat "$logfile" + echo "===== end debug log ==========" + echo +} + +TRANSPORTS="-U -T" + +while [ $# -gt 0 ]; do + case "$1" in + "--valgrind") + VALGRIND=`which valgrind` + if [ -z "$VALGRIND" ]; then + echoerr "no valgrind executable found" + fi + # log valgrind output to stdout, since stderr is used for + # debug output from dnsproxy.c already and we want to parse + # that. + # also cause an error exit it valgrind error occur so that + # they're easily noticed. + VALGRIND="$VALGRIND --log-fd=1 --error-exitcode=10" + ;; + "--gdb") + WAIT_GDB=1 + # wait forever to avoid timeout conditions during debugging + HOST_OPTS="-w" + ;; + "--show-log") + SHOW_LOG=1 + ;; + "--testdomain="*) + TESTDOMAIN=`echo $1 | cut -d '=' -f2-` + CUSTOM_TESTDOMAIN=1 + ;; + "--only-tcp") + TRANSPORTS="-T" + ;; + "--only-udp") + TRANSPORTS="-U" + ;; + "-h") + echo "$0 [--valgrind] [--gdb] [--show-log] [--only-tcp] [--only-udp] [--testdomain=]" + echo "--valgrind: run dnsproxy-standalone in valgrind" + echo "--gdb: allows you to attach via GDB before tests are started" + echo "--show-log: dump debug log from dnsproxy at end of test" + echo "--only-tcp: only perform TCP protocol based tests" + echo "--only-udp: only perform UDP protocol based tests" + echo "--testdomain=: the domain name to resolve" + exit 2 + ;; + *) + echoerr "Unknown argument $1" + ;; + esac + shift +done + +if [ -n "$VALGRIND" -a -n "$WAIT_GDB" ]; then + echo "Cannot mix valgrind frontend and GDB attachment" 1>&2 + exit 2 +fi + +if [ -e "Makefile" ]; then + BUILDROOT="$PWD" +else + if [ ! -n "$BUILDROOT" ]; then + echoerr "You need to set the BUILDROOT environment variable or run this script from the connman build tree root" + fi + + pushd "$BUILDROOT" >/dev/null || echoerr "couldn't enter $BUILDROOT" +fi +make tools/dnsproxy-standalone || echoerr "failed to build dnsproxy-standalone" + +HOST=`which host` +if [ -z "$HOST" ]; then + echoerr "Couldn't find 'host' DNS utility" +fi + +DNSPROXY="$BUILDROOT/tools/dnsproxy-standalone" + +if [ ! -f "$DNSPROXY" ]; then + echoerr "standalone dnsproxy does not exist at $DNSPROXY" +fi + +NS1=`grep -w nameserver -m 1 /etc/resolv.conf | cut -d ' ' -f 2` +if [ -z "$NS1" ]; then + echoerr "Failed to determine system's nameserver from /etc/resolv.conf" +fi + +DOMAIN1=`grep -w search -m 1 /etc/resolv.conf | cut -d ' ' -f 2` +if [ -z "$DOMAIN1" ]; then + echoerr "Failed to determine default DNS domain from /etc/resolv.conf" +fi + +# use an unprivileged port for the proxy so we don't need special permissions +# to run this test +PORT=8053 + +# run the proxy in the background +logfile=`mktemp` +$VALGRIND $DNSPROXY $PORT "$DOMAIN1" "$NS1" 2>"$logfile" & +proxy_pid=$! + +cleanup() { + if [ $proxy_pid -eq -1 ]; then + return 0 + fi + kill $proxy_pid + wait $proxy_pid + ret=$? + proxy_pid=-1 + if [ -n "$logfile" ]; then + if [ -n "$SHOW_LOG" ]; then + showlog + fi + rm -f "$logfile" + unset logfile + fi + return $ret +} + +trap cleanup err exit + +sleep 1 +echo -e "\n\n" + +if [ -n "$WAIT_GDB" ]; then + echo "You can now attach to the dnsproxy process at PID $proxy_pid." + echo "Press ENTER to continue test execution" + read _ +fi + +if [ -z "$TESTDOMAIN" ]; then + TESTDOMAIN="www.example.com" +fi + +# perform each test twice to actually get cached responses served for each +# combination +for I in `seq 2`; do + # test both UDP and TCP mode + for TRANSPORT in $TRANSPORTS; do + # test both IPv4 and IPv6 + for IP in -4 -6; do + echo "Testing resolution using transport $TRANSPORT and IP${IP}" + set -x + $HOST $HOST_OPTS $TRANSPORT $IP -p$PORT $TESTDOMAIN 127.0.0.1 + RES=$? + set +x + if [ $RES -ne 0 ]; then + echoerr "resolution failed" + fi + + echo -e "\n\n" + done + done +done + +NUM_HITS=`grep "cache hit.*$TESTDOMAIN" "$logfile" | wc -l` + +echo -e "\n\nDNS resolution succeeded for all test combinations" +echo -e "\nNumber of cache hits: $NUM_HITS\n" +# assert we have seen the expected number of cache hits in the log +# this is the amount of cache hits for the default domain tests as seen before +# refactoring of dnsproxy started. +if [ -z "$CUSTOM_TESTDOMAIN" -a "$NUM_HITS" -ne 15 ]; then + echoerr "Unexpected number of cache hits encountered" +elif [ "$NUM_HITS" -lt 8 ]; then + echoerr "Too low number of cache hits encountered" +fi +cleanup +if [ $? -eq 0 ]; then + exit 0 +else + echoerr "dnsproxy returned non-zero exit status $?" +fi + diff --git a/tools/dnsproxy-standalone.c b/tools/dnsproxy-standalone.c new file mode 100644 index 0000000..1b9698b --- /dev/null +++ b/tools/dnsproxy-standalone.c @@ -0,0 +1,155 @@ +#include +#include +#include +#include +#include + +#include + +#include "connman.h" + +/* + * This is a minimal connman setup that only runs the internal dnsproxy + * component for testing. The advantage is that we can do a full integration + * test of the dnsproxy logic without requiring root privileges or setting up + * other complexities like D-Bus access etc. + */ + +static GMainLoop *main_loop = NULL; + +static void usage(const char *prog) +{ + fprintf(stderr, "%s: \n", prog); + exit(1); +} + +static unsigned int to_uint(const char *s) +{ + char *end = NULL; + unsigned int ret; + + ret = strtoul(s, &end, 10); + + if (*end != '\0') { + fprintf(stderr, "invalid argument: %s", s); + exit(1); + } + + return ret; +} + +static gboolean signal_handler(GIOChannel *channel, GIOCondition cond, + gpointer user_data) +{ + struct signalfd_siginfo si; + ssize_t result; + int fd; + + if (cond & (G_IO_NVAL | G_IO_ERR | G_IO_HUP)) + return FALSE; + + fd = g_io_channel_unix_get_fd(channel); + + result = read(fd, &si, sizeof(si)); + if (result != sizeof(si)) + return FALSE; + + switch (si.ssi_signo) { + case SIGINT: + case SIGTERM: + printf("Terminating due to signal\n"); + g_main_loop_quit(main_loop); + break; + } + + return TRUE; +} + +static guint setup_signalfd(void) +{ + GIOChannel *channel; + guint source; + sigset_t mask; + int fd; + + sigemptyset(&mask); + sigaddset(&mask, SIGINT); + sigaddset(&mask, SIGTERM); + + if (sigprocmask(SIG_BLOCK, &mask, NULL) < 0) { + perror("Failed to set signal mask"); + return 0; + } + + fd = signalfd(-1, &mask, 0); + if (fd < 0) { + perror("Failed to create signal descriptor"); + return 0; + } + + channel = g_io_channel_unix_new(fd); + + g_io_channel_set_close_on_unref(channel, TRUE); + g_io_channel_set_encoding(channel, NULL, NULL); + g_io_channel_set_buffered(channel, FALSE); + + source = g_io_add_watch(channel, + G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL, + signal_handler, NULL); + + g_io_channel_unref(channel); + + return source; +} + +int main(int argc, const char **argv) +{ + unsigned int port = 0; + const char *domain = argv[2]; + const char *server = argv[3]; + guint signal = 0; + + if (argc != 4) + { + usage(argv[0]); + } + + port = to_uint(argv[1]); + + __connman_util_init(); + printf("Listening on local port %u\n", port); + __connman_dnsproxy_set_listen_port(port); + + if (__connman_dnsproxy_init() < 0) { + fprintf(stderr, "failed to initialize dnsproxy\n"); + return 1; + } + + printf("Using DNS server %s on domain %s\n", server, domain); + + if (__connman_dnsproxy_append(-1, domain, server) < 0) { + fprintf(stderr, "failed to add DNS server\n"); + return 1; + } + + /* we need to trick a bit to make the server entry enter "enabled" + * state in dnsproxy. Appending and removing an arbitrary entry causes + * "enable_fallback()" to be called which does what we want. Doesn't + * make much sense but it is good enough for the standalone server at + * the moment. + */ + __connman_dnsproxy_append(15, domain, server); + __connman_dnsproxy_remove(15, domain, server); + + signal = setup_signalfd(); + + main_loop = g_main_loop_new(NULL, FALSE); + + g_main_loop_run(main_loop); + + __connman_dnsproxy_cleanup(); + __connman_util_cleanup(); + g_source_remove(signal); + + return 0; +} diff --git a/tools/dnsproxy-test.c b/tools/dnsproxy-test.c index 371e2e2..01dcc51 100644 --- a/tools/dnsproxy-test.c +++ b/tools/dnsproxy-test.c @@ -99,6 +99,8 @@ static unsigned char msg_invalid[] = { 0x31, 0xC0, /* tran id */ }; +static const char *dns_port = "53"; + static int create_tcp_socket(int family) { int sk, err; @@ -139,7 +141,7 @@ static int connect_tcp_socket(char *server) hints.ai_socktype = SOCK_STREAM; hints.ai_family = AF_UNSPEC; hints.ai_flags = AI_NUMERICSERV | AI_NUMERICHOST; - getaddrinfo(server, "53", &hints, &rp); + getaddrinfo(server, dns_port, &hints, &rp); sk = create_tcp_socket(rp->ai_family); err = sk; @@ -201,7 +203,7 @@ static int connect_udp_socket(char *server, struct sockaddr *sa, hints.ai_socktype = SOCK_DGRAM; hints.ai_family = AF_UNSPEC; hints.ai_flags = AI_NUMERICSERV | AI_NUMERICHOST; - getaddrinfo(server, "53", &hints, &rp); + getaddrinfo(server, dns_port, &hints, &rp); sk = create_udp_socket(rp->ai_family); err = sk; @@ -430,6 +432,11 @@ int main(int argc, char *argv[]) { g_test_init(&argc, &argv, NULL); + if (argc == 2) { + /* alternative dns port */ + dns_port = argv[1]; + } + g_test_add_func("/dnsproxy/ipv4 udp msg", test_ipv4_udp_msg); diff --git a/vpn/plugins/vpnc.c b/vpn/plugins/vpnc.c index d11b911..73012c5 100644 --- a/vpn/plugins/vpnc.c +++ b/vpn/plugins/vpnc.c @@ -54,7 +54,7 @@ #include "../vpn.h" #define ARRAY_SIZE(a) (sizeof(a)/sizeof(a[0])) -#define PID_PATH_ROOT "/var/run/user" +#define PID_PATH_ROOT RUNSTATEDIR "/user" enum { OPT_STRING = 1, diff --git a/vpn/vpn-polkit.policy b/vpn/vpn-polkit.policy index 0c42722..b187b37 100644 --- a/vpn/vpn-polkit.policy +++ b/vpn/vpn-polkit.policy @@ -13,7 +13,7 @@ Policy prevents modification of settings no - auth_self_keep_session + auth_self_keep @@ -22,7 +22,7 @@ Policy prevents modification of secrets no - auth_admin_keep_session + auth_admin_keep diff --git a/vpn/vpn-util.c b/vpn/vpn-util.c index 9ef14d3..bc3b01d 100644 --- a/vpn/vpn-util.c +++ b/vpn/vpn-util.c @@ -102,8 +102,8 @@ struct group *vpn_util_get_group(const char *groupname) * running a VPN plugin as a different user and thus, user specific run dir is * allowed and limitation to access any other system dir is restricted. */ -static const char *allowed_prefixes[] = { "/var/run/connman-vpn/", - "/var/run/user/", "/tmp/", NULL }; +static const char *allowed_prefixes[] = { RUNSTATEDIR "/connman-vpn/", + RUNSTATEDIR "/user/", "/tmp/", NULL }; static int is_path_allowed(const char *path) { -- 2.7.4