/* * * AT chat library with GLib integration * * Copyright (C) 2008-2011 Intel Corporation. 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 * published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #define IFCONFIG_PATH "/sbin/ifconfig" static const char *none_prefix[] = { NULL }; static const char *cfun_prefix[] = { "+CFUN:", NULL }; static const char *creg_prefix[] = { "+CREG:", NULL }; static const char *cgreg_prefix[] = { "+CGREG:", NULL }; static gchar *option_ip = NULL; static gint option_port = 0; static gchar *option_modem = NULL; static gchar *option_control = NULL; static gint option_cid = 0; static gchar *option_apn = NULL; static gint option_offmode = 0; static gboolean option_legacy = FALSE; static gchar *option_username = NULL; static gchar *option_password = NULL; static gchar *option_pppdump = NULL; static gboolean option_bluetooth = FALSE; static gboolean option_acfc = FALSE; static gboolean option_pfc = FALSE; static GAtPPP *ppp; static GAtChat *control; static GAtChat *modem; static GMainLoop *event_loop; enum state { STATE_NONE = 0, STATE_REGISTERING, STATE_ATTACHING, STATE_ACTIVATING }; static int state = 0; static int oldmode = 0; static void gsmdial_debug(const char *str, void *data) { g_print("%s: %s\n", (const char *) data, str); } static gboolean quit_eventloop(gpointer user_data) { g_main_loop_quit(event_loop); return FALSE; } static void power_down(gboolean ok, GAtResult *result, gpointer user_data) { g_main_loop_quit(event_loop); } static void kill_ppp(gboolean ok, GAtResult *result, gpointer user_data) { g_print("kill_ppp: %d\n", ok); if (ok == FALSE) return; g_at_ppp_unref(ppp); ppp = NULL; } static void ppp_suspend_ath0(gpointer user_data) { g_at_chat_resume(modem); g_at_chat_send(modem, "ATH0", none_prefix, kill_ppp, NULL, NULL); } static void resume_ppp(gboolean ok, GAtResult *result, gpointer user_data) { g_print("resume_ppp: %d\n", ok); if (ok == FALSE) return; g_at_chat_suspend(modem); g_at_ppp_resume(ppp); } static void ppp_suspend_ato0(gpointer user_data) { g_at_chat_resume(modem); g_at_chat_send(modem, "ATO0", none_prefix, resume_ppp, NULL, NULL); } static gboolean signal_cb(GIOChannel *channel, GIOCondition cond, gpointer data) { static int terminated = 0; int signal_fd = GPOINTER_TO_INT(data); struct signalfd_siginfo si; ssize_t res; if (cond & (G_IO_NVAL | G_IO_ERR)) return FALSE; res = read(signal_fd, &si, sizeof(si)); if (res != sizeof(si)) return FALSE; switch (si.ssi_signo) { case SIGINT: case SIGTERM: if (terminated == 0) { g_timeout_add_seconds(10, quit_eventloop, NULL); if (ppp == NULL) { char buf[64]; sprintf(buf, "AT+CFUN=%u", option_offmode); g_at_chat_send(control, buf, none_prefix, power_down, NULL, NULL); } else g_at_ppp_shutdown(ppp); } terminated++; break; case SIGUSR1: if (ppp == NULL) break; g_at_ppp_set_suspend_function(ppp, ppp_suspend_ato0, NULL); g_at_ppp_suspend(ppp); break; case SIGUSR2: if (ppp == NULL) break; g_at_ppp_set_suspend_function(ppp, ppp_suspend_ath0, NULL); g_at_ppp_suspend(ppp); break; default: break; } return TRUE; } static gboolean at_util_parse_reg_unsolicited(GAtResult *result, const char *prefix, int *status, int *lac, int *ci, int *tech) { GAtResultIter iter; int s; int l = -1, c = -1, t = -1; const char *str; g_at_result_iter_init(&iter, result); if (g_at_result_iter_next(&iter, prefix) == FALSE) return FALSE; if (g_at_result_iter_next_number(&iter, &s) == FALSE) return FALSE; if (g_at_result_iter_next_string(&iter, &str) == TRUE) l = strtol(str, NULL, 16); else goto out; if (g_at_result_iter_next_string(&iter, &str) == TRUE) c = strtol(str, NULL, 16); else goto out; g_at_result_iter_next_number(&iter, &t); out: if (status) *status = s; if (lac) *lac = l; if (ci) *ci = c; if (tech) *tech = t; return TRUE; } static gboolean at_util_parse_reg(GAtResult *result, const char *prefix, int *mode, int *status, int *lac, int *ci, int *tech) { GAtResultIter iter; int m, s; int l = -1, c = -1, t = -1; const char *str; g_at_result_iter_init(&iter, result); while (g_at_result_iter_next(&iter, prefix)) { g_at_result_iter_next_number(&iter, &m); /* Sometimes we get an unsolicited CREG/CGREG here, skip it */ if (g_at_result_iter_next_number(&iter, &s) == FALSE) continue; if (g_at_result_iter_next_string(&iter, &str) == TRUE) l = strtol(str, NULL, 16); else goto out; if (g_at_result_iter_next_string(&iter, &str) == TRUE) c = strtol(str, NULL, 16); else goto out; g_at_result_iter_next_number(&iter, &t); out: if (mode) *mode = m; if (status) *status = s; if (lac) *lac = l; if (ci) *ci = c; if (tech) *tech = t; return TRUE; } return FALSE; } static gboolean execute(const char *cmd) { int status; status = system(cmd); if (status < 0) { g_print("Failed to execute command: %s\n", strerror(errno)); return FALSE; } return TRUE; } static void ppp_connect(const char *iface, const char *local, const char *peer, const char *dns1, const char *dns2, gpointer user_data) { char buf[512]; /* print out the negotiated address and dns server */ g_print("Network Device: %s\n", iface); g_print("IP Address: %s\n", local); g_print("Peer IP Address: %s\n", peer); g_print("Primary DNS Server: %s\n", dns1); g_print("Secondary DNS Server: %s\n", dns2); if (getuid() != 0) { g_print("Need root privilege to config PPP interface\n"); return; } snprintf(buf, sizeof(buf), "%s %s up", IFCONFIG_PATH, iface); execute(buf); snprintf(buf, sizeof(buf), "%s %s %s pointopoint %s", IFCONFIG_PATH, iface, local, peer); execute(buf); } static void no_carrier_notify(GAtResult *result, gpointer user_data) { char buf[64]; if (option_bluetooth) { g_main_loop_quit(event_loop); return; } sprintf(buf, "AT+CFUN=%u", option_offmode); g_at_chat_send(control, buf, none_prefix, power_down, NULL, NULL); } static void ppp_disconnect(GAtPPPDisconnectReason reason, gpointer user_data) { g_print("PPP Link down: %d\n", reason); g_at_ppp_unref(ppp); ppp = NULL; if (option_modem == NULL) g_at_chat_set_debug(modem, gsmdial_debug, ""); else g_at_chat_set_debug(modem, gsmdial_debug, "Modem"); g_at_chat_register(modem, "NO CARRIER", no_carrier_notify, FALSE, NULL, NULL); g_at_chat_resume(modem); } static void connect_cb(gboolean ok, GAtResult *result, gpointer user_data) { GAtIO *io; if (!ok) { g_print("Unable to define context\n"); exit(1); } /* get the data IO channel */ io = g_at_chat_get_io(modem); /* * shutdown gatchat or else it tries to take all the input * from the modem and does not let PPP get it. */ g_at_chat_suspend(modem); /* open ppp */ ppp = g_at_ppp_new(); if (ppp == NULL) { g_print("Unable to create PPP object\n"); exit(1); } g_at_ppp_set_debug(ppp, gsmdial_debug, "PPP"); g_at_ppp_set_credentials(ppp, option_username, option_password); g_at_ppp_set_acfc_enabled(ppp, option_acfc); g_at_ppp_set_pfc_enabled(ppp, option_pfc); /* set connect and disconnect callbacks */ g_at_ppp_set_connect_function(ppp, ppp_connect, NULL); g_at_ppp_set_disconnect_function(ppp, ppp_disconnect, NULL); /* open the ppp connection */ g_at_ppp_open(ppp, io); if (option_pppdump) g_at_ppp_set_recording(ppp, option_pppdump); } static void at_cgdcont_cb(gboolean ok, GAtResult *result, gpointer user_data) { char buf[64]; if (!ok) { g_print("Unable to define context\n"); exit(1); } if (option_legacy == TRUE) sprintf(buf, "ATD*99***%u#", option_cid); else sprintf(buf, "AT+CGDATA=\"PPP\",%u", option_cid); g_at_chat_send(modem, buf, none_prefix, connect_cb, NULL, NULL); } static void setup_context(int status) { char buf[1024]; int len; state = STATE_ACTIVATING; g_print("Registered to GPRS network, roaming=%s\n", status == 5 ? "true" : "false"); len = sprintf(buf, "AT+CGDCONT=%u,\"IP\"", option_cid); snprintf(buf + len, sizeof(buf) - len - 3, ",\"%s\"", option_apn); g_at_chat_send(control, buf, none_prefix, at_cgdcont_cb, NULL, NULL); } static void cgreg_notify(GAtResult *result, gpointer user_data) { int status, lac, ci, tech; if (state != STATE_ATTACHING) return; if (at_util_parse_reg_unsolicited(result, "+CGREG:", &status, &lac, &ci, &tech) == FALSE) return; if (status != 1 && status != 5) return; setup_context(status); } static void cgreg_cb(gboolean ok, GAtResult *result, gpointer user_data) { int status, lac, ci, tech; if (!ok) return; if (at_util_parse_reg(result, "+CGREG:", NULL, &status, &lac, &ci, &tech) == FALSE) return; if (status != 1 && status != 5) { g_at_chat_register(control, "+CGREG:", cgreg_notify, FALSE, NULL, NULL); return; } setup_context(status); } static void attached_cb(gboolean ok, GAtResult *result, gpointer user_data) { if (!ok) return; g_at_chat_send(control, "AT+CGREG?", cgreg_prefix, cgreg_cb, NULL, NULL); } static void activate_gprs(int status) { state = STATE_ATTACHING; g_print("Registered to network, roaming=%s\n", status == 5 ? "true" : "false"); g_print("Activating GPRS network...\n"); g_at_chat_send(control, "AT+CGATT=1", none_prefix, attached_cb, NULL, NULL); } static void creg_notify(GAtResult *result, gpointer user_data) { int status, lac, ci, tech; if (state != STATE_REGISTERING) return; if (at_util_parse_reg_unsolicited(result, "+CREG:", &status, &lac, &ci, &tech) == FALSE) return; if (status != 1 && status != 5) return; activate_gprs(status); } static void creg_cb(gboolean ok, GAtResult *result, gpointer user_data) { int status, lac, ci, tech; if (!ok) return; if (at_util_parse_reg(result, "+CREG:", NULL, &status, &lac, &ci, &tech) == FALSE) return; if (status != 1 && status != 5) { g_at_chat_register(control, "+CREG:", creg_notify, FALSE, NULL, NULL); return; } activate_gprs(status); } static void register_cb(gboolean ok, GAtResult *result, gpointer user_data) { if (!ok) { g_print("Couldn't register to network, exiting...\n"); exit(1); } state = STATE_REGISTERING; g_print("Waiting for network registration...\n"); g_at_chat_send(control, "AT+CREG?", creg_prefix, creg_cb, NULL, NULL); } static void start_dial(gboolean ok, GAtResult *result, gpointer user_data) { if (!ok) { g_print("Checking PIN status failed\n"); exit(1); } g_at_chat_send(control, "AT+CREG=2", none_prefix, NULL, NULL, NULL); g_at_chat_send(control, "AT+CGREG=2", none_prefix, NULL, NULL, NULL); g_at_chat_send(control, "AT+COPS=0", none_prefix, register_cb, NULL, NULL); } static void check_pin(gboolean ok, GAtResult *result, gpointer user_data) { if (!ok) { g_print("Turning on the modem failed\n"); exit(1); } g_at_chat_send(control, "AT+CPIN?", NULL, start_dial, NULL, NULL); } static void check_mode(gboolean ok, GAtResult *result, gpointer user_data) { GAtResultIter iter; if (!ok) { g_print("Checking modem mode failed\n"); exit(1); } g_at_result_iter_init(&iter, result); g_at_result_iter_next(&iter, "+CFUN:"); g_at_result_iter_next_number(&iter, &oldmode); g_print("Current modem mode is %d\n", oldmode); if (oldmode == 1) { check_pin(ok, result, user_data); return; } g_at_chat_send(control, "AT+CFUN=1", NULL, check_pin, NULL, NULL); } static int open_serial(void) { GAtSyntax *syntax; GIOChannel *channel; channel = g_at_tty_open(option_control, NULL); if (channel == NULL) return -EIO; syntax = g_at_syntax_new_gsm_permissive(); control = g_at_chat_new(channel, syntax); g_io_channel_unref(channel); g_at_syntax_unref(syntax); if (control == NULL) return -EIO; if (option_modem == NULL) { g_at_chat_ref(control); modem = control; g_at_chat_set_debug(control, gsmdial_debug, ""); } else { g_at_chat_set_debug(control, gsmdial_debug, "Control"); channel = g_at_tty_open(option_modem, NULL); if (channel == NULL) return -EIO; syntax = g_at_syntax_new_gsm_permissive(); modem = g_at_chat_new(channel, syntax); g_io_channel_unref(channel); g_at_syntax_unref(syntax); if (modem == NULL) return -EIO; g_at_chat_set_debug(modem, gsmdial_debug, "Modem"); } return 0; } static int open_ip(void) { int sk, err; struct sockaddr_in addr; GAtSyntax *syntax; GIOChannel *channel; sk = socket(PF_INET, SOCK_STREAM, 0); if (sk < 0) return -EINVAL; memset(&addr, 0, sizeof(addr)); addr.sin_family = AF_INET; addr.sin_addr.s_addr = inet_addr(option_ip); addr.sin_port = htons(option_port); err = connect(sk, (struct sockaddr *) &addr, sizeof(addr)); if (err < 0) { close(sk); return err; } channel = g_io_channel_unix_new(sk); if (channel == NULL) { close(sk); return -ENOMEM; } syntax = g_at_syntax_new_gsmv1(); control = g_at_chat_new(channel, syntax); g_io_channel_unref(channel); g_at_syntax_unref(syntax); if (control == NULL) return -ENOMEM; g_at_chat_ref(control); modem = control; g_at_chat_set_debug(control, gsmdial_debug, ""); return 0; } static GOptionEntry options[] = { { "ip", 'i', 0, G_OPTION_ARG_STRING, &option_ip, "Specify IP" }, { "port", 'p', 0, G_OPTION_ARG_INT, &option_port, "Specify IP Port" }, { "control", 'n', 0, G_OPTION_ARG_FILENAME, &option_control, "Specify Modem Control port" }, { "modem", 'm', 0, G_OPTION_ARG_FILENAME, &option_modem, "Specify Modem port (ppp), if not provided" " the control port will be used" }, { "cid", 'c', 0, G_OPTION_ARG_INT, &option_cid, "Specify CID to use" }, { "apn", 'a', 0, G_OPTION_ARG_STRING, &option_apn, "Specify APN" }, { "offmode", 'o', 0, G_OPTION_ARG_INT, &option_offmode, "Specify CFUN offmode" }, { "legacy", 'l', 0, G_OPTION_ARG_NONE, &option_legacy, "Use ATD*99***#" }, { "bluetooth", 'b', 0, G_OPTION_ARG_NONE, &option_bluetooth, "Use only ATD*99" }, { "username", 'u', 0, G_OPTION_ARG_STRING, &option_username, "Specify PPP username" }, { "password", 'w', 0, G_OPTION_ARG_STRING, &option_password, "Specify PPP password" }, { "pppdump", 'D', 0, G_OPTION_ARG_STRING, &option_pppdump, "Specify pppdump filename" }, { "pfc", 0, 0, G_OPTION_ARG_NONE, &option_pfc, "Use Protocol Field Compression" }, { "acfc", 0, 0, G_OPTION_ARG_NONE, &option_acfc, "Use Address & Control Field Compression" }, { NULL }, }; int main(int argc, char **argv) { GOptionContext *context; GError *err = NULL; sigset_t mask; int signal_fd; GIOChannel *signal_io; int signal_source; context = g_option_context_new(NULL); g_option_context_add_main_entries(context, options, NULL); if (g_option_context_parse(context, &argc, &argv, &err) == FALSE) { if (err != NULL) { g_printerr("%s\n", err->message); g_error_free(err); return 1; } g_printerr("An unknown error occurred\n"); return 1; } g_option_context_free(context); if (option_control) { int ret; g_print("Control: %s\n", option_control); if (option_modem) g_print("Modem: %s\n", option_modem); ret = open_serial(); g_free(option_control); g_free(option_modem); if (ret < 0) goto out; } else { int ret; g_print("IP: %s\n", option_ip); g_print("Port: %d\n", option_port); ret = open_ip(); g_free(option_ip); if (ret < 0) goto out; } g_print("APN: %s\n", option_apn); g_print("CID: %d\n", option_cid); sigemptyset(&mask); sigaddset(&mask, SIGTERM); sigaddset(&mask, SIGINT); sigaddset(&mask, SIGUSR1); sigaddset(&mask, SIGUSR2); sigaddset(&mask, SIGPIPE); if (sigprocmask(SIG_BLOCK, &mask, NULL) < 0) { perror("Can't set signal mask"); return 1; } signal_fd = signalfd(-1, &mask, 0); if (signal_fd < 0) { perror("Can't create signal filedescriptor"); return 1; } signal_io = g_io_channel_unix_new(signal_fd); g_io_channel_set_close_on_unref(signal_io, TRUE); signal_source = g_io_add_watch(signal_io, G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL, signal_cb, GINT_TO_POINTER(signal_fd)); g_io_channel_unref(signal_io); event_loop = g_main_loop_new(NULL, FALSE); if (option_bluetooth) { g_at_chat_send(control, "ATD*99#", none_prefix, connect_cb, NULL, NULL); } else { g_at_chat_send(control, "ATE0Q0V1", NULL, NULL, NULL, NULL); g_at_chat_send(control, "AT+CFUN?", cfun_prefix, check_mode, NULL, NULL); } g_main_loop_run(event_loop); g_source_remove(signal_source); g_main_loop_unref(event_loop); out: if (ppp == NULL) { g_at_chat_unref(control); g_at_chat_unref(modem); } else g_at_ppp_unref(ppp); g_free(option_apn); return 0; }