diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/agent-connman.c | 14 | ||||
-rw-r--r-- | src/agent.c | 24 | ||||
-rw-r--r-- | src/bridge.c | 3 | ||||
-rw-r--r-- | src/clock.c | 12 | ||||
-rw-r--r-- | src/config.c | 6 | ||||
-rw-r--r-- | src/connection.c | 23 | ||||
-rw-r--r-- | src/connman.h | 32 | ||||
-rw-r--r-- | src/dbus.c | 26 | ||||
-rw-r--r-- | src/dhcp.c | 5 | ||||
-rw-r--r-- | src/dhcpv6.c | 2 | ||||
-rw-r--r-- | src/dns-systemd-resolved.c | 4 | ||||
-rw-r--r-- | src/dnsproxy.c | 1994 | ||||
-rw-r--r-- | src/inet.c | 669 | ||||
-rw-r--r-- | src/ipaddress.c | 12 | ||||
-rw-r--r-- | src/ipconfig.c | 243 | ||||
-rw-r--r-- | src/iptables.c | 6 | ||||
-rw-r--r-- | src/main.c | 191 | ||||
-rw-r--r-- | src/main.conf | 25 | ||||
-rw-r--r-- | src/manager.c | 3 | ||||
-rw-r--r-- | src/network.c | 52 | ||||
-rw-r--r-- | src/ntp.c | 2 | ||||
-rw-r--r-- | src/peer.c | 7 | ||||
-rw-r--r-- | src/provider.c | 110 | ||||
-rw-r--r-- | src/resolver.c | 33 | ||||
-rw-r--r-- | src/rtnl.c | 145 | ||||
-rw-r--r-- | src/service.c | 637 | ||||
-rw-r--r-- | src/session.c | 2 | ||||
-rw-r--r-- | src/shared/util.c | 1 | ||||
-rw-r--r-- | src/technology.c | 70 | ||||
-rw-r--r-- | src/tethering.c | 3 | ||||
-rw-r--r-- | src/timeserver.c | 194 | ||||
-rw-r--r-- | src/timezone.c | 105 | ||||
-rw-r--r-- | src/wispr.c | 246 |
33 files changed, 3088 insertions, 1813 deletions
diff --git a/src/agent-connman.c b/src/agent-connman.c index fca7cc1f..2bd33e04 100644 --- a/src/agent-connman.c +++ b/src/agent-connman.c @@ -865,3 +865,17 @@ int __connman_agent_request_peer_authorization(struct connman_peer *peer, return -EINPROGRESS; } + +bool __connman_agent_is_request_pending(struct connman_service *service, + const char *dbus_sender) +{ + void *agent; + + /* Default agent will be returned if no dbus_sender */ + agent = connman_agent_get_info(dbus_sender, NULL, NULL); + + if (!service || !agent) + return false; + + return connman_agent_queue_search(service, agent); +} diff --git a/src/agent.c b/src/agent.c index d6113af7..23517d9b 100644 --- a/src/agent.c +++ b/src/agent.c @@ -257,6 +257,28 @@ int connman_agent_queue_message(void *user_context, return err; } +bool connman_agent_queue_search(void *user_context, void *agent_data) +{ + struct connman_agent *agent = agent_data; + struct connman_agent_request *queue_data; + GList *iter; + + if (!agent || !user_context) + return false; + + if (agent->pending && agent->pending->user_context == user_context) + return true; + + for (iter = agent->queue; iter; iter = iter->next) { + queue_data = iter->data; + + if (queue_data && queue_data->user_context == user_context) + return true; + } + + return false; +} + static void set_default_agent(void) { struct connman_agent *agent = NULL; @@ -366,9 +388,9 @@ static void report_error_reply(DBusMessage *reply, void *user_data) retry = true; } +out: report_error->callback(report_error->user_context, retry, report_error->user_data); -out: g_free(report_error); } diff --git a/src/bridge.c b/src/bridge.c index cd2d9cee..df19a6af 100644 --- a/src/bridge.c +++ b/src/bridge.c @@ -122,7 +122,8 @@ int __connman_bridge_enable(const char *name, const char *ip_address, err = __connman_inet_modify_address(RTM_NEWADDR, NLM_F_REPLACE | NLM_F_ACK, index, AF_INET, - ip_address, NULL, prefix_len, broadcast); + ip_address, NULL, prefix_len, broadcast, + false); if (err < 0) return err; diff --git a/src/clock.c b/src/clock.c index 0fde2c34..54ac274a 100644 --- a/src/clock.c +++ b/src/clock.c @@ -173,6 +173,7 @@ static DBusMessage *get_properties(DBusConnection *conn, { DBusMessage *reply; DBusMessageIter array, dict; + dbus_bool_t is_synced; struct timeval tv; const char *str; @@ -210,6 +211,10 @@ static DBusMessage *get_properties(DBusConnection *conn, connman_dbus_dict_append_array(&dict, "Timeservers", DBUS_TYPE_STRING, append_timeservers, NULL); + is_synced = __connman_timeserver_is_synced(); + connman_dbus_dict_append_basic(&dict, "TimeserverSynced", + DBUS_TYPE_BOOLEAN, &is_synced); + connman_dbus_dict_close(&array, &dict); return reply; @@ -258,12 +263,14 @@ static DBusMessage *set_property(DBusConnection *conn, if (settimeofday(&tv, NULL) < 0) return __connman_error_invalid_arguments(msg); + __connman_timeserver_set_synced(false); connman_dbus_property_changed_basic(CONNMAN_MANAGER_PATH, CONNMAN_CLOCK_INTERFACE, "Time", DBUS_TYPE_UINT64, &newval); } 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); @@ -283,6 +290,9 @@ static DBusMessage *set_property(DBusConnection *conn, connman_dbus_property_changed_basic(CONNMAN_MANAGER_PATH, CONNMAN_CLOCK_INTERFACE, "TimeUpdates", DBUS_TYPE_STRING, &strval); + + service = connman_service_get_default(); + __connman_timeserver_conf_update(service); } else if (g_str_equal(name, "Timezone")) { const char *strval; @@ -362,6 +372,8 @@ static DBusMessage *set_property(DBusConnection *conn, connman_dbus_property_changed_array(CONNMAN_MANAGER_PATH, CONNMAN_CLOCK_INTERFACE, "Timeservers", DBUS_TYPE_STRING, append_timeservers, NULL); + } else if (g_str_equal(name, "TimeserverSynced")) { + return __connman_error_permission_denied(msg); } else return __connman_error_invalid_property(msg); diff --git a/src/config.c b/src/config.c index 62023b10..33fdc737 100644 --- a/src/config.c +++ b/src/config.c @@ -1106,8 +1106,7 @@ static char *config_pem_fsid(const char *pem_file) static void provision_service_wifi(struct connman_config_service *config, struct connman_service *service, - struct connman_network *network, - const void *ssid, unsigned int ssid_len) + struct connman_network *network) { if (config->eap) __connman_service_set_string(service, "EAP", config->eap); @@ -1418,8 +1417,7 @@ static int try_provision_service(struct connman_config_service *config, config->timeservers); if (type == CONNMAN_SERVICE_TYPE_WIFI) { - provision_service_wifi(config, service, network, - ssid, ssid_len); + provision_service_wifi(config, service, network); } __connman_service_mark_dirty(); diff --git a/src/connection.c b/src/connection.c index 303e9922..9d2c6961 100644 --- a/src/connection.c +++ b/src/connection.c @@ -1066,6 +1066,29 @@ int __connman_connection_get_vpn_index(int phy_index) return -1; } +int __connman_connection_get_vpn_phy_index(int vpn_index) +{ + GHashTableIter iter; + gpointer value, key; + + g_hash_table_iter_init(&iter, gateway_hash); + + while (g_hash_table_iter_next(&iter, &key, &value)) { + struct gateway_data *data = value; + + if (data->index != vpn_index) + continue; + + if (data->ipv4_gateway) + return data->ipv4_gateway->vpn_phy_index; + + if (data->ipv6_gateway) + return data->ipv6_gateway->vpn_phy_index; + } + + return -1; +} + int __connman_connection_init(void) { int err; diff --git a/src/connman.h b/src/connman.h index 3bdc0dc7..b955d98b 100644 --- a/src/connman.h +++ b/src/connman.h @@ -126,6 +126,8 @@ int __connman_agent_request_peer_authorization(struct connman_peer *peer, bool wps_requested, const char *dbus_sender, void *user_data); +bool __connman_agent_is_request_pending(struct connman_service *service, + const char *dbus_sender); #include <connman/log.h> @@ -138,8 +140,6 @@ void __connman_log_enable(struct connman_debug_desc *start, #include <connman/backtrace.h> -#include <connman/option.h> - #include <connman/setting.h> #include <connman/plugin.h> @@ -159,7 +159,8 @@ int __connman_inet_modify_address(int cmd, int flags, int index, int family, const char *address, const char *peer, unsigned char prefixlen, - const char *broadcast); + const char *broadcast, + bool is_p2p); int __connman_inet_get_interface_address(int index, int family, void *address); int __connman_inet_get_interface_ll_address(int index, int family, void *address); int __connman_inet_get_interface_mac_address(int index, uint8_t *mac_address); @@ -302,6 +303,7 @@ struct connman_ipaddress { char *peer; char *broadcast; char *gateway; + bool is_p2p; /* P2P connection or VPN, broadcast is excluded. */ }; struct connman_ipconfig_ops { @@ -449,7 +451,12 @@ 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); -int __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); +void __connman_timeserver_set_synced(bool status); enum __connman_dhcpv6_status { CONNMAN_DHCPV6_STATUS_FAIL = 0, @@ -502,6 +509,7 @@ int __connman_connection_gateway_add(struct connman_service *service, void __connman_connection_gateway_remove(struct connman_service *service, enum connman_ipconfig_type type); int __connman_connection_get_vpn_index(int phy_index); +int __connman_connection_get_vpn_phy_index(int vpn_index); bool __connman_connection_update_gateway(void); @@ -600,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, @@ -609,6 +618,7 @@ const char *__connman_network_get_type(struct connman_network *network); const char *__connman_network_get_group(struct connman_network *network); const char *__connman_network_get_ident(struct connman_network *network); bool __connman_network_get_weakness(struct connman_network *network); +bool __connman_network_native_autoconnect(struct connman_network *network); int __connman_config_init(); void __connman_config_cleanup(void); @@ -656,6 +666,8 @@ void __connman_provider_list(DBusMessageIter *iter, void *user_data); bool __connman_provider_is_immutable(struct connman_provider *provider); int __connman_provider_create_and_connect(DBusMessage *msg); const char * __connman_provider_get_ident(struct connman_provider *provider); +const char * __connman_provider_get_transport_ident( + struct connman_provider *provider); int __connman_provider_indicate_state(struct connman_provider *provider, enum connman_provider_state state); int __connman_provider_indicate_error(struct connman_provider *provider, @@ -670,6 +682,8 @@ int __connman_provider_init(void); int __connman_service_init(void); void __connman_service_cleanup(void); +int __connman_service_move(struct connman_service *service, + struct connman_service *target, bool before); int __connman_service_load_modifiable(struct connman_service *service); void __connman_service_list_struct(DBusMessageIter *iter); @@ -720,8 +734,9 @@ int __connman_service_set_mdns(struct connman_service *service, void __connman_service_set_string(struct connman_service *service, const char *key, const char *value); -int __connman_service_online_check_failed(struct connman_service *service, - enum connman_ipconfig_type type); +void __connman_service_online_check(struct connman_service *service, + enum connman_ipconfig_type type, + bool success); int __connman_service_ipconfig_indicate_state(struct connman_service *service, enum connman_service_state new_state, enum connman_ipconfig_type type); @@ -737,7 +752,6 @@ int __connman_service_indicate_default(struct connman_service *service); int __connman_service_connect(struct connman_service *service, enum connman_service_connect_reason reason); int __connman_service_disconnect(struct connman_service *service); -int __connman_service_disconnect_all(void); void __connman_service_set_active_session(bool enable, GSList *list); void __connman_service_auto_connect(enum connman_service_connect_reason reason); bool __connman_service_remove(struct connman_service *service); @@ -779,6 +793,9 @@ void __connman_service_set_pac(struct connman_service *service, bool __connman_service_is_hidden(struct connman_service *service); bool __connman_service_is_split_routing(struct connman_service *service); bool __connman_service_index_is_split_routing(int index); +void __connman_service_set_split_routing(struct connman_service *service, + bool split_routing); +void __connman_service_split_routing_changed(struct connman_service *service); int __connman_service_get_index(struct connman_service *service); void __connman_service_set_hidden(struct connman_service *service); void __connman_service_set_hostname(struct connman_service *service, @@ -970,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); @@ -246,9 +246,7 @@ dbus_bool_t connman_dbus_property_changed_basic(const char *path, dbus_message_iter_init_append(signal, &iter); connman_dbus_property_append_basic(&iter, key, type, val); - g_dbus_send_message(connection, signal); - - return TRUE; + return g_dbus_send_message(connection, signal); } dbus_bool_t connman_dbus_property_changed_dict(const char *path, @@ -268,9 +266,7 @@ dbus_bool_t connman_dbus_property_changed_dict(const char *path, dbus_message_iter_init_append(signal, &iter); connman_dbus_property_append_dict(&iter, key, function, user_data); - g_dbus_send_message(connection, signal); - - return TRUE; + return g_dbus_send_message(connection, signal); } dbus_bool_t connman_dbus_property_changed_array(const char *path, @@ -291,9 +287,7 @@ dbus_bool_t connman_dbus_property_changed_array(const char *path, connman_dbus_property_append_array(&iter, key, type, function, user_data); - g_dbus_send_message(connection, signal); - - return TRUE; + return g_dbus_send_message(connection, signal); } dbus_bool_t connman_dbus_setting_changed_basic(const char *owner, @@ -319,9 +313,7 @@ dbus_bool_t connman_dbus_setting_changed_basic(const char *owner, connman_dbus_dict_close(&array, &dict); - g_dbus_send_message(connection, msg); - - return TRUE; + return g_dbus_send_message(connection, msg); } dbus_bool_t connman_dbus_setting_changed_dict(const char *owner, @@ -348,9 +340,7 @@ dbus_bool_t connman_dbus_setting_changed_dict(const char *owner, connman_dbus_dict_close(&array, &dict); - g_dbus_send_message(connection, msg); - - return TRUE; + return g_dbus_send_message(connection, msg); } dbus_bool_t connman_dbus_setting_changed_array(const char *owner, @@ -377,9 +367,7 @@ dbus_bool_t connman_dbus_setting_changed_array(const char *owner, connman_dbus_dict_close(&array, &dict); - g_dbus_send_message(connection, msg); - - return TRUE; + return g_dbus_send_message(connection, msg); } dbus_bool_t __connman_dbus_append_objpath_dict_array(DBusMessage *msg, @@ -699,7 +687,7 @@ int __connman_dbus_init(DBusConnection *conn) connection = conn; - return 0; + return g_dbus_attach_object_manager(conn); } void __connman_dbus_cleanup(void) @@ -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")) @@ -616,7 +615,7 @@ static int dhcp_initialize(struct connman_dhcp *dhcp) g_dhcp_client_set_request(dhcp_client, G_DHCP_ROUTER); g_dhcp_client_set_request(dhcp_client, G_DHCP_SUBNET); - vendor_class_id = connman_option_get_string("VendorClassID"); + vendor_class_id = connman_setting_get_string("VendorClassID"); if (vendor_class_id) g_dhcp_client_set_send(dhcp_client, G_DHCP_VENDOR_CLASS_ID, vendor_class_id); diff --git a/src/dhcpv6.c b/src/dhcpv6.c index 2d5f8f6a..8b683599 100644 --- a/src/dhcpv6.c +++ b/src/dhcpv6.c @@ -945,7 +945,7 @@ static void do_dad(GDHCPClient *dhcp_client, struct connman_dhcpv6 *dhcp) ref_own_address(user_data); - if (inet_pton(AF_INET6, address, &addr) < 0) { + if (inet_pton(AF_INET6, address, &addr) != 1) { DBG("Invalid IPv6 address %s %d/%s", address, -errno, strerror(errno)); goto fail; diff --git a/src/dns-systemd-resolved.c b/src/dns-systemd-resolved.c index 5fe306c3..912ab3fe 100644 --- a/src/dns-systemd-resolved.c +++ b/src/dns-systemd-resolved.c @@ -106,7 +106,7 @@ static void setlinkdns_append(DBusMessageIter *iter, void *user_data) if (type == AF_INET) { result = inet_pton(type, server, ipv4_bytes); - if (!result) { + if (result != 1) { DBG("Failed to parse IPv4 address: %s", server); return; @@ -128,7 +128,7 @@ static void setlinkdns_append(DBusMessageIter *iter, void *user_data) &byte_array); } else if (type == AF_INET6) { result = inet_pton(type, server, ipv6_bytes); - if (!result) { + if (result != 1) { DBG("Failed to parse IPv6 address: %s", server); return; } diff --git a/src/dnsproxy.c b/src/dnsproxy.c index a7bf87a1..7ebffbc0 100644 --- a/src/dnsproxy.c +++ b/src/dnsproxy.c @@ -3,6 +3,7 @@ * Connection Manager * * Copyright (C) 2007-2014 Intel Corporation. All rights reserved. + * Copyright (C) 2022 Matthias Gerstner of SUSE. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as @@ -41,7 +42,13 @@ #include "connman.h" -#define debug(fmt...) do { } while (0) +#ifdef DNSPROXY_DEBUG +# define debug(fmt...) do { fprintf(stderr, fmt); fprintf(stderr, "\n"); } while (0) +#else +# define debug(fmt...) do { } while (0) +#endif + +#define NUM_ARRAY_ELEMENTS(a) sizeof(a) / sizeof(a[0]) #if __BYTE_ORDER == __LITTLE_ENDIAN struct domain_hdr { @@ -172,11 +179,17 @@ struct cache_data { struct cache_entry { char *key; bool want_refresh; - int hits; + size_t hits; struct cache_data *ipv4; struct cache_data *ipv6; }; +struct cache_timeout { + time_t current_time; + time_t max_timeout; + bool try_harder; +}; + struct domain_question { uint16_t type; uint16_t class; @@ -214,38 +227,91 @@ struct domain_rr { */ #define MAX_CACHE_SIZE 256 +#define DNS_HEADER_SIZE sizeof(struct domain_hdr) +#define DNS_HEADER_TCP_EXTRA_BYTES 2 +#define DNS_TCP_HEADER_SIZE DNS_HEADER_SIZE + DNS_HEADER_TCP_EXTRA_BYTES +#define DNS_QUESTION_SIZE sizeof(struct domain_question) +#define DNS_RR_SIZE sizeof(struct domain_rr) +#define DNS_QTYPE_QCLASS_SIZE sizeof(struct qtype_qclass) + +enum dns_type { + /* IPv4 address 32-bit */ + DNS_TYPE_A = ns_t_a, + /* IPv6 address 128-bit */ + DNS_TYPE_AAAA = ns_t_aaaa, + /* alias to another name */ + DNS_TYPE_CNAME = ns_t_cname, + /* start of a zone of authority */ + DNS_TYPE_SOA = ns_t_soa +}; + +enum dns_class { + DNS_CLASS_IN = ns_c_in, + DNS_CLASS_ANY = ns_c_any /* only valid for QCLASS fields */ +}; + static int cache_size; static GHashTable *cache; static int cache_refcount; -static GSList *server_list = NULL; -static GSList *request_list = NULL; -static GHashTable *listener_table = NULL; +static GSList *server_list; +static GSList *request_list; +static GHashTable *listener_table; static time_t next_refresh; static GHashTable *partial_tcp_req_table; -static guint cache_timer = 0; +static guint cache_timer; +static in_port_t dns_listen_port = 53; +/* we can keep using the same resolve's */ +static GResolv *ipv4_resolve; +static GResolv *ipv6_resolve; static guint16 get_id(void) { uint64_t rand; + /* TODO: return code is ignored, should we rather abort() on error? */ __connman_util_get_random(&rand); return rand; } -static int protocol_offset(int protocol) +static size_t protocol_offset(int protocol) { switch (protocol) { case IPPROTO_UDP: return 0; case IPPROTO_TCP: - return 2; + return DNS_HEADER_TCP_EXTRA_BYTES; default: - return -EINVAL; + /* this should never happen */ + abort(); } +} +static const char* protocol_label(int protocol) +{ + switch(protocol) { + case IPPROTO_UDP: + return "UDP"; + case IPPROTO_TCP: + return "TCP"; + default: + return "BAD_PROTOCOL"; + } +} + +static int socket_type(int protocol, int extra_flags) +{ + switch (protocol) { + case IPPROTO_UDP: + return SOCK_DGRAM | extra_flags; + case IPPROTO_TCP: + return SOCK_STREAM | extra_flags; + default: + /* this should never happen */ + abort(); + } } /* @@ -271,9 +337,7 @@ static time_t round_down_ttl(time_t end_time, int ttl) static struct request_data *find_request(guint16 id) { - GSList *list; - - for (list = request_list; list; list = list->next) { + for (GSList *list = request_list; list; list = list->next) { struct request_data *req = list->data; if (req->dstid == id || req->altid == id) @@ -287,11 +351,9 @@ static struct server_data *find_server(int index, const char *server, int protocol) { - GSList *list; - debug("index %d server %s proto %d", index, server, protocol); - for (list = server_list; list; list = list->next) { + for (GSList *list = server_list; list; list = list->next) { struct server_data *data = list->data; if (index < 0 && data->index < 0 && @@ -312,10 +374,6 @@ static struct server_data *find_server(int index, return NULL; } -/* we can keep using the same resolve's */ -static GResolv *ipv4_resolve; -static GResolv *ipv6_resolve; - static void dummy_resolve_func(GResolvResultStatus status, char **results, gpointer user_data) { @@ -353,95 +411,86 @@ static void refresh_dns_entry(struct cache_entry *entry, char *name) age = 4; } - entry->hits -= age; - if (entry->hits < 0) + if (entry->hits > age) + entry->hits -= age; + else entry->hits = 0; } -static int dns_name_length(unsigned char *buf) +static size_t dns_name_length(const unsigned char *buf) { if ((buf[0] & NS_CMPRSFLGS) == NS_CMPRSFLGS) /* compressed name */ return 2; - return strlen((char *)buf) + 1; + return strlen((const char *)buf) + 1; } -static void update_cached_ttl(unsigned char *buf, int len, int new_ttl) +static void update_cached_ttl(unsigned char *ptr, int len, int new_ttl) { - unsigned char *c; - uint16_t w; - int l; + size_t name_len; + const uint32_t raw_ttl = ntohl((uint32_t)new_ttl); + + if (new_ttl < 0) + return; /* skip the header */ - c = buf + 12; - len -= 12; + ptr += DNS_HEADER_SIZE; + len -= DNS_HEADER_SIZE; + + if (len < DNS_QUESTION_SIZE + 1) + return; - /* 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; + /* 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 (true) { + const char *dot = strchr(data, '.'); + len = dot ? dot - data : strlen(data); - while (query) { - const char *tmp; - - 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 = tmp - query; - memcpy(ptr + 1, query, tmp - query); - ptr += tmp - query + 1; + *ptr = len; + memcpy(ptr + 1, data, len); + ptr += len + 1; + size -= len + 1; + + if (!dot) + break; - query = tmp + 1; + data = dot + 1; } - while (domain) { - const char *tmp; + return ptr - buf; +} - tmp = strchr(domain, '.'); - if (!tmp) { - len = strlen(domain); - if (len == 0) - break; - *ptr = len; - memcpy(ptr + 1, domain, len); - ptr += len + 1; - break; - } +static int append_query(unsigned char *buf, size_t size, + const char *query, const char *domain) +{ + size_t added; + size_t left_size = size; + int res; - *ptr = tmp - domain; - memcpy(ptr + 1, domain, tmp - domain); - ptr += tmp - domain + 1; + debug("query %s domain %s", query, domain); - domain = tmp + 1; - } + res = append_data(buf, left_size, query); + if (res < 0) + return -1; + left_size -= res; - *ptr++ = 0x00; + res = append_data(buf + res, left_size, domain); + if (res < 0) + return -1; + left_size -= res; - return ptr - buf; + if (left_size == 0) + return -1; + + added = size - left_size; + *(buf + added) = 0x00; + + return added; } -static bool cache_check_is_valid(struct cache_data *data, - time_t current_time) +static bool cache_check_is_valid(struct cache_data *data, time_t current_time) { if (!data) return false; - - if (data->cache_until < current_time) + else if (data->cache_until < current_time) return false; return true; } +static void cache_free_ipv4(struct cache_entry *entry) +{ + if (!entry->ipv4) + return; + + g_free(entry->ipv4->data); + g_free(entry->ipv4); + entry->ipv4 = NULL; +} + +static void cache_free_ipv6(struct cache_entry *entry) +{ + if (!entry->ipv6) + return; + + g_free(entry->ipv6->data); + g_free(entry->ipv6); + entry->ipv6 = NULL; +} + /* * remove stale cached entries so that they can be refreshed */ @@ -677,76 +740,65 @@ static void cache_enforce_validity(struct cache_entry *entry) { time_t current_time = time(NULL); - if (!cache_check_is_valid(entry->ipv4, current_time) - && entry->ipv4) { + if (entry->ipv4 && !cache_check_is_valid(entry->ipv4, current_time)) { debug("cache timeout \"%s\" type A", entry->key); - g_free(entry->ipv4->data); - g_free(entry->ipv4); - entry->ipv4 = NULL; - + cache_free_ipv4(entry); } - if (!cache_check_is_valid(entry->ipv6, current_time) - && entry->ipv6) { + if (entry->ipv6 && !cache_check_is_valid(entry->ipv6, current_time)) { debug("cache timeout \"%s\" type AAAA", entry->key); - g_free(entry->ipv6->data); - g_free(entry->ipv6); - entry->ipv6 = NULL; + cache_free_ipv6(entry); } } -static uint16_t cache_check_validity(char *question, uint16_t type, +static bool cache_check_validity(const char *question, uint16_t type, struct cache_entry *entry) { - time_t current_time = time(NULL); - bool want_refresh = false; - - /* - * if we have a popular entry, we want a refresh instead of - * total destruction of the entry. - */ - if (entry->hits > 2) - want_refresh = true; + struct cache_data *cached_ip = NULL, *other_ip = NULL; + const time_t current_time = time(NULL); + bool want_refresh; cache_enforce_validity(entry); switch (type) { - case 1: /* IPv4 */ - if (!cache_check_is_valid(entry->ipv4, current_time)) { - debug("cache %s \"%s\" type A", entry->ipv4 ? - "timeout" : "entry missing", question); - - if (want_refresh) - entry->want_refresh = true; + case DNS_TYPE_A: /* IPv4 */ + cached_ip = entry->ipv4; + other_ip = entry->ipv6; + break; - /* - * We do not remove cache entry if there is still - * valid IPv6 entry found in the cache. - */ - if (!cache_check_is_valid(entry->ipv6, current_time) && !want_refresh) { - g_hash_table_remove(cache, question); - type = 0; - } - } + case DNS_TYPE_AAAA: /* IPv6 */ + cached_ip = entry->ipv6; + other_ip = entry->ipv4; break; + default: + return false; + } - case 28: /* IPv6 */ - if (!cache_check_is_valid(entry->ipv6, current_time)) { - debug("cache %s \"%s\" type AAAA", entry->ipv6 ? - "timeout" : "entry missing", question); + /* + * if we have a popular entry, we want a refresh instead of + * total destruction of the entry. + */ + want_refresh = entry->hits > 2 ? true : false; - if (want_refresh) - entry->want_refresh = true; + if (!cache_check_is_valid(cached_ip, current_time)) { + debug("cache %s \"%s\" type %s", + cached_ip ? "timeout" : "entry missing", + question, + cached_ip == entry->ipv4 ? "A" : "AAAA"); - if (!cache_check_is_valid(entry->ipv4, current_time) && !want_refresh) { - g_hash_table_remove(cache, question); - type = 0; - } + if (want_refresh) + entry->want_refresh = true; + /* + * We do not remove cache entry if there is still a + * valid entry for another IP version found in the cache. + */ + else if (!cache_check_is_valid(other_ip, current_time)) { + g_hash_table_remove(cache, question); + return false; } - break; } - return type; + return true; } static void cache_element_destroy(gpointer value) @@ -756,19 +808,13 @@ static void cache_element_destroy(gpointer value) if (!entry) return; - if (entry->ipv4) { - g_free(entry->ipv4->data); - g_free(entry->ipv4); - } - - if (entry->ipv6) { - g_free(entry->ipv6->data); - g_free(entry->ipv6); - } + cache_free_ipv4(entry); + cache_free_ipv6(entry); g_free(entry->key); g_free(entry); + /* TODO: this would be a worrying condition. Does this ever happen? */ if (--cache_size < 0) cache_size = 0; } @@ -782,6 +828,7 @@ static gboolean try_remove_cache(gpointer user_data) g_hash_table_destroy(cache); cache = NULL; + cache_size = 0; } return FALSE; @@ -789,36 +836,28 @@ static gboolean try_remove_cache(gpointer user_data) static void create_cache(void) { - if (__sync_fetch_and_add(&cache_refcount, 1) == 0) + if (__sync_fetch_and_add(&cache_refcount, 1) == 0) { cache = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, cache_element_destroy); + cache_size = 0; + } } -static struct cache_entry *cache_check(gpointer request, int *qtype, int proto) +static struct cache_entry *cache_check(gpointer request, uint16_t *qtype, int proto) { - char *question; - struct cache_entry *entry; - struct domain_question *q; - uint16_t type; - int offset, proto_offset; - if (!request) return NULL; - proto_offset = protocol_offset(proto); - if (proto_offset < 0) - return NULL; - - question = request + proto_offset + 12; - - offset = strlen(question) + 1; - q = (void *) (question + offset); - type = ntohs(q->type); + const char *question = request + protocol_offset(proto) + DNS_HEADER_SIZE; + const size_t offset = strlen(question) + 1; + const struct domain_question *q = (void *) (question + offset); + const uint16_t type = ntohs(q->type); + struct cache_entry *entry; /* We only cache either A (1) or AAAA (28) requests */ - if (type != 1 && type != 28) + if (type != DNS_TYPE_A && type != DNS_TYPE_AAAA) return NULL; if (!cache) { @@ -830,8 +869,7 @@ static struct cache_entry *cache_check(gpointer request, int *qtype, int proto) if (!entry) return NULL; - type = cache_check_validity(question, type, entry); - if (type == 0) + if (!cache_check_validity(question, type, entry)) return NULL; *qtype = type; @@ -846,20 +884,19 @@ static struct cache_entry *cache_check(gpointer request, int *qtype, int proto) * format so that we can cache the wire format string directly. */ static int get_name(int counter, - unsigned char *pkt, unsigned char *start, unsigned char *max, + const unsigned char *pkt, const unsigned char *start, const unsigned char *max, unsigned char *output, int output_max, int *output_len, - unsigned char **end, char *name, size_t max_name, int *name_len) + const unsigned char **end, char *name, size_t max_name, int *name_len) { - unsigned char *p; + const unsigned char *p = start; /* Limit recursion to 10 (this means up to 10 labels in domain name) */ if (counter > 10) return -EINVAL; - p = start; while (*p) { if ((*p & NS_CMPRSFLGS) == NS_CMPRSFLGS) { - uint16_t offset = (*p & 0x3F) * 256 + *(p + 1); + const uint16_t offset = (*p & 0x3F) * 256 + *(p + 1); if (offset >= max - pkt) return -ENOBUFS; @@ -875,11 +912,9 @@ static int get_name(int counter, if (pkt + label_len > max) return -ENOBUFS; - - if (*output_len > output_max) + else if (*output_len > output_max) return -ENOBUFS; - - if ((*name_len + 1 + label_len + 1) > max_name) + else if ((*name_len + 1 + label_len + 1) > max_name) return -ENOBUFS; /* @@ -908,25 +943,25 @@ static int get_name(int counter, return 0; } -static int parse_rr(unsigned char *buf, unsigned char *start, - unsigned char *max, - unsigned char *response, unsigned int *response_size, - uint16_t *type, uint16_t *class, int *ttl, int *rdlen, - unsigned char **end, +static int parse_rr(const unsigned char *buf, const unsigned char *start, + const unsigned char *max, + unsigned char *response, size_t *response_size, + uint16_t *type, uint16_t *class, int *ttl, uint16_t *rdlen, + const unsigned char **end, char *name, size_t max_name) { struct domain_rr *rr; - int err, offset; + size_t offset; int name_len = 0, output_len = 0, max_rsp = *response_size; + int err = get_name(0, buf, start, max, response, max_rsp, + &output_len, end, name, max_name, &name_len); - err = get_name(0, buf, start, max, response, max_rsp, - &output_len, end, name, max_name, &name_len); if (err < 0) return err; offset = output_len; - if ((unsigned int) offset > *response_size) + if (offset > *response_size) return -ENOBUFS; rr = (void *) (*end); @@ -942,31 +977,28 @@ static int parse_rr(unsigned char *buf, unsigned char *start, if (*ttl < 0) return -EINVAL; - memcpy(response + offset, *end, sizeof(struct domain_rr)); + memcpy(response + offset, *end, DNS_RR_SIZE); - offset += sizeof(struct domain_rr); - *end += sizeof(struct domain_rr); + offset += DNS_RR_SIZE; + *end += DNS_RR_SIZE; - if ((unsigned int) (offset + *rdlen) > *response_size) + if ((offset + *rdlen) > *response_size) return -ENOBUFS; memcpy(response + offset, *end, *rdlen); *end += *rdlen; - *response_size = offset + *rdlen; return 0; } -static bool check_alias(GSList *aliases, char *name) +static bool check_alias(GSList *aliases, const char *name) { - GSList *list; - if (aliases) { - for (list = aliases; list; list = list->next) { - int len = strlen((char *)list->data); - if (strncmp((char *)list->data, name, len) == 0) + for (GSList *list = aliases; list; list = list->next) { + const char *cmpname = (const char*)list->data; + if (strncmp(cmpname, name, NS_MAXDNAME) == 0) return true; } } @@ -974,55 +1006,67 @@ static bool check_alias(GSList *aliases, char *name) return false; } -static int parse_response(unsigned char *buf, int buflen, - char *question, int qlen, +/* + * Parses the DNS response packet found in 'buf' consisting of 'buflen' bytes. + * + * The parsed question label, response type and class, ttl and number of + * answer sections are output parameters. The response output buffer will + * receive all matching resource records to be cached. + * + * Return value is < 0 on error (negative errno) or zero on success. + */ +static int parse_response(const unsigned char *buf, size_t buflen, + char *question, size_t qlen, uint16_t *type, uint16_t *class, int *ttl, - unsigned char *response, unsigned int *response_len, + unsigned char *response, size_t *response_len, uint16_t *answers) { struct domain_hdr *hdr = (void *) buf; struct domain_question *q; - unsigned char *ptr; - uint16_t qdcount = ntohs(hdr->qdcount); - uint16_t ancount = ntohs(hdr->ancount); - int err, i; - uint16_t qtype, qclass; - unsigned char *next = NULL; - unsigned int maxlen = *response_len; - GSList *aliases = NULL, *list; - char name[NS_MAXDNAME + 1]; - - if (buflen < 12) + uint16_t qtype; + int err = -ENOMSG; + uint16_t ancount, qclass; + GSList *aliases = NULL; + const size_t maxlen = *response_len; + + *response_len = 0; + *answers = 0; + + if (buflen < DNS_HEADER_SIZE) return -EINVAL; + const uint16_t qdcount = ntohs(hdr->qdcount); + const unsigned char *ptr = buf + DNS_HEADER_SIZE; + const unsigned char *eptr = buf + buflen; + debug("qr %d qdcount %d", hdr->qr, qdcount); /* We currently only cache responses where question count is 1 */ if (hdr->qr != 1 || qdcount != 1) return -EINVAL; - ptr = buf + sizeof(struct domain_hdr); - - strncpy(question, (char *) ptr, qlen); + /* + * NOTE: currently the *caller* ensures that the `question' buffer is + * always zero terminated. + */ + strncpy(question, (const char *) ptr, MIN(qlen, buflen - DNS_HEADER_SIZE)); qlen = strlen(question); ptr += qlen + 1; /* skip \0 */ + if ((eptr - ptr) < DNS_QUESTION_SIZE) + return -EINVAL; + q = (void *) ptr; qtype = ntohs(q->type); /* We cache only A and AAAA records */ - if (qtype != 1 && qtype != 28) + if (qtype != DNS_TYPE_A && qtype != DNS_TYPE_AAAA) return -ENOMSG; - qclass = ntohs(q->class); - - ptr += 2 + 2; /* ptr points now to answers */ + ptr += DNS_QUESTION_SIZE; /* advance to answers section */ - err = -ENOMSG; - *response_len = 0; - *answers = 0; - - memset(name, 0, sizeof(name)); + ancount = ntohs(hdr->ancount); + qclass = ntohs(q->class); /* * We have a bunch of answers (like A, AAAA, CNAME etc) to @@ -1030,7 +1074,8 @@ static int parse_response(unsigned char *buf, int buflen, * resource records. Only A and AAAA records are cached, all * the other records in answers are skipped. */ - for (i = 0; i < ancount; i++) { + for (uint16_t i = 0; i < ancount; i++) { + char name[NS_MAXDNAME + 1] = {0}; /* * Get one address at a time to this buffer. * The max size of the answer is @@ -1038,23 +1083,27 @@ static int parse_response(unsigned char *buf, int buflen, * 4 (ttl) + 2 (rdlen) + addr (16 or 4) = 28 * for A or AAAA record. * For CNAME the size can be bigger. + * TODO: why are we using the MAXCDNAME constant as buffer + * size then? */ - unsigned char rsp[NS_MAXCDNAME]; - unsigned int rsp_len = sizeof(rsp) - 1; - int ret, rdlen; - - memset(rsp, 0, sizeof(rsp)); + unsigned char rsp[NS_MAXCDNAME] = {0}; + size_t rsp_len = sizeof(rsp) - 1; + const unsigned char *next = NULL; + uint16_t rdlen; - ret = parse_rr(buf, ptr, buf + buflen, rsp, &rsp_len, + int ret = parse_rr(buf, ptr, buf + buflen, rsp, &rsp_len, type, class, ttl, &rdlen, &next, name, sizeof(name) - 1); if (ret != 0) { err = ret; - goto out; + break; } + /* set pointer to the next RR for the next iteration */ + ptr = next; + /* - * Now rsp contains compressed or uncompressed resource + * Now rsp contains a compressed or an uncompressed resource * record. Next we check if this record answers the question. * The name var contains the uncompressed label. * One tricky bit is the CNAME records as they alias @@ -1066,8 +1115,6 @@ static int parse_response(unsigned char *buf, int buflen, * looking for. */ if (*class != qclass) { - ptr = next; - next = NULL; continue; } @@ -1092,7 +1139,7 @@ static int parse_response(unsigned char *buf, int buflen, * address of ipv6.l.google.com. For caching purposes this * should not cause any issues. */ - if (*type == 5 && strncmp(question, name, qlen) == 0) { + if (*type == DNS_TYPE_CNAME && strncmp(question, name, qlen) == 0) { /* * So now the alias answered the question. This is * not very useful from caching point of view as @@ -1100,7 +1147,7 @@ static int parse_response(unsigned char *buf, int buflen, * question. We need to find the real A/AAAA record * of the alias and cache that. */ - unsigned char *end = NULL; + const unsigned char *end = NULL; int name_len = 0, output_len = 0; memset(rsp, 0, sizeof(rsp)); @@ -1116,8 +1163,6 @@ static int parse_response(unsigned char *buf, int buflen, name, sizeof(name) - 1, &name_len); if (ret != 0) { /* just ignore the error at this point */ - ptr = next; - next = NULL; continue; } @@ -1129,12 +1174,8 @@ static int parse_response(unsigned char *buf, int buflen, */ aliases = g_slist_prepend(aliases, g_strdup(name)); - ptr = next; - next = NULL; continue; - } - - if (*type == qtype) { + } else if (*type == qtype) { /* * We found correct type (A or AAAA) */ @@ -1151,7 +1192,7 @@ static int parse_response(unsigned char *buf, int buflen, */ if (*response_len + rsp_len > maxlen) { err = -ENOBUFS; - goto out; + break; } memcpy(response + *response_len, rsp, rsp_len); *response_len += rsp_len; @@ -1159,31 +1200,21 @@ static int parse_response(unsigned char *buf, int buflen, err = 0; } } - - ptr = next; - next = NULL; } -out: - for (list = aliases; list; list = list->next) + for (GSList *list = aliases; list; list = list->next) g_free(list->data); g_slist_free(aliases); return err; } -struct cache_timeout { - time_t current_time; - int max_timeout; - int try_harder; -}; - static gboolean cache_check_entry(gpointer key, gpointer value, gpointer user_data) { struct cache_timeout *data = user_data; struct cache_entry *entry = value; - int max_timeout; + time_t max_timeout; /* Scale the number of hits by half as part of cache aging */ @@ -1224,14 +1255,14 @@ static gboolean cache_check_entry(gpointer key, gpointer value, static void cache_cleanup(void) { - static int max_timeout; - struct cache_timeout data; + static time_t max_timeout; + struct cache_timeout data = { + .current_time = time(NULL), + .max_timeout = 0, + .try_harder = false + }; int count = 0; - data.current_time = time(NULL); - data.max_timeout = 0; - data.try_harder = 0; - /* * In the first pass, we only remove entries that have timed out. * We use a cache of the first time to expire to do this only @@ -1248,7 +1279,7 @@ static void cache_cleanup(void) * we also expire entries with a low hit count, * while aging the hit count at the same time. */ - data.try_harder = 1; + data.try_harder = true; if (count == 0) count = g_hash_table_foreach_remove(cache, cache_check_entry, &data); @@ -1278,23 +1309,11 @@ static gboolean cache_invalidate_entry(gpointer key, gpointer value, entry->want_refresh = true; /* delete the cached data */ - if (entry->ipv4) { - g_free(entry->ipv4->data); - g_free(entry->ipv4); - entry->ipv4 = NULL; - } - - if (entry->ipv6) { - g_free(entry->ipv6->data); - g_free(entry->ipv6); - entry->ipv6 = NULL; - } + cache_free_ipv4(entry); + cache_free_ipv6(entry); /* keep the entry if we want it refreshed, delete it otherwise */ - if (entry->want_refresh) - return FALSE; - else - return TRUE; + return entry->want_refresh ? FALSE : TRUE; } /* @@ -1315,25 +1334,24 @@ static void cache_invalidate(void) static void cache_refresh_entry(struct cache_entry *entry) { - cache_enforce_validity(entry); - if (entry->hits > 2 && !entry->ipv4) - entry->want_refresh = true; - if (entry->hits > 2 && !entry->ipv6) + if (entry->hits > 2 && (!entry->ipv4 || !entry->ipv6)) entry->want_refresh = true; if (entry->want_refresh) { - char *c; char dns_name[NS_MAXDNAME + 1]; + char *c; + entry->want_refresh = false; /* turn a DNS name into a hostname with dots */ strncpy(dns_name, entry->key, NS_MAXDNAME); c = dns_name; - while (c && *c) { - int jump; - jump = *c; + while (*c) { + /* fetch the size of the current component and replace + it by a dot */ + int jump = *c; *c = '.'; c += jump + 1; } @@ -1359,43 +1377,44 @@ static void cache_refresh(void) g_hash_table_foreach(cache, cache_refresh_iterator, NULL); } -static int reply_query_type(unsigned char *msg, int len) +static int reply_query_type(const unsigned char *msg, int len) { - unsigned char *c; - int l; - int type; - /* skip the header */ - c = msg + sizeof(struct domain_hdr); - len -= sizeof(struct domain_hdr); + const unsigned char *c = msg + DNS_HEADER_SIZE; + int type; + len -= DNS_HEADER_SIZE; if (len < 0) return 0; - /* now the query, which is a name and 2 16 bit words */ - l = dns_name_length(c); - c += l; + /* now the query, which is a name and 2 16 bit words for type and class */ + c += dns_name_length(c); + type = c[0] << 8 | c[1]; return type; } -static int cache_update(struct server_data *srv, unsigned char *msg, - unsigned int msg_len) +/* + * update the cache with the DNS reply found in msg + */ +static int cache_update(struct server_data *srv, const unsigned char *msg, size_t msg_len) { - int offset = protocol_offset(srv->protocol); - int err, qlen, ttl = 0; + const size_t offset = protocol_offset(srv->protocol); + int err, ttl = 0; + uint16_t *lenhdr; + size_t qlen; + bool is_new_entry = false; uint16_t answers = 0, type = 0, class = 0; struct domain_hdr *hdr = (void *)(msg + offset); - struct domain_question *q; + struct domain_question *q = NULL; struct cache_entry *entry; struct cache_data *data; char question[NS_MAXDNAME + 1]; unsigned char response[NS_MAXDNAME + 1]; - unsigned char *ptr; - unsigned int rsplen; - bool new_entry = true; - time_t current_time; + unsigned char *ptr = NULL; + size_t rsplen = sizeof(response) - 1; + const time_t current_time = time(NULL); if (cache_size >= MAX_CACHE_SIZE) { cache_cleanup(); @@ -1403,18 +1422,13 @@ static int cache_update(struct server_data *srv, unsigned char *msg, return 0; } - current_time = time(NULL); - /* don't do a cache refresh more than twice a minute */ if (next_refresh < current_time) { cache_refresh(); next_refresh = current_time + 30; } - if (offset < 0) - return 0; - - debug("offset %d hdr %p msg %p rcode %d", offset, hdr, msg, hdr->rcode); + debug("offset %zd hdr %p msg %p rcode %d", offset, hdr, msg, hdr->rcode); /* Continue only if response code is 0 (=ok) */ if (hdr->rcode != ns_r_noerror) @@ -1423,9 +1437,7 @@ static int cache_update(struct server_data *srv, unsigned char *msg, if (!cache) create_cache(); - rsplen = sizeof(response) - 1; question[sizeof(question) - 1] = '\0'; - err = parse_response(msg + offset, msg_len - offset, question, sizeof(question) - 1, &type, &class, &ttl, @@ -1438,26 +1450,29 @@ static int cache_update(struct server_data *srv, unsigned char *msg, */ if ((err == -ENOMSG || err == -ENOBUFS) && reply_query_type(msg + offset, - msg_len - offset) == 28) { + msg_len - offset) == DNS_TYPE_AAAA) { entry = g_hash_table_lookup(cache, question); if (entry && entry->ipv4 && !entry->ipv6) { - int cache_offset = 0; + struct cache_data *data = g_try_new(struct cache_data, 1); - data = g_try_new(struct cache_data, 1); if (!data) return -ENOMEM; data->inserted = entry->ipv4->inserted; data->type = type; data->answers = ntohs(hdr->ancount); data->timeout = entry->ipv4->timeout; - if (srv->protocol == IPPROTO_UDP) - cache_offset = 2; - data->data_len = msg_len + cache_offset; - data->data = ptr = g_malloc(data->data_len); - ptr[0] = (data->data_len - 2) / 256; - ptr[1] = (data->data_len - 2) - ptr[0] * 256; - if (srv->protocol == IPPROTO_UDP) - ptr += 2; + data->data_len = msg_len + + (offset ? 0 : DNS_HEADER_TCP_EXTRA_BYTES); + data->data = g_malloc(data->data_len); + ptr = data->data; + if (srv->protocol == IPPROTO_UDP) { + /* add the two bytes length header also for + * UDP responses */ + lenhdr = (void*)ptr; + *lenhdr = htons(data->data_len - + DNS_HEADER_TCP_EXTRA_BYTES); + ptr += DNS_HEADER_TCP_EXTRA_BYTES; + } data->valid_until = entry->ipv4->valid_until; data->cache_until = entry->ipv4->cache_until; memcpy(ptr, msg, msg_len); @@ -1466,9 +1481,7 @@ static int cache_update(struct server_data *srv, unsigned char *msg, * we will get a "hit" when we serve the response * out of the cache */ - entry->hits--; - if (entry->hits < 0) - entry->hits = 0; + entry->hits = entry->hits ? entry->hits - 1 : 0; return 0; } } @@ -1476,8 +1489,6 @@ static int cache_update(struct server_data *srv, unsigned char *msg, if (err < 0 || ttl == 0) return 0; - qlen = strlen(question); - /* * If the cache contains already data, check if the * type of the cached data is the same and do not add @@ -1485,7 +1496,11 @@ static int cache_update(struct server_data *srv, unsigned char *msg, * This is needed so that we can cache both A and AAAA * records for the same name. */ + entry = g_hash_table_lookup(cache, question); + data = NULL; + is_new_entry = !entry; + if (!entry) { entry = g_try_new(struct cache_entry, 1); if (!entry) @@ -1502,37 +1517,28 @@ static int cache_update(struct server_data *srv, unsigned char *msg, entry->want_refresh = false; entry->hits = 0; - if (type == 1) - entry->ipv4 = data; - else - entry->ipv6 = data; } else { - if (type == 1 && entry->ipv4) + if (type == DNS_TYPE_A && entry->ipv4) return 0; - - if (type == 28 && entry->ipv6) + else if (type == DNS_TYPE_AAAA && entry->ipv6) return 0; data = g_try_new(struct cache_data, 1); if (!data) return -ENOMEM; - if (type == 1) - entry->ipv4 = data; - else - entry->ipv6 = data; - /* * compensate for the hit we'll get for serving * the response out of the cache */ - entry->hits--; - if (entry->hits < 0) - entry->hits = 0; - - new_entry = false; + entry->hits = entry->hits ? entry->hits - 1 : 0; } + if (type == DNS_TYPE_A) + entry->ipv4 = data; + else + entry->ipv6 = data; + if (ttl < MIN_CACHE_TTL) ttl = MIN_CACHE_TTL; @@ -1540,14 +1546,21 @@ static int cache_update(struct server_data *srv, unsigned char *msg, data->type = type; data->answers = answers; data->timeout = ttl; + data->valid_until = current_time + ttl; + + qlen = strlen(question); /* - * The "2" in start of the length is the TCP offset. We allocate it - * here even for UDP packet because it simplifies the sending - * of cached packet. + * We allocate the extra TCP header bytes here even for UDP packet + * because it simplifies the sending of cached packet. */ - data->data_len = 2 + 12 + qlen + 1 + 2 + 2 + rsplen; - data->data = ptr = g_malloc(data->data_len); - data->valid_until = current_time + ttl; + data->data_len = DNS_TCP_HEADER_SIZE + qlen + 1 + 2 + 2 + rsplen; + data->data = g_malloc(data->data_len); + if (!data->data) { + g_free(entry->key); + g_free(data); + g_free(entry); + return -ENOMEM; + } /* * Restrict the cached DNS record TTL to some sane value @@ -1558,45 +1571,39 @@ static int cache_update(struct server_data *srv, unsigned char *msg, data->cache_until = round_down_ttl(current_time + ttl, ttl); - if (!data->data) { - g_free(entry->key); - g_free(data); - g_free(entry); - return -ENOMEM; - } + ptr = data->data; /* * We cache the two extra bytes at the start of the message - * in a TCP packet. When sending UDP packet, we skip the first + * in a TCP packet. When sending UDP packet, we pad the first * two bytes. This way we do not need to know the format * (UDP/TCP) of the cached message. */ - if (srv->protocol == IPPROTO_UDP) - memcpy(ptr + 2, msg, offset + 12); - else - memcpy(ptr, msg, offset + 12); + lenhdr = (void*)ptr; + *lenhdr = htons(data->data_len - DNS_HEADER_TCP_EXTRA_BYTES); + ptr += DNS_HEADER_TCP_EXTRA_BYTES; - ptr[0] = (data->data_len - 2) / 256; - ptr[1] = (data->data_len - 2) - ptr[0] * 256; - if (srv->protocol == IPPROTO_UDP) - ptr += 2; + memcpy(ptr, hdr, DNS_HEADER_SIZE); + ptr += DNS_HEADER_SIZE; - memcpy(ptr + offset + 12, question, qlen + 1); /* copy also the \0 */ + memcpy(ptr, question, qlen + 1); /* copy also the \0 */ + ptr += qlen + 1; - q = (void *) (ptr + offset + 12 + qlen + 1); + q = (void *)ptr; q->type = htons(type); q->class = htons(class); - memcpy(ptr + offset + 12 + qlen + 1 + sizeof(struct domain_question), - response, rsplen); + ptr += DNS_QUESTION_SIZE; - if (new_entry) { + memcpy(ptr, response, rsplen); + + 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; - if (data && req->protocol == IPPROTO_TCP) { + ttl_left = data->valid_until - time(NULL); + entry->hits++; + + 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,23 +1775,21 @@ 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 */ + char * const uncomp_end = uncompressed + uncomp_len - 1; debug("count %d ptr %p end %p uptr %p", field_count, ptr, end, uptr); @@ -1780,21 +1803,22 @@ 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 * tmp buffer. */ - ulen = strlen(name); - strncpy(uptr, name, uncomp_len - (uptr - uncompressed)); + ulen = strlen(name) + 1; + if ((uptr + ulen) > uncomp_end) + return NULL; + memcpy(uptr, name, ulen); debug("pos %d ulen %d left %d name %s", pos, ulen, - (int)(uncomp_len - (uptr - uncompressed)), uptr); + (int)(uncomp_end - (uptr + ulen)), uptr); uptr += ulen; - *uptr++ = '\0'; ptr += pos; @@ -1802,13 +1826,17 @@ static char *uncompress(int16_t field_count, char *start, char *end, * We copy also the fixed portion of the result (type, class, * ttl, address length and the address) */ + if ((uptr + NS_RRFIXEDSZ) > uncomp_end) { + debug("uncompressed data too large for buffer"); + 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; @@ -1818,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; @@ -1830,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) { + 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; @@ -1850,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]; @@ -1861,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; @@ -1872,6 +1900,10 @@ static char *uncompress(int16_t field_count, char *start, char *end, * refresh interval, retry interval, expiration * limit and minimum ttl). They are 20 bytes long. */ + if ((uptr + 20) > uncomp_end || (ptr + 20) > end) { + debug("soa record too long"); + return NULL; + } memcpy(uptr, ptr, 20); uptr += 20; ptr += 20; @@ -1888,239 +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; } } - answers += strlen(answers) + 1; - answers += 2 + 2 + 4; /* skip type, class and ttl fields */ - - data_len = answers[0] << 8 | answers[1]; - answers += 2; /* skip the length field */ + /* skip to the next answer section */ - if (answers + data_len > end) + /* the labels up to the root null label */ + answers += strlen(answers) + 1; + /* 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; - maxlen -= answers - ptr; } - return end - start; + if (answers > end) + return -EINVAL; + + return length; } -static int forward_dns_reply(unsigned char *reply, int reply_len, int protocol, - struct server_data *data) +/* + * 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) { - struct domain_hdr *hdr; - struct request_data *req; - int dns_id, sk, err, offset = protocol_offset(protocol); + 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; - if (offset < 0) - return offset; + const uint16_t section_counts[] = { + hdr->ancount, + hdr->nscount, + hdr->arcount + }; - hdr = (void *)(reply + offset); - dns_id = reply[offset] | reply[offset + 1] << 8; + /* + * 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; - debug("Received %d bytes (id 0x%04x)", reply_len, dns_id); + const uint16_t domain_len = host_len ? strnlen(domain, eom - domain) : 0; - req = find_request(dns_id); - if (!req) + /* + * 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; - debug("req %p dstid 0x%04x altid 0x%04x rcode %d", - req, req->dstid, req->altid, hdr->rcode); + const uint16_t dns_type = ntohs(qtc->qtype); + const uint16_t dns_class = ntohs(qtc->qclass); - reply[offset] = req->srcid & 0xff; - reply[offset + 1] = req->srcid >> 8; + if (domain_len == 0) { + /* nothing to do */ + return 0; + } - req->numresp++; + /* 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; + } - if (hdr->rcode == ns_r_noerror || !req->resp) { - unsigned char *new_reply = NULL; + /* + * 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 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. - * - * 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; - 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; + /* NOTE: length checks up and including to qtype_qclass have already + been done above */ - /* - * ptr points to the first char of the hostname. - * ->hostname.domain.net - */ - header_len = offset + sizeof(struct domain_hdr); - ptr = (char *)reply + header_len; + /* + * First copy host (without domain name) into tmp buffer. + */ + uptr = &uncompressed[0]; + memcpy(uptr, ptr, host_len + 1); - host_len = *ptr; - if (host_len > 0) - domain_len = strnlen(ptr + 1 + host_len, - reply_len - header_len); + uptr[host_len + 1] = '\0'; /* host termination */ + uptr += host_len + 2; - /* - * 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; - - 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; - } + /* + * Copy type and class fields of the question. + */ + memcpy(uptr, qtc, sizeof(*qtc)); - /* - * 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; + /* + * ptr points to answers after this + */ + ptr = (void*)(qtc + 1); + uptr += sizeof(*qtc); + answers = uptr; + fixed_len = answers - uncompressed; - /* - * First copy host (without domain name) into - * tmp buffer. - */ - uptr = &uncompressed[0]; - memcpy(uptr, ptr, len); + /* + * 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. + */ - uptr[len] = '\0'; /* host termination */ - uptr += len + 1; + 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; + } + } - /* - * Copy type and class fields of the question. - */ - ptr += len + domain_len + 1; - memcpy(uptr, ptr, NS_QFIXEDSZ); + /* + * 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; + } - /* - * ptr points to answers after this - */ - ptr += NS_QFIXEDSZ; - uptr += NS_QFIXEDSZ; - answers = uptr; - fixed_len = answers - uncompressed; + /* + * 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. + */ - /* - * 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. - */ + reply_len = header_len + new_an_len + fixed_len; - 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; + *new_reply = g_try_malloc(reply_len); + if (!*new_reply) + return -ENOMEM; - /* - * 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; - } + memcpy(*new_reply, reply, header_len); + memcpy(*new_reply + header_len, uncompressed, new_an_len + fixed_len); - /* - * Because we have now uncompressed the answers - * we might have to create a bigger buffer to - * hold all that data. - */ + return reply_len; +} + +static struct request_data* lookup_request( + const unsigned char *reply, size_t len, int protocol) +{ + const size_t offset = protocol_offset(protocol); + struct request_data *req; + struct domain_hdr *hdr = (void *)(reply + offset); + + debug("Received %zd bytes (id 0x%04x)", len, hdr->id); - reply_len = header_len + new_len + fixed_len; + if (len < DNS_HEADER_SIZE + offset) + return NULL; + + req = find_request(hdr->id); + + if (!req) + return NULL; + + debug("req %p dstid 0x%04x altid 0x%04x rcode %d", + req, req->dstid, req->altid, hdr->rcode); + + req->numresp++; + + 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; - new_reply = g_try_malloc(reply_len); - if (!new_reply) - return -ENOMEM; + /* replace with original request ID from our client */ + hdr->id = req->srcid; - memcpy(new_reply, reply, header_len); - memcpy(new_reply + header_len, uncompressed, - new_len + fixed_len); + if (hdr->rcode == ns_r_noerror || !req->resp) { + /* + * 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. + */ + 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; @@ -2131,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; @@ -2156,6 +2248,9 @@ out: err = sendto(sk, req->resp, req->resplen, 0, &req->sa, req->sa_len); } else { + 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; err = send(sk, req->resp, req->resplen, MSG_NOSIGNAL); } @@ -2166,8 +2261,6 @@ out: else debug("proto %d sent %d bytes to %d", protocol, err, sk); - destroy_request_data(req); - return err; } @@ -2231,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); @@ -2241,11 +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); - if (len >= 12) - 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; } @@ -2253,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; @@ -2274,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; /* @@ -2292,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); @@ -2306,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); @@ -2326,17 +2427,26 @@ hangup: } } + /* + * Remove the G_IO_OUT flag from the watch, otherwise we end + * up in a busy loop, because the socket is constantly writable. + * + * There seems to be no better way in g_io to do that than + * re-adding the watch. + */ + g_source_remove(server->watch); + server->watch = g_io_add_watch(server->channel, + G_IO_IN | G_IO_HUP | G_IO_NVAL | G_IO_ERR, + tcp_server_event, server); + server->connected = true; server_list = g_slist_append(server_list, server); - if (server->timeout > 0) { - g_source_remove(server->timeout); - server->timeout = 0; - } - - 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; @@ -2356,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; } @@ -2381,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) { @@ -2396,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); @@ -2409,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; @@ -2431,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; @@ -2449,7 +2575,7 @@ static gboolean tcp_idle_timeout(gpointer user_data) { struct server_data *server = user_data; - debug(""); + debug("\n"); if (!server) return FALSE; @@ -2461,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", @@ -2540,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) @@ -2557,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; @@ -2580,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; @@ -2645,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); @@ -2656,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) { @@ -2687,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; + 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; @@ -2739,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; @@ -2769,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); @@ -2802,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) @@ -2814,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, @@ -2829,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) { @@ -2874,9 +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; + bool any_server_enabled = false; + int index, vpn_index; DBG("service %p", service); @@ -2893,20 +2998,30 @@ static void dnsproxy_default_changed(struct connman_service *service) if (index < 0) return; - for (list = server_list; list; list = list->next) { + /* + * In case non-split-routed VPN is set as split routed the DNS servers + * the VPN must be enabled as well, when the transport becomes the + * default service. + */ + vpn_index = __connman_connection_get_vpn_index(index); + + 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; } else { DBG("Disabling DNS server %s", data->server); data->enabled = false; } } - if (!server_enabled) + if (!any_server_enabled) enable_fallback(true); cache_refresh(); @@ -2954,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"); @@ -3002,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; } @@ -3019,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); @@ -3067,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]; } @@ -3078,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); @@ -3099,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; } @@ -3141,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; @@ -3159,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, @@ -3182,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) @@ -3233,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, @@ -3250,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; } @@ -3276,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, @@ -3294,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; @@ -3310,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) { @@ -3330,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); @@ -3345,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); @@ -3354,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); @@ -3380,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) { @@ -3446,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; @@ -3460,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)); @@ -3479,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, @@ -3514,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"); @@ -3529,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; @@ -3540,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; @@ -3563,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; @@ -3610,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; } @@ -3666,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, @@ -3683,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, @@ -3704,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)); @@ -3818,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; @@ -3830,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"); @@ -3844,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)", @@ -3914,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) @@ -3967,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) @@ -4012,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; +} @@ -79,7 +79,8 @@ int __connman_inet_modify_address(int cmd, int flags, const char *address, const char *peer, unsigned char prefixlen, - const char *broadcast) + const char *broadcast, + bool is_p2p) { uint8_t request[NLMSG_ALIGN(sizeof(struct nlmsghdr)) + NLMSG_ALIGN(sizeof(struct ifaddrmsg)) + @@ -94,8 +95,9 @@ int __connman_inet_modify_address(int cmd, int flags, int sk, err; DBG("cmd %#x flags %#x index %d family %d address %s peer %s " - "prefixlen %hhu broadcast %s", cmd, flags, index, family, - address, peer, prefixlen, broadcast); + "prefixlen %hhu broadcast %s p2p %s", cmd, flags, index, + family, address, peer, prefixlen, broadcast, + is_p2p ? "true" : "false"); if (!address) return -EINVAL; @@ -119,17 +121,11 @@ int __connman_inet_modify_address(int cmd, int flags, ifaddrmsg->ifa_index = index; if (family == AF_INET) { - if (inet_pton(AF_INET, address, &ipv4_addr) < 1) + if (inet_pton(AF_INET, address, &ipv4_addr) != 1) return -1; - if (broadcast) - inet_pton(AF_INET, broadcast, &ipv4_bcast); - else - ipv4_bcast.s_addr = ipv4_addr.s_addr | - htonl(0xfffffffflu >> prefixlen); - if (peer) { - if (inet_pton(AF_INET, peer, &ipv4_dest) < 1) + if (inet_pton(AF_INET, peer, &ipv4_dest) != 1) return -1; err = __connman_inet_rtnl_addattr_l(header, @@ -149,16 +145,27 @@ int __connman_inet_modify_address(int cmd, int flags, if (err < 0) return err; - err = __connman_inet_rtnl_addattr_l(header, - sizeof(request), - IFA_BROADCAST, - &ipv4_bcast, - sizeof(ipv4_bcast)); - if (err < 0) - return err; + /* + * Broadcast address must not be added for P2P / VPN as + * getifaddrs() cannot interpret destination address. + */ + if (!is_p2p) { + if (broadcast) + inet_pton(AF_INET, broadcast, &ipv4_bcast); + else + ipv4_bcast.s_addr = ipv4_addr.s_addr | + htonl(0xfffffffflu >> prefixlen); + err = __connman_inet_rtnl_addattr_l(header, + sizeof(request), + IFA_BROADCAST, + &ipv4_bcast, + sizeof(ipv4_bcast)); + if (err < 0) + return err; + } } else if (family == AF_INET6) { - if (inet_pton(AF_INET6, address, &ipv6_addr) < 1) + if (inet_pton(AF_INET6, address, &ipv6_addr) != 1) return -1; err = __connman_inet_rtnl_addattr_l(header, @@ -189,13 +196,46 @@ done: return err; } +static bool is_addr_unspec(int family, struct sockaddr *addr) +{ + struct sockaddr_in *in4; + struct sockaddr_in6 *in6; + + switch (family) { + case AF_INET: + in4 = (struct sockaddr_in*) addr; + return in4->sin_addr.s_addr == INADDR_ANY; + case AF_INET6: + in6 = (struct sockaddr_in6*) addr; + return IN6_IS_ADDR_UNSPECIFIED(&in6->sin6_addr); + default: + return false; + } +} + +static bool is_addr_ll(int family, struct sockaddr *addr) +{ + struct sockaddr_in *in4; + struct sockaddr_in6 *in6; + + switch (family) { + case AF_INET: + in4 = (struct sockaddr_in*) addr; + return (in4->sin_addr.s_addr & IN_CLASSB_NET) == + ((in_addr_t) htonl(0xa9fe0000)); + case AF_INET6: + in6 = (struct sockaddr_in6*) addr; + return IN6_IS_ADDR_LINKLOCAL(&in6->sin6_addr); + default: + return false; + } +} + bool __connman_inet_is_any_addr(const char *address, int family) { bool ret = false; struct addrinfo hints; struct addrinfo *result = NULL; - struct sockaddr_in6 *in6 = NULL; - struct sockaddr_in *in4 = NULL; if (!address || !*address) goto out; @@ -208,14 +248,7 @@ bool __connman_inet_is_any_addr(const char *address, int family) goto out; if (result) { - if (result->ai_family == AF_INET6) { - in6 = (struct sockaddr_in6*)result->ai_addr; - ret = IN6_IS_ADDR_UNSPECIFIED(&in6->sin6_addr); - } else if (result->ai_family == AF_INET) { - in4 = (struct sockaddr_in*)result->ai_addr; - ret = in4->sin_addr.s_addr == INADDR_ANY; - } - + ret = is_addr_unspec(result->ai_family, result->ai_addr); freeaddrinfo(result); } @@ -409,18 +442,20 @@ int connman_inet_set_ipv6_address(int index, int err; unsigned char prefix_len; const char *address; + bool is_p2p; if (!ipaddress->local) return 0; prefix_len = ipaddress->prefixlen; address = ipaddress->local; + is_p2p = ipaddress->is_p2p; DBG("index %d address %s prefix_len %d", index, address, prefix_len); err = __connman_inet_modify_address(RTM_NEWADDR, NLM_F_REPLACE | NLM_F_ACK, index, AF_INET6, - address, NULL, prefix_len, NULL); + address, NULL, prefix_len, NULL, is_p2p); if (err < 0) { connman_error("%s: %s", __func__, strerror(-err)); return err; @@ -434,6 +469,7 @@ int connman_inet_set_address(int index, struct connman_ipaddress *ipaddress) int err; unsigned char prefix_len; const char *address, *broadcast, *peer; + bool is_p2p; if (!ipaddress->local) return -1; @@ -442,12 +478,13 @@ int connman_inet_set_address(int index, struct connman_ipaddress *ipaddress) address = ipaddress->local; broadcast = ipaddress->broadcast; peer = ipaddress->peer; + is_p2p = ipaddress->is_p2p; DBG("index %d address %s prefix_len %d", index, address, prefix_len); err = __connman_inet_modify_address(RTM_NEWADDR, NLM_F_REPLACE | NLM_F_ACK, index, AF_INET, - address, peer, prefix_len, broadcast); + address, peer, prefix_len, broadcast, is_p2p); if (err < 0) { connman_error("%s: %s", __func__, strerror(-err)); return err; @@ -456,10 +493,17 @@ int connman_inet_set_address(int index, struct connman_ipaddress *ipaddress) return 0; } -int connman_inet_clear_ipv6_address(int index, const char *address, - int prefix_len) +int connman_inet_clear_ipv6_address(int index, + struct connman_ipaddress *ipaddress) { int err; + int prefix_len; + const char *address; + bool is_p2p; + + address = ipaddress->local; + prefix_len = ipaddress->prefixlen; + is_p2p = ipaddress->is_p2p; DBG("index %d address %s prefix_len %d", index, address, prefix_len); @@ -467,7 +511,7 @@ int connman_inet_clear_ipv6_address(int index, const char *address, return -EINVAL; err = __connman_inet_modify_address(RTM_DELADDR, 0, index, AF_INET6, - address, NULL, prefix_len, NULL); + address, NULL, prefix_len, NULL, is_p2p); if (err < 0) { connman_error("%s: %s", __func__, strerror(-err)); return err; @@ -481,11 +525,13 @@ int connman_inet_clear_address(int index, struct connman_ipaddress *ipaddress) int err; unsigned char prefix_len; const char *address, *broadcast, *peer; + bool is_p2p; prefix_len = ipaddress->prefixlen; address = ipaddress->local; broadcast = ipaddress->broadcast; peer = ipaddress->peer; + is_p2p = ipaddress->is_p2p; DBG("index %d address %s prefix_len %d peer %s broadcast %s", index, address, prefix_len, peer, broadcast); @@ -494,7 +540,7 @@ int connman_inet_clear_address(int index, struct connman_ipaddress *ipaddress) return -EINVAL; err = __connman_inet_modify_address(RTM_DELADDR, 0, index, AF_INET, - address, peer, prefix_len, broadcast); + address, peer, prefix_len, broadcast, is_p2p); if (err < 0) { connman_error("%s: %s", __func__, strerror(-err)); return err; @@ -661,7 +707,7 @@ int connman_inet_del_ipv6_network_route(int index, const char *host, rt.rtmsg_dst_len = prefix_len; - if (inet_pton(AF_INET6, host, &rt.rtmsg_dst) < 0) { + if (inet_pton(AF_INET6, host, &rt.rtmsg_dst) != 1) { err = -errno; goto out; } @@ -711,7 +757,7 @@ int connman_inet_add_ipv6_network_route(int index, const char *host, rt.rtmsg_dst_len = prefix_len; - if (inet_pton(AF_INET6, host, &rt.rtmsg_dst) < 0) { + if (inet_pton(AF_INET6, host, &rt.rtmsg_dst) != 1) { err = -errno; goto out; } @@ -727,7 +773,7 @@ int connman_inet_add_ipv6_network_route(int index, const char *host, */ if (gateway && !__connman_inet_is_any_addr(gateway, AF_INET6) && - inet_pton(AF_INET6, gateway, &rt.rtmsg_gateway) > 0) + inet_pton(AF_INET6, gateway, &rt.rtmsg_gateway) == 1) rt.rtmsg_flags |= RTF_GATEWAY; rt.rtmsg_metric = 1; @@ -770,7 +816,7 @@ int connman_inet_clear_ipv6_gateway_address(int index, const char *gateway) memset(&rt, 0, sizeof(rt)); - if (inet_pton(AF_INET6, gateway, &rt.rtmsg_gateway) < 0) { + if (inet_pton(AF_INET6, gateway, &rt.rtmsg_gateway) != 1) { err = -errno; goto out; } @@ -1066,54 +1112,161 @@ out: return err; } -bool connman_inet_compare_subnet(int index, const char *host) +#define ADDR_TYPE_MAX 4 + +struct interface_address { + int index; + int family; + bool allow_unspec; + /* Applies only to ADDR_TYPE_IPADDR in ipaddrs */ + bool require_ll; + /* Real types must be in_addr for AF_INET and in6_addr for AF_INET6 */ + void *ipaddrs[ADDR_TYPE_MAX]; +}; + +enum ipaddr_type { + ADDR_TYPE_IPADDR = 0, + ADDR_TYPE_NETMASK, + ADDR_TYPE_BRDADDR, + ADDR_TYPE_DSTADDR +}; + +static int get_interface_addresses(struct interface_address *if_addr) { - struct ifreq ifr; - struct in_addr _host_addr; - in_addr_t host_addr, netmask_addr, if_addr; - struct sockaddr_in *netmask, *addr; - int sk; + struct ifaddrs *ifaddr; + struct ifaddrs *ifa; + struct sockaddr *addrs[ADDR_TYPE_MAX] = { 0 }; + struct sockaddr_in *addr_in; + struct sockaddr_in6 *addr_in6; + char name[IF_NAMESIZE] = { 0 }; + size_t len; + int err = -ENOENT; + int i; - DBG("host %s", host); + if (!if_addr) + return -EINVAL; - if (!host) - return false; + if (!if_indextoname(if_addr->index, name)) + return -EINVAL; - if (inet_aton(host, &_host_addr) == 0) - return false; - host_addr = _host_addr.s_addr; + DBG("index %d interface %s", if_addr->index, name); - sk = socket(PF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0); - if (sk < 0) - return false; + if (getifaddrs(&ifaddr) < 0) { + connman_error("Cannot get addresses err %d/%s", errno, + strerror(errno)); + return -errno; + } - memset(&ifr, 0, sizeof(ifr)); - ifr.ifr_ifindex = index; + for (ifa = ifaddr; ifa; ifa = ifa->ifa_next) { + if (!ifa->ifa_addr) + continue; - if (ioctl(sk, SIOCGIFNAME, &ifr) < 0) { - close(sk); - return false; + if (g_strcmp0(ifa->ifa_name, name) || + ifa->ifa_addr->sa_family != + if_addr->family) + continue; + + + if (if_addr->ipaddrs[ADDR_TYPE_IPADDR]) { + if (!if_addr->allow_unspec && is_addr_unspec( + if_addr->family, + ifa->ifa_addr)) + continue; + + if (if_addr->require_ll && !is_addr_ll(if_addr->family, + ifa->ifa_addr)) + continue; + + addrs[ADDR_TYPE_IPADDR] = ifa->ifa_addr; + } + + if (if_addr->ipaddrs[ADDR_TYPE_NETMASK]) { + if (!if_addr->allow_unspec && is_addr_unspec( + if_addr->family, + ifa->ifa_netmask)) + continue; + + addrs[ADDR_TYPE_NETMASK] = ifa->ifa_netmask; + } + + if (if_addr->ipaddrs[ADDR_TYPE_BRDADDR] && + (ifa->ifa_flags & IFF_BROADCAST)) { + if (!if_addr->allow_unspec && is_addr_unspec( + if_addr->family, + ifa->ifa_ifu.ifu_broadaddr)) + continue; + + addrs[ADDR_TYPE_BRDADDR] = ifa->ifa_ifu.ifu_broadaddr; + } + + if (if_addr->ipaddrs[ADDR_TYPE_DSTADDR] && + (ifa->ifa_flags & IFF_POINTOPOINT)) { + if (!if_addr->allow_unspec && is_addr_unspec( + if_addr->family, + ifa->ifa_ifu.ifu_dstaddr)) + continue; + + addrs[ADDR_TYPE_DSTADDR] = ifa->ifa_ifu.ifu_dstaddr; + } + + err = 0; + + break; } - if (ioctl(sk, SIOCGIFNETMASK, &ifr) < 0) { - close(sk); - return false; + if (err) + goto out; + + for (i = 0; i < ADDR_TYPE_MAX; i++) { + if (!addrs[i]) + continue; + + switch (if_addr->family) { + case AF_INET: + len = sizeof(struct in_addr); + addr_in = (struct sockaddr_in*) addrs[i]; + memcpy(if_addr->ipaddrs[i], &addr_in->sin_addr, len); + break; + case AF_INET6: + len = sizeof(struct in6_addr); + addr_in6 = (struct sockaddr_in6*) addrs[i]; + memcpy(if_addr->ipaddrs[i], &addr_in6->sin6_addr, len); + break; + default: + err = -EINVAL; + break; + } } - netmask = (struct sockaddr_in *)&ifr.ifr_netmask; - netmask_addr = netmask->sin_addr.s_addr; +out: + freeifaddrs(ifaddr); + return err; +} - if (ioctl(sk, SIOCGIFADDR, &ifr) < 0) { - close(sk); +bool connman_inet_compare_subnet(int index, const char *host) +{ + struct interface_address if_addr = { 0 }; + struct in_addr iaddr = { 0 }; + struct in_addr imask = { 0 }; + struct in_addr haddr = { 0 }; + + DBG("host %s", host); + + if (!host) return false; - } - close(sk); + if (inet_pton(AF_INET, host, &haddr) != 1) + return false; + + if_addr.index = index; + if_addr.family = AF_INET; + if_addr.ipaddrs[ADDR_TYPE_IPADDR] = &iaddr; + if_addr.ipaddrs[ADDR_TYPE_NETMASK] = &imask; - addr = (struct sockaddr_in *)&ifr.ifr_addr; - if_addr = addr->sin_addr.s_addr; + if (get_interface_addresses(&if_addr)) + return false; - return ((if_addr & netmask_addr) == (host_addr & netmask_addr)); + return (iaddr.s_addr & imask.s_addr) == (haddr.s_addr & imask.s_addr); } static bool mem_mask_equal(const void *a, const void *b, @@ -1134,47 +1287,23 @@ static bool mem_mask_equal(const void *a, const void *b, bool connman_inet_compare_ipv6_subnet(int index, const char *host) { - struct ifaddrs *ifaddr, *ifa; - bool rv = false; - char name[IF_NAMESIZE]; - struct in6_addr haddr; + struct interface_address addr = { 0 }; + struct in6_addr iaddr = { 0 }; + struct in6_addr imask = { 0 }; + struct in6_addr haddr = { 0 }; - if (inet_pton(AF_INET6, host, &haddr) <= 0) + if (inet_pton(AF_INET6, host, &haddr) != 1) return false; - if (!if_indextoname(index, name)) - return false; - - DBG("index %d interface %s", index, name); + addr.index = index; + addr.family = AF_INET6; + addr.ipaddrs[ADDR_TYPE_IPADDR] = &iaddr; + addr.ipaddrs[ADDR_TYPE_NETMASK] = &imask; - if (getifaddrs(&ifaddr) < 0) { - DBG("Cannot get addresses err %d/%s", errno, strerror(errno)); + if (get_interface_addresses(&addr)) return false; - } - - for (ifa = ifaddr; ifa; ifa = ifa->ifa_next) { - struct sockaddr_in6 *iaddr; - struct sockaddr_in6 *imask; - - if (!ifa->ifa_addr) - continue; - if (strncmp(ifa->ifa_name, name, IF_NAMESIZE) != 0 || - ifa->ifa_addr->sa_family != AF_INET6) - continue; - - iaddr = (struct sockaddr_in6 *)ifa->ifa_addr; - imask = (struct sockaddr_in6 *)ifa->ifa_netmask; - - rv = mem_mask_equal(&iaddr->sin6_addr, &haddr, - &imask->sin6_addr, - sizeof(haddr)); - goto out; - } - -out: - freeifaddrs(ifaddr); - return rv; + return mem_mask_equal(&iaddr, &haddr, &imask, sizeof(haddr)); } int connman_inet_remove_from_bridge(int index, const char *bridge) @@ -1707,13 +1836,6 @@ int __connman_inet_ipv6_send_rs(int index, int timeout, return 0; } -static inline void ipv6_addr_advert_mult(const struct in6_addr *addr, - struct in6_addr *advert) -{ - ipv6_addr_set(advert, htonl(0xFF020000), 0, htonl(0x2), - htonl(0xFF000000) | addr->s6_addr32[3]); -} - #define MSG_SIZE_SEND 1452 static int inc_len(int len, int inc) @@ -2147,98 +2269,156 @@ GSList *__connman_inet_ipv6_get_prefixes(struct nd_router_advert *hdr, return prefixes; } -static int get_dest_addr(int family, int index, char *buf, int len) +int connman_inet_get_dest_addr(int index, char **dest) { - struct ifreq ifr; - void *addr; - int sk; + struct interface_address if_addr = { 0 }; + struct in_addr dstaddr = { 0 }; + char buf[INET_ADDRSTRLEN] = { 0 }; + int err; - sk = socket(family, SOCK_DGRAM | SOCK_CLOEXEC, 0); - if (sk < 0) - return -errno; + if (!dest) + return -EINVAL; - memset(&ifr, 0, sizeof(ifr)); - ifr.ifr_ifindex = index; + if_addr.index = index; + if_addr.family = AF_INET; + if_addr.allow_unspec = true; + if_addr.ipaddrs[ADDR_TYPE_DSTADDR] = &dstaddr; - if (ioctl(sk, SIOCGIFNAME, &ifr) < 0) { - DBG("SIOCGIFNAME (%d/%s)", errno, strerror(errno)); - close(sk); - return -errno; - } + err = get_interface_addresses(&if_addr); + if (err) + return err; - if (ioctl(sk, SIOCGIFFLAGS, &ifr) < 0) { - DBG("SIOCGIFFLAGS (%d/%s)", errno, strerror(errno)); - close(sk); - return -errno; - } + if (inet_ntop(AF_INET, &dstaddr, buf, INET_ADDRSTRLEN)) + *dest = g_strdup(buf); - if ((ifr.ifr_flags & IFF_POINTOPOINT) == 0) { - close(sk); - errno = EINVAL; - return -errno; - } + DBG("destination %s", *dest); - DBG("index %d %s", index, ifr.ifr_name); + return *dest && **dest ? 0 : -ENOENT; +} - if (ioctl(sk, SIOCGIFDSTADDR, &ifr) < 0) { - connman_error("Get destination address failed (%s)", - strerror(errno)); - close(sk); - return -errno; - } +int connman_inet_ipv6_get_dest_addr(int index, char **dest) +{ + struct interface_address if_addr = { 0 }; + struct in_addr dstaddr = { 0 }; + char buf[INET6_ADDRSTRLEN] = { 0 }; + int err; - close(sk); + if (!dest) + return -EINVAL; - switch (family) { - case AF_INET: - addr = &((struct sockaddr_in *)&ifr.ifr_dstaddr)->sin_addr; - break; - case AF_INET6: - addr = &((struct sockaddr_in6 *)&ifr.ifr_dstaddr)->sin6_addr; - break; - default: - errno = EINVAL; - return -errno; - } + if_addr.index = index; + if_addr.family = AF_INET6; + if_addr.allow_unspec = true; + if_addr.ipaddrs[ADDR_TYPE_DSTADDR] = &dstaddr; - if (!inet_ntop(family, addr, buf, len)) { - DBG("error %d/%s", errno, strerror(errno)); - return -errno; - } + err = get_interface_addresses(&if_addr); + if (err) + return err; - return 0; + if (inet_ntop(AF_INET6, &dstaddr, buf, INET6_ADDRSTRLEN)) + *dest = g_strdup(buf); + + DBG("destination %s", *dest); + + return *dest && **dest ? 0 : -ENOENT; } -int connman_inet_get_dest_addr(int index, char **dest) +/* destination is optional */ +int connman_inet_get_route_addresses(int index, char **network, char **netmask, + char **destination) { - char addr[INET_ADDRSTRLEN]; - int ret; + struct interface_address if_addr = { 0 }; + struct in_addr addr = { 0 }; + struct in_addr mask = { 0 }; + struct in_addr dest = { 0 }; + struct in_addr nw_addr = { 0 }; + char buf[INET_ADDRSTRLEN] = { 0 }; + int err; - ret = get_dest_addr(PF_INET, index, addr, INET_ADDRSTRLEN); - if (ret < 0) - return ret; + if (!network || !netmask) + return -EINVAL; - *dest = g_strdup(addr); + if_addr.index = index; + if_addr.family = AF_INET; + if_addr.ipaddrs[ADDR_TYPE_IPADDR] = &addr; + if_addr.ipaddrs[ADDR_TYPE_NETMASK] = &mask; + if_addr.ipaddrs[ADDR_TYPE_DSTADDR] = &dest; - DBG("destination %s", *dest); + err = get_interface_addresses(&if_addr); + if (err) + return err; - return 0; + nw_addr.s_addr = (addr.s_addr & mask.s_addr); + + if (inet_ntop(AF_INET, &nw_addr, buf, INET_ADDRSTRLEN)) + *network = g_strdup(buf); + + memset(&buf, 0, INET_ADDRSTRLEN); + + if (inet_ntop(AF_INET, &mask, buf, INET_ADDRSTRLEN)) + *netmask = g_strdup(buf); + + if (destination) { + memset(&buf, 0, INET_ADDRSTRLEN); + + if (inet_ntop(AF_INET, &dest, buf, INET_ADDRSTRLEN)) + *destination = g_strdup(buf); + } + + DBG("network %s netmask %s destination %s", *network, *netmask, + destination ? *destination : NULL); + + return *network && **network && *netmask && **netmask ? 0 : -ENOENT; } -int connman_inet_ipv6_get_dest_addr(int index, char **dest) +int connman_inet_ipv6_get_route_addresses(int index, char **network, + char **netmask, char **destination) { - char addr[INET6_ADDRSTRLEN]; - int ret; + struct interface_address if_addr = { 0 }; + struct in6_addr addr = { 0 }; + struct in6_addr mask = { 0 }; + struct in6_addr dest = { 0 }; + struct in6_addr nw_addr = { 0 }; + char buf[INET6_ADDRSTRLEN] = { 0 }; + int err; - ret = get_dest_addr(PF_INET6, index, addr, INET6_ADDRSTRLEN); - if (ret < 0) - return ret; + if (!network) + return -EINVAL; - *dest = g_strdup(addr); + if_addr.index = index; + if_addr.family = AF_INET6; + if_addr.ipaddrs[ADDR_TYPE_IPADDR] = &addr; + if_addr.ipaddrs[ADDR_TYPE_NETMASK] = &mask; + if_addr.ipaddrs[ADDR_TYPE_DSTADDR] = &dest; - DBG("destination %s", *dest); + err = get_interface_addresses(&if_addr); + if (err) + return err; - return 0; + ipv6_addr_set(&nw_addr, addr.s6_addr32[0] & mask.s6_addr32[0], + addr.s6_addr32[1] & mask.s6_addr32[1], + addr.s6_addr32[2] & mask.s6_addr32[2], + addr.s6_addr32[3] & mask.s6_addr32[3]); + + if (inet_ntop(AF_INET6, &nw_addr, buf, INET6_ADDRSTRLEN)) + *network = g_strdup(buf); + + memset(&buf, 0, INET6_ADDRSTRLEN); + + if (inet_ntop(AF_INET6, &mask, buf, INET6_ADDRSTRLEN)) + *netmask = g_strdup(buf); + + if (destination) { + memset(&buf, 0, INET6_ADDRSTRLEN); + + if (inet_ntop(AF_INET6, &dest, buf, INET6_ADDRSTRLEN)) + *destination = g_strdup(buf); + } + + DBG("network %s netmask %s destination %s", *network, *netmask, + destination ? *destination : NULL); + + return *network && **network && *netmask && **netmask ? 0 : -ENOENT; } int __connman_inet_rtnl_open(struct __connman_inet_rtnl_handle *rth) @@ -2835,58 +3015,30 @@ bool connman_inet_is_ipv6_supported() return true; } -int __connman_inet_get_interface_address(int index, int family, void *address) +/* + * Omits checking of the gateway matching the actual gateway IP since both + * connmand and vpnd use inet.c, getting the route is via ipconfig and ipconfig + * is different for both. Gateway is left here for possible future use. + * + * Gateway can be NULL and connection.c then assigns 0.0.0.0 address or ::, + * depending on IP family. + */ +bool connman_inet_is_default_route(int family, const char *host, + const char *gateway, const char *netmask) { - struct ifaddrs *ifaddr, *ifa; - int err = -ENOENT; - char name[IF_NAMESIZE]; - - if (!if_indextoname(index, name)) - return -EINVAL; - - DBG("index %d interface %s", index, name); - - if (getifaddrs(&ifaddr) < 0) { - err = -errno; - DBG("Cannot get addresses err %d/%s", err, strerror(-err)); - return err; - } - - for (ifa = ifaddr; ifa; ifa = ifa->ifa_next) { - if (!ifa->ifa_addr) - continue; - - if (strncmp(ifa->ifa_name, name, IF_NAMESIZE) == 0 && - ifa->ifa_addr->sa_family == family) { - if (family == AF_INET) { - struct sockaddr_in *in4 = (struct sockaddr_in *) - ifa->ifa_addr; - if (in4->sin_addr.s_addr == INADDR_ANY) - continue; - memcpy(address, &in4->sin_addr, - sizeof(struct in_addr)); - } else if (family == AF_INET6) { - struct sockaddr_in6 *in6 = - (struct sockaddr_in6 *)ifa->ifa_addr; - if (memcmp(&in6->sin6_addr, &in6addr_any, - sizeof(struct in6_addr)) == 0) - continue; - memcpy(address, &in6->sin6_addr, - sizeof(struct in6_addr)); + return __connman_inet_is_any_addr(host, family) && + __connman_inet_is_any_addr(netmask, family); +} - } else { - err = -EINVAL; - goto out; - } +int __connman_inet_get_interface_address(int index, int family, void *address) +{ + struct interface_address if_addr = { 0 }; - err = 0; - break; - } - } + if_addr.index = index; + if_addr.family = family; + if_addr.ipaddrs[ADDR_TYPE_IPADDR] = address; -out: - freeifaddrs(ifaddr); - return err; + return get_interface_addresses(&if_addr); } int __connman_inet_get_interface_mac_address(int index, uint8_t *mac_address) @@ -3020,7 +3172,7 @@ static int iproute_default_modify(int cmd, uint32_t table_id, int ifindex, ret = inet_pton(family, dst ? dst : gateway, buf); g_free(dst); - if (ret <= 0) + if (ret != 1) return -EINVAL; memset(&rth, 0, sizeof(rth)); @@ -3095,61 +3247,14 @@ int __connman_inet_del_subnet_from_table(uint32_t table_id, int ifindex, int __connman_inet_get_interface_ll_address(int index, int family, void *address) { - struct ifaddrs *ifaddr, *ifa; - int err = -ENOENT; - char name[IF_NAMESIZE]; - - if (!if_indextoname(index, name)) - return -EINVAL; + struct interface_address if_addr = { 0 }; - DBG("index %d interface %s", index, name); + if_addr.index = index; + if_addr.family = family; + if_addr.require_ll = true; + if_addr.ipaddrs[ADDR_TYPE_IPADDR] = address; - if (getifaddrs(&ifaddr) < 0) { - err = -errno; - DBG("Cannot get addresses err %d/%s", err, strerror(-err)); - return err; - } - - for (ifa = ifaddr; ifa; ifa = ifa->ifa_next) { - if (!ifa->ifa_addr) - continue; - - if (strncmp(ifa->ifa_name, name, IF_NAMESIZE) == 0 && - ifa->ifa_addr->sa_family == family) { - if (family == AF_INET) { - struct sockaddr_in *in4 = (struct sockaddr_in *) - ifa->ifa_addr; - if (in4->sin_addr.s_addr == INADDR_ANY) - continue; - if ((in4->sin_addr.s_addr & IN_CLASSB_NET) != - ((in_addr_t) 0xa9fe0000)) - continue; - memcpy(address, &in4->sin_addr, - sizeof(struct in_addr)); - } else if (family == AF_INET6) { - struct sockaddr_in6 *in6 = - (struct sockaddr_in6 *)ifa->ifa_addr; - if (memcmp(&in6->sin6_addr, &in6addr_any, - sizeof(struct in6_addr)) == 0) - continue; - if (!IN6_IS_ADDR_LINKLOCAL(&in6->sin6_addr)) - continue; - - memcpy(address, &in6->sin6_addr, - sizeof(struct in6_addr)); - } else { - err = -EINVAL; - goto out; - } - - err = 0; - break; - } - } - -out: - freeifaddrs(ifaddr); - return err; + return get_interface_addresses(&if_addr); } int __connman_inet_get_address_netmask(int ifindex, @@ -3303,7 +3408,7 @@ static int get_nfs_server_ip(const char *cmdline_file, const char *pnp_file, addrstr[len] = '\0'; err = inet_pton(AF_INET, addrstr, addr); - if (err <= 0) { + if (err != 1) { connman_error("%s: Cannot convert to numeric addr \"%s\"\n", __func__, addrstr); err = -1; diff --git a/src/ipaddress.c b/src/ipaddress.c index d63d95c3..201d8345 100644 --- a/src/ipaddress.c +++ b/src/ipaddress.c @@ -70,10 +70,19 @@ struct connman_ipaddress *connman_ipaddress_alloc(int family) ipaddress->peer = NULL; ipaddress->broadcast = NULL; ipaddress->gateway = NULL; + ipaddress->is_p2p = false; return ipaddress; } +void connman_ipaddress_set_p2p(struct connman_ipaddress *ipaddress, bool value) +{ + if (!ipaddress) + return; + + ipaddress->is_p2p = value; +} + void connman_ipaddress_free(struct connman_ipaddress *ipaddress) { if (!ipaddress) @@ -95,7 +104,7 @@ static bool check_ipv6_address(const char *address) return false; err = inet_pton(AF_INET6, address, buf); - if (err > 0) + if (err == 1) return true; return false; @@ -223,6 +232,7 @@ connman_ipaddress_copy(struct connman_ipaddress *ipaddress) copy->peer = g_strdup(ipaddress->peer); copy->broadcast = g_strdup(ipaddress->broadcast); copy->gateway = g_strdup(ipaddress->gateway); + copy->is_p2p = ipaddress->is_p2p; return copy; } diff --git a/src/ipconfig.c b/src/ipconfig.c index 915c0823..34b1724a 100644 --- a/src/ipconfig.c +++ b/src/ipconfig.c @@ -258,153 +258,165 @@ static const char *scope2str(unsigned char scope) return ""; } -static bool get_ipv6_state(gchar *ifname) +#define PROC_IPV4_CONF_PREFIX "/proc/sys/net/ipv4/conf" +#define PROC_IPV6_CONF_PREFIX "/proc/sys/net/ipv6/conf" + +static int read_conf_value(const char *prefix, const char *ifname, + const char *suffix, int *value) { - int disabled; gchar *path; FILE *f; - bool enabled = false; - - if (!ifname) - path = g_strdup("/proc/sys/net/ipv6/conf/all/disable_ipv6"); - else - path = g_strdup_printf( - "/proc/sys/net/ipv6/conf/%s/disable_ipv6", ifname); + int err; + path = g_build_filename(prefix, ifname ? ifname : "all", suffix, NULL); if (!path) - return enabled; + return -ENOMEM; + errno = 0; f = fopen(path, "r"); + if (!f) { + err = -errno; + } else { + errno = 0; /* Avoid stale errno values with fscanf */ - g_free(path); + err = fscanf(f, "%d", value); + if (err <= 0 && errno) + err = -errno; - if (f) { - if (fscanf(f, "%d", &disabled) > 0) - enabled = !disabled; fclose(f); } - return enabled; + if (err <= 0) + connman_error("failed to read %s", path); + + g_free(path); + + return err; +} + +static int read_ipv4_conf_value(const char *ifname, const char *suffix, + int *value) +{ + return read_conf_value(PROC_IPV4_CONF_PREFIX, ifname, suffix, value); } -static void set_ipv6_state(gchar *ifname, bool enable) +static int read_ipv6_conf_value(const char *ifname, const char *suffix, + int *value) { + return read_conf_value(PROC_IPV6_CONF_PREFIX, ifname, suffix, value); +} + +static int write_conf_value(const char *prefix, const char *ifname, + const char *suffix, int value) { gchar *path; FILE *f; + int rval; - if (!ifname) - path = g_strdup("/proc/sys/net/ipv6/conf/all/disable_ipv6"); - else - path = g_strdup_printf( - "/proc/sys/net/ipv6/conf/%s/disable_ipv6", ifname); - + path = g_build_filename(prefix, ifname ? ifname : "all", suffix, NULL); if (!path) - return; + return -ENOMEM; f = fopen(path, "r+"); + if (!f) { + rval = -errno; + } else { + rval = fprintf(f, "%d", value); + fclose(f); + } + + if (rval <= 0) + connman_error("failed to set %s value %d", path, value); g_free(path); - if (!f) - return; + return rval; +} - if (!enable) - fprintf(f, "1"); - else - fprintf(f, "0"); +static int write_ipv4_conf_value(const char *ifname, const char *suffix, + int value) +{ + return write_conf_value(PROC_IPV4_CONF_PREFIX, ifname, suffix, value); +} - fclose(f); +static int write_ipv6_conf_value(const char *ifname, const char *suffix, + int value) +{ + return write_conf_value(PROC_IPV6_CONF_PREFIX, ifname, suffix, value); } -static int get_ipv6_privacy(gchar *ifname) +static bool get_ipv6_state(gchar *ifname) { - gchar *path; - FILE *f; - int value; + int disabled; + bool enabled = false; - if (!ifname) - return 0; + if (read_ipv6_conf_value(ifname, "disable_ipv6", &disabled) > 0) + enabled = !disabled; - path = g_strdup_printf("/proc/sys/net/ipv6/conf/%s/use_tempaddr", - ifname); + return enabled; +} - if (!path) - return 0; +static int set_ipv6_state(gchar *ifname, bool enable) +{ + int disabled = enable ? 0 : 1; - f = fopen(path, "r"); + DBG("%s %d", ifname, disabled); - g_free(path); + return write_ipv6_conf_value(ifname, "disable_ipv6", disabled); +} - if (!f) +static int get_ipv6_privacy(gchar *ifname) +{ + int value; + + if (!ifname) return 0; - if (fscanf(f, "%d", &value) <= 0) + if (read_ipv6_conf_value(ifname, "use_tempaddr", &value) < 0) value = 0; - fclose(f); - return value; } /* Enable the IPv6 privacy extension for stateless address autoconfiguration. * The privacy extension is described in RFC 3041 and RFC 4941 */ -static void set_ipv6_privacy(gchar *ifname, int value) +static int set_ipv6_privacy(gchar *ifname, int value) { - gchar *path; - FILE *f; - if (!ifname) - return; - - path = g_strdup_printf("/proc/sys/net/ipv6/conf/%s/use_tempaddr", - ifname); - - if (!path) - return; + return -EINVAL; if (value < 0) value = 0; - f = fopen(path, "r+"); - - g_free(path); - - if (!f) - return; - - fprintf(f, "%d", value); - fclose(f); + return write_ipv6_conf_value(ifname, "use_tempaddr", value); } static int get_rp_filter(void) { - FILE *f; - int value = -EINVAL, tmp; - - f = fopen("/proc/sys/net/ipv4/conf/all/rp_filter", "r"); + int value; - if (f) { - if (fscanf(f, "%d", &tmp) == 1) - value = tmp; - fclose(f); - } + if (read_ipv4_conf_value(NULL, "rp_filter", &value) < 0) + value = -EINVAL; return value; } -static void set_rp_filter(int value) +static int set_rp_filter(int value) { - FILE *f; - - f = fopen("/proc/sys/net/ipv4/conf/all/rp_filter", "r+"); - - if (!f) - return; - - fprintf(f, "%d", value); + /* 0 = no validation, 1 = strict mode, 2 = loose mode */ + switch (value) { + case -1: + value = 0; + /* fall through */ + case 0: + case 1: + case 2: + break; + default: + return -EINVAL; + } - fclose(f); + return write_ipv4_conf_value(NULL, "rp_filter", value); } int __connman_ipconfig_set_rp_filter() @@ -696,6 +708,25 @@ static inline gint check_duplicate_address(gconstpointer a, gconstpointer b) return g_strcmp0(addr1->local, addr2->local); } +static bool is_index_p2p_service(int index) +{ + struct connman_service *service; + enum connman_service_type type; + + service = __connman_service_lookup_from_index(index); + if (!service) + return false; + + type = connman_service_get_type(service); + switch (type) { + case CONNMAN_SERVICE_TYPE_P2P: + case CONNMAN_SERVICE_TYPE_VPN: + return true; + default: + return false; + } +} + int __connman_ipconfig_newaddr(int index, int family, const char *label, unsigned char prefixlen, const char *address) { @@ -718,6 +749,9 @@ int __connman_ipconfig_newaddr(int index, int family, const char *label, ipaddress->prefixlen = prefixlen; ipaddress->local = g_strdup(address); + if (is_index_p2p_service(index)) + connman_ipaddress_set_p2p(ipaddress, true); + if (g_slist_find_custom(ipdevice->address_list, ipaddress, check_duplicate_address)) { connman_ipaddress_free(ipaddress); @@ -1186,6 +1220,15 @@ void __connman_ipconfig_set_prefixlen(struct connman_ipconfig *ipconfig, ipconfig->address->prefixlen = prefixlen; } +static void ipconfig_set_p2p(int index, struct connman_ipconfig *ipconfig) +{ + if (!is_index_p2p_service(index)) + return; + + connman_ipaddress_set_p2p(ipconfig->address, true); + connman_ipaddress_set_p2p(ipconfig->system, true); +} + static struct connman_ipconfig *create_ipv6config(int index) { struct connman_ipconfig *ipv6config; @@ -1217,6 +1260,8 @@ static struct connman_ipconfig *create_ipv6config(int index) ipv6config->system = connman_ipaddress_alloc(AF_INET6); + ipconfig_set_p2p(index, ipv6config); + DBG("ipconfig %p index %d method %s", ipv6config, index, __connman_ipconfig_method2string(ipv6config->method)); @@ -1255,6 +1300,8 @@ struct connman_ipconfig *__connman_ipconfig_create(int index, ipconfig->system = connman_ipaddress_alloc(AF_INET); + ipconfig_set_p2p(index, ipconfig); + DBG("ipconfig %p index %d", ipconfig, index); return ipconfig; @@ -1451,10 +1498,8 @@ int __connman_ipconfig_address_unset(struct connman_ipconfig *ipconfig) err = connman_inet_clear_address(ipconfig->index, ipconfig->address); else if (ipconfig->type == CONNMAN_IPCONFIG_TYPE_IPV6) - err = connman_inet_clear_ipv6_address( - ipconfig->index, - ipconfig->address->local, - ipconfig->address->prefixlen); + err = connman_inet_clear_ipv6_address(ipconfig->index, + ipconfig->address); else err = -EINVAL; @@ -1548,6 +1593,9 @@ static void disable_ipv6(struct connman_ipconfig *ipconfig) ifname = connman_inet_ifname(ipconfig->index); + if (!ifname) + return; + set_ipv6_state(ifname, false); g_free(ifname); @@ -1567,6 +1615,9 @@ static void enable_ipv6(struct connman_ipconfig *ipconfig) ifname = connman_inet_ifname(ipconfig->index); + if (!ifname) + return; + if (ipconfig->method == CONNMAN_IPCONFIG_METHOD_AUTO) set_ipv6_privacy(ifname, ipconfig->ipv6_privacy_config); @@ -1647,6 +1698,9 @@ int __connman_ipconfig_enable(struct connman_ipconfig *ipconfig) connman_ipaddress_clear(ipdevice->config_ipv4->system); __connman_ipconfig_unref(ipdevice->config_ipv4); + + g_free(ipdevice->ipv4_gateway); + ipdevice->ipv4_gateway = NULL; } if (type == CONNMAN_IPCONFIG_TYPE_IPV6 && @@ -1657,6 +1711,9 @@ int __connman_ipconfig_enable(struct connman_ipconfig *ipconfig) connman_ipaddress_clear(ipdevice->config_ipv6->system); __connman_ipconfig_unref(ipdevice->config_ipv6); + + g_free(ipdevice->ipv6_gateway); + ipdevice->ipv6_gateway = NULL; } if (type == CONNMAN_IPCONFIG_TYPE_IPV4) @@ -1719,6 +1776,10 @@ int __connman_ipconfig_disable(struct connman_ipconfig *ipconfig) connman_ipaddress_clear(ipdevice->config_ipv4->system); __connman_ipconfig_unref(ipdevice->config_ipv4); ipdevice->config_ipv4 = NULL; + + g_free(ipdevice->ipv4_gateway); + ipdevice->ipv4_gateway = NULL; + return 0; } @@ -1728,6 +1789,10 @@ int __connman_ipconfig_disable(struct connman_ipconfig *ipconfig) connman_ipaddress_clear(ipdevice->config_ipv6->system); __connman_ipconfig_unref(ipdevice->config_ipv6); ipdevice->config_ipv6 = NULL; + + g_free(ipdevice->ipv6_gateway); + ipdevice->ipv6_gateway = NULL; + return 0; } diff --git a/src/iptables.c b/src/iptables.c index 47ea1c2d..664b27f1 100644 --- a/src/iptables.c +++ b/src/iptables.c @@ -2931,7 +2931,7 @@ static int parse_ip_and_mask(const char *str, struct in_addr *ip, if (!tokens) return -1; - if (!inet_pton(AF_INET, tokens[0], ip)) { + if (inet_pton(AF_INET, tokens[0], ip) != 1) { err = -1; goto out; } @@ -2972,7 +2972,7 @@ static int parse_ipv6_and_mask(const char *str, struct in6_addr *ip, if (!tokens) return -1; - if (!inet_pton(AF_INET6, tokens[0], ip)) { + if (inet_pton(AF_INET6, tokens[0], ip) != 1) { err = -1; goto out; } @@ -3383,7 +3383,7 @@ static int parse_rule_spec(struct connman_iptables *table, break; if (invert) - ctx->ip->invflags |= IP6T_INV_DSTIP; + ctx->ipv6->invflags |= IP6T_INV_DSTIP; } break; @@ -44,6 +44,18 @@ #define DEFAULT_INPUT_REQUEST_TIMEOUT (120 * 1000) #define DEFAULT_BROWSER_LAUNCH_TIMEOUT (300 * 1000) +#define DEFAULT_ONLINE_CHECK_IPV4_URL "http://ipv4.connman.net/online/status.html" +#define DEFAULT_ONLINE_CHECK_IPV6_URL "http://ipv6.connman.net/online/status.html" + +/* + * We set the integer to 1 sec so that we have a chance to get + * necessary IPv6 router advertisement messages that might have + * DNS data etc. + */ +#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 @@ -66,6 +78,7 @@ static char *default_blacklist[] = { "ifb", "ve-", "vb-", + "ham", NULL }; @@ -88,9 +101,17 @@ static struct { bool enable_6to4; char *vendor_class_id; bool enable_online_check; + bool enable_online_to_ready_transition; + char *online_check_ipv4_url; + char *online_check_ipv6_url; + unsigned int online_check_initial_interval; + unsigned int online_check_max_interval; 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, @@ -110,9 +131,16 @@ static struct { .enable_6to4 = false, .vendor_class_id = NULL, .enable_online_check = true, + .enable_online_to_ready_transition = false, + .online_check_ipv4_url = NULL, + .online_check_ipv6_url = NULL, + .online_check_initial_interval = DEFAULT_ONLINE_CHECK_INITIAL_INTERVAL, + .online_check_max_interval = DEFAULT_ONLINE_CHECK_MAX_INTERVAL, .auto_connect_roaming_services = false, .acd = false, .use_gateways_as_timeservers = false, + .localtime = NULL, + .resolv_conf = NULL, }; #define CONF_BG_SCAN "BackgroundScanning" @@ -133,14 +161,23 @@ static struct { #define CONF_ENABLE_6TO4 "Enable6to4" #define CONF_VENDOR_CLASS_ID "VendorClassID" #define CONF_ENABLE_ONLINE_CHECK "EnableOnlineCheck" +#define CONF_ENABLE_ONLINE_TO_READY_TRANSITION "EnableOnlineToReadyTransition" +#define CONF_ONLINE_CHECK_IPV4_URL "OnlineCheckIPv4URL" +#define CONF_ONLINE_CHECK_IPV6_URL "OnlineCheckIPv6URL" +#define CONF_ONLINE_CHECK_INITIAL_INTERVAL "OnlineCheckInitialInterval" +#define CONF_ONLINE_CHECK_MAX_INTERVAL "OnlineCheckMaxInterval" #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, CONF_PREF_TIMESERVERS, CONF_AUTO_CONNECT_TECHS, + CONF_FAVORITE_TECHS, CONF_ALWAYS_CONNECTED_TECHS, CONF_PREFERRED_TECHS, CONF_FALLBACK_NAMESERVERS, @@ -155,9 +192,17 @@ static const char *supported_options[] = { CONF_ENABLE_6TO4, CONF_VENDOR_CLASS_ID, CONF_ENABLE_ONLINE_CHECK, + CONF_ENABLE_ONLINE_TO_READY_TRANSITION, + CONF_ONLINE_CHECK_IPV4_URL, + CONF_ONLINE_CHECK_IPV6_URL, + CONF_ONLINE_CHECK_INITIAL_INTERVAL, + CONF_ONLINE_CHECK_MAX_INTERVAL, CONF_AUTO_CONNECT_ROAMING_SERVICES, CONF_ACD, CONF_USE_GATEWAYS_AS_TIMESERVERS, + CONF_LOCALTIME, + CONF_REGDOM_FOLLOWS_TIMEZONE, + CONF_RESOLV_CONF, NULL }; @@ -280,17 +325,23 @@ static void parse_config(GKeyFile *config) char **interfaces; char **str_list; char **tethering; - char *vendor_class_id; + char *string; gsize len; - int timeout; + int integer; 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; } @@ -320,6 +371,8 @@ static void parse_config(GKeyFile *config) connman_settings.auto_connect = parse_service_types(default_auto_connect, CONF_ARRAY_SIZE(default_auto_connect)); + g_strfreev(str_list); + g_clear_error(&error); str_list = __connman_config_get_string_list(config, "General", @@ -369,17 +422,17 @@ static void parse_config(GKeyFile *config) g_clear_error(&error); - timeout = g_key_file_get_integer(config, "General", + integer = g_key_file_get_integer(config, "General", CONF_TIMEOUT_INPUTREQ, &error); - if (!error && timeout >= 0) - connman_settings.timeout_inputreq = timeout * 1000; + if (!error && integer >= 0) + connman_settings.timeout_inputreq = integer * 1000; g_clear_error(&error); - timeout = g_key_file_get_integer(config, "General", + integer = g_key_file_get_integer(config, "General", CONF_TIMEOUT_BROWSERLAUNCH, &error); - if (!error && timeout >= 0) - connman_settings.timeout_browserlaunch = timeout * 1000; + if (!error && integer >= 0) + connman_settings.timeout_browserlaunch = integer * 1000; g_clear_error(&error); @@ -440,10 +493,10 @@ static void parse_config(GKeyFile *config) g_clear_error(&error); - vendor_class_id = __connman_config_get_string(config, "General", + string = __connman_config_get_string(config, "General", CONF_VENDOR_CLASS_ID, &error); if (!error) - connman_settings.vendor_class_id = vendor_class_id; + connman_settings.vendor_class_id = string; g_clear_error(&error); @@ -458,6 +511,61 @@ static void parse_config(GKeyFile *config) g_clear_error(&error); boolean = __connman_config_get_bool(config, "General", + CONF_ENABLE_ONLINE_TO_READY_TRANSITION, &error); + if (!error) { + connman_settings.enable_online_to_ready_transition = boolean; + } + + g_clear_error(&error); + + string = __connman_config_get_string(config, "General", + CONF_ONLINE_CHECK_IPV4_URL, &error); + if (!error) + connman_settings.online_check_ipv4_url = string; + else + connman_settings.online_check_ipv4_url = + g_strdup(DEFAULT_ONLINE_CHECK_IPV4_URL); + + g_clear_error(&error); + + string = __connman_config_get_string(config, "General", + CONF_ONLINE_CHECK_IPV6_URL, &error); + if (!error) + connman_settings.online_check_ipv6_url = string; + else + connman_settings.online_check_ipv6_url = + g_strdup(DEFAULT_ONLINE_CHECK_IPV6_URL); + + + g_clear_error(&error); + + integer = g_key_file_get_integer(config, "General", + CONF_ONLINE_CHECK_INITIAL_INTERVAL, &error); + if (!error && integer >= 0) + connman_settings.online_check_initial_interval = integer; + + g_clear_error(&error); + + integer = g_key_file_get_integer(config, "General", + CONF_ONLINE_CHECK_MAX_INTERVAL, &error); + if (!error && integer >= 0) + connman_settings.online_check_max_interval = integer; + + g_clear_error(&error); + + if (connman_settings.online_check_initial_interval < 1 || + connman_settings.online_check_initial_interval > + connman_settings.online_check_max_interval) { + connman_warn("Incorrect online check intervals [%u, %u]", + connman_settings.online_check_initial_interval, + connman_settings.online_check_max_interval); + connman_settings.online_check_initial_interval = + DEFAULT_ONLINE_CHECK_INITIAL_INTERVAL; + connman_settings.online_check_max_interval = + DEFAULT_ONLINE_CHECK_MAX_INTERVAL; + } + + boolean = __connman_config_get_bool(config, "General", CONF_AUTO_CONNECT_ROAMING_SERVICES, &error); if (!error) connman_settings.auto_connect_roaming_services = boolean; @@ -476,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) @@ -648,11 +779,17 @@ static GOptionEntry options[] = { { NULL }, }; -const char *connman_option_get_string(const char *key) +char *connman_setting_get_string(const char *key) { if (g_str_equal(key, CONF_VENDOR_CLASS_ID)) return connman_settings.vendor_class_id; + if (g_str_equal(key, CONF_ONLINE_CHECK_IPV4_URL)) + return connman_settings.online_check_ipv4_url; + + if (g_str_equal(key, CONF_ONLINE_CHECK_IPV6_URL)) + return connman_settings.online_check_ipv6_url; + if (g_strcmp0(key, "wifi") == 0) { if (!option_wifi) return "nl80211,wext"; @@ -660,6 +797,10 @@ const char *connman_option_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; } @@ -686,6 +827,9 @@ bool connman_setting_get_bool(const char *key) if (g_str_equal(key, CONF_ENABLE_ONLINE_CHECK)) return connman_settings.enable_online_check; + if (g_str_equal(key, CONF_ENABLE_ONLINE_TO_READY_TRANSITION)) + return connman_settings.enable_online_to_ready_transition; + if (g_str_equal(key, CONF_AUTO_CONNECT_ROAMING_SERVICES)) return connman_settings.auto_connect_roaming_services; @@ -695,9 +839,26 @@ 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; } +unsigned int connman_setting_get_uint(const char *key) +{ + if (g_str_equal(key, CONF_ONLINE_CHECK_INITIAL_INTERVAL)) + return connman_settings.online_check_initial_interval; + + if (g_str_equal(key, CONF_ONLINE_CHECK_MAX_INTERVAL)) + return connman_settings.online_check_max_interval; + + return 0; +} + char **connman_setting_get_string_list(const char *key) { if (g_str_equal(key, CONF_PREF_TIMESERVERS)) @@ -919,6 +1080,10 @@ int main(int argc, char *argv[]) g_strfreev(connman_settings.fallback_nameservers); g_strfreev(connman_settings.blacklisted_interfaces); g_strfreev(connman_settings.tethering_technologies); + g_free(connman_settings.vendor_class_id); + g_free(connman_settings.online_check_ipv4_url); + g_free(connman_settings.online_check_ipv6_url); + g_free(connman_settings.localtime); g_free(option_debug); g_free(option_wifi); diff --git a/src/main.conf b/src/main.conf index 14965e12..ddd57996 100644 --- a/src/main.conf +++ b/src/main.conf @@ -125,6 +125,31 @@ # Default value is true. # EnableOnlineCheck = false +# Urls (IPv4 and IPv6 respectively) used during the online status check. +# Please refer to the README for more detailed information. +# Default values are http://ipv4.connman.net/online/status.html and +# http://ipv6.connman.net/online/status.html respectively. +# OnlineCheckIPv4URL= http://ipv4.connman.net/online/status.html +# OnlineCheckIPv6URL= http://ipv6.connman.net/online/status.html + +# Range of intervals between two online check requests. +# Please refer to the README for more detailed information. +# Default values are 1 and 12 respectively. +# OnlineCheckInitialInterval = 1 +# OnlineCheckMaxInterval = 12 + +# WARNING: Experimental feature!!! +# In addition to EnableOnlineCheck setting, enable or disable use of HTTP GET +# to detect the loss of end-to-end connectivity. +# If this setting is false, when the default service transitions to ONLINE +# state, the HTTP GET request is no more called until next cycle, initiated +# by a transition of the default service to DISCONNECT state. +# If this setting is true, the HTTP GET request keeps beeing called to guarantee +# that end-to-end connectivity is still successful. If not, the default service +# will transition to READY state, enabling another service to become the +# default one, in replacement. +# EnableOnlineToReadyTransition = false + # List of technologies with AutoConnect = true which are always connected # regardless of PreferredTechnologies setting. Default value is empty and # will connect a technology only if it is at a higher preference than any diff --git a/src/manager.c b/src/manager.c index 3bf8f4e4..892d3a42 100644 --- a/src/manager.c +++ b/src/manager.c @@ -600,6 +600,9 @@ static const GDBusSignalTable manager_signals[] = { { GDBUS_SIGNAL("PeersChanged", GDBUS_ARGS({ "changed", "a(oa{sv})" }, { "removed", "ao" })) }, + { GDBUS_SIGNAL("TetheringClientsChanged", + GDBUS_ARGS({ "registered", "as" }, + { "removed", "as" })) }, { }, }; diff --git a/src/network.c b/src/network.c index f2ab16bd..e3e02d16 100644 --- a/src/network.c +++ b/src/network.c @@ -940,7 +940,7 @@ static void set_disconnected(struct connman_network *network) { struct connman_ipconfig *ipconfig_ipv4, *ipconfig_ipv6; enum connman_ipconfig_method ipv4_method, ipv6_method; - enum connman_service_state state; + enum connman_service_state state_ipv4, state_ipv6; struct connman_service *service; service = connman_service_lookup_from_network(network); @@ -1006,18 +1006,18 @@ static void set_disconnected(struct connman_network *network) * or in failure. It does not make sense to go to disconnect * state if we were not connected. */ - state = __connman_service_ipconfig_get_state(service, + state_ipv4 = __connman_service_ipconfig_get_state(service, CONNMAN_IPCONFIG_TYPE_IPV4); - if (state != CONNMAN_SERVICE_STATE_IDLE && - state != CONNMAN_SERVICE_STATE_FAILURE) + if (state_ipv4 != CONNMAN_SERVICE_STATE_IDLE && + state_ipv4 != CONNMAN_SERVICE_STATE_FAILURE) __connman_service_ipconfig_indicate_state(service, CONNMAN_SERVICE_STATE_DISCONNECT, CONNMAN_IPCONFIG_TYPE_IPV4); - state = __connman_service_ipconfig_get_state(service, + state_ipv6 = __connman_service_ipconfig_get_state(service, CONNMAN_IPCONFIG_TYPE_IPV6); - if (state != CONNMAN_SERVICE_STATE_IDLE && - state != CONNMAN_SERVICE_STATE_FAILURE) + if (state_ipv6 != CONNMAN_SERVICE_STATE_IDLE && + state_ipv6 != CONNMAN_SERVICE_STATE_FAILURE) __connman_service_ipconfig_indicate_state(service, CONNMAN_SERVICE_STATE_DISCONNECT, CONNMAN_IPCONFIG_TYPE_IPV6); @@ -1041,11 +1041,15 @@ static void set_disconnected(struct connman_network *network) } } - __connman_service_ipconfig_indicate_state(service, + if (state_ipv4 != CONNMAN_SERVICE_STATE_IDLE && + state_ipv4 != CONNMAN_SERVICE_STATE_FAILURE) + __connman_service_ipconfig_indicate_state(service, CONNMAN_SERVICE_STATE_IDLE, CONNMAN_IPCONFIG_TYPE_IPV4); - __connman_service_ipconfig_indicate_state(service, + if (state_ipv6 != CONNMAN_SERVICE_STATE_IDLE && + state_ipv6 != CONNMAN_SERVICE_STATE_FAILURE) + __connman_service_ipconfig_indicate_state(service, CONNMAN_SERVICE_STATE_IDLE, CONNMAN_IPCONFIG_TYPE_IPV6); @@ -1797,8 +1801,6 @@ int __connman_network_connect(struct connman_network *network) if (!network->device) return -ENODEV; - __connman_device_disconnect(network->device); - network->connecting = true; err = network->driver->connect(network); @@ -1850,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) { @@ -2112,6 +2127,21 @@ int connman_network_set_wifi_channel(struct connman_network *network, return 0; } +int connman_network_set_autoconnect(struct connman_network *network, + bool autoconnect) +{ + if (!network->driver || !network->driver->set_autoconnect) + return 0; + return network->driver->set_autoconnect(network, autoconnect); +} + +bool __connman_network_native_autoconnect(struct connman_network *network) +{ + if (!network->driver || !network->driver->set_autoconnect) + return false; + return true; +} + uint16_t connman_network_get_wifi_channel(struct connman_network *network) { return network->wifi.channel; @@ -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); } @@ -154,7 +154,7 @@ static int start_dhcp_server(struct connman_peer *peer) err = __connman_inet_modify_address(RTM_NEWADDR, NLM_F_REPLACE | NLM_F_ACK, index, AF_INET, - gateway, NULL, prefixlen, broadcast); + gateway, NULL, prefixlen, broadcast, true); if (err < 0) goto error; @@ -983,7 +983,10 @@ void connman_peer_add_service(struct connman_peer *peer, service = g_malloc0(sizeof(struct _peer_service)); service->type = type; - service->data = g_memdup(data, data_length * sizeof(unsigned char)); + if (data_length > 0) { + service->data = g_malloc(data_length * sizeof(unsigned char)); + memcpy(service->data, data, data_length * sizeof(unsigned char)); + } service->length = data_length; peer->services = g_slist_prepend(peer->services, service); diff --git a/src/provider.c b/src/provider.c index 7d663e0c..e2091846 100644 --- a/src/provider.c +++ b/src/provider.c @@ -53,6 +53,7 @@ void __connman_provider_append_properties(struct connman_provider *provider, DBusMessageIter *iter) { const char *host, *domain, *type; + dbus_bool_t split_routing; if (!provider->driver || !provider->driver->get_property) return; @@ -72,6 +73,12 @@ void __connman_provider_append_properties(struct connman_provider *provider, if (type) connman_dbus_dict_append_basic(iter, "Type", DBUS_TYPE_STRING, &type); + + if (provider->vpn_service) { + split_routing = connman_provider_is_split_routing(provider); + connman_dbus_dict_append_basic(iter, "SplitRouting", + DBUS_TYPE_BOOLEAN, &split_routing); + } } struct connman_provider * @@ -435,6 +442,15 @@ const char *__connman_provider_get_ident(struct connman_provider *provider) return provider->identifier; } +const char * __connman_provider_get_transport_ident( + struct connman_provider *provider) +{ + if (provider && provider && provider->driver && provider->driver->get_property) + return provider->driver->get_property(provider, "Transport"); + + return NULL; +} + int connman_provider_set_string(struct connman_provider *provider, const char *key, const char *value) { @@ -597,6 +613,100 @@ void connman_provider_set_autoconnect(struct connman_provider *provider, __connman_service_save(provider->vpn_service); } +bool connman_provider_is_split_routing(struct connman_provider *provider) +{ + if (!provider || !provider->vpn_service) + return false; + + return __connman_service_is_split_routing(provider->vpn_service); +} + +int connman_provider_set_split_routing(struct connman_provider *provider, + bool split_routing) +{ + struct connman_service *service; + enum connman_ipconfig_type type; + int service_index; + int vpn_index; + bool service_split_routing; + int err = 0; + + DBG(""); + + if (!provider || !provider->vpn_service) + return -EINVAL; + + service_split_routing = __connman_service_is_split_routing( + provider->vpn_service); + + if (service_split_routing == split_routing) { + DBG("split_routing already set %s", + split_routing ? "true" : "false"); + return -EALREADY; + } + + switch (provider->family) { + case AF_INET: + type = CONNMAN_IPCONFIG_TYPE_IPV4; + break; + case AF_INET6: + type = CONNMAN_IPCONFIG_TYPE_IPV6; + break; + case AF_UNSPEC: + type = CONNMAN_IPCONFIG_TYPE_ALL; + break; + default: + type = CONNMAN_IPCONFIG_TYPE_UNKNOWN; + } + + if (!__connman_service_is_connected_state(provider->vpn_service, + type)) { + DBG("%p VPN not connected", provider->vpn_service); + goto save; + } + + vpn_index = __connman_service_get_index(provider->vpn_service); + service_index = __connman_connection_get_vpn_phy_index(vpn_index); + service = __connman_service_lookup_from_index(service_index); + if (!service) + goto save; + + if (split_routing) + err = __connman_service_move(service, provider->vpn_service, + true); + else + err = __connman_service_move(provider->vpn_service, service, + true); + + if (err) { + connman_warn("cannot move service %p and VPN %p error %d", + service, provider->vpn_service, err); + + /* + * In case of error notify vpnd about the current split routing + * state. + */ + __connman_service_split_routing_changed(provider->vpn_service); + goto out; + } + +save: + __connman_service_set_split_routing(provider->vpn_service, + split_routing); + __connman_service_save(provider->vpn_service); + +out: + return err; +} + +int connman_provider_get_family(struct connman_provider *provider) +{ + if (!provider) + return AF_UNSPEC; + + return provider->family; +} + static void unregister_provider(gpointer data) { struct connman_provider *provider = data; diff --git a/src/resolver.c b/src/resolver.c index 618353fd..4ab51d6b 100644 --- a/src/resolver.c +++ b/src/resolver.c @@ -103,6 +103,7 @@ static int resolvfile_export(void) int fd, err; unsigned int count; mode_t old_umask; + const char *resolv_conf; content = g_string_new("# Generated by Connection Manager\n"); @@ -161,15 +162,33 @@ static int resolvfile_export(void) old_umask = umask(022); - fd = open(RESOLV_CONF_STATEDIR, O_RDWR | O_CREAT | O_CLOEXEC, - S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); - if (fd < 0) { - connman_warn_once("Cannot create "RESOLV_CONF_STATEDIR" " - "falling back to "RESOLV_CONF_ETC); + resolv_conf = connman_setting_get_string("ResolvConf"); + /* + * TODO: This is mainly for backward compatibility. In some future version, + * "ResolvConf" setting should default to RESOLV_CONF_STATEDIR or + * RESOLV_CONF_ETC and this branch can be removed. + */ + if (resolv_conf == NULL) { + fd = open(RESOLV_CONF_STATEDIR, O_RDWR | O_CREAT | O_CLOEXEC, + S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + if (fd < 0) { + connman_warn_once("Cannot create "RESOLV_CONF_STATEDIR" " + "falling back to "RESOLV_CONF_ETC); - fd = open(RESOLV_CONF_ETC, O_RDWR | O_CREAT | O_CLOEXEC, - S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + fd = open(RESOLV_CONF_ETC, O_RDWR | O_CREAT | O_CLOEXEC, + S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + if (fd < 0) { + err = -errno; + goto done; + } + } + } else if (resolv_conf[0] == '\0' || strcmp(resolv_conf, "/dev/null") == 0) { + err = 0; + goto done; + } else { + fd = open(resolv_conf, O_RDWR | O_CREAT | O_CLOEXEC, + S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); if (fd < 0) { err = -errno; goto done; @@ -177,6 +177,9 @@ static void read_uevent(struct interface_data *interface) } else if (strcmp(line + 8, "bond") == 0) { interface->service_type = CONNMAN_SERVICE_TYPE_ETHERNET; interface->device_type = CONNMAN_DEVICE_TYPE_ETHERNET; + } else if (strcmp(line + 8, "dsa") == 0) { + interface->service_type = CONNMAN_SERVICE_TYPE_ETHERNET; + interface->device_type = CONNMAN_DEVICE_TYPE_ETHERNET; } else { interface->service_type = CONNMAN_SERVICE_TYPE_UNKNOWN; interface->device_type = CONNMAN_DEVICE_TYPE_UNKNOWN; @@ -883,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; @@ -925,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: @@ -957,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)); @@ -1305,75 +1319,71 @@ static const char *type2string(uint16_t type) static GIOChannel *channel = NULL; static guint channel_watch = 0; -struct rtnl_request { - struct nlmsghdr hdr; - struct rtgenmsg msg; -}; -#define RTNL_REQUEST_SIZE (sizeof(struct nlmsghdr) + sizeof(struct rtgenmsg)) +#define RTNL_REQUEST_SIZE (NLMSG_HDRLEN + NLMSG_ALIGN(sizeof(struct rtgenmsg))) static GSList *request_list = NULL; static guint32 request_seq = 0; -static struct rtnl_request *find_request(guint32 seq) +static struct nlmsghdr *find_request(guint32 seq) { GSList *list; for (list = request_list; list; list = list->next) { - struct rtnl_request *req = list->data; + struct nlmsghdr *hdr = list->data; - if (req->hdr.nlmsg_seq == seq) - return req; + if (hdr->nlmsg_seq == seq) + return hdr; } return NULL; } -static int send_request(struct rtnl_request *req) +static int send_request(struct nlmsghdr *hdr) { struct sockaddr_nl addr; int sk; DBG("%s len %d type %d flags 0x%04x seq %d", - type2string(req->hdr.nlmsg_type), - req->hdr.nlmsg_len, req->hdr.nlmsg_type, - req->hdr.nlmsg_flags, req->hdr.nlmsg_seq); + type2string(hdr->nlmsg_type), + hdr->nlmsg_len, hdr->nlmsg_type, + hdr->nlmsg_flags, hdr->nlmsg_seq); sk = g_io_channel_unix_get_fd(channel); memset(&addr, 0, sizeof(addr)); addr.nl_family = AF_NETLINK; - return sendto(sk, req, req->hdr.nlmsg_len, 0, + return sendto(sk, hdr, hdr->nlmsg_len, 0, (struct sockaddr *) &addr, sizeof(addr)); } -static int queue_request(struct rtnl_request *req) +static int queue_request(struct nlmsghdr *hdr) { - request_list = g_slist_append(request_list, req); + request_list = g_slist_append(request_list, hdr); if (g_slist_length(request_list) > 1) return 0; - return send_request(req); + return send_request(hdr); } static int process_response(guint32 seq) { - struct rtnl_request *req; + struct nlmsghdr *hdr; DBG("seq %d", seq); - req = find_request(seq); - if (req) { - request_list = g_slist_remove(request_list, req); - g_free(req); + hdr = find_request(seq); + if (hdr) { + request_list = g_slist_remove(request_list, hdr); + g_free(hdr); } - req = g_slist_nth_data(request_list, 0); - if (!req) + hdr = g_slist_nth_data(request_list, 0); + if (!hdr) return 0; - return send_request(req); + return send_request(hdr); } static void rtnl_message(void *buf, size_t len) @@ -1471,62 +1481,65 @@ static gboolean netlink_event(GIOChannel *chan, GIOCondition cond, gpointer data static int send_getlink(void) { - struct rtnl_request *req; + struct nlmsghdr *hdr; + struct rtgenmsg *msg; DBG(""); - req = g_try_malloc0(RTNL_REQUEST_SIZE); - if (!req) - return -ENOMEM; + hdr = g_malloc0(RTNL_REQUEST_SIZE); + + hdr->nlmsg_len = RTNL_REQUEST_SIZE; + hdr->nlmsg_type = RTM_GETLINK; + hdr->nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP; + hdr->nlmsg_pid = 0; + hdr->nlmsg_seq = request_seq++; - req->hdr.nlmsg_len = RTNL_REQUEST_SIZE; - req->hdr.nlmsg_type = RTM_GETLINK; - req->hdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP; - req->hdr.nlmsg_pid = 0; - req->hdr.nlmsg_seq = request_seq++; - req->msg.rtgen_family = AF_INET; + msg = (struct rtgenmsg *) NLMSG_DATA(hdr); + msg->rtgen_family = AF_INET; - return queue_request(req); + return queue_request(hdr); } static int send_getaddr(void) { - struct rtnl_request *req; + struct nlmsghdr *hdr; + struct rtgenmsg *msg; DBG(""); - req = g_try_malloc0(RTNL_REQUEST_SIZE); - if (!req) - return -ENOMEM; + hdr = g_malloc0(RTNL_REQUEST_SIZE); - req->hdr.nlmsg_len = RTNL_REQUEST_SIZE; - req->hdr.nlmsg_type = RTM_GETADDR; - req->hdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP; - req->hdr.nlmsg_pid = 0; - req->hdr.nlmsg_seq = request_seq++; - req->msg.rtgen_family = AF_INET; + hdr->nlmsg_len = RTNL_REQUEST_SIZE; + hdr->nlmsg_type = RTM_GETADDR; + hdr->nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP; + hdr->nlmsg_pid = 0; + hdr->nlmsg_seq = request_seq++; - return queue_request(req); + msg = (struct rtgenmsg *) NLMSG_DATA(hdr); + msg->rtgen_family = AF_INET; + + return queue_request(hdr); } static int send_getroute(void) { - struct rtnl_request *req; + struct nlmsghdr *hdr; + struct rtgenmsg *msg; DBG(""); - req = g_try_malloc0(RTNL_REQUEST_SIZE); - if (!req) - return -ENOMEM; + hdr = g_malloc0(RTNL_REQUEST_SIZE); + + hdr->nlmsg_len = RTNL_REQUEST_SIZE; + hdr->nlmsg_type = RTM_GETROUTE; + hdr->nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP; + hdr->nlmsg_pid = 0; + hdr->nlmsg_seq = request_seq++; - req->hdr.nlmsg_len = RTNL_REQUEST_SIZE; - req->hdr.nlmsg_type = RTM_GETROUTE; - req->hdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP; - req->hdr.nlmsg_pid = 0; - req->hdr.nlmsg_seq = request_seq++; - req->msg.rtgen_family = AF_INET; + msg = (struct rtgenmsg *) NLMSG_DATA(hdr); + msg->rtgen_family = AF_INET; - return queue_request(req); + return queue_request(hdr); } static gboolean update_timeout_cb(gpointer user_data) @@ -1670,14 +1683,14 @@ void __connman_rtnl_cleanup(void) update_list = NULL; for (list = request_list; list; list = list->next) { - struct rtnl_request *req = list->data; + struct nlmsghdr *hdr= list->data; DBG("%s len %d type %d flags 0x%04x seq %d", - type2string(req->hdr.nlmsg_type), - req->hdr.nlmsg_len, req->hdr.nlmsg_type, - req->hdr.nlmsg_flags, req->hdr.nlmsg_seq); + type2string(hdr->nlmsg_type), + hdr->nlmsg_len, hdr->nlmsg_type, + hdr->nlmsg_flags, hdr->nlmsg_seq); - g_free(req); + g_free(hdr); list->data = NULL; } diff --git a/src/service.c b/src/service.c index 2f497d10..06d02322 100644 --- a/src/service.c +++ b/src/service.c @@ -49,11 +49,15 @@ static DBusConnection *connection = NULL; static GList *service_list = NULL; static GHashTable *service_hash = NULL; +static GHashTable *passphrase_requested = NULL; static GSList *counter_list = NULL; static unsigned int autoconnect_id = 0; static unsigned int vpn_autoconnect_id = 0; static struct connman_service *current_default = NULL; static bool services_dirty = false; +static bool enable_online_to_ready_transition = false; +static unsigned int online_check_initial_interval = 0; +static unsigned int online_check_max_interval = 0; struct connman_stats { bool valid; @@ -133,9 +137,10 @@ struct connman_service { char *pac; bool wps; bool wps_advertizing; - guint online_timeout; - int online_check_interval_ipv4; - int online_check_interval_ipv6; + guint online_timeout_ipv4; + guint online_timeout_ipv6; + unsigned int online_check_interval_ipv4; + unsigned int online_check_interval_ipv6; bool do_split_routing; bool new_service; bool hidden_service; @@ -151,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; @@ -192,6 +198,8 @@ static const char *reason2string(enum connman_service_connect_reason reason) return "auto"; case CONNMAN_SERVICE_CONNECT_REASON_SESSION: return "session"; + case CONNMAN_SERVICE_CONNECT_REASON_NATIVE: + return "native"; } return "unknown"; @@ -367,7 +375,26 @@ static enum connman_service_proxy_method string2proxymethod(const char *method) return CONNMAN_SERVICE_PROXY_METHOD_UNKNOWN; } -static void set_split_routing(struct connman_service *service, bool value) +void __connman_service_split_routing_changed(struct connman_service *service) +{ + dbus_bool_t split_routing; + + if (!service->path) + return; + + if (!allow_property_changed(service)) + return; + + split_routing = service->do_split_routing; + if (!connman_dbus_property_changed_basic(service->path, + CONNMAN_SERVICE_INTERFACE, "SplitRouting", + DBUS_TYPE_BOOLEAN, &split_routing)) + connman_warn("cannot send SplitRouting property change on %s", + service->identifier); +} + +void __connman_service_set_split_routing(struct connman_service *service, + bool value) { if (service->type != CONNMAN_SERVICE_TYPE_VPN) return; @@ -378,6 +405,12 @@ static void set_split_routing(struct connman_service *service, bool value) service->order = 0; else service->order = 10; + + /* + * In order to make sure the value is propagated also when loading the + * VPN service signal the value regardless of the value change. + */ + __connman_service_split_routing_changed(service); } int __connman_service_load_modifiable(struct connman_service *service) @@ -400,9 +433,10 @@ int __connman_service_load_modifiable(struct connman_service *service) case CONNMAN_SERVICE_TYPE_P2P: break; case CONNMAN_SERVICE_TYPE_VPN: - set_split_routing(service, g_key_file_get_boolean(keyfile, - service->identifier, - "SplitRouting", NULL)); + __connman_service_set_split_routing(service, + g_key_file_get_boolean(keyfile, + service->identifier, + "SplitRouting", NULL)); /* fall through */ case CONNMAN_SERVICE_TYPE_WIFI: @@ -456,9 +490,10 @@ static int service_load(struct connman_service *service) case CONNMAN_SERVICE_TYPE_P2P: break; case CONNMAN_SERVICE_TYPE_VPN: - set_split_routing(service, g_key_file_get_boolean(keyfile, - service->identifier, - "SplitRouting", NULL)); + __connman_service_set_split_routing(service, + g_key_file_get_boolean(keyfile, + service->identifier, + "SplitRouting", NULL)); autoconnect = g_key_file_get_boolean(keyfile, service->identifier, "AutoConnect", &error); @@ -545,8 +580,10 @@ static int service_load(struct connman_service *service) str = g_key_file_get_string(keyfile, service->identifier, "Passphrase", NULL); if (str) { + char *dec = g_strcompress(str); + g_free(str); g_free(service->passphrase); - service->passphrase = str; + service->passphrase = dec; } if (service->ipconfig_ipv4) @@ -709,9 +746,12 @@ static int service_save(struct connman_service *service) g_free(str); } - if (service->passphrase && strlen(service->passphrase) > 0) + if (service->passphrase && strlen(service->passphrase) > 0) { + char *enc = g_strescape(service->passphrase, NULL); g_key_file_set_string(keyfile, service->identifier, - "Passphrase", service->passphrase); + "Passphrase", enc); + g_free(enc); + } if (service->ipconfig_ipv4) __connman_ipconfig_save(service->ipconfig_ipv4, keyfile, @@ -1400,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, @@ -1416,8 +1460,14 @@ static void start_online_check(struct connman_service *service, "Default service remains in READY state."); return; } + enable_online_to_ready_transition = + connman_setting_get_bool("EnableOnlineToReadyTransition"); + online_check_initial_interval = + connman_setting_get_uint("OnlineCheckInitialInterval"); + 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); } @@ -1432,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); } } @@ -1551,6 +1602,25 @@ bool __connman_service_index_is_default(int index) return __connman_service_get_index(service) == index; } +static void start_wispr_when_connected(struct connman_service *service) +{ + if (!connman_setting_get_bool("EnableOnlineCheck")) { + connman_info("Online check disabled. " + "Default service remains in READY state."); + return; + } + + if (__connman_service_is_connected_state(service, + CONNMAN_IPCONFIG_TYPE_IPV4)) + __connman_service_wispr_start(service, + CONNMAN_IPCONFIG_TYPE_IPV4); + + if (__connman_service_is_connected_state(service, + CONNMAN_IPCONFIG_TYPE_IPV6)) + __connman_service_wispr_start(service, + CONNMAN_IPCONFIG_TYPE_IPV6); +} + static void default_changed(void) { struct connman_service *service = connman_service_get_default(); @@ -1575,6 +1645,8 @@ static void default_changed(void) connman_setting_get_bool("AllowDomainnameUpdates")) __connman_utsname_set_domainname(service->domainname); + start_wispr_when_connected(service); + /* * Connect VPN automatically when new default service * is set and connected, unless new default is VPN @@ -1693,6 +1765,8 @@ bool connman_service_set_autoconnect(struct connman_service *service, service->autoconnect = autoconnect; autoconnect_changed(service); + connman_network_set_autoconnect(service->network, autoconnect); + return true; } @@ -2525,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: @@ -2536,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: @@ -3167,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) { @@ -3191,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; } @@ -3428,15 +3509,28 @@ static void do_auto_connect(struct connman_service *service, return; /* + * Only user interaction should get VPN or WIFI connected in failure + * state. + */ + if (service->state == CONNMAN_SERVICE_STATE_FAILURE && + reason != CONNMAN_SERVICE_CONNECT_REASON_USER && + (service->type == CONNMAN_SERVICE_TYPE_VPN || + service->type == CONNMAN_SERVICE_TYPE_WIFI)) + return; + + /* + * Do not use the builtin auto connect, instead rely on the + * native auto connect feature of the service. + */ + if (service->connect_reason == CONNMAN_SERVICE_CONNECT_REASON_NATIVE) + return; + + /* * Run service auto connect for other than VPN services. Afterwards * start also VPN auto connect process. */ if (service->type != CONNMAN_SERVICE_TYPE_VPN) __connman_service_auto_connect(reason); - /* Only user interaction should get VPN connected in failure state. */ - else if (service->state == CONNMAN_SERVICE_STATE_FAILURE && - reason != CONNMAN_SERVICE_CONNECT_REASON_USER) - return; vpn_auto_connect(); } @@ -3515,14 +3609,6 @@ int __connman_service_reset_ipconfig(struct connman_service *service, return err; } -/* - * We set the timeout to 1 sec so that we have a chance to get - * necessary IPv6 router advertisement messages that might have - * DNS data etc. - */ -#define ONLINE_CHECK_INITIAL_INTERVAL 1 -#define ONLINE_CHECK_MAX_INTERVAL 12 - void __connman_service_wispr_start(struct connman_service *service, enum connman_ipconfig_type type) { @@ -3530,17 +3616,14 @@ void __connman_service_wispr_start(struct connman_service *service, if (type == CONNMAN_IPCONFIG_TYPE_IPV4) service->online_check_interval_ipv4 = - ONLINE_CHECK_INITIAL_INTERVAL; + online_check_initial_interval; else service->online_check_interval_ipv6 = - ONLINE_CHECK_INITIAL_INTERVAL; + online_check_initial_interval; __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) { @@ -3665,13 +3748,7 @@ static DBusMessage *set_property(DBusConnection *conn, nameserver_add_all(service, CONNMAN_IPCONFIG_TYPE_ALL); dns_configuration_changed(service); - if (__connman_service_is_connected_state(service, - CONNMAN_IPCONFIG_TYPE_IPV4)) - __connman_service_wispr_start(service, CONNMAN_IPCONFIG_TYPE_IPV4); - - if (__connman_service_is_connected_state(service, - CONNMAN_IPCONFIG_TYPE_IPV6)) - __connman_service_wispr_start(service, CONNMAN_IPCONFIG_TYPE_IPV6); + start_wispr_when_connected(service); service_save(service); } else if (g_str_equal(name, "Timeservers.Configuration")) { @@ -3717,9 +3794,7 @@ static DBusMessage *set_property(DBusConnection *conn, service_save(service); timeservers_configuration_changed(service); - - if (service == connman_service_get_default()) - __connman_timeserver_sync(service); + __connman_timeserver_conf_update(service); } else if (g_str_equal(name, "Domains.Configuration")) { DBusMessageIter entry; @@ -4151,6 +4226,7 @@ static bool auto_connect_service(GList *services, bool ignore[MAX_CONNMAN_SERVICE_TYPES] = { }; bool autoconnecting = false; GList *list; + int index; DBG("preferred %d sessions %d reason %s", preferred, active_count, reason2string(reason)); @@ -4166,6 +4242,17 @@ static bool auto_connect_service(GList *services, continue; } + if (service->connect_reason == + CONNMAN_SERVICE_CONNECT_REASON_NATIVE) { + DBG("service %p uses native autonnect, skip", service); + continue; + } + + index = __connman_service_get_index(service); + if (g_hash_table_lookup(passphrase_requested, + GINT_TO_POINTER(index))) + return true; + if (service->pending || is_connecting(service->state) || is_connected(service->state)) { @@ -4486,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); @@ -4495,7 +4585,7 @@ static DBusMessage *connect_service(DBusConnection *conn, struct connman_service *temp = list->data; if (!is_connecting(temp->state) && !is_connected(temp->state)) - break; + continue; if (service == temp) continue; @@ -4555,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; @@ -4647,15 +4739,11 @@ static void apply_relevant_default_downgrade(struct connman_service *service) struct connman_service *def_service; def_service = connman_service_get_default(); - if (!def_service) + if (!def_service || def_service != service || + def_service->state != CONNMAN_SERVICE_STATE_ONLINE) return; - if (def_service == service && - def_service->state == CONNMAN_SERVICE_STATE_ONLINE) { - def_service->state = CONNMAN_SERVICE_STATE_READY; - __connman_notifier_leave_online(def_service->type); - state_changed(def_service); - } + downgrade_state(def_service); } static void switch_default_service(struct connman_service *default_service, @@ -4762,27 +4850,22 @@ static void service_schedule_changed(void) services_notify->id = g_timeout_add(100, service_send_changed, NULL); } -static DBusMessage *move_service(DBusConnection *conn, - DBusMessage *msg, void *user_data, - bool before) +int __connman_service_move(struct connman_service *service, + struct connman_service *target, bool before) { - struct connman_service *service = user_data; - struct connman_service *target; - const char *path; enum connman_ipconfig_method target4, target6; enum connman_ipconfig_method service4, service6; DBG("service %p", service); - dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &path, - DBUS_TYPE_INVALID); + if (!service) + return -EINVAL; if (!service->favorite) - return __connman_error_not_supported(msg); + return -EOPNOTSUPP; - target = find_service(path); if (!target || !target->favorite || target == service) - return __connman_error_invalid_service(msg); + return -EINVAL; if (target->type == CONNMAN_SERVICE_TYPE_VPN) { /* @@ -4793,14 +4876,14 @@ static DBusMessage *move_service(DBusConnection *conn, connman_info("Cannot move service. " "No routes defined for provider %s", __connman_provider_get_ident(target->provider)); - return __connman_error_invalid_service(msg); + return -EINVAL; } - set_split_routing(target, true); + __connman_service_set_split_routing(target, true); } else - set_split_routing(target, false); + __connman_service_set_split_routing(target, false); - set_split_routing(service, false); + __connman_service_set_split_routing(service, false); target4 = __connman_ipconfig_get_method(target->ipconfig_ipv4); target6 = __connman_ipconfig_get_method(target->ipconfig_ipv6); @@ -4823,7 +4906,7 @@ static DBusMessage *move_service(DBusConnection *conn, if (service6 != CONNMAN_IPCONFIG_METHOD_OFF) { if (!check_suitable_state(target->state_ipv6, service->state_ipv6)) - return __connman_error_invalid_service(msg); + return -EINVAL; } } @@ -4831,7 +4914,7 @@ static DBusMessage *move_service(DBusConnection *conn, if (service4 != CONNMAN_IPCONFIG_METHOD_OFF) { if (!check_suitable_state(target->state_ipv4, service->state_ipv4)) - return __connman_error_invalid_service(msg); + return -EINVAL; } } @@ -4839,7 +4922,7 @@ static DBusMessage *move_service(DBusConnection *conn, if (target6 != CONNMAN_IPCONFIG_METHOD_OFF) { if (!check_suitable_state(target->state_ipv6, service->state_ipv6)) - return __connman_error_invalid_service(msg); + return -EINVAL; } } @@ -4847,7 +4930,7 @@ static DBusMessage *move_service(DBusConnection *conn, if (target4 != CONNMAN_IPCONFIG_METHOD_OFF) { if (!check_suitable_state(target->state_ipv4, service->state_ipv4)) - return __connman_error_invalid_service(msg); + return -EINVAL; } } @@ -4870,6 +4953,39 @@ static DBusMessage *move_service(DBusConnection *conn, service_schedule_changed(); + return 0; +} + +static DBusMessage *move_service(DBusConnection *conn, + DBusMessage *msg, void *user_data, + bool before) +{ + struct connman_service *service = user_data; + struct connman_service *target; + const char *path; + int err; + + DBG("service %p", service); + + dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &path, + DBUS_TYPE_INVALID); + + target = find_service(path); + + err = __connman_service_move(service, target, before); + switch (err) { + case 0: + break; + case -EINVAL: + return __connman_error_invalid_service(msg); + case -EOPNOTSUPP: + return __connman_error_not_supported(msg); + default: + connman_warn("unsupported error code %d in move_service()", + err); + break; + } + return g_dbus_create_reply(msg, DBUS_TYPE_INVALID); } @@ -5190,6 +5306,59 @@ void connman_service_unref_debug(struct connman_service *service, g_hash_table_remove(service_hash, service->identifier); } +static gint service_compare(gconstpointer a, gconstpointer b); + +static gint service_compare_vpn(struct connman_service *a, + struct connman_service *b) +{ + struct connman_provider *provider; + struct connman_service *service; + struct connman_service *transport; + const char *ident; + bool reverse; + + if (a->provider) { + provider = a->provider; + service = b; + reverse = false; + } else if (b->provider) { + provider = b->provider; + service = a; + reverse = true; + } else { + return 0; + } + + ident = __connman_provider_get_transport_ident(provider); + transport = connman_service_lookup_from_identifier(ident); + if (!transport) + return 0; + + if (reverse) + return service_compare(service, transport); + + return service_compare(transport, service); +} + +static gint service_compare_preferred(struct connman_service *service_a, + struct connman_service *service_b) +{ + unsigned int *tech_array; + int i; + + tech_array = connman_setting_get_uint_list("PreferredTechnologies"); + if (tech_array) { + for (i = 0; tech_array[i]; i++) { + if (tech_array[i] == service_a->type) + return -1; + + if (tech_array[i] == service_b->type) + return 1; + } + } + return 0; +} + static gint service_compare(gconstpointer a, gconstpointer b) { struct connman_service *service_a = (void *) a; @@ -5204,11 +5373,26 @@ static gint service_compare(gconstpointer a, gconstpointer b) b_connected = is_connected(state_b); if (a_connected && b_connected) { + int rval; + + /* Compare the VPN transport and the service */ + if ((service_a->type == CONNMAN_SERVICE_TYPE_VPN || + service_b->type == CONNMAN_SERVICE_TYPE_VPN) && + service_b->type != service_a->type) { + rval = service_compare_vpn(service_a, service_b); + if (rval) + return rval; + } + if (service_a->order > service_b->order) return -1; if (service_a->order < service_b->order) return 1; + + rval = service_compare_preferred(service_a, service_b); + if (rval) + return rval; } if (state_a != state_b) { @@ -5239,20 +5423,11 @@ static gint service_compare(gconstpointer a, gconstpointer b) return 1; if (service_a->type != service_b->type) { - unsigned int *tech_array; - int i; + int rval; - tech_array = connman_setting_get_uint_list( - "PreferredTechnologies"); - if (tech_array) { - for (i = 0; tech_array[i]; i++) { - if (tech_array[i] == service_a->type) - return -1; - - if (tech_array[i] == service_b->type) - return 1; - } - } + rval = service_compare_preferred(service_a, service_b); + if (rval) + return rval; if (service_a->type == CONNMAN_SERVICE_TYPE_ETHERNET) return -1; @@ -5454,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) { @@ -5596,6 +5774,7 @@ static void report_error_cb(void *user_context, bool retry, __connman_service_clear_error(service); service_complete(service); + service_list_sort(); __connman_connection_update_gateway(); } } @@ -5642,6 +5821,7 @@ static void request_input_cb(struct connman_service *service, struct connman_device *device; const char *security; int err = 0; + int index; DBG("RequestInput return, %p", service); @@ -5667,14 +5847,18 @@ static void request_input_cb(struct connman_service *service, goto done; } - if (service->hidden && name_len > 0 && name_len <= 32) { - device = connman_network_get_device(service->network); - security = connman_network_get_string(service->network, - "WiFi.Security"); - err = __connman_device_request_hidden_scan(device, - name, name_len, - identity, passphrase, - security, user_data); + if (service->hidden) { + if (name_len > 0 && name_len <= 32) { + device = connman_network_get_device(service->network); + security = connman_network_get_string(service->network, + "WiFi.Security"); + err = __connman_device_request_hidden_scan(device, + name, name_len, + identity, passphrase, + security, user_data); + } else { + err = -EINVAL; + } if (err < 0) __connman_service_return_error(service, -err, user_data); @@ -5700,6 +5884,10 @@ static void request_input_cb(struct connman_service *service, err = __connman_service_set_passphrase(service, passphrase); done: + index = __connman_service_get_index(service); + g_hash_table_remove(passphrase_requested, + GINT_TO_POINTER(index)); + if (err >= 0) { /* We forget any previous error. */ set_error(service, CONNMAN_SERVICE_ERROR_UNKNOWN); @@ -5753,27 +5941,14 @@ static int service_update_preferred_order(struct connman_service *default_servic struct connman_service *new_service, enum connman_service_state new_state) { - unsigned int *tech_array; - int i; - - if (!default_service || default_service == new_service || - default_service->state != new_state) + if (!default_service || default_service == new_service) return 0; - tech_array = connman_setting_get_uint_list("PreferredTechnologies"); - if (tech_array) { - - for (i = 0; tech_array[i] != 0; i += 1) { - if (default_service->type == tech_array[i]) - return -EALREADY; - - if (new_service->type == tech_array[i]) { - switch_default_service(default_service, - new_service); - __connman_connection_update_gateway(); - return 0; - } - } + if (service_compare_preferred(default_service, new_service) > 0) { + switch_default_service(default_service, + new_service); + __connman_connection_update_gateway(); + return 0; } return -EALREADY; @@ -5867,6 +6042,15 @@ static int service_indicate_state(struct connman_service *service) break; case CONNMAN_SERVICE_STATE_IDLE: + if (old_state == CONNMAN_SERVICE_STATE_FAILURE && + service->connect_reason == + CONNMAN_SERVICE_CONNECT_REASON_NATIVE && + service->error == + CONNMAN_SERVICE_ERROR_INVALID_KEY) { + __connman_service_clear_error(service); + service_complete(service); + } + if (old_state != CONNMAN_SERVICE_STATE_DISCONNECT) __connman_service_disconnect(service); @@ -5909,12 +6093,12 @@ static int service_indicate_state(struct connman_service *service) service->new_service = false; - default_changed(); - def_service = connman_service_get_default(); service_update_preferred_order(def_service, service, new_state); + default_changed(); + __connman_service_set_favorite(service, true); reply_pending(service, 0); @@ -5983,12 +6167,14 @@ static int service_indicate_state(struct connman_service *service) break; case CONNMAN_SERVICE_STATE_FAILURE: - if (service->connect_reason == CONNMAN_SERVICE_CONNECT_REASON_USER) { + if (service->connect_reason == CONNMAN_SERVICE_CONNECT_REASON_USER || + service->connect_reason == CONNMAN_SERVICE_CONNECT_REASON_NATIVE) { connman_agent_report_error(service, service->path, error2string(service->error), report_error_cb, get_dbus_sender(service), NULL); + goto notifier; } service_complete(service); break; @@ -5998,6 +6184,7 @@ static int service_indicate_state(struct connman_service *service) __connman_connection_update_gateway(); +notifier: if ((old_state == CONNMAN_SERVICE_STATE_ONLINE && new_state != CONNMAN_SERVICE_STATE_READY) || (old_state == CONNMAN_SERVICE_STATE_READY && @@ -6156,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; @@ -6179,16 +6366,24 @@ 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; } -int __connman_service_online_check_failed(struct connman_service *service, - enum connman_ipconfig_type type) +void __connman_service_online_check(struct connman_service *service, + enum connman_ipconfig_type type, + bool success) { GSourceFunc redo_func; - int *interval; + 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; @@ -6198,19 +6393,37 @@ int __connman_service_online_check_failed(struct connman_service *service, redo_func = redo_wispr_ipv6; } + if(!enable_online_to_ready_transition) + goto redo_func; + + if (success) { + *interval = online_check_max_interval; + } else { + current_state = service->state; + downgrade_state(service); + if (current_state != service->state) + *interval = online_check_initial_interval; + if (service != connman_service_get_default()) { + return; + } + } + +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 * ONLINE_CHECK_MAX_INTERVAL seconds. + * online_check_max_interval seconds * online_check_max_interval seconds. */ - if (*interval < ONLINE_CHECK_MAX_INTERVAL) + if (*interval < online_check_max_interval) (*interval)++; - - return EAGAIN; } int __connman_service_ipconfig_indicate_state(struct connman_service *service, @@ -6317,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); } @@ -6533,6 +6747,7 @@ static int service_connect(struct connman_service *service) int __connman_service_connect(struct connman_service *service, enum connman_service_connect_reason reason) { + int index; int err; DBG("service %p state %s connect reason %s -> %s", @@ -6567,6 +6782,12 @@ int __connman_service_connect(struct connman_service *service, __connman_service_clear_error(service); + if (service->network && service->autoconnect && + __connman_network_native_autoconnect(service->network)) { + DBG("service %p switch connecting reason to native", service); + reason = CONNMAN_SERVICE_CONNECT_REASON_NATIVE; + } + err = service_connect(service); DBG("service %p err %d", service, err); @@ -6590,7 +6811,8 @@ int __connman_service_connect(struct connman_service *service, service->provider) connman_provider_disconnect(service->provider); - if (service->connect_reason == CONNMAN_SERVICE_CONNECT_REASON_USER) { + if (reason == CONNMAN_SERVICE_CONNECT_REASON_USER || + reason == CONNMAN_SERVICE_CONNECT_REASON_NATIVE) { if (err == -ENOKEY || err == -EPERM) { DBusMessage *pending = NULL; const char *dbus_sender = get_dbus_sender(service); @@ -6606,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, @@ -6613,6 +6842,13 @@ int __connman_service_connect(struct connman_service *service, if (service->hidden && err != -EINPROGRESS) service->pending = pending; + if (err == -EINPROGRESS) { + index = __connman_service_get_index(service); + g_hash_table_replace(passphrase_requested, + GINT_TO_POINTER(index), + GINT_TO_POINTER(true)); + } + return err; } } @@ -6665,36 +6901,6 @@ int __connman_service_disconnect(struct connman_service *service) return err; } -int __connman_service_disconnect_all(void) -{ - struct connman_service *service; - GSList *services = NULL, *list; - GList *iter; - - DBG(""); - - for (iter = service_list; iter; iter = iter->next) { - service = iter->data; - - if (!is_connected(service->state)) - break; - - services = g_slist_prepend(services, service); - } - - for (list = services; list; list = list->next) { - struct connman_service *service = list->data; - - service->ignore = true; - - __connman_service_disconnect(service); - } - - g_slist_free(services); - - return 0; -} - /** * lookup_by_identifier: * @identifier: service identifier @@ -6810,14 +7016,14 @@ static int service_register(struct connman_service *service) DBG("path %s", service->path); - if (__connman_config_provision_service(service) < 0) - service_load(service); - g_dbus_register_interface(connection, service->path, CONNMAN_SERVICE_INTERFACE, service_methods, service_signals, NULL, service, NULL); + if (__connman_config_provision_service(service) < 0) + service_load(service); + service_list_sort(); __connman_connection_update_gateway(); @@ -7222,6 +7428,50 @@ static void update_from_network(struct connman_service *service, service_list_sort(); } +static void trigger_autoconnect(struct connman_service *service) +{ + struct connman_device *device; + bool native; + + if (!service->favorite) + return; + + native = __connman_network_native_autoconnect(service->network); + if (native && service->autoconnect) { + DBG("trigger native autoconnect"); + connman_network_set_autoconnect(service->network, true); + return; + } + + device = connman_network_get_device(service->network); + if (device && connman_device_get_scanning(device, CONNMAN_SERVICE_TYPE_UNKNOWN)) + return; + + switch (service->type) { + case CONNMAN_SERVICE_TYPE_UNKNOWN: + case CONNMAN_SERVICE_TYPE_SYSTEM: + case CONNMAN_SERVICE_TYPE_P2P: + break; + + case CONNMAN_SERVICE_TYPE_GADGET: + case CONNMAN_SERVICE_TYPE_ETHERNET: + if (service->autoconnect) { + __connman_service_connect(service, + CONNMAN_SERVICE_CONNECT_REASON_AUTO); + break; + } + + /* fall through */ + case CONNMAN_SERVICE_TYPE_BLUETOOTH: + case CONNMAN_SERVICE_TYPE_GPS: + case CONNMAN_SERVICE_TYPE_VPN: + case CONNMAN_SERVICE_TYPE_WIFI: + case CONNMAN_SERVICE_TYPE_CELLULAR: + do_auto_connect(service, CONNMAN_SERVICE_CONNECT_REASON_AUTO); + break; + } +} + /** * __connman_service_create_from_network: * @network: network structure @@ -7231,7 +7481,6 @@ static void update_from_network(struct connman_service *service, struct connman_service * __connman_service_create_from_network(struct connman_network *network) { struct connman_service *service; - struct connman_device *device; const char *ident, *group; char *name; unsigned int *auto_connect_types, *favorite_types; @@ -7261,8 +7510,19 @@ struct connman_service * __connman_service_create_from_network(struct connman_ne if (__connman_network_get_weakness(network)) return service; + index = connman_network_get_index(network); + if (service->path) { update_from_network(service, network); + + if (service->ipconfig_ipv4) + __connman_ipconfig_set_index(service->ipconfig_ipv4, + index); + + if (service->ipconfig_ipv6) + __connman_ipconfig_set_index(service->ipconfig_ipv6, + index); + __connman_connection_update_gateway(); return service; } @@ -7293,49 +7553,21 @@ struct connman_service * __connman_service_create_from_network(struct connman_ne update_from_network(service, network); - index = connman_network_get_index(network); - if (!service->ipconfig_ipv4) service->ipconfig_ipv4 = create_ip4config(service, index, CONNMAN_IPCONFIG_METHOD_DHCP); + else + __connman_ipconfig_set_index(service->ipconfig_ipv4, index); if (!service->ipconfig_ipv6) service->ipconfig_ipv6 = create_ip6config(service, index); + else + __connman_ipconfig_set_index(service->ipconfig_ipv6, index); service_register(service); service_schedule_added(service); - if (service->favorite) { - device = connman_network_get_device(service->network); - if (device && !connman_device_get_scanning(device, - CONNMAN_SERVICE_TYPE_UNKNOWN)) { - - switch (service->type) { - case CONNMAN_SERVICE_TYPE_UNKNOWN: - case CONNMAN_SERVICE_TYPE_SYSTEM: - case CONNMAN_SERVICE_TYPE_P2P: - break; - - case CONNMAN_SERVICE_TYPE_GADGET: - case CONNMAN_SERVICE_TYPE_ETHERNET: - if (service->autoconnect) { - __connman_service_connect(service, - CONNMAN_SERVICE_CONNECT_REASON_AUTO); - break; - } - - /* fall through */ - case CONNMAN_SERVICE_TYPE_BLUETOOTH: - case CONNMAN_SERVICE_TYPE_GPS: - case CONNMAN_SERVICE_TYPE_VPN: - case CONNMAN_SERVICE_TYPE_WIFI: - case CONNMAN_SERVICE_TYPE_CELLULAR: - do_auto_connect(service, - CONNMAN_SERVICE_CONNECT_REASON_AUTO); - break; - } - } - } + trigger_autoconnect(service); __connman_notifier_service_add(service, service->name); @@ -7601,6 +7833,8 @@ int __connman_service_init(void) service_hash = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, service_free); + passphrase_requested = g_hash_table_new(g_direct_hash, g_direct_equal); + services_notify = g_new0(struct _services_notify, 1); services_notify->remove = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL); @@ -7633,6 +7867,9 @@ void __connman_service_cleanup(void) g_hash_table_destroy(service_hash); service_hash = NULL; + g_hash_table_destroy(passphrase_requested); + passphrase_requested = NULL; + g_slist_free(counter_list); counter_list = NULL; diff --git a/src/session.c b/src/session.c index 2a1dd9aa..eeefe3f2 100644 --- a/src/session.c +++ b/src/session.c @@ -1804,7 +1804,7 @@ static void session_activate(struct connman_session *session) struct connman_service *service; struct connman_service_info *info; GSList *service_list = NULL; - enum connman_service_state state = CONNMAN_SESSION_STATE_DISCONNECTED; + enum connman_service_state state = CONNMAN_SERVICE_STATE_DISCONNECT; g_hash_table_iter_init(&iter, service_hash); diff --git a/src/shared/util.c b/src/shared/util.c index 73c24aef..bda2d2b3 100644 --- a/src/shared/util.c +++ b/src/shared/util.c @@ -28,6 +28,7 @@ #include <stdio.h> #include <ctype.h> #include <stdarg.h> +#include <string.h> #include "src/shared/util.h" diff --git a/src/technology.c b/src/technology.c index 4e053fc9..5c469111 100644 --- a/src/technology.c +++ b/src/technology.c @@ -66,6 +66,7 @@ struct connman_technology { */ char *tethering_ident; char *tethering_passphrase; + int tethering_freq; bool enable_persistent; /* Save the tech state */ @@ -185,10 +186,19 @@ static void technology_save(struct connman_technology *technology) "Tethering.Identifier", technology->tethering_ident); - if (technology->tethering_passphrase) + if (technology->tethering_passphrase) { + char *enc = g_strescape(technology->tethering_passphrase, NULL); g_key_file_set_string(keyfile, identifier, - "Tethering.Passphrase", - technology->tethering_passphrase); + "Tethering.Passphrase", enc); + g_free(enc); + } + + if (technology->tethering_freq == 0) + technology->tethering_freq = 2412; + + g_key_file_set_integer(keyfile, identifier, + "Tethering.Freq", + technology->tethering_freq); done: g_free(identifier); @@ -262,8 +272,7 @@ static int set_tethering(struct connman_technology *technology, if (!driver || !driver->set_tethering) continue; - err = driver->set_tethering(technology, ident, passphrase, - bridge, enabled); + err = driver->set_tethering(technology, bridge, enabled); if (result == -EINPROGRESS) continue; @@ -354,25 +363,32 @@ enum connman_service_type connman_technology_get_type return technology->type; } -bool connman_technology_get_wifi_tethering(const char **ssid, - const char **psk) +bool connman_technology_get_wifi_tethering(const struct connman_technology *technology, + const char **ssid, const char **psk, + int *freq) { - struct connman_technology *technology; + bool force = true; if (!ssid || !psk) return false; *ssid = *psk = NULL; - technology = technology_find(CONNMAN_SERVICE_TYPE_WIFI); + /* Workaround for the neard plugin */ + if (!technology) { + technology = technology_find(CONNMAN_SERVICE_TYPE_WIFI); + force = false; + } + if (!technology) return false; - if (!technology->tethering) + if (!force && !technology->tethering) return false; *ssid = technology->tethering_ident; *psk = technology->tethering_passphrase; + *freq = technology->tethering_freq; return true; } @@ -390,6 +406,7 @@ static void technology_load(struct connman_technology *technology) gchar *identifier; GError *error = NULL; bool enable, need_saving = false; + char *enc; DBG("technology %p", technology); @@ -436,8 +453,14 @@ static void technology_load(struct connman_technology *technology) technology->tethering_ident = g_key_file_get_string(keyfile, identifier, "Tethering.Identifier", NULL); - technology->tethering_passphrase = g_key_file_get_string(keyfile, + enc = g_key_file_get_string(keyfile, identifier, "Tethering.Passphrase", NULL); + if (enc) + technology->tethering_passphrase = g_strcompress(enc); + + technology->tethering_freq = g_key_file_get_integer(keyfile, + identifier, "Tethering.Freq", NULL); + done: g_free(identifier); @@ -549,6 +572,10 @@ static void append_properties(DBusMessageIter *iter, DBUS_TYPE_STRING, &technology->tethering_passphrase); + connman_dbus_dict_append_basic(&dict, "TetheringFreq", + DBUS_TYPE_INT32, + &technology->tethering_freq); + connman_dbus_dict_close(iter, &dict); } @@ -963,6 +990,27 @@ static DBusMessage *set_property(DBusConnection *conn, DBUS_TYPE_STRING, &technology->tethering_passphrase); } + } else if (g_str_equal(name, "TetheringFreq")) { + dbus_int32_t freq; + + if (type != DBUS_TYPE_INT32) + return __connman_error_invalid_arguments(msg); + + dbus_message_iter_get_basic(&value, &freq); + + if (technology->type != CONNMAN_SERVICE_TYPE_WIFI) + return __connman_error_not_supported(msg); + + if (freq >= 0) { + technology->tethering_freq = freq; + technology_save(technology); + + connman_dbus_property_changed_basic(technology->path, + CONNMAN_TECHNOLOGY_INTERFACE, + "TetheringFreq", + DBUS_TYPE_INT32, + &technology->tethering_freq); + } } else if (g_str_equal(name, "Powered")) { dbus_bool_t enable; diff --git a/src/tethering.c b/src/tethering.c index e2687b6e..f930a26b 100644 --- a/src/tethering.c +++ b/src/tethering.c @@ -386,7 +386,8 @@ static void setup_tun_interface(unsigned int flags, unsigned change, if ((__connman_inet_modify_address(RTM_NEWADDR, NLM_F_REPLACE | NLM_F_ACK, pn->index, AF_INET, - server_ip, peer_ip, prefixlen, NULL)) < 0) { + server_ip, peer_ip, prefixlen, NULL, true)) + < 0) { DBG("address setting failed"); return; } diff --git a/src/timeserver.c b/src/timeserver.c index decca153..d23776fa 100644 --- a/src/timeserver.c +++ b/src/timeserver.c @@ -29,6 +29,7 @@ #include <stdlib.h> #include <gweb/gresolv.h> #include <netdb.h> +#include <sys/time.h> #include "connman.h" @@ -40,11 +41,13 @@ static GSList *ts_list = NULL; static char *ts_current = NULL; static int ts_recheck_id = 0; static int ts_backoff_id = 0; +static bool ts_is_synced = false; 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) { @@ -53,10 +56,26 @@ static void resolv_debug(const char *str, void *data) static void ntp_callback(bool success, void *user_data) { + dbus_uint64_t timestamp; + struct timeval tv; + DBG("success %d", success); - if (!success) + __connman_timeserver_set_synced(success); + if (!success) { sync_next(); + return; + } + + if (gettimeofday(&tv, NULL) < 0) { + connman_warn("Failed to get current time"); + } + + timestamp = tv.tv_sec; + connman_dbus_property_changed_basic( + CONNMAN_MANAGER_PATH, + CONNMAN_CLOCK_INTERFACE, "Time", + DBUS_TYPE_UINT64, ×tamp); } static void save_timeservers(char **servers) @@ -165,6 +184,7 @@ static void sync_next(void) } __connman_ntp_stop(); + ts_set_nameservers(ts_service); while (ts_list) { ts_current = ts_list->data; @@ -272,6 +292,7 @@ GSList *__connman_timeserver_get_all(struct connman_service *service) static gboolean ts_recheck(gpointer user_data) { + struct connman_service *service; GSList *ts; ts = __connman_timeserver_get_all(connman_service_get_default()); @@ -287,7 +308,9 @@ static gboolean ts_recheck(gpointer user_data) g_slist_free_full(ts, g_free); - __connman_timeserver_sync(NULL); + service = connman_service_get_default(); + __connman_timeserver_sync(service, + CONNMAN_TIMESERVER_SYNC_REASON_TS_CHANGE); return FALSE; } @@ -327,37 +350,34 @@ static void ts_recheck_enable(void) NULL); } -/* - * This function must be called every time the default service changes, the - * service timeserver(s) or gateway changes or the global timeserver(s) changes. - */ -int __connman_timeserver_sync(struct connman_service *default_service) +static int ts_setup_resolv(struct connman_service *service) { - struct connman_service *service; - char **nameservers; int i; - if (default_service) - service = default_service; - else - service = connman_service_get_default(); - - if (!service) + i = __connman_service_get_index(service); + if (i < 0) return -EINVAL; - if (service == ts_service) - return -EALREADY; + if (resolv) { + g_resolv_unref(resolv); + resolv = NULL; + } + resolv = g_resolv_new(i); if (!resolv) - return 0; - /* - * Before we start creating the new timeserver list we must stop - * any ongoing ntp query and server resolution. - */ + return -ENOMEM; - __connman_ntp_stop(); + if (getenv("CONNMAN_RESOLV_DEBUG")) + g_resolv_set_debug(resolv, resolv_debug, "RESOLV"); + + return 0; +} - 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); @@ -365,73 +385,121 @@ int __connman_timeserver_sync(struct connman_service *default_service) g_resolv_flush_nameservers(resolv); nameservers = connman_service_get_nameservers(service); - if (!nameservers) - return -EINVAL; + if (nameservers) { + for (i = 0; nameservers[i]; i++) + g_resolv_add_nameserver(resolv, nameservers[i], 53, 0); - for (i = 0; nameservers[i]; i++) - g_resolv_add_nameserver(resolv, nameservers[i], 53, 0); + g_strfreev(nameservers); + } +} + +static void ts_reset(struct connman_service *service) +{ + if (!resolv) + return; + + __connman_timeserver_set_synced(false); - g_strfreev(nameservers); + /* + * 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); if (!timeservers_list) { DBG("No timeservers set."); - return 0; + return; } ts_recheck_enable(); ts_service = service; timeserver_sync_start(); +} - return 0; +void __connman_timeserver_sync(struct connman_service *service, + enum connman_timeserver_sync_reason reason) +{ + 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); } -static int timeserver_start(struct connman_service *service) +void __connman_timeserver_conf_update(struct connman_service *service) { - char **nameservers; - int i; + if (!service || (ts_service && ts_service != service)) + return; - DBG("service %p", service); + ts_reset(service); +} - i = __connman_service_get_index(service); - if (i < 0) - return -EINVAL; - nameservers = connman_service_get_nameservers(service); +bool __connman_timeserver_is_synced(void) +{ + return ts_is_synced; +} - /* Stop an already ongoing resolution, if there is one */ - if (resolv && resolv_id > 0) - g_resolv_cancel_lookup(resolv, resolv_id); +void __connman_timeserver_set_synced(bool status) +{ + dbus_bool_t is_synced; - /* get rid of the old resolver */ - if (resolv) { - g_resolv_unref(resolv); - resolv = NULL; - } + if (ts_is_synced == status) + return; - resolv = g_resolv_new(i); - if (!resolv) { - g_strfreev(nameservers); - return -ENOMEM; - } + ts_is_synced = status; + is_synced = status; + connman_dbus_property_changed_basic(CONNMAN_MANAGER_PATH, + CONNMAN_CLOCK_INTERFACE, "TimeserverSynced", + DBUS_TYPE_BOOLEAN, &is_synced); +} - if (getenv("CONNMAN_RESOLV_DEBUG")) - g_resolv_set_debug(resolv, resolv_debug, "RESOLV"); +static int timeserver_start(struct connman_service *service) +{ + int rv; - if (nameservers) { - for (i = 0; nameservers[i]; i++) - g_resolv_add_nameserver(resolv, nameservers[i], 53, 0); + DBG("service %p", service); - g_strfreev(nameservers); - } + /* get rid of the old resolver */ + rv = ts_setup_resolv(service); + if (rv) + return rv; + + ts_set_nameservers(service); + + __connman_timeserver_sync(service, + CONNMAN_TIMESERVER_SYNC_REASON_START); - return __connman_timeserver_sync(service); + return 0; } static void timeserver_stop(void) @@ -458,9 +526,13 @@ static void timeserver_stop(void) int __connman_timeserver_system_set(char **servers) { + struct connman_service *service; + save_timeservers(servers); - __connman_timeserver_sync(NULL); + service = connman_service_get_default(); + if (service) + ts_reset(service); return 0; } diff --git a/src/timezone.c b/src/timezone.c index cc499097..f8d192df 100644 --- a/src/timezone.c +++ b/src/timezone.c @@ -38,9 +38,9 @@ #include "connman.h" -#define ETC_LOCALTIME "/etc/localtime" #define ETC_SYSCONFIG_CLOCK "/etc/sysconfig/clock" #define USR_SHARE_ZONEINFO "/usr/share/zoneinfo" +#define USR_SHARE_ZONEINFO_MAP USR_SHARE_ZONEINFO "/zone1970.tab" static char *read_key_file(const char *pathname, const char *key) { @@ -228,18 +228,104 @@ static char *find_origin(void *src_map, struct stat *src_st, return NULL; } +static char *get_timezone_alpha2(const char *zone) +{ + GIOChannel *channel; + struct stat st; + char **tokens; + char *line; + char *alpha2 = NULL; + gsize len; + int fd; + + if (!zone) + return NULL; + + fd = open(USR_SHARE_ZONEINFO_MAP, O_RDONLY | O_CLOEXEC); + if (fd < 0) { + connman_warn("failed to open zoneinfo map %s", + USR_SHARE_ZONEINFO_MAP); + return NULL; + } + + if (fstat(fd, &st) < 0 || !S_ISREG(st.st_mode)) { + connman_warn("zoneinfo map does not exist/not regular file"); + close(fd); + return NULL; + } + + channel = g_io_channel_unix_new(fd); + if (!channel) { + connman_warn("failed to create io channel for %s", + USR_SHARE_ZONEINFO_MAP); + close(fd); + return NULL; + } + + DBG("read %s for %s", USR_SHARE_ZONEINFO_MAP, zone); + g_io_channel_set_encoding(channel, "UTF-8", NULL); + + while (g_io_channel_read_line(channel, &line, &len, NULL, NULL) == + G_IO_STATUS_NORMAL) { + if (!line || !*line || *line == '#' || *line == '\n') { + g_free(line); + continue; + } + + /* File format: Countrycodes Coordinates TZ Comments */ + tokens = g_strsplit_set(line, " \t", 4); + if (!tokens) { + connman_warn("line %s failed to parse", line); + g_free(line); + continue; + } + + if (g_strv_length(tokens) >= 3 && !g_strcmp0( + g_strstrip(tokens[2]), zone)) { + /* + * Multiple country codes can be listed, use the first + * 2 chars. + */ + alpha2 = g_strndup(g_strstrip(tokens[0]), 2); + } + + g_strfreev(tokens); + g_free(line); + + if (alpha2) { + if (strlen(alpha2) != 2) { + connman_warn("Invalid ISO3166 code %s", alpha2); + g_free(alpha2); + alpha2 = NULL; + } else { + DBG("Zone %s ISO3166 country code %s", zone, + alpha2); + } + + break; + } + } + + g_io_channel_unref(channel); + close(fd); + + return alpha2; +} + char *__connman_timezone_lookup(void) { struct stat st; void *map; int fd; char *zone; + char *alpha2; zone = read_key_file(ETC_SYSCONFIG_CLOCK, "ZONE"); DBG("sysconfig zone %s", zone); - fd = open(ETC_LOCALTIME, O_RDONLY | O_CLOEXEC); + fd = open(connman_setting_get_string("Localtime"), + O_RDONLY | O_CLOEXEC); if (fd < 0) { g_free(zone); return NULL; @@ -283,6 +369,15 @@ done: DBG("localtime zone %s", zone); + if (connman_setting_get_bool("RegdomFollowsTimezone")) { + alpha2 = get_timezone_alpha2(zone); + if (alpha2) { + DBG("change regdom to %s", alpha2); + connman_technology_set_regdom(alpha2); + g_free(alpha2); + } + } + return zone; } @@ -338,7 +433,7 @@ int __connman_timezone_change(const char *zone) return -EIO; } - err = write_file(map, &st, ETC_LOCALTIME); + err = write_file(map, &st, connman_setting_get_string("Localtime")); munmap(map, st.st_size); @@ -432,9 +527,9 @@ int __connman_timezone_init(void) g_io_channel_unref(channel); - dirname = g_path_get_dirname(ETC_LOCALTIME); + dirname = g_path_get_dirname(connman_setting_get_string("Localtime")); - wd = inotify_add_watch(fd, dirname, IN_DONT_FOLLOW | + wd = inotify_add_watch(fd, dirname, IN_CREATE | IN_DONT_FOLLOW | IN_CLOSE_WRITE | IN_MOVED_TO); g_free(dirname); diff --git a/src/wispr.c b/src/wispr.c index 41157580..a4372018 100644 --- a/src/wispr.c +++ b/src/wispr.c @@ -30,9 +30,6 @@ #include "connman.h" -#define STATUS_URL_IPV4 "http://ipv4.connman.net/online/status.html" -#define STATUS_URL_IPV6 "http://ipv6.connman.net/online/status.html" - struct connman_wispr_message { bool has_error; const char *current_element; @@ -59,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; @@ -94,12 +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; @@ -159,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; @@ -200,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) @@ -214,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); } @@ -450,10 +479,11 @@ static void portal_manage_status(GWebResult *result, &str)) connman_info("Client-Timezone: %s", str); - free_connman_wispr_portal_context(wp_context); - __connman_service_ipconfig_indicate_state(service, CONNMAN_SERVICE_STATE_ONLINE, type); + + if (enable_online_to_ready_transition) + __connman_service_online_check(service, type, true); } static bool wispr_route_request(const char *address, int ai_family, @@ -507,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, @@ -571,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; @@ -581,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, @@ -611,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; } @@ -663,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; @@ -716,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; } @@ -733,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); @@ -744,18 +784,25 @@ 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: + case 301: case 302: + case 303: + case 307: + case 308: if (!g_web_supports_tls() || !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); @@ -766,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); @@ -773,15 +821,12 @@ static bool wispr_portal_web_result(GWebResult *result, gpointer user_data) goto done; case 400: case 404: - if (__connman_service_online_check_failed(wp_context->service, - wp_context->type) == 0) { - wispr_portal_error(wp_context); - free_connman_wispr_portal_context(wp_context); - return false; - } + __connman_service_online_check(wp_context->service, + wp_context->type, false); 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); @@ -794,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); @@ -828,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) @@ -844,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; @@ -854,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; @@ -903,10 +956,10 @@ static int wispr_portal_detect(struct connman_wispr_portal_context *wp_context) if (wp_context->type == CONNMAN_IPCONFIG_TYPE_IPV4) { g_web_set_address_family(wp_context->web, AF_INET); - wp_context->status_url = STATUS_URL_IPV4; + wp_context->status_url = online_check_ipv4_url; } else { g_web_set_address_family(wp_context->web, AF_INET6); - wp_context->status_url = STATUS_URL_IPV6; + wp_context->status_url = online_check_ipv6_url; } for (i = 0; nameservers[i]; i++) @@ -922,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); @@ -940,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; @@ -986,33 +1055,58 @@ 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) { + struct connman_wispr_portal *wispr_portal; int index; DBG("service %p", service); - if (!wispr_portal_list) + if (!wispr_portal_hash) return; index = __connman_service_get_index(service); if (index < 0) return; - g_hash_table_remove(wispr_portal_list, GINT_TO_POINTER(index)); + wispr_portal = g_hash_table_lookup(wispr_portal_hash, + GINT_TO_POINTER(index)); + if (!wispr_portal) + return; + + 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); + online_check_ipv4_url = + connman_setting_get_string("OnlineCheckIPv4URL"); + online_check_ipv6_url = + connman_setting_get_string("OnlineCheckIPv6URL"); + + enable_online_to_ready_transition = + connman_setting_get_bool("EnableOnlineToReadyTransition"); + return 0; } @@ -1020,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; } |