APN配置流程详解

本文代码基于Android9.0
APN的完整说明在3GPP规范TS23.003 Clause 9中进行了详细定义
MCCMNC的定义在3GPP规范TS23.003 Clause 2的IMSI定义中
MCC 移动设备国家代码 (Mobile country code)
MNC 移动设备网络代码/运营商 (Mobile Network Code)

概述

APN(Access Point Name),即“接入点名称”,用来标识GPRS的业务种类,APN在GPRS骨干网中用来标识要使用的外部PDN(Packet data network,分组数据网,即常说的Internet),在GPRS网络中代表外部数据网络的总称。

结构

APN由以下两部分组成:

  • APN网络标识(APN Network Identifier):

是用户通过GGSN/PGW(Gateway GPRS Support Node,GPRS网关支持节点/PDN Gateway ,分组数据网网关)可连接到外部网络的标识,该标识由网络运营者分配给ISP(Internet Service Provider,因特网业务提供者)或公司,与其固定Internet域名一致,是APN的必选组成部分。例如 , 定义移动用户通过该接入某公司的企业网,则APN的网络标识可以规划为“www.ABC123.com"。

  • APN运营者标识(APN Operator Identifier):

用于标识GGSN/PGW所归属的网络,是APN的可选组成部分。其形式为“MNCxxxx.MCCyyyy.gprs”(3G网络中),或者“MNCxxxx.MCCyyyy.3gppnetwork.org(4G网络中)。APN实际上就是对一个外部PDN的标识,这些PDN包括企业内部网、Internet、WAP网站、行业内部网等专用网络。

Android系统中APN的配置

Android系统的APN配置文件:frameworks/base/core/res/res/xml/apns.xml

第三方的APN配置文件:device/generic/goldfish/data/etc/apns-conf.xml

在编译该product(goldfish)时会将device/generic/goldfish/data/etc/apns-conf.xml文件拷贝到system/etc/目录下,最后打包到system.img中

Apn参数解析

下面是配置一条APN信息的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<apn carrier="中国移动彩信 (China Mobile)"
mcc="460"
mnc="00"
apn="cmwap"
proxy="10.0.0.172"
port="80"
mmsc="http://mmsc.monternet.com"
mmsproxy="10.0.0.172"
mmsport="80"
user="mms"
password="mms"
type="mms"
authtype="1"
protocol="IPV4V6"
/>

其对应的属性含义如下:

参数 作用
Carrier apn的名字,可为空,只用来显示apn列表中此apn的显示名字。
Mcc 由三位数组成。 用于识别移动用户的所在国家;
Mnc 由两位或三位组成。 用于识别移动用户的归属PLMN。 MNC的长度(两位或三位数)取决于MCC的值。
Apn APN网络标识(接入点名称),是APN参数中的必选组成部分。此标识由运营商分配。
Proxy 代理服务器的地址
Port 代理服务器的端口号
Mmsc MMS中继服务器/多媒体消息业务中心,是彩信的交换服务器。
Mmsproxy 彩信代理服务器的地址
Mmsport 彩信代理服务器的端口号
Protocol 支持的协议,不配置默认为IPV4。
User 用户
Password 密码
Authtype apn的认证协议,PAP为口令认证协议,是二次握手机制。CHAP是质询握手认证协议,是三次握手机制。

最后一条认证协议中三次握手:None(0)、PAP(1)、CHAP(2)、PAP or CHAP(3)

APN接入点类型

类型 作用
Default 默认网络连接
Mms 彩信专用连接,此连接与default类似,用于与载体的多媒体信息服务器对话的应用程序
Supl 是Secure User Plane Location“安全用户面定位”的简写,此连接与default类似,用于帮助定位设备与载体的安全用户面定位服务器对话的应用程序
Dun Dial Up Networking拨号网络的简称,此连接与default连接类似,用于执行一个拨号网络网桥,使载体能知道拨号网络流量的应用程序
Hipri 高优先级网络,与default类似,但路由设置不同。只有当进程访问移动DNS服务器,并明确要求使用requestRouteToHost(int, int)才会使用此连接

此表中的数据连接优先级是由低到高,即default数据连接的优先级最低,而hipri数据连接的优先级最高。比如:手机上网聊天,建立的是default数据连接。如果此时接到一条彩信,由于彩信的数据连接是mms,优先级比default高,所以会先断开default数据连接,建立mms数据连接,让手机先收到彩信。所以收发彩信的同时不能上网。(单条pdp连接的情况)

mnc的位数由mcc决定。比如,墨西哥334020,此国家的mnc为020,mccmnc的值都固定在了SIM卡保存的IMSI中,配置apn参数时mnc不可简洁为20,否则apn列表中将读取不到此国家的334020运营商的参数。

Android系统中的APN

apn在手机中的存储

apn文件:System/etc/apn-conf.xml

apn数据存储的数据库:/data/data/com.android.providers.telephony/databases/ telephony.db Carriers表

apn的初始化

在启动手机时,需要初始化telephony.db数据库,这时候会读取手机目录System/etc/apn-conf.xml并把其中的内容加入到Carriers表中。以后查询有关apn的配置参数都是从Carriers表中取出。

创建并初始化Carriers表:

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

内部类:DatabaseHelper.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
//创建数据库
//由于添加了UNIQUE 约束条件,如果两个差不多的apn参数满足约束条件内的属性都相等,那么认为是同一组apn参数,将不会重复插入到数据库。
@VisibleForTesting
public static String getStringForCarrierTableCreation(String tableName) {
return "CREATE TABLE " + tableName +
"(_id INTEGER PRIMARY KEY," +
NAME + " TEXT DEFAULT ''," +
NUMERIC + " TEXT DEFAULT ''," +
MCC + " TEXT DEFAULT ''," +
MNC + " TEXT DEFAULT ''," +
APN + " TEXT DEFAULT ''," +
USER + " TEXT DEFAULT ''," +
SERVER + " TEXT DEFAULT ''," +
PASSWORD + " TEXT DEFAULT ''," +
PROXY + " TEXT DEFAULT ''," +
PORT + " TEXT DEFAULT ''," +
MMSPROXY + " TEXT DEFAULT ''," +
MMSPORT + " TEXT DEFAULT ''," +
MMSC + " TEXT DEFAULT ''," +
AUTH_TYPE + " INTEGER DEFAULT -1," +
TYPE + " TEXT DEFAULT ''," +
CURRENT + " INTEGER," +
PROTOCOL + " TEXT DEFAULT " + DEFAULT_PROTOCOL + "," +
ROAMING_PROTOCOL + " TEXT DEFAULT " + DEFAULT_ROAMING_PROTOCOL + "," +
CARRIER_ENABLED + " BOOLEAN DEFAULT 1," +
BEARER + " INTEGER DEFAULT 0," +
BEARER_BITMASK + " INTEGER DEFAULT 0," +
NETWORK_TYPE_BITMASK + " INTEGER DEFAULT 0," +
MVNO_TYPE + " TEXT DEFAULT ''," +
MVNO_MATCH_DATA + " TEXT DEFAULT ''," +
SUBSCRIPTION_ID + " INTEGER DEFAULT "
+ SubscriptionManager.INVALID_SUBSCRIPTION_ID + "," +
PROFILE_ID + " INTEGER DEFAULT 0," +
MODEM_COGNITIVE + " BOOLEAN DEFAULT 0," +
MAX_CONNS + " INTEGER DEFAULT 0," +
WAIT_TIME + " INTEGER DEFAULT 0," +
MAX_CONNS_TIME + " INTEGER DEFAULT 0," +
MTU + " INTEGER DEFAULT 0," +
EDITED + " INTEGER DEFAULT " + UNEDITED + "," +
USER_VISIBLE + " BOOLEAN DEFAULT 1," +
USER_EDITABLE + " BOOLEAN DEFAULT 1," +
OWNED_BY + " INTEGER DEFAULT " + OWNED_BY_OTHERS + "," +
APN_SET_ID + " INTEGER DEFAULT " + NO_SET_SET + "," +
// Uniqueness collisions are used to trigger merge code so if a field is listed
// here it means we will accept both (user edited + new apn_conf definition)
// Columns not included in UNIQUE constraint: name, current, edited,
// user, server, password, authtype, type, sub_id, modem_cognitive, max_conns,
// wait_time, max_conns_time, mtu, bearer_bitmask, user_visible,
// network_type_bitmask.
"UNIQUE (" + TextUtils.join(", ", CARRIERS_UNIQUE_FIELDS) + "));";
}

读取配置文件中配置的APN信息,并将其保存到数据库中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
/**
* 此函数将xml文件中的APN添加到db。 db开头可能是空的也可能不是空的。
*/
private void initDatabase(SQLiteDatabase db) {
// 读取内部APNS数据
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();
}

// 读取外部APNS数据 (第三方合作伙伴提供)
XmlPullParser confparser = null;
File confFile = getApnConfFile();

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

// 完整性检查。 强制内部版本和机密版本达成一致
int confversion = Integer.parseInt(confparser.getAttributeValue(null, "version"));
if (publicversion != confversion) {
log("initDatabase: throwing exception due to version mismatch");
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("initDatabase: Exception while parsing '" + confFile.getAbsolutePath() + "'" +
e);
} finally {
// Get rid of user/carrier deleted entries that are not present in apn xml file.
// Those entries have edited value USER_DELETED/CARRIER_DELETED.
if (VDBG) {
log("initDatabase: deleting USER_DELETED and replacing "
+ "DELETED_BUT_PRESENT_IN_XML with DELETED");
}

// Delete USER_DELETED
db.delete(CARRIERS_TABLE, IS_USER_DELETED + " or " + IS_CARRIER_DELETED, null);

// Change USER_DELETED_BUT_PRESENT_IN_XML to USER_DELETED
ContentValues cv = new ContentValues();
cv.put(EDITED, USER_DELETED);
db.update(CARRIERS_TABLE, cv, IS_USER_DELETED_BUT_PRESENT_IN_XML, null);

// Change CARRIER_DELETED_BUT_PRESENT_IN_XML to CARRIER_DELETED
cv = new ContentValues();
cv.put(EDITED, CARRIER_DELETED);
db.update(CARRIERS_TABLE, cv, IS_CARRIER_DELETED_BUT_PRESENT_IN_XML, null);

if (confreader != null) {
try {
confreader.close();
} catch (IOException e) {
// do nothing
}
}

// 更新存储的校验和
setApnConfChecksum(getChecksum(confFile));
}
}

apn的设置

packages/apps/Settings/src/com/android/settings/ApnSettings.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
private void fillList() {
final TelephonyManager tm = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
final int subId = mSubscriptionInfo != null ? mSubscriptionInfo.getSubscriptionId()
: SubscriptionManager.INVALID_SUBSCRIPTION_ID;
final String mccmnc = mSubscriptionInfo == null ? "" : tm.getSimOperator(subId);
Log.d(TAG, "mccmnc = " + mccmnc);
StringBuilder where = new StringBuilder("numeric=\"" + mccmnc +
"\" AND NOT (type='ia' AND (apn=\"\" OR apn IS NULL)) AND user_visible!=0");

if (mHideImsApn) {
where.append(" AND NOT (type='ims')");
}

Cursor cursor = getContentResolver().query(Telephony.Carriers.CONTENT_URI, new String[] {
"_id", "name", "apn", "type", "mvno_type", "mvno_match_data"}, where.toString(),
null, Telephony.Carriers.DEFAULT_SORT_ORDER);

if (cursor != null) {
IccRecords r = null;
if (mUiccController != null && mSubscriptionInfo != null) {
r = mUiccController.getIccRecords(
SubscriptionManager.getPhoneId(subId), UiccController.APP_FAM_3GPP);
}
PreferenceGroup apnList = (PreferenceGroup) findPreference("apn_list");
apnList.removeAll();

ArrayList<ApnPreference> mnoApnList = new ArrayList<ApnPreference>();
ArrayList<ApnPreference> mvnoApnList = new ArrayList<ApnPreference>();
ArrayList<ApnPreference> mnoMmsApnList = new ArrayList<ApnPreference>();
ArrayList<ApnPreference> mvnoMmsApnList = new ArrayList<ApnPreference>();

mSelectedKey = getSelectedApnKey();
cursor.moveToFirst();
while (!cursor.isAfterLast()) {
String name = cursor.getString(NAME_INDEX);
String apn = cursor.getString(APN_INDEX);
String key = cursor.getString(ID_INDEX);
String type = cursor.getString(TYPES_INDEX);
String mvnoType = cursor.getString(MVNO_TYPE_INDEX);
String mvnoMatchData = cursor.getString(MVNO_MATCH_DATA_INDEX);

ApnPreference pref = new ApnPreference(getPrefContext());

pref.setKey(key);
pref.setTitle(name);
pref.setSummary(apn);
pref.setPersistent(false);
pref.setOnPreferenceChangeListener(this);
pref.setSubId(subId);

boolean selectable = ((type == null) || !type.equals("mms"));
pref.setSelectable(selectable);
if (selectable) {
if ((mSelectedKey != null) && mSelectedKey.equals(key)) {
pref.setChecked();
}
addApnToList(pref, mnoApnList, mvnoApnList, r, mvnoType, mvnoMatchData);
} else {
addApnToList(pref, mnoMmsApnList, mvnoMmsApnList, r, mvnoType, mvnoMatchData);
}
cursor.moveToNext();
}
cursor.close();

if (!mvnoApnList.isEmpty()) {
mnoApnList = mvnoApnList;
mnoMmsApnList = mvnoMmsApnList;

// Also save the mvno info
}

for (Preference preference : mnoApnList) {
apnList.addPreference(preference);
}
for (Preference preference : mnoMmsApnList) {
apnList.addPreference(preference);
}
}
}