diff --git a/drivers/rilmodem/gprs-context.c b/drivers/rilmodem/gprs-context.c index d6432e1f1..d21d4aff0 100644 --- a/drivers/rilmodem/gprs-context.c +++ b/drivers/rilmodem/gprs-context.c @@ -47,6 +47,8 @@ #include "gprs.h" #include "rilmodem.h" +#define DEF_IPV6_PREF_LENGTH 128 + enum state { STATE_IDLE, STATE_ENABLING, @@ -132,6 +134,105 @@ static void ril_gprs_context_call_list_changed(struct ril_msg *message, g_ril_unsol_free_data_call_list(call_list); } +static gboolean is_ipv4(const char *ip) +{ + return strchr(ip, '.') == NULL ? FALSE : TRUE; +} + +static gboolean is_ipv6(const char *ip) +{ + return strchr(ip, '.') == NULL ? TRUE : FALSE; +} + +typedef gboolean (*is_of_type_t)(const char *str); + +static char **get_strs_subset(char **strs, is_of_type_t is_of_type) +{ + char **out; + char **str; + int n_str = 0, i; + + if (strs == NULL) + return NULL; + + for (str = strs; *str != NULL; ++str) + if (is_of_type(*str)) + ++n_str; + + if (n_str == 0) + return NULL; + + out = g_try_new0(char *, n_str + 1); + if (out == NULL) + return NULL; + + for (str = strs, i = 0; i < n_str; ++str) { + if (!is_of_type(*str)) + continue; + + out[i++] = g_strdup(*str); + } + + return out; +} + +static void set_ipv4_settings(struct ofono_gprs_context *gc, + struct ril_data_call *call, int addr_i) +{ + char **gw; + char **dnses; + + ofono_gprs_context_set_ipv4_address(gc, call->ip_addrs[addr_i], TRUE); + ofono_gprs_context_set_ipv4_netmask(gc, + ril_util_get_ipv4_netmask(call->prefix_lengths[addr_i])); + + for (gw = call->gateways; *gw != NULL; ++gw) { + if (!is_ipv4(*gw)) + continue; + + ofono_gprs_context_set_ipv4_gateway(gc, *gw); + break; + } + + dnses = get_strs_subset(call->dns_addrs, is_ipv4); + ofono_gprs_context_set_ipv4_dns_servers(gc, (const char **) dnses); + g_strfreev(dnses); +} + +static void set_ipv6_settings(struct ofono_gprs_context *gc, + struct ril_data_call *call, int addr_i) +{ + char *endp; + char **gw; + int prefix; + char **dnses; + + ofono_gprs_context_set_ipv6_address(gc, call->ip_addrs[addr_i]); + + if (*call->prefix_lengths[addr_i] == '\0') { + prefix = DEF_IPV6_PREF_LENGTH; + } else { + prefix = strtol(call->prefix_lengths[addr_i], &endp, 10); + if (*endp != '\0' || prefix < 0 || + prefix > DEF_IPV6_PREF_LENGTH) + prefix = DEF_IPV6_PREF_LENGTH; + } + + ofono_gprs_context_set_ipv6_prefix_length(gc, prefix); + + for (gw = call->gateways; *gw != NULL; ++gw) { + if (!is_ipv6(*gw)) + continue; + + ofono_gprs_context_set_ipv6_gateway(gc, *gw); + break; + } + + dnses = get_strs_subset(call->dns_addrs, is_ipv6); + ofono_gprs_context_set_ipv6_dns_servers(gc, (const char **) dnses); + g_strfreev(dnses); +} + static void ril_setup_data_call_cb(struct ril_msg *message, gpointer user_data) { struct cb_data *cbd = user_data; @@ -140,6 +241,8 @@ static void ril_setup_data_call_cb(struct ril_msg *message, gpointer user_data) struct gprs_context_data *gcd = ofono_gprs_context_get_data(gc); struct ril_data_call *call = NULL; struct ril_data_call_list *call_list = NULL; + int i; + gboolean found_v4 = FALSE, found_v6 = FALSE; DBG("*gc: %p", gc); @@ -194,14 +297,17 @@ static void ril_setup_data_call_cb(struct ril_msg *message, gpointer user_data) gcd->state = STATE_ACTIVE; ofono_gprs_context_set_interface(gc, call->ifname); - ofono_gprs_context_set_ipv4_netmask(gc, - ril_util_get_netmask(call->ip_addr)); - ofono_gprs_context_set_ipv4_address(gc, call->ip_addr, TRUE); - ofono_gprs_context_set_ipv4_gateway(gc, call->gateways[0]); + for (i = 0; call->ip_addrs[i] != NULL && !(found_v4 && found_v6); ++i) { - ofono_gprs_context_set_ipv4_dns_servers(gc, - (const char **) call->dns_addrs); + if (is_ipv4(call->ip_addrs[i])) { + set_ipv4_settings(gc, call, i); + found_v4 = TRUE; + } else { + set_ipv6_settings(gc, call, i); + found_v6 = TRUE; + } + } g_ril_unsol_free_data_call_list(call_list); diff --git a/drivers/rilmodem/rilutil.c b/drivers/rilmodem/rilutil.c index c173940e5..a177f693a 100644 --- a/drivers/rilmodem/rilutil.c +++ b/drivers/rilmodem/rilutil.c @@ -28,6 +28,7 @@ #include #include #include +#include #define OFONO_API_SUBJECT_TO_CHANGE #include @@ -120,39 +121,33 @@ static gboolean cpin_check(gpointer userdata) return FALSE; } -gchar *ril_util_get_netmask(const gchar *address) +char *ril_util_get_ipv4_netmask(const char *prefix_length) { char *result; + char *endp; + int bits; + uint32_t mask = 0; + int max_bits = sizeof(mask) * CHAR_BIT; - if (g_str_has_suffix(address, "/30")) { - result = PREFIX_30_NETMASK; - } else if (g_str_has_suffix(address, "/29")) { - result = PREFIX_29_NETMASK; - } else if (g_str_has_suffix(address, "/28")) { - result = PREFIX_28_NETMASK; - } else if (g_str_has_suffix(address, "/27")) { - result = PREFIX_27_NETMASK; - } else if (g_str_has_suffix(address, "/26")) { - result = PREFIX_26_NETMASK; - } else if (g_str_has_suffix(address, "/25")) { - result = PREFIX_25_NETMASK; - } else if (g_str_has_suffix(address, "/24")) { - result = PREFIX_24_NETMASK; + result = g_try_malloc(sizeof(mask) * 4); + if (result == NULL) + return result; + + if (prefix_length == NULL || prefix_length[0] == '\0') { + bits = max_bits; } else { - /* - * This handles the case where the - * Samsung RILD returns an address without - * a prefix, however it explicitly sets a - * /24 netmask ( which isn't returned as - * an attribute of the DATA_CALL. - * - * TODO/OEM: this might need to be quirked - * for specific devices. - */ - result = PREFIX_24_NETMASK; + bits = (int) strtol(prefix_length, &endp, 10); + if (*endp != '\0' || bits < 0 || bits > max_bits) + bits = max_bits; } - DBG("address: %s netmask: %s", address, result); + for (; bits > 0; --bits) + mask |= 1 << (max_bits - bits); + + sprintf(result, "%d.%d.%d.%d", mask >> 24, (mask >> 16) & 0xFF, + (mask >> 8) & 0xFF, mask & 0xFF); + + DBG("netmask: %s", result); return result; } diff --git a/drivers/rilmodem/rilutil.h b/drivers/rilmodem/rilutil.h index 9ad799d6a..5f1ab7539 100644 --- a/drivers/rilmodem/rilutil.h +++ b/drivers/rilmodem/rilutil.h @@ -27,15 +27,6 @@ #include #include -/* TODO: create a table lookup*/ -#define PREFIX_30_NETMASK "255.255.255.252" -#define PREFIX_29_NETMASK "255.255.255.248" -#define PREFIX_28_NETMASK "255.255.255.240" -#define PREFIX_27_NETMASK "255.255.255.224" -#define PREFIX_26_NETMASK "255.255.255.192" -#define PREFIX_25_NETMASK "255.255.255.128" -#define PREFIX_24_NETMASK "255.255.255.0" - #define MODEM_PROP_LTE_CAPABLE "lte-capable" enum ril_util_sms_store { @@ -100,7 +91,7 @@ gint ril_util_call_compare_by_status(gconstpointer a, gconstpointer b); gint ril_util_call_compare_by_phone_number(gconstpointer a, gconstpointer b); gint ril_util_call_compare_by_id(gconstpointer a, gconstpointer b); gint ril_util_call_compare(gconstpointer a, gconstpointer b); -gchar *ril_util_get_netmask(const char *address); +char *ril_util_get_ipv4_netmask(const char *prefix_length); struct ril_util_sim_state_query *ril_util_sim_state_query_new(GRil *ril, guint interval, guint num_times, diff --git a/gril/grilunsol.c b/gril/grilunsol.c index a3ea5b66d..cb10af5be 100644 --- a/gril/grilunsol.c +++ b/gril/grilunsol.c @@ -68,9 +68,10 @@ static void free_data_call(gpointer data, gpointer user_data) if (call) { g_free(call->ifname); - g_free(call->ip_addr); - g_free(call->dns_addrs); - g_free(call->gateways); + g_strfreev(call->ip_addrs); + g_strfreev(call->prefix_lengths); + g_strfreev(call->dns_addrs); + g_strfreev(call->gateways); g_free(call); } } @@ -89,7 +90,7 @@ static gboolean handle_settings(struct ril_data_call *call, char *type, char *raw_dns, char *raw_gws) { gboolean result = FALSE; - int protocol; + int protocol, num_addrs, i; char **dns_addrs = NULL, **gateways = NULL; char **ip_addrs = NULL, **split_ip_addr = NULL; @@ -122,46 +123,46 @@ static gboolean handle_settings(struct ril_data_call *call, char *type, goto done; } - /* TODO: - * RILD can return multiple addresses; oFono only supports - * setting a single IPv4 address. At this time, we only - * use the first address. It's possible that a RIL may - * just specify the end-points of the point-to-point - * connection, in which case this code will need to - * changed to handle such a device. - * - * For now split into a maximum of three, and only use - * the first address for the remaining operations. - */ if (raw_ip_addrs) - ip_addrs = g_strsplit(raw_ip_addrs, " ", 3); + ip_addrs = g_strsplit(raw_ip_addrs, " ", -1); - if (ip_addrs == NULL || g_strv_length(ip_addrs) == 0) { + num_addrs = g_strv_length(ip_addrs); + + if (ip_addrs == NULL || num_addrs == 0) { ofono_error("%s: no IP address: %s", __func__, raw_ip_addrs); goto done; } - DBG("num ip addrs is: %d", g_strv_length(ip_addrs)); + DBG("num ip addrs is: %d", num_addrs); - if (g_strv_length(ip_addrs) > 1) + if (num_addrs > 1) ofono_warn("%s: more than one IP addr returned: %s", __func__, raw_ip_addrs); - /* - * Note - the address may optionally include a prefix size - * ( Eg. "/30" ). As this confuses NetworkManager, we - * explicitly strip any prefix after calculating the netmask. - */ - split_ip_addr = g_strsplit(ip_addrs[0], "/", 2); - if (split_ip_addr == NULL || g_strv_length(split_ip_addr) == 0) { - ofono_error("%s: invalid IP address field returned: %s", - __func__, ip_addrs[0]); - goto done; + /* Build arrays of addresses and prefix lengths */ + + call->ip_addrs = g_new0(char *, num_addrs + 1); + call->prefix_lengths = g_new0(char *, num_addrs + 1); + + for (i = 0; i < num_addrs; ++i) { + int n_parts; + + split_ip_addr = g_strsplit(ip_addrs[i], "/", 2); + n_parts = g_strv_length(split_ip_addr); + + if (split_ip_addr == NULL || n_parts == 0) { + ofono_error("%s: invalid IP address field returned: %s", + __func__, ip_addrs[i]); + goto done; + } + + call->ip_addrs[i] = g_strdup(split_ip_addr[0]); + call->prefix_lengths[i] = + g_strdup(n_parts > 1 ? split_ip_addr[1] : ""); } call->protocol = protocol; call->ifname = g_strdup(ifname); - call->ip_addr = g_strdup(split_ip_addr[0]); call->dns_addrs = g_strdupv(dns_addrs); call->gateways = g_strdupv(gateways); @@ -180,6 +181,14 @@ static gboolean handle_settings(struct ril_data_call *call, char *type, if (split_ip_addr) g_strfreev(split_ip_addr); + if (result == FALSE && call->ip_addrs != NULL) { + for (i = 0; call->ip_addrs[i] != NULL; ++i) { + g_free(call->ip_addrs[i]); + g_free(call->prefix_lengths[i]); + } + g_free(call->ip_addrs); + g_free(call->prefix_lengths); + } return result; } diff --git a/gril/grilunsol.h b/gril/grilunsol.h index a7ddfc942..e8ea08e33 100644 --- a/gril/grilunsol.h +++ b/gril/grilunsol.h @@ -37,7 +37,8 @@ struct ril_data_call { guint active; guint protocol; char *ifname; - gchar *ip_addr; + gchar **ip_addrs; + gchar **prefix_lengths; gchar **dns_addrs; gchar **gateways; };