前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >解决Android模拟器中修改IMSI后无法上网问题

解决Android模拟器中修改IMSI后无法上网问题

作者头像
drunkdream
发布2018-07-06 18:13:40
3.5K0
发布2018-07-06 18:13:40
举报
文章被收录于专栏:醉梦轩

0x00 前言

百度百科中对IMSI的介绍如下:

国际移动用户识别码(IMSI:International Mobile Subscriber Identification Number)是区别移动用户的标志,储存在SIM卡中,可用于区别移动用户的有效信息。其总长度不超过15位,同样使用0~9的数字。其中MCC是移动用户所属国家代号,占3位数字,中国的MCC规定为460;MNC是移动网号码,由两位或者三位数字组成,中国移动的移动网络编码(MNC)为00;用于识别移动用户所归属的移动通信网;MSIN是移动用户识别码,用以识别某一移动通信网中的移动用户。

通过IMSI可以知道移动用户所在的国家。在Android中可以通过以下方法获取设备的IMSI号:

代码语言:javascript
复制
TelephonyManager telephonyManager= (TelephonyManager)getSystemService(Context.TELEPHONY_SERVICE);
String android_imsi = telephonyManager.getSubscriberId();

Android模拟器中默认使用的IMSI是:310260000000000。其中,310代表美国,260代表T-Mobile US。事实上,我们期望在模拟器上获取的IMSI应当以460开头(460代表中国)。

0x01 问题定位

但是,这串数字是硬编码在模拟器中的,路径是external/qemu/android/telephony/modem.c,只能通过修改模拟器源码来实现。

代码语言:javascript
复制
#define  OPERATOR_HOME_MCC   310
#define  OPERATOR_HOME_MNC   260
#define  OPERATOR_HOME_MCCMNC  STRINGIFY(OPERATOR_HOME_MCC) \
                               STRINGIFY(OPERATOR_HOME_MNC)
{ "+CIMI", OPERATOR_HOME_MCCMNC "0000000000", NULL },   /* request internation subscriber identification number */

将以上代码改为:

代码语言:javascript
复制
#define  OPERATOR_HOME_MCC   460 //中国
#define  OPERATOR_HOME_MNC   00  //移动
#define  OPERATOR_HOME_MCCMNC  STRINGIFY(OPERATOR_HOME_MCC) \
                               STRINGIFY(OPERATOR_HOME_MNC)
{ "+CIMI", OPERATOR_HOME_MCCMNC "00000000000", NULL }

重新编译,运行,使用getSubscriberId获取的值的确变成了我们期望的值:460000000000000 ,但是出现了新的问题:模拟器不能上网了。一番Google之后,发现也有别人遇到了这个问题,但是也没有找到好的解决方法。

于是,决定自己寻找原因,从TelephonyManager一路翻下去,最终到ril层,也没有看出问题出在哪儿。但是,直觉告诉我,问题应当出在APN上。

Android系统中APN的配置信息是在/system/etc/apns-conf.xml中。下面是模拟器中默认的APN配置。

代码语言:javascript
复制
<!-- use empty string to specify no proxy or port -->
<!-- This version must agree with that in apps/common/res/apns.xml -->
<apns version="8">
    <apn carrier="T-Mobile US"
         mcc="310"
         mnc="260"
         apn="epc.tmobile.com"
         user="none"
         server="*"
         password="none"
         mmsc="http://mms.msg.eng.t-mobile.com/mms/wapenc"
    />

    <apn carrier="T-Mobile US 250"
         mcc="310"
         mnc="250"
         apn="epc.tmobile.com"
         user="none"
         server="*"
         password="none"
         mmsc="http://mms.msg.eng.t-mobile.com/mms/wapenc"
            />

    <apn carrier="T-Mobile US 660"
         mcc="310"
         mnc="660"
         apn="epc.tmobile.com"
         user="none"
         server="*"
         password="none"
         mmsc="http://mms.msg.eng.t-mobile.com/mms/wapenc"
            />

    <apn carrier="T-Mobile US 230"
         mcc="310"
         mnc="230"
         apn="epc.tmobile.com"
         user="none"
         server="*"
         password="none"
         mmsc="http://mms.msg.eng.t-mobile.com/mms/wapenc"
            />

    <apn carrier="T-Mobile US 310"
         mcc="310"
         mnc="310"
         apn="epc.tmobile.com"
         user="none"
         server="*"
         password="none"
         mmsc="http://mms.msg.eng.t-mobile.com/mms/wapenc"
            />

    <apn carrier="T-Mobile US 580"
         mcc="310"
         mnc="580"
         apn="epc.tmobile.com"
         user="none"
         server="*"
         password="none"
         mmsc="http://mms.msg.eng.t-mobile.com/mms/wapenc"
            />

    <apn carrier="T-Mobile US 240"
         mcc="310"
         mnc="240"
         apn="epc.tmobile.com"
         user="none"
         server="*"
         password="none"
         mmsc="http://mms.msg.eng.t-mobile.com/mms/wapenc"
            />

    <apn carrier="T-Mobile US 800"
         mcc="310"
         mnc="800"
         apn="epc.tmobile.com"
         user="none"
         server="*"
         password="none"
         mmsc="http://mms.msg.eng.t-mobile.com/mms/wapenc"
            />

    <apn carrier="T-Mobile US 210"
         mcc="310"
         mnc="210"
         apn="epc.tmobile.com"
         user="none"
         server="*"
         password="none"
         mmsc="http://mms.msg.eng.t-mobile.com/mms/wapenc"
            />

    <apn carrier="T-Mobile US 160"
         mcc="310"
         mnc="160"
         apn="epc.tmobile.com"
         user="none"
         server="*"
         password="none"
         mmsc="http://mms.msg.eng.t-mobile.com/mms/wapenc"
            />

    <apn carrier="T-Mobile US 270"
         mcc="310"
         mnc="270"
         apn="epc.tmobile.com"
         user="none"
         server="*"
         password="none"
         mmsc="http://mms.msg.eng.t-mobile.com/mms/wapenc"
            />

    <apn carrier="T-Mobile US 200"
         mcc="310"
         mnc="200"
         apn="epc.tmobile.com"
         user="none"
         server="*"
         password="none"
         mmsc="http://mms.msg.eng.t-mobile.com/mms/wapenc"
            />

    <apn carrier="T-Mobile US 220"
         mcc="310"
         mnc="220"
         apn="epc.tmobile.com"
         user="none"
         server="*"
         password="none"
         mmsc="http://mms.msg.eng.t-mobile.com/mms/wapenc"
            />

    <apn carrier="T-Mobile US 490"
         mcc="310"
         mnc="490"
         apn="epc.tmobile.com"
         user="none"
         server="*"
         password="none"
         mmsc="http://mms.msg.eng.t-mobile.com/mms/wapenc"
            />

    <!-- T-Mobile Europe -->
    <apn carrier="T-Mobile UK"
         mcc="234"
         mnc="30"
         apn="general.t-mobile.uk"
         user="t-mobile"
         password="tm"
         server="*"
         mmsproxy="149.254.201.135"
         mmsport="8080"
         mmsc="http://mmsc.t-mobile.co.uk:8002"
    />

    <apn carrier="T-Mobile D"
         mcc="262"
         mnc="01"
         apn="internet.t-mobile"
         user="t-mobile"
         password="tm"
         server="*"
         mmsproxy="172.028.023.131"
         mmsport="8008"
         mmsc="http://mms.t-mobile.de/servlets/mms"
    />

    <apn carrier="T-Mobile A"
         mcc="232"
         mnc="03"
         apn="gprsinternet"
         user="t-mobile"
         password="tm"
         server="*"
         mmsproxy="010.012.000.020"
         mmsport="80"
         mmsc="http://mmsc.t-mobile.at/servlets/mms"
         type="default,supl"
    />

    <apn carrier="T-Mobile A MMS"
         mcc="232"
         mnc="03"
         apn="gprsmms"
         user="t-mobile"
         password="tm"
         server="*"
         mmsproxy="010.012.000.020"
         mmsport="80"
         mmsc="http://mmsc.t-mobile.at/servlets/mms"
         type="mms"
    />

    <apn carrier="T-Mobile CZ"
         mcc="230"
         mnc="01"
         apn="internet.t-mobile.cz"
         user="wap"
         password="wap"
         server="*"
         mmsproxy="010.000.000.010"
         mmsport="80"
         mmsc="http://mms"
         type="default,supl"
    />

    <apn carrier="T-Mobile CZ MMS"
         mcc="230"
         mnc="01"
         apn="mms.t-mobile.cz"
         user="mms"
         password="mms"
         server="*"
         mmsproxy="010.000.000.010"
         mmsport="80"
         mmsc="http://mms"
         type="mms"
    />

    <apn carrier="T-Mobile NL"
         mcc="204"
         mnc="16"
         apn="internet"
         user="*"
         password="*"
         server="*"
         mmsproxy="010.010.010.011"
         mmsport="8080"
         mmsc="http://t-mobilemms"
         type="default,supl"
    />

    <apn carrier="T-Mobile NL MMS"
         mcc="204"
         mnc="16"
         apn="mms"
         user="tmobilemms"
         password="tmobilemms"
         server="*"
         mmsproxy="010.010.010.011"
         mmsport="8080"
         mmsc="http://t-mobilemms"
         type="mms"
    />
</apns>

可以看出,这里是没有国内运营商的配置的。我从小米手机中提取出的国内运营商配置。

代码语言:javascript
复制
<apn carrier="中国移动 (China Mobile) GPRS"
    mcc="460"
    mnc="00"
    apn="cmnet"
    type="default,supl"
/>

<apn carrier="中国移动 (China Mobile) WAP"
    mcc="460"
    mnc="00"
    apn="cmwap"
    port="80"
    proxy="10.0.0.172"
    type="default,supl"
/>

<apn carrier="中国移动彩信 (China Mobile)"
    mcc="460"
    mnc="00"
    apn="cmwap"
    mmsc="http://mmsc.monternet.com"
    mmsport="80"
    mmsproxy="10.0.0.172"
    port="80"
    proxy="10.0.0.172"
    type="mms"
/>

<apn carrier="沃3G连接互联网 (China Unicom)"
    mcc="460"
    mnc="01"
    apn="3gnet"
    type="default,supl"
/>

<apn carrier="沃3G手机上网 (China Unicom)"
    mcc="460"
    mnc="01"
    apn="3gwap"
    port="80"
    proxy="10.0.0.172"
    type="default,supl"
/>

<apn carrier="联通彩信 (China Unicom)"
    mcc="460"
    mnc="01"
    apn="3gwap"
    mmsc="http://mmsc.myuni.com.cn"
    mmsport="80"
    mmsproxy="10.0.0.172"
    type="mms"
/>

<apn carrier="中国移动 (China Mobile) GPRS"
    mcc="460"
    mnc="02"
    apn="cmnet"
    type="default,supl"
/>

<apn carrier="中国移动 (China Mobile) WAP"
    mcc="460"
    mnc="02"
    apn="cmwap"
    port="80"
    proxy="10.0.0.172"
    type="default,supl"
/>

<apn carrier="中国移动彩信 (China Mobile)"
    mcc="460"
    mnc="02"
    apn="cmwap"
    mmsc="http://mmsc.monternet.com"
    mmsport="80"
    mmsproxy="10.0.0.172"
    port="80"
    proxy="10.0.0.172"
    type="mms"
/>

添加到apns-conf.xml中,重启模拟器,能识别出“中国移动”了,但依然不能上网。

此时,我想到通过对比默认情况和修改后的radio日志,来分析原因。

使用

adb logcat -b radio

命令可以查查看ril相关的日志。接着,就发现了如下两条日志:

D/DCT ( 1739): [0]createAllApnList: selection=numeric = ‘460000’ D/DCT ( 1739): [0]createAllApnList: No APN found for carrier: 460000

注意到这里的460000,正确的应该是46000,多了一个0。由于美国的MNC是3个字符,而中国的MNC是2个字符,所以导致这里多了一个字符。来看下源码在这里是怎么实现的。

/frameworks/opt/telephony/src/java/com/android/internal/telephony/dataconnection/DcTracker.java

代码语言:javascript
复制
/**
 * Based on the sim operator numeric, create a list for all possible
 * Data Connections and setup the preferredApn.
 */
private void createAllApnList() {
    mAllApnSettings = new ArrayList<ApnSetting>();
    IccRecords r = mIccRecords.get();
    String operator = (r != null) ? r.getOperatorNumeric() : "";
    if (operator != null) {
        String selection = "numeric = '" + operator + "'";
        // query only enabled apn.
        // carrier_enabled : 1 means enabled apn, 0 disabled apn.
        // selection += " and carrier_enabled = 1";
        if (DBG) log("createAllApnList: selection=" + selection);

        Cursor cursor = mPhone.getContext().getContentResolver().query(
                Telephony.Carriers.CONTENT_URI, null, selection, null, null);

        if (cursor != null) {
            if (cursor.getCount() > 0) {
                mAllApnSettings = createApnList(cursor);
            }
            cursor.close();
        }
    }

    addEmergencyApnSetting();

    dedupeApnSettings();

    if (mAllApnSettings.isEmpty()) {
        if (DBG) log("createAllApnList: No APN found for carrier: " + operator);
        mPreferredApn = null;
        // TODO: What is the right behavior?
        //notifyNoData(DataConnection.FailCause.MISSING_UNKNOWN_APN);
    } else {
        mPreferredApn = getPreferredApn();
        if (mPreferredApn != null && !mPreferredApn.numeric.equals(operator)) {
            mPreferredApn = null;
            setPreferredApn(-1);
        }
        if (DBG) log("createAllApnList: mPreferredApn=" + mPreferredApn);
    }
    if (DBG) log("createAllApnList: X mAllApnSettings=" + mAllApnSettings);

    setDataProfilesAsNeeded();
}

getOperatorNumeric的实现是在/frameworks/opt/telephony/src/java/com/android/internal/telephony/uicc/SIMRecords.java中。

代码语言:javascript
复制
@Override
public String getOperatorNumeric() {
    if (mImsi == null) {
        log("getOperatorNumeric: IMSI == null");
        return null;
    }
    if (mMncLength == UNINITIALIZED || mMncLength == UNKNOWN) {
        log("getSIMOperatorNumeric: bad mncLength");
        return null;
    }

    // Length = length of MCC + length of MNC
    // length of mcc = 3 (TS 23.003 Section 2.2)
    return mImsi.substring(0, 3 + mMncLength);
}

可见,numeric的长度是受mMncLength控制的,而mMncLength的值是从SIM卡中读出来的。

代码语言:javascript
复制
case EVENT_GET_AD_DONE:
    try {
        isRecordLoadResponse = true;

        ar = (AsyncResult)msg.obj;
        data = (byte[])ar.result;

        if (ar.exception != null) {
            break;
        }

        log("EF_AD: " + IccUtils.bytesToHexString(data));

        if (data.length < 3) {
            log("Corrupt AD data on SIM");
            break;
        }

        if (data.length == 3) {
            log("MNC length not present in EF_AD");
            break;
        }

        mMncLength = data[3] & 0xf;
        log("setting4 mMncLength=" + mMncLength);

        if (mMncLength == 0xf) {
            mMncLength = UNKNOWN;
            log("setting5 mMncLength=" + mMncLength);
        }
    }

最终定位到模拟器中的external/qemu/android/telephony/sim_card.c中。

代码语言:javascript
复制
    { "+CRSM=192,28589,0,0,15", "+CRSM: 144,0,000000046fad04000aa0aa01020000" },
    { "+CRSM=176,28589,0,0,4",  "+CRSM: 144,0,00000003" },

00000003改成00000002,重新编译,运行,终于可以正常上网了。

0x02 解决更新问题

此时,对于新创建的模拟器已经正常了,但是对于存量模拟器,由于telephony.db数据库中的carriers表中的数据没有更新,因此重启后还是不能上网。

查看carriers表中的内容可以在adb shell中执行命令: content query --uri content://telephony/carriers

/packages/providers/TelephonyProvider/src/com/android/providers/telephony/TelephonyProvider.java

代码语言:javascript
复制
@Override
public void onCreate(SQLiteDatabase db) {
    if (DBG) log("dbh.onCreate:+ db=" + db);
    createSimInfoTable(db);
    createCarriersTable(db);
    initDatabase(db);
    if (DBG) log("dbh.onCreate:- db=" + db);
}

private void initDatabase(SQLiteDatabase db) {
    if (VDBG) log("dbh.initDatabase:+ db=" + db);
    // Read internal APNS data
    Resources r = mContext.getResources();
    XmlResourceParser parser = r.getXml(com.android.internal.R.xml.apns);
    int publicversion = -1;
    try {
        XmlUtils.beginDocument(parser, "apns");
        publicversion = Integer.parseInt(parser.getAttributeValue(null, "version"));
        loadApns(db, parser);
    } catch (Exception e) {
        loge("Got exception while loading APN database." + e);
    } finally {
        parser.close();
    }

    // Read external APNS data (partner-provided)
    XmlPullParser confparser = null;
    // Environment.getRootDirectory() is a fancy way of saying ANDROID_ROOT or "/system".
    File confFile = new File(Environment.getRootDirectory(), PARTNER_APNS_PATH);
    File oemConfFile =  new File(Environment.getOemDirectory(), OEM_APNS_PATH);
    if (oemConfFile.exists()) {
        // OEM image exist APN xml, get the timestamp from OEM & System image for comparison
        long oemApnTime = oemConfFile.lastModified();
        long sysApnTime = confFile.lastModified();
        if (DBG) log("APNs Timestamp: oemTime = " + oemApnTime + " sysTime = "
                + sysApnTime);

        // To get the latest version from OEM or System image
        if (oemApnTime > sysApnTime) {
            if (DBG) log("APNs Timestamp: OEM image is greater than System image");
            confFile = oemConfFile;
        }
    } else {
        // No Apn in OEM image, so load it from system image.
        if (DBG) log("No APNs in OEM image = " + oemConfFile.getPath() +
                " Load APNs from system image");
    }

    FileReader confreader = null;
    if (DBG) log("confFile = " + confFile);
    try {
        confreader = new FileReader(confFile);
        confparser = Xml.newPullParser();
        confparser.setInput(confreader);
        XmlUtils.beginDocument(confparser, "apns");

        // Sanity check. Force internal version and confidential versions to agree
        int confversion = Integer.parseInt(confparser.getAttributeValue(null, "version"));
        if (publicversion != confversion) {
            throw new IllegalStateException("Internal APNS file version doesn't match "
                    + confFile.getAbsolutePath());
        }

        loadApns(db, confparser);
    } catch (FileNotFoundException e) {
        // It's ok if the file isn't found. It means there isn't a confidential file
        // Log.e(TAG, "File not found: '" + confFile.getAbsolutePath() + "'");
    } catch (Exception e) {
        loge("Exception while parsing '" + confFile.getAbsolutePath() + "'" + e);
    } finally {
        try { if (confreader != null) confreader.close(); } catch (IOException e) { }
    }
    if (VDBG) log("dbh.initDatabase:- db=" + db);

}

private DatabaseHelper mOpenHelper;

private void restoreDefaultAPN(int subId) {
    SQLiteDatabase db = mOpenHelper.getWritableDatabase();

    try {
        db.delete(CARRIERS_TABLE, null, null);
    } catch (SQLException e) {
        loge("got exception when deleting to restore: " + e);
    }
    setPreferredApnId((long)-1, subId);
    mOpenHelper.initDatabase(db);
}

可以看出,在restoreDefaultAPN的时候可以重新初始化CARRIERS_TABLE。也就说,只要进入APN界面,点击右上角菜单 => 重置为默认设置,就可以解决存量设备的上网问题了。

0x03 解决方法总结

  1. 修改模拟器源码modem.c中的MCC和MNC
  2. 修改模拟器源码sim_card.c中控制mMncLength的值
  3. 修改Android镜像中的/system/etc/apns-conf.xml,添加国内运营商的配置信息
  4. 存量设备更新APN
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2018-01-25,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 0x00 前言
  • 0x01 问题定位
  • 0x02 解决更新问题
  • 0x03 解决方法总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档