| |
| /****************************************************************************** |
| *(C) Copyright 2015 Marvell International Ltd. |
| * All Rights Reserved |
| ******************************************************************************/ |
| |
| #include <errno.h> |
| #include <unistd.h> |
| #include <stdio.h> |
| #include <string.h> |
| #include <stdlib.h> |
| #include <include/log.h> |
| #include <uci.h> |
| #include <limits.h> |
| #include <cm.h> |
| #include "ril.h" |
| #include "ml_utils.h" |
| #include "chl.h" |
| #include "chl_db.h" |
| #include "chl_ril.h" |
| #include "chl_ubus.h" |
| #include "chl_main.h" |
| #include "chl_uci.h" |
| #include "chl_network.h" |
| |
| #define IPV6_POLL_RETRIES 15 |
| #define IPV6_POLL_INTERVAL 1000 |
| #define CHL_ACT_RETRIES 5 |
| |
| static void __chl_poll_ipv6(struct uloop_timeout *t); |
| |
| static struct chl_conf chl_conf = { |
| .internet = NULL, |
| .autoconf = false, |
| .ipv6_poll_interval = IPV6_POLL_INTERVAL, |
| .activation_retries = CHL_ACT_RETRIES, |
| }; |
| |
| static struct uloop_timeout ip6_to = { |
| .cb = __chl_poll_ipv6, |
| }; |
| |
| struct chl_conf *chl_get_conf() |
| { |
| return &chl_conf; |
| } |
| |
| static void chl_set_internet(struct chl_pdp *pdp) |
| { |
| struct chl_conf *conf = chl_get_conf(); |
| conf->internet = pdp; |
| |
| CHL_INFO("New default gateway is [%s]\n", pdp->apn); |
| } |
| |
| static bool chl_has_internet() |
| { |
| struct chl_conf *conf = chl_get_conf(); |
| return conf->internet != NULL; |
| } |
| |
| static bool chl_is_internet(struct chl_pdp *pdp) |
| { |
| struct chl_conf *conf = chl_get_conf(); |
| return conf->internet == pdp; |
| } |
| |
| static struct chl_pdp *chl_get_internet() |
| { |
| struct chl_conf *conf = chl_get_conf(); |
| return conf->internet; |
| } |
| |
| static void chl_del_internet() |
| { |
| struct chl_conf *conf = chl_get_conf(); |
| |
| if (conf->internet) { |
| CHL_INFO("Deleting internet pdp [%s]\n", conf->internet->apn); |
| conf->internet = NULL; |
| } |
| } |
| |
| static int chl_del_all_routes(struct chl_client *cl) |
| { |
| struct uci_context *c = NULL; |
| |
| CHL_INFO("deleting all routes for [%s]\n", cl->name); |
| |
| if (chl_uci_init(&c)) |
| goto err; |
| |
| if (chl_uci_del_spec_route(c, cl, NULL)) |
| goto err; |
| |
| chl_uci_done(c, true); |
| chl_network_reload(); |
| |
| return 0; |
| err: |
| chl_uci_done(c, false); |
| CHL_ERR("chl_del_all_routes failed\n"); |
| return -1; |
| } |
| |
| static int chl_unconfig_ipv6(struct uci_context *c, struct chl_pdp *gw) |
| { |
| CHL_INFO("Uconfiguring ipv6 for [%s]\n", gw->apn); |
| |
| chl_uci_del_prefix(c, gw); |
| |
| chl_uci_del_lan_route(c, gw); |
| |
| memset(gw->gb6addr, 0, sizeof(gw->gb6addr)); |
| gw->prefix_len = 0; |
| gw->ipv6_state = CHL_IPV6_IDLE; |
| |
| return 0; |
| } |
| |
| static int chl_config_ipv6(struct chl_pdp *gw) |
| { |
| struct uci_context *c = NULL; |
| |
| CHL_INFO("Configuring ipv6 for [%s]\n", gw->apn); |
| |
| if (chl_uci_init(&c)) |
| goto err; |
| |
| if (chl_uci_add_prefix(c, gw)) |
| goto err; |
| |
| if (chl_uci_add_lan_route(c, gw)) |
| goto err; |
| |
| chl_uci_done(c, true); |
| |
| chl_network_reload(); |
| |
| return 0; |
| err: |
| CHL_ERR("chl_config_ipv6 failed\n"); |
| chl_uci_done(c, false); |
| return -1; |
| } |
| |
| static void __chl_poll_ipv6(struct uloop_timeout *t) |
| { |
| char ccinet[MAX_IFNAME_STR]; |
| char address[MAX_IPV6_STR]; |
| int prefix; |
| static int retries = IPV6_POLL_RETRIES; |
| |
| struct chl_conf *conf = chl_get_conf(); |
| struct chl_pdp *gw = chl_get_internet(); |
| |
| if (!gw || gw->nw_status != DATA_AVAILABLE || |
| gw->ipv6_state != CHL_IPV6_SEARCHING) { |
| CHL_INFO("ipv6 prefix no longer needed\n"); |
| goto reset; |
| } |
| |
| CHL_INFO("Searching global ipv6 address for [%s]\n", gw->apn); |
| |
| if (!retries) { |
| CHL_ERR("could not find gb ipv6 after [%d] retries\n", |
| IPV6_POLL_RETRIES); |
| goto reset; |
| } |
| |
| retries--; |
| |
| cid_to_ccinet(gw->cid, ccinet); |
| |
| if (get_global_ipv6(ccinet, address, &prefix)) |
| goto retry; |
| |
| CHL_INFO("found global ipv6 for [%s]: [%s/%d]\n", ccinet, address, prefix); |
| |
| strcpy(gw->gb6addr, address); |
| gw->prefix_len = prefix; |
| gw->ipv6_state = CHL_IPV6_FOUND; |
| |
| if (chl_config_ipv6(gw)) |
| CHL_ERR("chl_add_del_ipv6 failed for [%s]\n", ccinet); |
| |
| reset: |
| retries = IPV6_POLL_RETRIES; |
| return; |
| |
| retry: |
| uloop_timeout_set(t, conf->ipv6_poll_interval); |
| |
| } |
| |
| static void chl_poll_ipv6_start(struct chl_pdp *pdp) |
| { |
| pdp->ipv6_state = CHL_IPV6_SEARCHING; |
| __chl_poll_ipv6(&ip6_to); |
| } |
| |
| static void chl_send_pdp_ind(struct chl_pdp *pdp, struct chl_client *except) |
| { |
| struct chl_client *cl; |
| struct chl_indication ind; |
| |
| list_for_each_entry(cl, &pdp->clients, list) { |
| if (cl == except) |
| continue; |
| |
| CHL_INFO("Sending indication [%s], [%s]\n", |
| nw_status_to_str(pdp->nw_status), cl->name); |
| |
| ind.id = cl->id; |
| ind.nw_status = pdp->nw_status; |
| if (chl_ubus_send_ind(true, &ind)) |
| CHL_ERR("failed to send state ind for [%s]\n", cl->name); |
| } |
| } |
| |
| |
| static int chl_handle_pdp_deactivation(struct chl_pdp *pdp) |
| { |
| int ril_err = 0; |
| CHL_INFO("Deactivating pdp [%s]\n", pdp->apn); |
| |
| if (chl_ril_setup_data_call(pdp, false, NULL, &ril_err)) |
| goto err; |
| return 0; |
| err: |
| CHL_ERR("chl_handle_data_call failed\n"); |
| return -1; |
| } |
| |
| static int chl_handle_pdp_activation(struct chl_pdp *pdp) |
| { |
| int retries, ril_err = 0, ret = 0; |
| Ubus_Data_Call_Response rsp; |
| struct chl_conf *conf = chl_get_conf(); |
| |
| CHL_INFO("Activating pdp [%s]\n", pdp->apn); |
| |
| retries = conf->activation_retries; |
| |
| while (retries--) { |
| ret = chl_ril_setup_data_call(pdp, true, &rsp, &ril_err); |
| if (ret == UBUS_STATUS_TIMEOUT) { |
| CHL_WARN("data call timeout, setting pdp [%s] as pending\n", |
| pdp->apn); |
| pdp->last_ril_status = PDP_FAIL_ERROR_UNSPECIFIED; |
| pdp->nw_status = DATA_PENDING; |
| goto done; |
| } else if (ret) { |
| goto err; |
| } |
| |
| switch (ril_err) { |
| case RIL_E_SUCCESS: |
| break; |
| case RIL_E_OP_NOT_ALLOWED_BEFORE_REG_TO_NW: |
| pdp->last_ril_status = PDP_FAIL_DATA_REGISTRATION_FAIL; |
| pdp->nw_status = DATA_PENDING; |
| goto done; |
| case RIL_E_RADIO_NOT_AVAILABLE: |
| pdp->last_ril_status = PDP_FAIL_RADIO_POWER_OFF; |
| pdp->nw_status = DATA_PENDING; |
| goto done; |
| default: |
| pdp->last_ril_status = PDP_FAIL_ERROR_UNSPECIFIED; |
| pdp->nw_status = DATA_UNAVAILABLE; |
| goto done; |
| } |
| |
| pdp->last_ril_status = rsp.status; |
| |
| if (rsp.status == PDP_FAIL_NONE) { |
| chl_db_update_pdp(&rsp, pdp, DATA_AVAILABLE); |
| goto done; |
| } |
| |
| if (rsp.suggestedRetryTime == INT_MAX || |
| rsp.suggestedRetryTime == -1) { |
| CHL_ERR("pdp act failed with [%d]\n", rsp.status); |
| pdp->nw_status = DATA_UNAVAILABLE; |
| goto done; |
| } else if (rsp.suggestedRetryTime) { |
| CHL_ERR("pdp act failed with timeout [%d], err [%s]\n", |
| rsp.suggestedRetryTime, |
| data_call_fail_to_str(rsp.status)); |
| pdp->nw_status = DATA_PENDING; |
| goto done; |
| } else { |
| |
| CHL_ERR("Retrying to open pdp [%s] immediatly\n", pdp->apn); |
| continue; |
| } |
| } |
| pdp->nw_status = DATA_UNAVAILABLE; |
| done: |
| return 0; |
| err: |
| CHL_ERR("chl_handle_data_call failed\n"); |
| return -1; |
| } |
| |
| static int chl_unconfig_gw(struct chl_pdp *pdp) |
| { |
| struct uci_context *c = NULL; |
| |
| CHL_INFO("Unconfiguring Gateway for [%s]\n", pdp->apn); |
| |
| if (chl_uci_init(&c)) |
| goto err; |
| |
| if (chl_uci_del_gw(c, pdp)) |
| goto err; |
| |
| if (pdp->ipv6_state == CHL_IPV6_FOUND) |
| chl_unconfig_ipv6(c, pdp); |
| |
| chl_uci_done(c, true); |
| |
| chl_network_reload(); |
| |
| return 0; |
| err: |
| CHL_ERR("chl_unconfig_gw failed for [%d]\n", pdp->cid); |
| chl_uci_done(c, false); |
| return -1; |
| } |
| |
| static int chl_config_gw(struct chl_pdp *pdp) |
| { |
| struct uci_context *c = NULL; |
| |
| CHL_INFO("Configuring Gateway for [%s]\n", pdp->apn); |
| |
| if (chl_uci_init(&c)) |
| goto err; |
| |
| if (chl_uci_add_gw(c, pdp)) |
| goto err; |
| |
| if (pdp->type != IPV4) |
| chl_poll_ipv6_start(pdp); |
| |
| chl_uci_done(c, true); |
| |
| chl_network_reload(); |
| |
| return 0; |
| err: |
| CHL_ERR("chl_config_gw failed for [%d]\n", pdp->cid); |
| chl_uci_done(c, false); |
| return -1; |
| } |
| |
| static int chl_find_new_gw(struct uci_context *c, struct chl_pdp *except) |
| { |
| struct chl_pdp *pdp; |
| |
| CHL_INFO("Looking for new gw\n"); |
| |
| pdp = chl_db_get_autoconf_pdp(except); |
| |
| if (!pdp) |
| return 0; |
| |
| CHL_INFO("found new gw [%s]\n", pdp->apn); |
| |
| if (chl_uci_add_gw(c, pdp)) |
| goto err; |
| |
| if (pdp->type != IPV4) |
| chl_poll_ipv6_start(pdp); |
| |
| return 0; |
| err: |
| CHL_ERR("chl_find_new_gw failed\n"); |
| return -1; |
| } |
| |
| static int chl_unconfig_pdp(struct chl_pdp *pdp) |
| { |
| struct uci_context *c = NULL; |
| |
| CHL_INFO("Unconfiguring pdp, apn [%s]\n", pdp->apn); |
| |
| if (chl_uci_init(&c)) |
| goto err; |
| |
| if (pdp->autoconf_pdp && pdp->req_gw) |
| chl_find_new_gw(c, pdp); |
| |
| chl_uci_del_addr(c, pdp); |
| |
| chl_uci_del_dns(c,pdp); |
| |
| chl_uci_del_dns_route(c, pdp); |
| |
| if (pdp->req_gw && chl_uci_del_gw(c, pdp)) |
| goto err; |
| |
| if (pdp->req_gw && pdp->ipv6_state == CHL_IPV6_FOUND) |
| chl_unconfig_ipv6(c, pdp); |
| |
| chl_uci_del_mask(c, pdp); |
| |
| chl_uci_done(c, true); |
| |
| chl_network_ifdown(pdp); |
| |
| chl_network_reload(); |
| |
| return 0; |
| err: |
| CHL_ERR("chl_unconfig_pdp failed for [%d]\n", pdp->cid); |
| chl_uci_done(c, false); |
| return -1; |
| } |
| |
| static int chl_config_pdp(struct chl_pdp *pdp) |
| { |
| struct uci_context *c = NULL; |
| |
| CHL_INFO("Configuring pdp, apn [%s]\n", pdp->apn); |
| |
| if (chl_uci_init(&c)) |
| goto err; |
| |
| if (chl_uci_add_addr(c, pdp)) |
| goto err; |
| |
| if (chl_uci_add_dns(c, pdp)) |
| goto err; |
| |
| if (chl_uci_add_dns_route(c, pdp)) |
| goto err; |
| |
| if (pdp->req_gw && chl_uci_add_gw(c, pdp)) |
| goto err; |
| |
| if (pdp->req_gw && pdp->type != IPV4) |
| chl_poll_ipv6_start(pdp); |
| |
| if (chl_uci_add_mask(c, pdp)) |
| goto err; |
| |
| chl_uci_done(c, true); |
| |
| if (chl_network_reload()) |
| goto err; |
| |
| chl_network_ifup(pdp); |
| |
| return 0; |
| err: |
| CHL_ERR("chl_config_pdp failed for [%d]\n", pdp->cid); |
| chl_uci_done(c, false); |
| return -1; |
| |
| } |
| |
| void chl_close(int id, struct chl_response *rsp) |
| { |
| int org_nw_status; |
| struct chl_client *cl; |
| struct chl_pdp *pdp; |
| |
| |
| cl = chl_db_get_client_by_id(id); |
| if (!cl) { |
| rsp->req_status = CHL_ID_NOT_FOUND; |
| CHL_ERR("failed to get client [%d]\n", id); |
| return; |
| } |
| |
| CHL_INFO("Closing client [%s]\n", cl->name); |
| |
| pdp = cl->pdp; |
| org_nw_status = pdp->nw_status; |
| |
| if (!cl->req_open) |
| goto done; |
| |
| cl->req_open = false; |
| pdp->open_count--; |
| |
| if (pdp->autoconf_pdp) |
| goto done; |
| |
| if (pdp->nw_status != DATA_AVAILABLE) |
| goto clear; |
| |
| if (pdp->open_count) { |
| if (cl->req_gw) |
| chl_unconfig_gw(pdp); |
| goto done; |
| } |
| |
| if (chl_handle_pdp_deactivation(pdp)) |
| goto err; |
| |
| if (chl_unconfig_pdp(pdp)) |
| goto err; |
| clear: |
| chl_db_clear_pdp(pdp, DATA_UNAVAILABLE); |
| done: |
| if (cl->req_gw) |
| pdp->req_gw = false; |
| |
| rsp->nw_status = pdp->nw_status; |
| if (org_nw_status != pdp->nw_status) |
| chl_send_pdp_ind(pdp, cl); |
| return; |
| err: |
| rsp->req_status = CHL_INTERNAL_ERROR; |
| CHL_ERR("chl_close failed for [%s]\n", cl->name); |
| chl_db_status(); |
| } |
| |
| static inline int chl_type_conflict(struct chl_pdp *pdp, struct chl_client *cl) |
| { |
| if (!cl->type) |
| return 0; |
| if (pdp->type == cl->type) |
| return 0; |
| if (!(pdp->type & cl->type)) |
| return 1; |
| if (pdp->type == IPV4V6) |
| return 1; |
| return 0; |
| } |
| |
| static int chl_handle_type_conflict(struct chl_pdp *pdp, struct chl_client *cl) |
| { |
| |
| CHL_INFO("Ip type conflict for [%s] - [%s] != [%s]\n", cl->name, |
| ip_type_to_str(cl->type), ip_type_to_str(pdp->type)); |
| |
| if (pdp->nw_status != DATA_AVAILABLE) |
| return 0; |
| |
| if (chl_handle_pdp_deactivation(pdp)) |
| goto err; |
| |
| if (chl_unconfig_pdp(pdp)) |
| goto err; |
| |
| pdp->type = cl->type; |
| |
| chl_db_clear_pdp(pdp, DATA_UNAVAILABLE); |
| |
| return 0; |
| |
| err: |
| CHL_ERR("chl_close failed for [%s]\n", cl->name); |
| chl_db_status(); |
| return -1; |
| } |
| |
| void chl_open(int id, struct chl_response *rsp) |
| { |
| int org_nw_status; |
| struct chl_client *cl; |
| struct chl_pdp *pdp; |
| |
| cl = chl_db_get_client_by_id(id); |
| if (!cl) { |
| rsp->req_status = CHL_ID_NOT_FOUND; |
| CHL_ERR("failed to get client [%d]\n", id); |
| return; |
| } |
| |
| CHL_INFO("Opening client [%s]\n", cl->name); |
| |
| pdp = cl->pdp; |
| org_nw_status = pdp->nw_status; |
| |
| if (cl->req_open) |
| goto done; |
| |
| cl->req_open = true; |
| pdp->open_count++; |
| |
| /* if ip types conflict, last one to open decides on type */ |
| if (chl_type_conflict(pdp, cl)) { |
| if (chl_handle_type_conflict(pdp, cl)) |
| goto err; |
| } else if (pdp->autoconf_pdp) { |
| goto done; |
| } else if (pdp->open_count > 1) { |
| if (cl->req_gw) { |
| pdp->req_gw = true; |
| if (pdp->nw_status == DATA_AVAILABLE) |
| chl_config_gw(pdp); |
| } |
| goto done; |
| } |
| |
| if (cl->req_gw) |
| pdp->req_gw = true; |
| |
| if (chl_handle_pdp_activation(pdp)) |
| goto err; |
| |
| if (pdp->nw_status != DATA_AVAILABLE) { |
| if (pdp->nw_status == DATA_UNAVAILABLE) |
| rsp->req_status = RIL_ERROR_OCCURED; |
| goto done; |
| } |
| |
| if (chl_config_pdp(pdp)) |
| goto err; |
| done: |
| rsp->ril_status = pdp->last_ril_status; |
| rsp->nw_status = pdp->nw_status; |
| if (org_nw_status != pdp->nw_status) |
| chl_send_pdp_ind(pdp, cl); |
| return; |
| err: |
| rsp->req_status = CHL_INTERNAL_ERROR; |
| CHL_ERR("chl_open failed for [%s]\n", cl->name); |
| chl_db_status(); |
| } |
| |
| static void chl_retry_open_pdp(struct chl_pdp *pdp, enum chl_nw_status org) |
| { |
| CHL_INFO("Retrying pdp [%s]\n", pdp->apn); |
| |
| if (chl_handle_pdp_activation(pdp)) { |
| CHL_ERR("chl_handle_pdp_activation failed for cid [%d]\n", |
| pdp->cid); |
| return; |
| } |
| |
| if (pdp->nw_status != DATA_AVAILABLE) { |
| CHL_INFO("pdp open failed, retry later\n"); |
| goto done; |
| } |
| if (chl_config_pdp(pdp)) { |
| CHL_ERR("chl_config_pdp failed for cid [%d]\n", pdp->cid); |
| return; |
| } |
| |
| done: |
| if (pdp->nw_status != org) |
| chl_send_pdp_ind(pdp, NULL); |
| } |
| |
| void chl_get_status(int id, struct chl_response *rsp) |
| { |
| struct chl_client *cl; |
| struct chl_pdp *pdp; |
| |
| cl = chl_db_get_client_by_id(id); |
| if (!cl) { |
| CHL_ERR("failed to get client [%d]\n", id); |
| rsp->req_status = CHL_ID_NOT_FOUND; |
| return; |
| } |
| |
| pdp = cl->pdp; |
| |
| rsp->nw_status = pdp->nw_status; |
| |
| if (pdp->nw_status == DATA_AVAILABLE) { |
| if (pdp->type & IPV4) |
| rsp->ipv4 = pdp->ipv4; |
| if ((pdp->type & IPV6) && (pdp->ipv6_state == CHL_IPV6_FOUND)) |
| rsp->ipv6 = pdp->gb6addr; |
| } |
| } |
| |
| void chl_unregister(int id, struct chl_response *rsp) |
| { |
| struct chl_client *cl; |
| struct chl_pdp *pdp; |
| struct chl_response close_rsp; |
| |
| memset(&close_rsp, 0, sizeof (struct chl_response)); |
| cl = chl_db_get_client_by_id(id); |
| if (!cl) { |
| rsp->req_status = CHL_ID_NOT_FOUND; |
| CHL_ERR("failed to get client [%d]\n", id); |
| return; |
| } |
| |
| CHL_INFO("Unregistering client [%s]\n", cl->name); |
| |
| chl_close(id, &close_rsp); |
| |
| if (close_rsp.req_status) |
| goto err; |
| |
| pdp = cl->pdp; |
| |
| |
| if (cl->req_gw && chl_is_internet(pdp)) |
| chl_del_internet(); |
| |
| if (cl->rt_idx) |
| chl_del_all_routes(cl); |
| |
| chl_db_del_client(cl); |
| |
| if (!pdp->client_count) { |
| chl_db_del_pdp(pdp); |
| rsp->nw_status = DATA_UNAVAILABLE; |
| return; |
| } |
| |
| rsp->nw_status = pdp->nw_status; |
| return; |
| err: |
| rsp->req_status = close_rsp.req_status; |
| CHL_ERR("chl_unregister failed for [%s]\n", cl->name); |
| } |
| |
| void chl_share_internet(struct reg_param *rp, struct chl_reg_response *rsp) |
| { |
| struct chl_client *cl; |
| struct chl_pdp *internet; |
| |
| CHL_INFO("Sharing internet for [%s]\n", rp->name); |
| |
| internet = chl_get_internet(); |
| |
| if (!internet || internet->nw_status != DATA_AVAILABLE) { |
| rsp->req_status = PDP_UNAVAILABLE; |
| CHL_INFO("data unavailable for [%s]\n", rp->name); |
| return; |
| } |
| |
| cl = chl_db_add_client(rp, internet); |
| if (!cl) { |
| rsp->req_status = CHL_REG_INTERNAL_ERROR; |
| CHL_ERR("chl_db_add_client failed\n"); |
| return; |
| } |
| |
| rsp->id = cl->id; |
| return; |
| } |
| |
| static struct chl_client *chl_reg_exists(struct chl_pdp *pdp, |
| struct reg_param *rp, |
| struct chl_reg_response *rsp) |
| { |
| enum chl_reg_req_status conflict; |
| struct chl_client *cl; |
| |
| /* |
| * pdp already in database. check for extra param conflict, and |
| * add the client. |
| */ |
| |
| if ((conflict = pdp_match_ext_param(rp, pdp))) { |
| CHL_ERR("reg request conflicts with [%s], conflict [%d]\n", |
| rp->apn, conflict); |
| rsp->req_status = conflict; |
| goto err; |
| } |
| |
| cl = chl_db_add_client(rp, pdp); |
| if (!cl) { |
| rsp->req_status = CHL_REG_INTERNAL_ERROR; |
| goto err; |
| } |
| |
| memcpy(&pdp->extra, rp->ext, sizeof(struct extra_param)); |
| |
| if (pdp->untracked) |
| chl_db_take_over_pdp(pdp); |
| |
| return cl; |
| err: |
| CHL_ERR("chl_reg_gw failed for [%s]\n", rp->apn); |
| return NULL; |
| } |
| |
| static struct chl_client *chl_reg_create(struct reg_param *rp, |
| struct chl_reg_response *rsp) |
| { |
| struct chl_pdp *pdp; |
| struct chl_client *cl; |
| |
| /* APN does not exist in database. add pdp and then add the client */ |
| |
| pdp = chl_db_add_pdp(rp); |
| if (!pdp) { |
| rsp->req_status = CHL_REG_INTERNAL_ERROR; |
| goto err; |
| } |
| |
| cl = chl_db_add_client(rp, pdp); |
| if (!cl) { |
| rsp->req_status = CHL_REG_INTERNAL_ERROR; |
| chl_db_del_pdp(pdp); |
| goto err; |
| } |
| |
| return cl; |
| err: |
| CHL_ERR("chl_reg_create failed for [%s]\n", rp->apn); |
| return NULL; |
| } |
| |
| void chl_register(struct reg_param *rp, struct chl_reg_response *rsp) |
| { |
| struct chl_pdp *pdp = NULL; |
| struct chl_client *cl = NULL; |
| struct chl_conf *conf = chl_get_conf(); |
| |
| CHL_INFO("Register apn [%s], ip type [%s]\n", rp->apn, |
| ip_type_to_str(rp->ip_type)); |
| |
| if (rp->internet && conf->autoconf) { |
| rsp->req_status = CHL_REG_INVALID_REQ; |
| CHL_WARN("cannot request internet pdp in autoconf"); |
| goto err; |
| } |
| |
| if (rp->internet && chl_has_internet()) { |
| rsp->req_status = GW_EXISTS; |
| CHL_WARN("internet pdp already taken\n"); |
| goto err; |
| } |
| |
| pdp = chl_db_get_pdp_by_apn(rp->apn); |
| if (!pdp) |
| cl = chl_reg_create(rp, rsp); |
| else |
| cl = chl_reg_exists(pdp, rp, rsp); |
| |
| if (!cl) |
| goto err; |
| |
| pdp = cl->pdp; |
| |
| if (rp->internet) |
| chl_set_internet(pdp); |
| |
| rsp->id = cl->id; |
| return; |
| err: |
| CHL_ERR("chl_register failed for [%s]\n", rp->apn); |
| return; |
| } |
| |
| void chl_set_lte_default(struct reg_param *rp, struct chl_response *rsp) |
| { |
| int ril_err = 0; |
| |
| if (chl_ril_set_lte_dflt(rp, &ril_err)) |
| rsp->req_status = CHL_INTERNAL_ERROR; |
| else if (ril_err) |
| rsp->req_status = LTE_DEFAULT_REQ_FAIL; |
| |
| /* RIL_REQUEST_DATA_REGISTRATION_STATE here*/ |
| } |
| |
| static void chl_handle_autoconf_inactive(struct chl_pdp *pdp) |
| { |
| CHL_INFO("handle_autoconf_inactive, apn [%s]\n", pdp->apn); |
| |
| chl_unconfig_pdp(pdp); |
| |
| if (chl_is_internet(pdp)) |
| chl_del_internet(); |
| |
| chl_db_clear_pdp(pdp, DATA_UNAVAILABLE); |
| |
| pdp->autoconf_pdp = false; |
| pdp->req_gw = false; |
| |
| if (pdp->open_count) |
| chl_retry_open_pdp(pdp, DATA_AVAILABLE); |
| else if (pdp->client_count) |
| chl_send_pdp_ind(pdp, NULL); |
| else |
| chl_db_del_pdp(pdp); |
| } |
| |
| static void chl_handle_untracked_inactive(struct chl_pdp *pdp) |
| { |
| |
| CHL_INFO("handle_untracked_inactive pdp, apn [%s]\n", pdp->apn); |
| |
| chl_db_del_pdp(pdp); |
| } |
| |
| static void chl_handle_inactive(struct chl_pdp *pdp) |
| { |
| CHL_INFO("handle_inactive pdp, apn [%s]\n", pdp->apn); |
| |
| if (pdp->untracked) |
| return chl_handle_untracked_inactive(pdp); |
| |
| if (pdp->autoconf_pdp) |
| return chl_handle_autoconf_inactive(pdp); |
| |
| if (pdp->nw_status != DATA_AVAILABLE) { |
| CHL_INFO("pdp already inactive [%s], cid [%d]\n",pdp->apn, |
| pdp->cid); |
| chl_db_clear_pdp(pdp, DATA_UNAVAILABLE); |
| return; |
| } |
| |
| chl_unconfig_pdp(pdp); |
| |
| chl_db_clear_pdp(pdp, DATA_UNAVAILABLE); |
| |
| /* if pdp is still requested - retry open */ |
| if (pdp->open_count) |
| chl_retry_open_pdp(pdp, DATA_AVAILABLE); |
| else |
| chl_send_pdp_ind(pdp, NULL); |
| |
| return; |
| } |
| |
| static void chl_handle_autoconf_active(Ubus_Data_Call_Response *rsp, |
| struct chl_pdp *pdp) |
| { |
| struct reg_param rp; |
| |
| CHL_INFO("handle_autoconf_active pdp, apn [%s]\n", rsp->apn); |
| |
| if (pdp) |
| goto config; |
| |
| memset(&rp, 0, sizeof(struct reg_param)); |
| |
| rp.apn = rsp->apn; |
| rp.ip_type = chl_ril_type_to_int(rsp->type); |
| |
| pdp = chl_db_add_pdp(&rp); |
| if (!pdp) { |
| CHL_ERR("failed to add pdp for cid [%d]\n", rsp->cid); |
| return; |
| } |
| config: |
| chl_db_update_pdp(rsp, pdp, DATA_AVAILABLE); |
| |
| /* in autoconf - select default gw according to FIFO */ |
| if (!chl_has_internet()) |
| chl_set_internet(pdp); |
| |
| pdp->req_gw = true; |
| |
| if (chl_config_pdp(pdp)) { |
| CHL_ERR("failed to config pdp [%s]\n", rsp->apn); |
| return; |
| } |
| |
| pdp->autoconf_pdp = true; |
| |
| if (pdp->client_count) |
| chl_send_pdp_ind(pdp, NULL); |
| |
| } |
| |
| static void chl_handle_untracked_active(Ubus_Data_Call_Response *rsp) |
| { |
| struct chl_pdp *pdp; |
| struct reg_param rp; |
| |
| CHL_INFO("handle_untracked_active pdp, apn [%s]\n", rsp->apn); |
| |
| memset(&rp, 0, sizeof(struct reg_param)); |
| |
| rp.apn = rsp->apn; |
| rp.ip_type = chl_ril_type_to_int(rsp->type); |
| |
| pdp = chl_db_add_pdp(&rp); |
| if (!pdp) { |
| CHL_ERR("failed to add pdp for cid [%d]\n", rsp->cid); |
| return; |
| } |
| |
| pdp->untracked = true; |
| chl_db_update_pdp(rsp, pdp, DATA_UNAVAILABLE); |
| } |
| |
| static void chl_handle_active(Ubus_Data_Call_Response *rsp) |
| { |
| struct chl_pdp *pdp; |
| struct chl_conf *conf = chl_get_conf(); |
| |
| CHL_INFO("handle_active pdp, apn [%s]\n", rsp->apn); |
| |
| /* pdp has a matching CID - it is active in database*/ |
| pdp = chl_db_get_pdp_by_cid(rsp->cid); |
| if (pdp) { |
| /* pdp changed but same cid - handle new cid here */ |
| if (!str_starts_with(pdp->apn, rsp->apn)) { |
| CHL_INFO("PDP info changed! reconfigure\n"); |
| goto check_apn; |
| } |
| |
| if (pdp->untracked) { |
| CHL_INFO("untracked pdp is active again! [%s]\n", |
| pdp->apn); |
| return; |
| } |
| |
| |
| CHL_INFO("apn [%s] was already opened by CHL\n", pdp->apn); |
| |
| /* copy the apn in case a suffix was added by network */ |
| strcpy(pdp->apn, rsp->apn); |
| return; |
| } |
| |
| /* pdp exists in db but not active - it was registered by is not |
| * currently open |
| */ |
| check_apn: |
| pdp = chl_db_get_pdp_by_apn(rsp->apn); |
| if (pdp) { |
| CHL_INFO("[%s] pdp activated!\n", pdp->apn); |
| if (pdp->open_count) { |
| chl_db_update_pdp(rsp, pdp, DATA_AVAILABLE); |
| chl_config_pdp(pdp); |
| chl_send_pdp_ind(pdp, NULL); |
| } else { |
| if (conf->autoconf) { |
| chl_handle_autoconf_active(rsp, pdp); |
| } else { |
| chl_db_update_pdp(rsp, pdp, DATA_UNAVAILABLE); |
| chl_db_take_over_pdp(pdp); |
| } |
| } |
| return; |
| } |
| |
| /* APN is not in database - create new untracked/autoconf pdp*/ |
| |
| if (conf->autoconf) |
| chl_handle_autoconf_active(rsp, NULL); |
| else |
| chl_handle_untracked_active(rsp); |
| } |
| |
| void chl_handle_dcl_changed(Ubus_Data_Call_Response *pdps, int num) |
| { |
| int i; |
| bool found; |
| |
| struct list_head *db; |
| struct chl_pdp *pdp, *iter; |
| |
| if(pdps == NULL || num == 0)
{ |
| CHL_INFO("num:%d\n",num); |
| return; |
| } |
| |
| for (i = 0; i < num; i++) { |
| if (pdps[i].status == PDP_FAIL_NONE) |
| chl_handle_active(&pdps[i]); |
| |
| } |
| |
| db = chl_db_get_list(); |
| list_for_each_entry_safe(pdp, iter, db, list) { |
| |
| found = false; |
| /* look for all pdps that are active in CHL but not in dcl */ |
| for (i = 0; i < num; i++) { |
| |
| if (str_starts_with(pdp->apn, pdps[i].apn)) |
| found = true; |
| } |
| |
| if (!found) |
| chl_handle_inactive(pdp); |
| } |
| } |
| |
| void chl_close_all(void) |
| { |
| struct chl_pdp *pdp; |
| struct list_head *db = chl_db_get_list(); |
| |
| CHL_INFO("Closing all clients\n"); |
| |
| list_for_each_entry(pdp, db, list) { |
| if (pdp->nw_status != DATA_AVAILABLE) |
| continue; |
| chl_handle_inactive(pdp); |
| } |
| } |
| |
| void chl_retry_open_all(void) |
| { |
| struct chl_pdp *pdp; |
| struct list_head *db = chl_db_get_list(); |
| |
| list_for_each_entry(pdp, db, list) { |
| if (pdp->nw_status == DATA_PENDING) |
| chl_retry_open_pdp(pdp, DATA_PENDING); |
| } |
| } |
| |
| void chl_add_del_spec_route(int id, char *ip, bool add, |
| struct chl_response *rsp) |
| { |
| struct chl_client *cl; |
| struct uci_context *c = NULL; |
| |
| cl = chl_db_get_client_by_id(id); |
| if(!cl) { |
| rsp->req_status = CHL_ID_NOT_FOUND; |
| CHL_ERR("failed to get client [%d]\n", id); |
| return; |
| } |
| |
| CHL_INFO("[%s] route for [%s]\n", add ? "Adding" : "Deleting", cl->name); |
| |
| if (add && cl->pdp->nw_status != DATA_AVAILABLE) { |
| CHL_ERR("cannot set route of on a closed PDP\n"); |
| rsp->req_status = CHL_INVALID_REQ; |
| return; |
| } |
| |
| if (chl_uci_init(&c)) |
| goto err; |
| |
| |
| if (add && chl_uci_add_spec_route(c, cl, ip)) |
| goto err; |
| if (!add && chl_uci_del_spec_route(c, cl, ip)) |
| goto err; |
| |
| chl_uci_done(c, true); |
| |
| chl_network_reload(); |
| |
| rsp->nw_status = cl->pdp->nw_status; |
| return; |
| |
| err: |
| rsp->req_status = CHL_INTERNAL_ERROR; |
| CHL_ERR("chl_add_del_spec_route failed for [%s]\n", cl->name); |
| chl_uci_done(c, false); |
| } |
| |
| int main(int argc, char *argv[]) |
| { |
| int c = 0; |
| int ret = 0; |
| struct chl_conf *conf = chl_get_conf(); |
| |
| if (argc > 2) { |
| CHL_ERR("invalid argument\n"); |
| exit(1); |
| } |
| |
| set_service_log_level(8); |
| |
| do { |
| c = getopt(argc, argv, "d"); |
| if (c == EOF) |
| break; |
| switch (c) { |
| case 'd': |
| set_service_log_level(ANDROID_LOG_DEBUG); |
| break; |
| } |
| } while (1); |
| |
| conf->autoconf = ml_get_property("persist.chl.autoconf"); |
| |
| set_service_log_tag("chl"); |
| |
| CHL_INFO("starting CHL with aoutoconf [%d]\n", conf->autoconf); |
| |
| if ((ret = chl_ubus_init())) { |
| CHL_ERR("Failed to init ubus. ret=[%d]\n", ret); |
| exit(1); |
| } |
| |
| if (chl_network_register()) { |
| CHL_ERR("Failed to register to network\n"); |
| exit(1); |
| } |
| |
| if (chl_ril_register()) { |
| CHL_ERR("Failed to register to RIL\n"); |
| exit(1); |
| } |
| |
| if ((ret = chl_ril_enable_indications())) { |
| CHL_ERR("Failed to enable RIL indications. ret=[%s]\n", |
| ubus_strerror(ret)); |
| exit(1); |
| } |
| |
| if (chl_wait_for_sim()) { |
| CHL_ERR("sim not ready\n"); |
| exit(1); |
| } |
| |
| chl_ril_request_ps_status(); |
| |
| chl_ril_request_dcl(); |
| |
| chl_ubus_run(); |
| |
| chl_ril_disable_indications(); |
| chl_ubus_exit(); |
| |
| return 0; |
| } |