## 查询ue标识信息命令行设计 ### mp2要求 `mp2`要求`mep`可调用相关接口,通过`upf`查询`ue`的标识信息,mep->upf会下发`mepId`、`ueIdType`和`ueIdValue`,均为`string`类型,`ueIdType`可以为`ipaddress`、`supi`、`gpsi`或者`pei`,其中`ipaddress`为`ue`的原始`ip`地址,也就是`nat`前的`ip`地址;upf->mep会返回查询到`ue`的`ipaddress`、`supi`、`gpsi`和`pei`,同样为字符串类型。 ### upf支持的状态 目前,因为我们核心网的限制,`upf`中只存储了`ipaddress`和`supi`信息,所以暂时只能支持通过`ipaddress`和`supi`去查询`ue`的标识信息;由于`mepId`对`upf`来说没有意义,所以在`upf`这边的命令行设计上,不下发该参数,该参数可在`vpp agent`端存储。 ### 代码流程图 ![ue_information_0](../../../_static/ue_information_0.png) ### 命令行格式设计 ```c VLIB_CLI_COMMAND(show_ue_command, static) = { .path = "show upf ue_information", .short_help = "show upf ue_information ueIdType ueIdValue ", .function = show_ue_command_fn, }; ``` `ueIdType`和`ueIdValue`均为必选项,且是`string`类型,根据规范,`ueIdType`可以为`ipaddress`、`supi`、`gpsi`和`pei`,但目前代码实现并不支持通过`gpsi`和`pei`查询。 ### 对下发参数的判定 #### 判定有无参数下发 ```c if (!unformat_user(input, unformat_line_input, line_input)) { return clib_error_return(0, "no parameter", format_unformat_error); } ``` 在程序最开始先判断下发参数是否为空,若为空则返回“no parameter”的报错。 #### 判断下发参数是否合法 ```c while (unformat_check_input(line_input) != UNFORMAT_END_OF_INPUT) { if (unformat(line_input, "ueIdType %s", &ueidtype)) { for (i = IPADDRESS; i < MAX_TYPE_NUM; i++) { if (0 == strncmp(ue_type_collection[i], (const char *)ueidtype, strlen((const char *)ueidtype))) { ueidtype_flag = i; break; } } if (0 == ueidtype_flag) { return clib_error_return(0, "ueIdType error", format_unformat_error); } } else if (unformat(line_input, "ueIdValue %s", &ueidvalue)) { ueidvalue_flag = 1; } else { return clib_error_return(0, "unknown input '%U'", format_unformat_error, line_input); } } ``` 判断是否下发了`ueIdType`和`ueIdValue`两个参数类型,若输入的不是此参数,则返回“unknown input”的报错;对`ueIdType`具体下发的值,也要判断是否为`ipaddress`、`supi`、`gpsi`和`pei`中的一项,否则返回“ueIdType error”的报错;若下发了`ueIdType`参数,则`ueidtype_flag`置位为具体的下发类型,若下发了`ueIdValue`参数,则`ueidvalue_flag`置位为1。 ```c if (ueidtype_flag && ueidvalue_flag) { if (IPADDRESS == ueidtype_flag) { ... } else if (SUPI == ueidtype_flag) { ... } else if (GPSI == ueidtype_flag) { return clib_error_return(0, "not support gpsi", format_unformat_error); } else if (PEI == ueidtype_flag) { return clib_error_return(0, "not support pei", format_unformat_error); } } else { return clib_error_return(0, "missing parameter", format_unformat_error); } ``` 只有`ueidtype_flag`和`ueidvalue_flag`同时置位,才进行后续的查找`ue`信息流程,否则返回“missing parameter”的错误;若`ueidtype_flag`置位为`GPSI`或者`PEI`,则返回不支持此类型的错误。 ### 通过ipaddress或者supi查询ue信息 ```c if (IPADDRESS == ueidtype_flag) { if (ipv4_to_u32(ueidvalue, &ipv4_addr)) /* 将传入ip由字符串类型转化为u32类型 */ { session = sx_lookup_by_ue_ipv4(ipv4_addr); /* 通过ipaddress查找session */ } } else if (SUPI == ueidtype_flag) { session = sx_lookup_by_imsi((u8 *)ueidvalue); /* 通过supi查找session */ } ``` 在`upf`初始化时,已经创建了基于`ipaddress`和`supi`的`hash`表(见下文说明),可通过这两张表查找到对应的`upf session`,我们需要的信息都在`upf session`结构体中,将需要的信息拷贝出打印即可。 ```c if (session) { if (ip46_address_is_ip4 (&session->ue_address)) { snprintf(ue.ipaddress, MAX_IP_LEN, "%hhu.%hhu.%hhu.%hhu", session->ue_address.ip4.data[0], session->ue_address.ip4.data[1], session->ue_address.ip4.data[2], session->ue_address.ip4.data[3]); } else { return clib_error_return(0, "not support ipv6", format_unformat_error); } strncpy(ue.supi, (const char *)session->user_id.imsi_str, strlen((const char *)session->user_id.imsi_str)); strncpy(ue.gpsi, (const char *)session->user_id.msisdn, session->user_id.msisdn_len); strncpy(ue.pei, (const char *)session->user_id.imei, session->user_id.imei_len); vlib_cli_output (vm, "ipaddress = %s supi = %s gpsi = %s pei = %s\n", ue.ipaddress, ue.supi, ue.gpsi, ue.pei); } else { return clib_error_return(0, "lookup error", format_unformat_error); } ``` 若查找到`session`,将需要的信息拷贝到`struct ue_information ue`这个变量中,并打印出来;若找不到对应的`session`,则返回“lookup error”的报错。 ### ipaddress的hash表 #### hash的创建 在`upf_init`函数中创建: ```c sm->session_by_ue_ipv4 = hash_create(0, sizeof(uword)); ``` 因为此处hash的key为u32类型的ip地址,所以选用hash_create函数,第一个参数为哈希桶的长度,第二个参数为存储value的字节数,设置为sizeof(uword),代码中uword为u32的别名,所以这里设置为四字节。 #### 节点的添加 在`build_sx_rules`函数中添加节点: ```c if (pdr->pdi.ue_addr.flags & IE_UE_IP_ADDRESS_V4) { sx->ue_address.ip4.as_u32 = pdr->pdi.ue_addr.ip4.as_u32; hash_set(gtm->session_by_ue_ipv4, ntohl(sx->ue_address.ip4.as_u32), sx - gtm->sessions); } ``` 获取了`ue ip`的地址,经过`ntohl`转换后,作为此`session`的`key`,`value`为`sx - gtm->sessions`,指向当前的`session`。 #### 节点的查找 ```c sx_lookup_by_ue_ipv4 (u32 ue_ipv4) { upf_main_t *gtm = &upf_main; uword *p = NULL; upf_debug("ue_ipv4 = %u\n", ue_ipv4); p = hash_get(gtm->session_by_ue_ipv4, ue_ipv4); if (!p) return NULL; else return pool_elt_at_index (gtm->sessions, p[0]); } ``` 传入`u32`类型的`ip`值作为key去查找,调用`hash_get`获取对应的`session`。 #### 节点的删除 `sx_disable_session`函数中,对`session`进行了释放,同时将此`session`从`ipaddress`的`hash`表中移除: ```c if (sx->ue_address.ip4.as_u32) { hash_unset(gtm->session_by_ue_ipv4, ntohl(sx->ue_address.ip4.as_u32)); } ``` 调用`hash_unset`函数删除该节点。 ### supi的hash表 与`ipaddress`的`hash`表操作类似,只是因为这里的`key`为`string`类型的`supi`,所以使用接口不一样。 #### hash的创建 在`upf_init`函数中创建: ```c sm->session_by_imsi = hash_create_string (0, sizeof (uword)); ``` #### 节点的添加 在`handle_session_establishment_request`函数中添加节点: ```c hash_set_mem (gtm->session_by_imsi, sess->user_id.imsi_str, sess - gtm->sessions); ``` #### 节点的查找 在`sx_lookup_by_imsi`函数中查找节点: ```c p = hash_get_mem (gtm->session_by_imsi, imsi); ``` #### 节点的删除 `sx_disable_session`函数中,删除节点: ```c hash_unset_mem (gtm->session_by_imsi, sx->user_id.imsi_str); ``` ### 测试验证 #### 环境说明 在管理`ip`为`172.18.22.216`的虚拟机上运行`upf`服务,`landslide`模拟`ue`、`smf`和`基站`,`upf`的配置文件如下: ``` startup.conf: heapsize 2G unix { # nodaemon cli-listen /run/vpp/cli.sock log /var/log/vpp/vpp.log full-coredump gid vpp exec /etc/vpp/upf.conf } api-trace { on } logging { default-syslog-log-level info unthrottle-time 1 } api-segment { gid vpp } upf { white-list-conf /etc/vpp/white_list_conf.xml grey-list-conf /etc/vpp/grey_list_conf.xml http-header-enrichment-conf /etc/vpp/http_header_enrichment_conf.xml https-header-enrichment-conf /etc/vpp/https_header_enrichment_conf.xml # predef-conf /etc/vpp/predef_conf.xml } buffers { buffers-per-numa 32768 } statseg { size 256m } socksvr { default } cpu { main-core 0 #corelist-pfcp-threads 1 #corelist-workers 2 workers 2 pfcp-threads 1 } dpdk { dev 0000:00:03.0 { num-rx-queues 1 } dev 0000:00:0a.0 { num-rx-queues 1 } dev 0000:00:0b.0 { num-rx-queues 1 } } plugins { plugin gtpu_plugin.so { disable } plugin ndpi_plugin.so { disable } } dns { max-ttl 120 } ``` ``` upf.conf: upf enable-disable upf log level debug upf feature FTUP support set ip6 address TenGigabitEthernet0/a/0 2000:200:600::1/64 ip6 nd TenGigabitEthernet0/a/0 ra-suppress set int state TenGigabitEthernet0/3/0 up set int state TenGigabitEthernet0/a/0 up set int state GigabitEthernet0/b/0 up set interface mtu ip4 1500 TenGigabitEthernet0/3/0 set interface mtu ip4 9000 GigabitEthernet0/b/0 set interface mtu ip6 9000 TenGigabitEthernet0/a/0 set interface reassembly TenGigabitEthernet0/3/0 on set interface reassembly TenGigabitEthernet0/a/0 on set interface reassembly GigabitEthernet0/b/0 on set int ip address GigabitEthernet0/b/0 192.168.4.100/24 set int ip address TenGigabitEthernet0/3/0 192.168.3.100/24 set int ip address TenGigabitEthernet0/a/0 192.168.6.100/24 ip route add 0.0.0.0/0 table 0 via 192.168.6.110 TenGigabitEthernet0/a/0 ip route add ::/0 table 0 via 2000:200:600::75 TenGigabitEthernet0/a/0 upf pfcp endpoint ip 192.168.4.100 vrf 0 upf nwi name epc vrf 0 upf gtpu endpoint ip 192.168.4.100 intf cp nwi epc TEID-Range-Indication 4 upf gtpu endpoint ip 192.168.3.100 intf access nwi epc TEID-Range-Indication 4 upf pfd-list appid 111 fd {permit out 17 from any 3333 to 70.70.70.1/24 2222} upf pfd-list appid 112 fd {permit out 17 from any 5555 to 70.70.70.1/24 4444} upf pfd-list appid 110 url {www.baidu.com} upf pre-def rule rule_name activf app_id 111 upf dnn name default.mnc092.mcc466.gprs rule_id activf upf pre-def rule rule_name active app_id 112 upf dnn name default.mnc092.mcc466.gprs rule_id active ``` #### 测试用例1-session创建后查询ue信息 1. 使用`landslide`发送报文: ![ue_information_1](../../../_static/ue_information_1.png) 2. 查询`upf session`信息: ![ue_information_2](../../../_static/ue_information_2.png) ![ue_information_3](../../../_static/ue_information_3.png) 可看到刚创建`session`的`ue_ip`为`70.70.70.1`,`IMSI`(`supi`)为`466920100001101`。 3. 使用命令查询`ue`信息: ![ue_information_4](../../../_static/ue_information_4.png) 可看到使用该命令可通过`ipaddress`或者`supi`查询到对应的`ue`信息。 #### 测试用例2-session删除后查询ue信息 1. `landslide`停止发送报文: ![ue_information_5](../../../_static/ue_information_5.png) 2. 查询`upf session`信息: ![ue_information_6](../../../_static/ue_information_6.png) 可看到`session`已被删除。 3. 使用命令查询ue信息: ![ue_information_7](../../../_static/ue_information_7.png) `session`删除后,使用该命令不能再查到,返回查找失败的错误。 #### 测试用例3-输入参数有误返回报错 1. 输入参数为空: ![ue_information_8](../../../_static/ue_information_8.png) 2. 参数类型不正确: ![ue_information_9](../../../_static/ue_information_9.png) 参数类型必须为`ueIdType`和`ueIdValue`。 3. 输入的参数类型值不对: ![ue_information_10](../../../_static/ue_information_10.png) `ueIdType`的值必须为`ipaddress`、`supi`、`gpsi`或者`pei`。 4. 漏输参数: ![ue_information_11](../../../_static/ue_information_11.png) `ueIdType`和`ueIdValue`缺一不可。 5. 输入了不支持的值: ![ue_information_12](../../../_static/ue_information_12.png) 暂不支持通过`gpsi`或者`pei`查询`ue`信息。