/* * ~ Simple 3GPP Network Scanner using the Qualcomm MSM Interface (QMI) ~ * * by plastinka * * WARNING: This is Research Code! For testing purposes only! * * 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 Street, Fifth Floor, Boston, MA 02110-1301, USA * * * Build command: gcc -o qmi_scanner qmi_scanner.c */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef TRUE #define TRUE 1 #endif #ifndef FALSE #define FALSE 0 #endif #define QMI_MSG_REQUEST 0x00 #define QMI_MSG_RESPONSE 0x02 #define QMI_MSG_INDICATION 0x04 #define QMI_CTL 0x00 //Control Service #define QMI_WDS 0x01 //Wireless Data Service #define QMI_DMS 0x02 //Device Managment Service #define QMI_NAS 0x03 //Network Access Service #define QMI_CTL_SET_INSTANCE_ID 0x0020 //Set the unique link instance ID #define QMI_CTL_GET_VERSION_INFO 0x0021 //Get supported service version info #define QMI_CTL_GET_CLIENT_ID 0x0022 //Get a unique client ID #define QMI_CTL_RELEASE_CLIENT_ID 0x0023 //Release the unique client ID #define QMI_CTL_REVOKE_CLIENT_ID_IND 0x0024 //Indication of client ID revocation #define QMI_CTL_INVALID_CLIENT_ID 0x0025 //Indication of invalid client ID #define QMI_CTL_SET_DATA_FORMAT 0x0026 //Set host driver data format #define QMI_CTL_SYNC 0x0027 //Synchronize client/server #define QMI_CTL_SYNC_IND 0x0027 //Synchronize indication #define QMI_CTL_SET_EVENT 0x0028 //Set event report conditions #define QMI_CTL_EVENT_IND 0x0028 //Event report indication #define QMI_DMS_GET_BAND_CAPS 0x0045 //This message requests the band capability of the device #define QMI_NAS_RESET 0x0000 //Reset NAS service state variables #define QMI_NAS_ABORT 0x0001 //Abort previously issued NAS command #define QMI_NAS_SET_EVENT 0x0002 //Set NAS state report conditions #define QMI_NAS_EVENT_IND 0x0002 //Connection state report indication #define QMI_NAS_SET_REG_EVENT 0x0003 //Set NAS registration report conditions #define QMI_NAS_SCAN_NETS 0x0021 //Scan for visible network #define QMI_NAS_GET_RF_BAND_INFO 0x0031 //Queries radio band/channel information regarding the system currently providing service #define QMI_NAS_SET_SYS_SELECTION 0x0033 //Sets the different system selection preferences of the device #define QMI_NAS_GET_SYS_SELECTION 0x0034 //Queries the different system selection preferences of the device #define QMI_RESULT_SUCCESS 0x0000 #define QMI_RESULT_FAILURE 0x0001 #define QMI_ERR_NONE 0x0000 #define QMI_ERR_MALFORMED_MSG 0x0001 #define QMI_ERR_NO_MEMORY 0x0002 #define QMI_ERR_INTERNAL 0x0003 #define QMI_ERR_ABORTED 0x0004 #define QMI_ERR_CLIENT_IDS_EXHAUSTED 0x0005 #define QMI_ERR_INVALID_CLIENT_ID 0x0007 #define QMI_TLV_TYPE_RESULT_CODE 0x02 //CTL is always client zero #define QMI_CTL_CLIENT 0x0000 #define RAT_GSM 0x04 #define RAT_UMTS 0x08 #define RAT_LTE 0x10 #define LTE800 0x00080000 //Band 20 #define LTE1800 0x00000004 //Band 3 #define LTE2600 0x00000040 //Band 7 #define LTE2100 0x00000001 //Band 1 #define GSM1800 0x00000080 #define GSM900E 0x00000100 #define GSM900P 0x00000200 #define GSM850 0x00080000 #define GSM1900 0x00200000 #define UMTS2100 0x00400000 //terminal control #define SET_RED fprintf(stderr,"\033[1;31;40\155"); #define SET_GREEN fprintf(stderr,"\033[0;32;40\155"); #define SET_WHITE fprintf(stderr,"\033[0;37;40\155"); #define SET_LWHITE fprintf(stderr,"\033[1;37;40\155"); #define SET_DEBUG fprintf(stderr,"\033[1;30;40\155"); #define SET_DBLUE fprintf(stderr,"\033[0;34;40\155"); #define SET_YELLOW fprintf(stderr,"\033[1;33;40\155"); #define SET_CYAN fprintf(stderr,"\033[0;35;40\155"); #define SET_BLUE fprintf(stderr,"\033[0;34;40\155"); #define CURSOR_ON fprintf(stderr,"\033[?25h"); #define CURSOR_OFF fprintf(stderr,"\033[?25l"); #define CLS fprintf(stderr,"\033[2J"); typedef struct qmux { uint8_t tf; uint16_t len; uint8_t cflags; uint8_t stype; uint8_t cid; }__attribute__((__packed__)) qmx_t; typedef struct qtrans { struct qmux qmh; uint8_t cflags; uint16_t tid; uint16_t msgid; uint16_t msgsize; } __attribute__((__packed__)) qmi_t; typedef struct qtrans_ctl { struct qmux qmh; uint8_t cflags; uint8_t tid; uint16_t msgid; uint16_t msgsize; } __attribute__((__packed__)) qmictl_t; struct result_code { uint16_t qmi_result; uint16_t qmi_error; } __attribute__((__packed__)); int debug = 1; // default qmi device char qmi_dev[32];// = "/dev/cdc-wdm0"; // service client id's uint16_t nas_cid = 0; // service transaction id's uint8_t ctl_tid = 1; uint8_t nas_tid = 1; uint8_t rbuf[2048]; uint8_t wbuf[512]; int qmi_fd; struct result_code rc; static void print_buf(const char* detail,const char* buf,size_t len) { int i = 0, z; int newline = FALSE, indent = FALSE; char f[512]; unsigned int flen; snprintf(f,500,"%s (%zu) ",detail,len); flen = strlen(f); printf("%s",f); for (i = 0; i < len; i++) { if(indent) { z = flen; while (z--) printf(" "); indent = FALSE; } printf("%02x ", buf[i]&0xFF); if(((i + 1) % 32) == 0) { printf("\n"); newline = TRUE; indent = TRUE; }else{ newline = FALSE; } } if (!newline) printf("\n"); } static int tlv_get2(void* msg,uint16_t msgsize,uint8_t tlv_type,void* buf,uint16_t bufsize) { int pos; uint16_t tlv_size = 0; memset(buf,0x00,bufsize); for(pos = 0;pos + 3 < msgsize; pos += tlv_size + 3) { tlv_size = *(uint16_t*)(msg+pos+1); if(*(uint8_t*)(msg + pos) == tlv_type) { if(bufsize < tlv_size) return -1; memcpy(buf,msg+pos+3,tlv_size); return 0; } } return -1; } static int get_result_code(void* msg,uint16_t msgsize) { int pos; uint16_t tlv_size = 0; uint16_t r[2]; for(pos = 0;pos + 3 < msgsize;pos += tlv_size + 3) { tlv_size = *(uint16_t*)(msg+pos+1); if(*(uint8_t*)(msg+pos) == QMI_TLV_TYPE_RESULT_CODE) { memcpy(&r,msg+pos+3,4); rc.qmi_result = le16toh(r[0]); rc.qmi_error = le16toh(r[1]); if(debug > 3) printf("Result: 0x%04x Error: 0x%04x\n",rc.qmi_result,rc.qmi_error); return rc.qmi_result; } } } static int send_msg(void* msg,size_t msglen) { ssize_t ret; if(debug > 2) { SET_DBLUE; print_buf("W ",msg,msglen); SET_WHITE; } ret = write(qmi_fd,msg,msglen); free(msg); msg = (void*)NULL; if(ret != msglen) { fprintf(stderr,"Failed to write: wrote %zd err %d\n", ret, errno); return 0; } return ret; } static size_t read_msg(int skip_ind,int timewait) { ssize_t num; fd_set in; int result,i,n,len; unsigned char c; qmi_t* resp; struct stat sb; struct timeval timeout; for(;;) { timeout.tv_sec = timewait; timeout.tv_usec = 0; if(stat(qmi_dev,&sb) == -1) { perror("stat"); SET_LWHITE; exit(EXIT_FAILURE); } FD_ZERO(&in); FD_SET(qmi_fd,&in); result = select(qmi_fd+1, &in, NULL, NULL, &timeout); if(result < 1) return result; memset(rbuf,2048,0x00); errno = 0; for(i = 0,n = 1,len = 2;i <= len && n == 1;i++) { n = read(qmi_fd,&c,1); if(i == 1) len = c; rbuf[i] = c; } //FIXME: How to deal with these server syncs??? if(rbuf[4] == 0x00 && rbuf[8] == 0x27) { if(debug > 1) { printf("Got QMI CTL Server Sync Indication Message:\n"); if(debug > 2) { SET_DEBUG; print_buf("R ",rbuf,len+1); SET_WHITE; } } continue; } if(skip_ind && rbuf[6] == QMI_MSG_INDICATION) { if(debug > 1) { resp = (qmi_t*)rbuf; printf("Got QMI Service Indication Message:\n"); if(debug > 2) { SET_DEBUG; print_buf("R ",rbuf,len+1); SET_WHITE; } } continue; } break; } if(debug > 2) { SET_DEBUG; print_buf("R ",rbuf,len+1); SET_WHITE; } return len+1; } static int get_cid(uint16_t* cid,uint8_t service_type) { size_t rlen; int err,i; struct getcid_req { struct qtrans_ctl qth; uint8_t tlv_type; uint16_t tlv_len; uint8_t tlv_value; } __attribute__((__packed__)) *req; struct qtrans_ctl* resp = (struct qtrans_ctl*)rbuf; req = (struct getcid_req*)calloc(sizeof(*req),1); req->qth.qmh.tf = 1; req->qth.qmh.len = sizeof(*req) - 1; req->qth.qmh.cflags = 0; req->qth.qmh.stype = QMI_CTL; req->qth.qmh.cid = 0x00; req->qth.cflags = 0x00; req->qth.tid = ctl_tid++; req->qth.msgid = QMI_CTL_GET_CLIENT_ID; req->qth.msgsize = 4; req->tlv_type = 0x01; req->tlv_len = 1; req->tlv_value = service_type; if(debug > 1) printf("Running %s...\n",__func__); send_msg(req,sizeof(*req)); for(i = 0;i < 8;i++) { if((rlen = read_msg(TRUE,10)) < 12) goto errout; if(resp->msgid != QMI_CTL_GET_CLIENT_ID) continue; if(!get_result_code(rbuf+sizeof(struct qtrans_ctl),resp->msgsize)) { tlv_get2(rbuf+sizeof(struct qtrans_ctl),resp->msgsize,0x01,cid,2); }else{ SET_RED; fprintf(stderr,"%s: Request failed! Error Code: 0x%04X\n",__func__,rc.qmi_error); goto errout; } break; } if(debug > 2) printf("CID 0x%04X\n", *cid); return 0; errout: fprintf(stderr,"ERROR: %s failed!\n",__func__); SET_WHITE; return -1; } static int release_cid(uint16_t cid) { size_t rlen; int err,i; struct releasecid_req { struct qtrans_ctl qth; uint8_t tlv_type; uint16_t tlv_len; uint16_t tlv_value; } __attribute__((__packed__)) *req; struct qtrans_ctl* resp = (struct qtrans_ctl*)rbuf; req = (struct releasecid_req*)calloc(sizeof(*req),1); req->qth.qmh.tf = 1; req->qth.qmh.len = sizeof(*req) - 1; req->qth.qmh.cflags = 0; req->qth.qmh.stype = QMI_CTL; req->qth.qmh.cid = 0x00; req->qth.cflags = 0x00; req->qth.tid = ctl_tid++; req->qth.msgid = QMI_CTL_RELEASE_CLIENT_ID; req->qth.msgsize = 5; req->tlv_type = 0x01; req->tlv_len = 2; req->tlv_value = cid; if(debug > 1) printf("Running %s...\n",__func__); send_msg(req,sizeof(*req)); for(i = 0;i < 4;i++) { if((rlen = read_msg(TRUE,10)) < 12) goto errout; if(resp->msgid != QMI_CTL_RELEASE_CLIENT_ID) continue; if(get_result_code(rbuf+sizeof(struct qtrans_ctl),resp->msgsize)) { SET_RED; fprintf(stderr,"Request failed! Error Code: 0x%04X\n",rc.qmi_error); goto errout; } break; } return 0; errout: fprintf(stderr,"ERROR: release_cid() failed!\n"); SET_WHITE; return -1; } static int abort_nas_req(uint16_t tid) { int i,rlen; struct abort_req { qmi_t qth; uint8_t tlv_type; uint16_t tlv_len; uint16_t tlv_value; }__attribute__((__packed__)) *req; qmi_t* resp = (qmi_t*)rbuf; req = (struct abort_req*)calloc(sizeof(*req),1); req->qth.qmh.tf = 1; req->qth.qmh.len = sizeof(*req) - 1; req->qth.qmh.cflags = 0; req->qth.qmh.stype = QMI_NAS; req->qth.qmh.cid = nas_cid>>8; req->qth.cflags = 0x00; req->qth.tid = nas_tid++; req->qth.msgid = QMI_NAS_ABORT; req->qth.msgsize = 0x0005; req->tlv_type = 0x01; req->tlv_len = 2; req->tlv_value = tid; if(debug > 1) printf("Running %s...\n",__func__); send_msg(req,sizeof(*req)); for(i = 0;i < 4;i++) { if((rlen = read_msg(TRUE,5)) < 12) goto errout; if(resp->msgid != QMI_NAS_ABORT) continue; if(get_result_code(rbuf+sizeof(qmi_t),resp->msgsize)) { fprintf(stderr,"Request failed! Error Code: 0x%04X\n",rc.qmi_error); goto errout; } break; } return 0; errout: fprintf(stderr,"ERROR: %s failed!\n",__func__); return -1; } static int set_sys_mode(uint8_t rat,uint32_t band,uint32_t lte_band) { int i,rlen; struct sys_select_pref_req { qmi_t qth; uint8_t rat; uint16_t rat_len; uint8_t rat_mode[2]; uint8_t band; uint16_t band_len; uint8_t rf_band[8]; uint8_t lte_band; uint16_t lte_band_len; uint8_t lte_rf_band[8]; uint8_t duration; uint16_t duration_len; uint8_t duration_mode; }__attribute__((__packed__)) *req; qmi_t* resp = (qmi_t*)rbuf; req = (struct sys_select_pref_req*)calloc(sizeof(*req),1); req->qth.qmh.tf = 1; req->qth.qmh.len = sizeof(*req) - 1; req->qth.qmh.cflags = 0; req->qth.qmh.stype = QMI_NAS; req->qth.qmh.cid = nas_cid>>8; req->qth.cflags = 0x00; req->qth.tid = nas_tid++; req->qth.msgid = QMI_NAS_SET_SYS_SELECTION; req->qth.msgsize = 31; req->rat = 0x11; req->rat_len = 0x0002; req->rat_mode[0] = rat; req->rat_mode[1] = 0x00; req->band = 0x12; req->band_len = 0x0008; req->rf_band[0] = band&0xFF; req->rf_band[1] = (band>>8)&0xFF; req->rf_band[2] = (band>>16)&0xFF; req->lte_band = 0x15; req->lte_band_len = 0x0008; req->lte_rf_band[0] = lte_band&0xFF; req->lte_rf_band[2] = (lte_band>>16)&0xFF; req->duration = 0x17; req->duration_len = 0x0001; req->duration_mode = 0x00; if(debug > 1) printf("Running %s...\n",__func__); send_msg(req,sizeof(*req)); for(i = 0;i < 4;i++) { if((rlen = read_msg(TRUE,5)) < 12) goto errout; if(resp->msgid != QMI_NAS_SET_SYS_SELECTION) continue; if(get_result_code(rbuf+sizeof(qmi_t),resp->msgsize)) { SET_RED; fprintf(stderr,"Request failed! Error Code: 0x%04X\n",rc.qmi_error); goto errout; } break; } return 0; errout: fprintf(stderr,"ERROR: set_sys_mode() failed!\n"); SET_WHITE; return -1; } static int set_nas_indication(uint8_t ind,uint8_t on_off) { int i,rlen; struct set_ind_req { qmi_t qth; uint8_t tlv_type; uint16_t tlv_len; uint8_t tlv_value; }__attribute__((__packed__)) *req; qmi_t* resp = (qmi_t*)rbuf; req = (struct set_ind_req*)calloc(sizeof(*req),1); req->qth.qmh.tf = 1; req->qth.qmh.len = sizeof(*req) - 1; req->qth.qmh.cflags = 0; req->qth.qmh.stype = QMI_NAS; req->qth.qmh.cid = nas_cid>>8; req->qth.cflags = 0x00; req->qth.tid = nas_tid++; req->qth.msgid = QMI_NAS_SET_REG_EVENT; req->qth.msgsize = 0x0004; req->tlv_type = ind; req->tlv_len = 1; req->tlv_value = on_off; if(debug > 1) printf("Running %s...\n",__func__); send_msg(req,sizeof(*req)); for(i = 0;i < 4;i++) { if((rlen = read_msg(TRUE,5)) < 12) goto errout; if(resp->msgid != QMI_NAS_SET_REG_EVENT) continue; if(get_result_code(rbuf+sizeof(qmi_t),resp->msgsize)) { fprintf(stderr,"Request failed! Error Code: 0x%04X\n",rc.qmi_error); goto errout; } break; } return 0; errout: fprintf(stderr,"ERROR: set_nas_indication() failed!\n"); return -1; } static int scan_network(uint8_t rat,const char* act) { int i,j,k,rlen; char desc[128]; uint8_t tlv_buf[256]; memset(tlv_buf,0x00,256); struct scan_req { qmi_t qth; uint8_t type; uint16_t type_len; uint8_t type_value; }__attribute__((__packed__)) *req; qmi_t* resp = (qmi_t*)rbuf; req = (struct scan_req*)calloc(sizeof(*req),1); req->qth.qmh.tf = 1; req->qth.qmh.len = sizeof(*req) - 1; req->qth.qmh.cflags = 0; req->qth.qmh.stype = QMI_NAS; req->qth.qmh.cid = nas_cid>>8; req->qth.cflags = 0x00; req->qth.tid = nas_tid++; req->qth.msgid = QMI_NAS_SCAN_NETS; req->qth.msgsize = 0x0004; req->type = 0x10; req->type_len = 0x0001; req->type_value = rat; if(debug > 1) printf("Running %s...\n",__func__); //print_buf(">>>",req,req_len); send_msg(req,sizeof(*req)); for(i = 0;i < 4;i++) { if((rlen = read_msg(TRUE,60)) < 12) { if(rlen == 0) { fprintf(stderr,"Scan timed out! Abort scan request!\n"); abort_nas_req(req->qth.tid); goto out; } goto errout; } if(resp->msgid != QMI_NAS_SCAN_NETS) continue; if(!get_result_code(rbuf+sizeof(struct qtrans),resp->msgsize)) { tlv_get2(rbuf+sizeof(struct qtrans),resp->msgsize,0x10,&tlv_buf,256); printf("%i network(s) found!\n",tlv_buf[0]); switch(rat) { case 1: SET_GREEN; break; case 2: SET_BLUE; break; case 4: SET_CYAN; break; default: SET_YELLOW; break; } for(j = 0,k = 2;j < tlv_buf[0];j++) { if(j == 0) printf("\n"); memset(desc,0x00,128); memcpy(desc,tlv_buf+k+6,tlv_buf[k+5]); printf("\t%i. -- [%s] MCC:%d MNC:%02d (%s)\n",j+1,act,*(uint16_t*)(tlv_buf+k),tlv_buf[k+2],desc); k += 6 + tlv_buf[k+5]; if(j == tlv_buf[0]-1) printf("\n"); } SET_WHITE; }else{ SET_RED; fprintf(stderr,"Request failed! Error Code: 0x%04X\n",rc.qmi_error); goto errout; } break; } out: return 0; errout: fprintf(stderr,"ERROR: scan_network() failed!\n"); SET_WHITE; return -1; } static void scan_network_bands() { fprintf(stderr,"\n\t----- QMI Network Scanner v0.1 -----\n\n"); set_nas_indication(0x13,0); //disable Serving System Events fprintf(stderr,"Scanning for GSM1800...\t\t "); set_sys_mode(RAT_GSM,GSM1800,0); scan_network(0x01,"GSM1800"); fprintf(stderr,"Scanning for GSM900 Extended...\t "); set_sys_mode(RAT_GSM,GSM900E,0); scan_network(0x01,"E-GSM900"); fprintf(stderr,"Scanning for GSM900 Primary...\t "); set_sys_mode(RAT_GSM,GSM900P,0); scan_network(0x01,"P-GSM900"); fprintf(stderr,"Scanning for UMTS2100...\t "); set_sys_mode(RAT_UMTS,UMTS2100,0); scan_network(0x02,"UMTS2100"); fprintf(stderr,"Scanning for LTE800...\t\t "); set_sys_mode(RAT_LTE,0,LTE800); scan_network(0x04,"LTE800"); fprintf(stderr,"Scanning for LTE1800...\t\t "); set_sys_mode(RAT_LTE,0,LTE1800); scan_network(0x04,"LTE1800"); fprintf(stderr,"Scanning for LTE2600...\t\t "); set_sys_mode(RAT_LTE,0,LTE2600); scan_network(0x04,"LTE 2600"); set_sys_mode(RAT_GSM,GSM850,0); } static void usage() { printf("\nusage: qmi_scanner [-d /dev/cdc-wdmX] [-D] [-h]\n\n"); printf("\t\t-d: qmi control device (defaults to /dev/cdc-wdm0)\n"); printf("\t\t-D: debug level\n"); printf("\t\t-h: show usage\n\n"); exit(0); } int main(int argc, char *argv[]) { int opt; SET_WHITE; strncpy(qmi_dev,"/dev/cdc-wdm0",30); for(;;) { opt = getopt(argc, argv, "d:Dh"); if(opt == -1) break; switch(opt) { case 'd': strncpy(qmi_dev,optarg,30); break; case 'D': debug++; break; case 'h': usage(); break; default: break; } } errno = 0; qmi_fd = open(qmi_dev, O_RDWR | O_EXCL | /*O_NONBLOCK |*/ O_NOCTTY); if(qmi_fd < 0) { SET_RED; fprintf(stderr,"Failed to open control device %s : %s\n",qmi_dev,strerror(errno)); fprintf(stderr,"Make sure the qmi_wwan module is loaded and your modem has been modeswitched!\n"); SET_LWHITE; exit(1); } while(get_cid(&nas_cid,QMI_NAS) == -1) sleep(1); scan_network_bands(); if(nas_cid) release_cid(nas_cid); close(qmi_fd); SET_LWHITE; return 0; }