blob: 7c019dd78a4a132c270a5787810ae9adb1607bbb [file] [log] [blame]
/******************************************************************************
*(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;
}