Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 98 additions & 0 deletions application/qmodem/files/usr/share/qmodem/auto_apn.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
#!/bin/sh

# Automatic APN selection shared by MBIM and QMI dial paths.
#
# The resolver is deliberately best-effort: callers keep their original APN
# and credentials when the modem, database, or parser is unavailable.

QMODEM_APN_DATABASE="${QMODEM_APN_DATABASE:-/usr/share/qmodem/apns.json}"

qmodem_apn_is_auto()
{
[ -z "${1:-}" ] || [ "$1" = "auto" ]
}

qmodem_extract_imsi()
{
awk '
{
candidate = $0
gsub(/\r/, "", candidate)
gsub(/[^0-9]/, "", candidate)
if (length(candidate) >= 14 && length(candidate) <= 16) {
print candidate
exit
}
}
'
}

qmodem_lookup_apn_record()
{
local imsi="$1"
local database="${2:-$QMODEM_APN_DATABASE}"
local mcc mnc2 mnc3

[ -r "$database" ] || return 1
command -v jq >/dev/null 2>&1 || return 1

mcc=$(printf '%s' "$imsi" | cut -c1-3)
mnc3=$(printf '%s' "$imsi" | cut -c4-6)
mnc2=$(printf '%s' "$imsi" | cut -c4-5)
[ "${#mcc}" -eq 3 ] && [ "${#mnc3}" -eq 3 ] &&
[ "${#mnc2}" -eq 2 ] || return 1

# Keep the lookup order compatible with the former C implementation:
# first try the three-digit MNC, then retry using its first two digits.
# In each pass the first database entry containing "default" wins.
jq -ce --arg mcc "$mcc" --arg mnc3 "$mnc3" --arg mnc2 "$mnc2" '
def candidates:
((.apns.apn? // [])[]?
| select(type == "object")
| ((._mcc? // "") | tostring) as $entry_mcc
| ((._mnc? // "") | tostring) as $entry_mnc
| ((._type? // "") | tostring) as $entry_type
| select($entry_mcc == $mcc)
| select($entry_type | contains("default"))
| { record: ., mnc: $entry_mnc });

([candidates | select(.mnc == $mnc3)][0].record //
[candidates | select((.mnc | length) >= 2 and
(.mnc | startswith($mnc2)))][0].record //
empty)
' "$database" 2>/dev/null
}

qmodem_resolve_auto_apn()
{
local port="$1"
local database="${2:-$QMODEM_APN_DATABASE}"
local response imsi record selected_apn selected_user selected_password

qmodem_apn_is_auto "${apn:-}" || return 1

response=$(cmd_dial_cimi_query "$port" 2>/dev/null) || return 1
imsi=$(printf '%s\n' "$response" | qmodem_extract_imsi) || return 1
[ -n "$imsi" ] || return 1

record=$(qmodem_lookup_apn_record "$imsi" "$database") || return 1
[ -n "$record" ] || return 1

selected_apn=$(printf '%s\n' "$record" |
jq -er '._apn | select(type == "string" and length > 0)' 2>/dev/null) || return 1
selected_user=$(printf '%s\n' "$record" |
jq -r 'if (._user | type) == "string" then ._user else "" end' 2>/dev/null) || return 1
selected_password=$(printf '%s\n' "$record" |
jq -r 'if (._password | type) == "string" then ._password else "" end' 2>/dev/null) || return 1

# Commit results only after every required step succeeds. This keeps all
# failure paths atomic and preserves the protocol's original dial flow.
apn="$selected_apn"
if [ -z "${username:-}" ] && [ -z "${password:-}" ] &&
[ -n "$selected_user" ] && [ -n "$selected_password" ]; then
username="$selected_user"
password="$selected_password"
fi

return 0
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
cmd_dial_command() { at "$1" "$2"; }
cmd_dial_cpin_unlock() { at "$1" "AT+CPIN=\"$2\""; }
cmd_dial_cpin_query() { at "$1" 'AT+CPIN?'; }
cmd_dial_cimi_query() { at "$1" 'AT+CIMI'; }
cmd_dial_neoway_simcross_iccid() { at "$1" 'AT+SIMCROSS=1,1;$MYCCID'; }
cmd_dial_cfun_enable() { at "$1" 'AT+CFUN=1'; }
cmd_dial_cgpaddr() { at "$1" "AT+CGPADDR=$2"; }
Expand Down
9 changes: 9 additions & 0 deletions application/qmodem/files/usr/share/qmodem/modem_dial.sh
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ log_file="${MODEM_RUNDIR}/${modem_config}_dir/dial_log"
debug_subject="modem_dial"
source "${SCRIPT_DIR}/generic.sh"
source "${SCRIPT_DIR}/cmds/modem_dial.sh"
source "${SCRIPT_DIR}/auto_apn.sh"
touch $log_file

exec_pre_dial()
Expand Down Expand Up @@ -1115,6 +1116,14 @@ mhi_dial()

qmi_dial()
{
if qmodem_apn_is_auto "${apn:-}"; then
if qmodem_resolve_auto_apn "$at_port"; then
m_debug "automatic APN selected: $apn"
else
m_debug "automatic APN selection unavailable, continuing with original dial flow"
fi
fi

cmd_line="quectel-CM"
[ -e "/usr/bin/quectel-CM-M" ] && cmd_line="quectel-CM-M" && tom_modified=1
case $pdp_type in
Expand Down
1 change: 0 additions & 1 deletion application/quectel_CM_5G_M/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ define Package/$(PKG_NAME)
SECTION:=utils
CATEGORY:=Utilities
TITLE:=quectel-CM-5G app building test with metric option ( -M )
DEPENDS:=+libjson-c
endef

define Package/$(PKG_NAME)/description
Expand Down
2 changes: 1 addition & 1 deletion application/quectel_CM_5G_M/src/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ endif
CFLAGS += -Wall -Wextra -Werror -O1 #-s
# Allow incompatible-pointer-types, unused-result, and stringop-overflow warnings for vendor code compatibility
CFLAGS += -Wno-error=incompatible-pointer-types -Wno-error=unused-result -Wno-error=stringop-overflow
LDFLAGS += -lpthread -ldl -lrt -ljson-c
LDFLAGS += -lpthread -ldl -lrt

release: clean qmi-proxy mbim-proxy atc-proxy #qrtr-proxy
$(CC) ${CFLAGS} ${QL_CM_SRC} ${QL_CM_DHCP} -o quectel-CM ${LDFLAGS}
Expand Down
156 changes: 0 additions & 156 deletions application/quectel_CM_5G_M/src/mbim-cm.c
Original file line number Diff line number Diff line change
Expand Up @@ -2313,166 +2313,10 @@ static int requestRegistrationState(UCHAR *pPSAttachedState) {
return retval;
}

#include <json-c/json.h>
static int mbim_get_imsi(char *imsi_buf, size_t buf_len) {
MBIM_MESSAGE_HEADER *pRequest = NULL;
MBIM_COMMAND_DONE_T *pCmdDone = NULL;
int err;

// Clear the output buffer first
if (buf_len > 0) {
imsi_buf[0] = '\0';
}

mbim_debug("%s()", __func__);

pRequest = compose_basic_connect_command(MBIM_CID_SUBSCRIBER_READY_STATUS, MBIM_CID_CMD_TYPE_QUERY, NULL, 0);
if (!pRequest) {
return -ENOMEM;
}

err = mbim_send_command(pRequest, &pCmdDone, mbim_default_timeout);

if (err || !pCmdDone || le32toh(pCmdDone->Status) != MBIM_STATUS_SUCCESS) {
mbim_debug("%s failed: send_command err=%d, response status=%d\n", __func__, err,
pCmdDone ? le32toh(pCmdDone->Status) : -1);
mbim_free(pRequest);
mbim_free(pCmdDone);
return (err != 0) ? err : -1;
}

if (le32toh(pCmdDone->InformationBufferLength)) {
MBIM_SUBSCRIBER_READY_STATUS_T *pInfo = (MBIM_SUBSCRIBER_READY_STATUS_T *)pCmdDone->InformationBuffer;

if (le32toh(pInfo->SubscriberIdSize) > 0) {
wchar2char((const char *)pInfo + le32toh(pInfo->SubscriberIdOffset),
le32toh(pInfo->SubscriberIdSize),
imsi_buf,
buf_len);
mbim_debug("IMSI successfully retrieved: %s", imsi_buf);
}
}

mbim_free(pRequest);
mbim_free(pCmdDone);
if (imsi_buf[0] == '\0') {
return -1;
}

return 0;
}

static int GetAPNConfig(PROFILE_T *profile, const char *json_path)
{
mbim_debug("GetAPNConfig");
if (!json_path) return -1;

char imsi[32];
int result = mbim_get_imsi(imsi, sizeof(imsi));
if (result != 0) {
mbim_debug("Failed to retrieve IMSI. Error code: %d", result);
return -1;
}
mbim_debug("Successfully retrieved IMSI: %s", imsi);
char sim_mcc[4] = {0};
char sim_mnc3[4] = {0};
char sim_mnc2[3] = {0};

strncpy(sim_mcc, imsi, 3);
strncpy(sim_mnc3, imsi + 3, 3);
strncpy(sim_mnc2, imsi + 3, 2);

FILE *fp = fopen(json_path, "r");
if (!fp) {
mbim_debug("Failed to open JSON file: %s", json_path);
return -1;
}

fseek(fp, 0, SEEK_END);
long size = ftell(fp);
fseek(fp, 0, SEEK_SET);

char *buffer = malloc(size + 1);
if (!buffer) { fclose(fp); return -1; }

fread(buffer, 1, size, fp);
buffer[size] = 0;
fclose(fp);

json_object *root = json_tokener_parse(buffer);
free(buffer);
if (!root) return -1;

json_object *apns_obj;
if (!json_object_object_get_ex(root, "apns", &apns_obj)) { json_object_put(root); return -1; }

json_object *apn_array;
if (!json_object_object_get_ex(apns_obj, "apn", &apn_array)) { json_object_put(root); return -1; }

int array_len = json_object_array_length(apn_array);
json_object *best_entry = NULL;

for (int i = 0; i < array_len; i++) {
json_object *entry = json_object_array_get_idx(apn_array, i);
const char *mcc = json_object_get_string(json_object_object_get(entry, "_mcc"));
const char *mnc = json_object_get_string(json_object_object_get(entry, "_mnc"));
const char *type = json_object_get_string(json_object_object_get(entry, "_type"));

if (!mcc || !mnc || !type) continue;

if (strcmp(mcc, sim_mcc) == 0 && strcmp(mnc, sim_mnc3) == 0 &&
strstr(type, "default")) {
best_entry = entry;
break;
}
}
if (!best_entry) {
for (int i = 0; i < array_len; i++) {
json_object *entry = json_object_array_get_idx(apn_array, i);
const char *mcc = json_object_get_string(json_object_object_get(entry, "_mcc"));
const char *mnc = json_object_get_string(json_object_object_get(entry, "_mnc"));
const char *type = json_object_get_string(json_object_object_get(entry, "_type"));

if (!mcc || !mnc || !type) continue;

if (strcmp(mcc, sim_mcc) == 0 && strlen(mnc) >= 2 &&
strncmp(mnc, sim_mnc2, 2) == 0 &&
strstr(type, "default")) {
best_entry = entry;
break;
}
}
}
if (!best_entry) {
mbim_debug("Failed to resolve APN");
}

if (best_entry) {
const char *apn = json_object_get_string(json_object_object_get(best_entry, "_apn"));
const char *user = json_object_get_string(json_object_object_get(best_entry, "_user"));
const char *pass = json_object_get_string(json_object_object_get(best_entry, "_password"));

if (apn && strlen(apn) > 0) profile->apn = strdup(apn);
if (user) profile->user = strdup(user);
if (pass) profile->password = strdup(pass);
mbim_debug("Select APN: %s",profile->apn);
}

json_object_put(root);
return 0;
}

static int requestSetupDataCall(PROFILE_T *profile, int curIpFamily) {
int retval;

(void)curIpFamily;
if (strcmp(profile->apn, "auto") == 0)
{
mbim_debug("No APN is set, start automatic selection");
GetAPNConfig(profile, "/usr/share/qmodem/apns.json");
}else {
mbim_debug("Use the APN set by the user");
}
if (profile->apn)
mbim_apn = profile->apn;
if (profile->user)
Expand Down
Loading