Imported Upstream version 1.42 68/302268/1 upstream upstream/1.42
authorAnjali Nijhara <a.nijhara@samsung.com>
Tue, 5 Dec 2023 03:30:23 +0000 (09:00 +0530)
committerAnjali Nijhara <a.nijhara@samsung.com>
Tue, 5 Dec 2023 03:31:01 +0000 (09:01 +0530)
Change-Id: Ib8320153c783db22181c568b14353d2b9651c1bc

42 files changed:
.gitignore
AUTHORS
ChangeLog
Makefile.am
Makefile.plugins
bootstrap-configure
configure.ac
doc/connman.8.in
doc/connman.conf.5.in
gdhcp/client.c
gsupplicant/supplicant.c
gweb/gresolv.c
gweb/gweb.c
include/agent.h
include/network.h
include/timeserver.h
plugins/iwd.c
plugins/ofono.c
plugins/wifi.c
scripts/libppp-compat.h [new file with mode: 0644]
scripts/libppp-plugin.c
src/agent-connman.c
src/agent.c
src/clock.c
src/connman.h
src/dhcp.c
src/dnsproxy.c
src/main.c
src/network.c
src/ntp.c
src/resolver.c
src/rtnl.c
src/service.c
src/timeserver.c
src/timezone.c
src/wispr.c
tools/dnsproxy-simple-test [new file with mode: 0755]
tools/dnsproxy-standalone.c [new file with mode: 0644]
tools/dnsproxy-test.c
vpn/plugins/vpnc.c
vpn/vpn-polkit.policy
vpn/vpn-util.c

index cd7d881..9e8d1a4 100644 (file)
@@ -2,6 +2,7 @@
 *.a
 *.lo
 *.la
+*~
 .deps
 .libs
 .dirstamp
diff --git a/AUTHORS b/AUTHORS
index 3ed0985..70cb07c 100644 (file)
--- a/AUTHORS
+++ b/AUTHORS
@@ -176,3 +176,10 @@ Lukáš Karas <lukas.karas@centrum.cz>
 Michael Nazzareno Trimarchi <michael@amarulasolutions.com>
 Christian Taedcke <christian.taedcke@lemonbeat.com>
 Matthias Gerstner <mgerstner@suse.de>
+Sebastian Pipping <sebastian@pipping.org>
+Daniel Linjama <daniel@dev.linjama.com>
+Nathan Crandall <ncrandall@tesla.com>
+Ben Kohler <bkohler@gentoo.org>
+Polina Smirnova <moe.hwr@gmail.com>
+Eivind Næss <eivnaes@yahoo.com>
+Oskar Roesler <o.roesler@oscloud.info>
index 11566a5..d3819e6 100644 (file)
--- 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.
index e5718b1..1a3dbe3 100644 (file)
@@ -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)
index 8e32361..bd5049e 100644 (file)
@@ -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
index 3f69798..ed5ecd7 100755 (executable)
@@ -18,4 +18,5 @@ fi
                --enable-vpnc=builtin \
                --enable-session-policy-local=builtin \
                --enable-nmcompat \
-               --enable-polkit $*
+               --enable-polkit $* \
+               --enable-test
index a573cef..f224bcc 100644 (file)
@@ -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))
index 85e7c5e..ffee8d3 100644 (file)
@@ -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)
index 82cceb7..1f9b290 100644 (file)
@@ -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.
index 3016dfc..8201769 100644 (file)
@@ -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)
index 470d99e..1b92ec4 100644 (file)
@@ -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);
        }
 }
index 954e7cf..8101d71 100644 (file)
@@ -36,6 +36,7 @@
 #include <arpa/inet.h>
 #include <arpa/nameser.h>
 #include <net/if.h>
+#include <ctype.h>
 
 #include "gresolv.h"
 
index 12fcb1d..13c6c5f 100644 (file)
@@ -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--;
index 6961f7a..2702020 100644 (file)
@@ -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);
index 8f9dd94..5bca62a 100644 (file)
@@ -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);
 };
index 48ea194..3177d4e 100644 (file)
 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
index ac3d1e1..2fe49a2 100644 (file)
@@ -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))
index f0bd3c5..8bb5394 100644 (file)
@@ -40,6 +40,7 @@
 #include <connman/dbus.h>
 #include <connman/log.h>
 #include <connman/technology.h>
+#include <connman/setting.h>
 
 #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);
index e947b16..ed7437f 100644 (file)
@@ -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 (file)
index 0000000..eee1d09
--- /dev/null
@@ -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 <pppd/pppd.h>
+
+#ifndef PPPD_VERSION
+#define PPPD_VERSION VERSION
+#endif
+
+#include <pppd/fsm.h>
+#include <pppd/ccp.h>
+#include <pppd/eui64.h>
+#include <pppd/ipcp.h>
+#include <pppd/ipv6cp.h>
+#include <pppd/eap.h>
+#include <pppd/upap.h>
+
+#ifdef HAVE_PPPD_CHAP_H
+#include <pppd/chap.h>
+#endif
+
+#ifdef HAVE_PPPD_CHAP_NEW_H
+#include <pppd/chap-new.h>
+#endif
+
+#ifdef HAVE_PPPD_CHAP_MS_H
+#include <pppd/chap_ms.h>
+#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__ */
index 0dd8b47..61641b5 100644 (file)
 #include <sys/types.h>
 #include <sys/stat.h>
 #include <fcntl.h>
-#include <pppd/pppd.h>
-#include <pppd/fsm.h>
-#include <pppd/ipcp.h>
 #include <netinet/in.h>
 #include <arpa/inet.h>
 
 #include <dbus/dbus.h>
 
+#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;
 }
index fca7cc1..2bd33e0 100644 (file)
@@ -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);
+}
index d4f9add..23517d9 100644 (file)
@@ -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;
index 906538a..54ac274 100644 (file)
@@ -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;
 
index 6817608..b955d98 100644 (file)
@@ -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 <connman/log.h>
 
@@ -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);
index 2d96c43..18dbab2 100644 (file)
@@ -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"))
index cf1d36c..7ebffbc 100644 (file)
@@ -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
 
 #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 */
-       = 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;
+}
index e209cf2..ae4a450 100644 (file)
@@ -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);
index 1cbdf9c..e3e02d1 100644 (file)
@@ -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)
 {
index e7fee22..5d0df9e 100644 (file)
--- 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);
        }
index 618353f..4ab51d6 100644 (file)
@@ -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;
index a87296f..e8a8325 100644 (file)
@@ -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));
index 1d2b78a..06d0232 100644 (file)
@@ -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,
index feef8e8..d23776f 100644 (file)
@@ -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;
 }
index cc49909..f8d192d 100644 (file)
@@ -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);
index 56007a3..a437201 100644 (file)
@@ -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 (executable)
index 0000000..5c2f729
--- /dev/null
@@ -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=<mydomain>]"
+               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=<mydomain>: 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 (file)
index 0000000..1b9698b
--- /dev/null
@@ -0,0 +1,155 @@
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/signalfd.h>
+#include <unistd.h>
+
+#include <glib.h>
+
+#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: <listen-port> <dns-domain> <dns-server>\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;
+}
index 371e2e2..01dcc51 100644 (file)
@@ -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);
 
index d11b911..73012c5 100644 (file)
@@ -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,
index 0c42722..b187b37 100644 (file)
@@ -13,7 +13,7 @@
     <message>Policy prevents modification of settings</message>
     <defaults>
       <allow_inactive>no</allow_inactive>
-      <allow_active>auth_self_keep_session</allow_active>
+      <allow_active>auth_self_keep</allow_active>
     </defaults>
   </action>
 
@@ -22,7 +22,7 @@
     <message>Policy prevents modification of secrets</message>
     <defaults>
       <allow_inactive>no</allow_inactive>
-      <allow_active>auth_admin_keep_session</allow_active>
+      <allow_active>auth_admin_keep</allow_active>
     </defaults>
   </action>
 
index 9ef14d3..bc3b01d 100644 (file)
@@ -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)
 {