BUILD 5GC

1. 目录结构 dongfeng@worker:~/work/5G/5gc-c-build$ tree . ├── build.sh ├── README.md └── source ├── 5gc-c.service ├── amf │ └── amf1 │ ├── bin │ │ ├── amf -> amf_1.0.0 │ │ ├── amf_1.0.0 │ │ ├── lo.bin │ │ └── ls.bin │ ├── config │ │ ├── amf.conf │ │ └── asn.log.properties │ ├── log │ │ └── amf.log │ └── xml │ ├── amfInfo.xml │ ├── ausf.xml │ ├── guami.xml │ ├── nrf.……

阅读全文

linux checksum

1. 关键结构 由于目前很多网卡设备是支持对L4层数据包进行校验和的计算和验证的,所以在L4协议软件的实现中, 会根据网卡的支持情况作不同的处理,为此内核在struct sk_buff结构和struct net_device中增加了校验和相关的参数,如下: struct sk_buff 上面的结构中,和校验和有关的几个字段如下: #define CHECKSUM_NONE 0 #define CHECKSUM_UNNECESSARY 1 #define CHECKSUM_COMPLETE 2 #define CHECKSUM_PARTIAL 3 struct sk_buff { union { __wsum csum; struct { __u16 csum_start; __u16 csum_offset; }; }; __u8 ip_summed:2, } 联合体中哪个成员有效取决于ip_summed的值,ip_summed共两个bit,可取四个标志,而且在发送和接收时的含义还有所不同 在接收过程中,ip_summed字段包含了设备驱动告诉L4软件当前校验和的状态,各取值含义如下: CHECKSUM_NONE:硬件没有提供校验和,可能是硬件不支持,也可能是硬件校验出错但是并未丢弃数据包,而是让L4软件重新校验 CHECKSUM_UNNECESSARY:硬件已经进行了完整的校验,无需软件再进行检查,L4收到数据包后如果检查ip_summed是这种情况,就可以跳过校验过程 CHECKSUM_COMPLETE:硬件已经校验了L4报头和其payload部分,并且校验和保存在了csum中,L4软件只需要再计算伪报头然后检查校验结果即可 在发送过程中,ip_summed字段包含了L4软件告诉设备驱动程序当前校验和的状态,各取值含义如下: CHECKSUM_NONE:L4软件已经进行了校验,硬件无需做任何事情 CHECKSUM_PARTIAL:L4软件计算了伪报头,并且将值保存在了首部的check字段中,硬件需要计算其余部分的校验和 struct net_device net_device结构中的feature字段中定义了如下和校验和相关的字段,这些字段表明了硬件计算校验和的能力 NETIF_F_NO_CSUM:该设备非常可靠,无需L4执行任何校验,环回设备一般设置该标记 NETIF_F_IP_CSUM:设备可以对基于IPv4的TCP和UDP数据包进行校验 NETIF_F_IPV6_CSUM:设备可以对基于IPv6的TCP和UDP数据包进行校验 NETIF_F_HW_CSUM: 设备可以对任何L4协议的数据包进行校验 注:这些概念和字段的含义同样适用于TCP校验和处理过程 2. 输入数据报的校验和计算 udp4_csum_init() @skb: 待校验的数据报 @uh:该数据报的UDP首部 @proto:L4协议号,为IPPROTO_UDP或者IPPROTO_UDPLITE static inline int udp4_csum_init(struct sk_buff *skb, struct udphdr *uh, int proto) { const struct iphdr *iph; int err; //这两个字段用于指示对报文的哪些部分进行校验,cov指coverage, //只有UDPLite使用,对于UDP,会对整个报文进行校验 UDP_SKB_CB(skb)->partial_cov = 0; UDP_SKB_CB(skb)->cscov = skb->len; //UDPLITE,忽略 if (proto == IPPROTO_UDPLITE) { err = udplite_checksum_init(skb, uh); if (err) return err; } iph = ip_hdr(skb); //UDP首部校验和字段为0,这种情况说明已经处理过了,设置为CHECKSUM_UNNECESSARY,后续无需再进行处理 if (uh->check == 0) { skb->ip_summed = CHECKSUM_UNNECESSARY; } else if (skb->ip_summed == CHECKSUM_COMPLETE) { //还有伪首部需要校验,所以添加伪首部校验,如果校验成功,设置为CHECKSUM_UNNECESSARY //csum_tcpudp_magic()计算伪首部校验和后进行验证,如果验证ok,返回0,该函数体系结构相关, //为了高效,用汇编语言实现 if (!……

阅读全文

二进制瘦身

1. 符号表信息和调试信息 符号表信息(symbols)和调试信息(debug info)是由不同段区分的。 使用 readelf -S binfile 可以查看ELF文件的所有段。 调试信息相关的段: # readelf -S a.out | grep debug [27] .debug_aranges PROGBITS 0000000000000000 000016d0 [28] .debug_info PROGBITS 0000000000000000 00001700 [29] .debug_abbrev PROGBITS 0000000000000000 00001a0f [30] .debug_line PROGBITS 0000000000000000 00001adb [31] .debug_str PROGBITS 0000000000000000 00001bd2 符号表相关的段: # readelf -S a.out | grep tab [32] .symtab SYMTAB 0000000000000000 00001e18 [33] .strtab STRTAB 0000000000000000 00002670 [34] .shstrtab STRTAB 0000000000000000 00002a8f 注: 下文中提及的符号表相关段将不包括 .……

阅读全文

EXPORT SYMBOL

1. 背景 EXPORT_SYMBOL只出现在2.6内核中,在2.4内核默认的非static 函数和变量都会自动导入到kernel 空间的, 都不用EXPORT_SYMBOL() 做标记的。2.6就必须用EXPORT_SYMBOL() 来导出来(因为2.6默认不导出所有的符号) 2. EXPORT_SYMBOL的作用 EXPORT_SYMBOL标签内定义的函数或者符号对全部内核代码公开, 不用修改内核代码就可以在您的内核模块中直接调用,即使用EXPORT_SYMBOL可以将一个函数以符号的方式导出给其他模块使用 EXPORT_SYMBOL(符号名); 可以导出static函数到符号表中 EXPORT_SYMBOL_GPL(符号名) //用于GPL协议认证的模块 EXPORT_SYMBOL_GPL的符号必须要用MODULE_LICENSE("GPL")或者用MODULE_LICENSE("Dual BSD/GPL")之后才能在模块中引用。 而且MODULE_LICENSE("char")中的char不可以是任意字符,否则错误和没有MODULE_LICENSE效果一样。 这里要和System.map做一下对比: System.map 中的是连接时的函数地址。连接完成以后,在2.6内核运行过程中,是不知道哪个符号在哪个地址的。 EXPORT_SYMBOL 的符号,是把这些符号和对应的地址保存起来,在内核运行的过程中,可以找到这些符号对应的地址。 而模块在加载过程中,其本质就是能动态连接到内核,如果在模块中引用了内核或其它模块的符号, 就要EXPORT_SYMBOL这些符号,这样才能找到对应的地址连接 3. 使用方法 在模块函数定义之后使用EXPORT_SYMBOL(函数名) 在掉用该函数的模块中使用extern对之声明 首先加载定义该函数的模块,再加载调用该函数的模块 另外,在编译调用某导出函数的模块时,往往会有WARNING: “****” [**********] undefined! 使用dmesg命令后会看到相同的信息。开始我以为只要有这个错误就不能加载模块,后来上网查了一下, 发现这主要是因为在编译连接的时候还没有和内核打交道,当然找不到symbol了,但是由于你生成的是一个内核模块, 所以LD不提示error,而是给出一个warning,寄希望于在insmod的时候,内核能够把这个symbol连接上 1.EXPORT_SYMBOL EXPORT_SYMBOL( my_pub_func); 在预编译阶段会解析为: extern void *__crc_my_pub_func __attribute__((weak)); static const unsigned long __kcrctab_my_pub_func __attribute__((__used__)) __attribute__((section("__kcrctab" ""), unused)) = (unsigned long) &__crc_my_pub_func; static const char __kstrtab_my_pub_func[] __attribute__((section("__ksymtab_strings"))) = "" "my_pub_func"; static const struct kernel_symbol __ksymtab_my_pub_func __attribute__((__used__)) __attribute__((section("__ksymtab" ""), unused)) = { (unsigned long)&my_pub_func, __kstrtab_my_pub_func }; 很显然__ksymtab_my_pub_func存储了my_pub_func的地址和符号信息,该符号对应的地址 只有insmod后才会确定; __ksymtab_my_pub_func会链接到__ksymtab section,__ksymtab section中的所有内容就构成了 内核"导出"的符号表,这个表在insmod 时候会用到.……

阅读全文

vpp和linux内核协议栈通信

1. 背景介绍 基于VPP开发一款网络设备,除使用VPP框架实现端口数据报文转发外,还需要对设备进行配置和管理, 比较常用的是要实现ssh、web等相关服务,另外,由于VPP并未实现dhcp server的功能,控制面可能还需移植isc-dhcp类似的dhcp server。 相关的用户态程序是基于Linux内核的socket来收发包的,因此,需要实现VPP和Linux内核协议栈通信的方法, 可以将访问控制面的报文由VPP上送至Linux协议的协议栈。 2. 实现原理 VPP和Linux内核协议栈通信有两种方法,分别是基于Linux的TAP/TUN和VETH两种机制, 对这两种机制不了解的朋友,建议先阅读下如下两个衔接的内容: TUN/TAP:https://segmentfault.com/a/1190000009249039 VETH:https://segmentfault.com/a/1190000009251098 基于TAP/TUN的方法: 如上图,PC的地址是192.168.1.2,Linux Kernel的管理接口地址是192.168.1.1,需要PC通过VPP可以ping同Linux内核的192.168.1.1地址 技术原理图如上,物理网卡对应VPP中的GE4/0/0,VPP创建tap接口,使用l2 bridge机制将GE4/0/0和tap桥接,进入GE4/0/0的报文通过l2 bridge转发到TAP, VPP的TAP相当于用户态进程,另一端对应的是Linux内核中的TAP接口,报文通过TAP机制重入到Linux内核,实现了VPP到控制面的通信 实现命令: vpp#set int l2 bridge GigabitEthernet4/0/0 1 vpp#set int state GigabitEthernet4/0/0 up vpp#tap connect lstack vpp#set int l2 bridge tap-0 1 vpp#set int state tap-0 up 上述操作完成后,在linux后台,ifconfig发现多了一个lstack接口,给lstack接口配置好IP地址,就可以实现PC和设备的通信了。 基于VETH方法: VETH技术原理如上图,通过在vpp上创建host-interface的方式实现,使用VETH方式的效率更高,实际使用时推荐此种方法 配置步骤如下 linux后台配置: ~# ip netns add ns0 ~# ip link add vpp0 type veth peer name vethns0 ~# ip link set vethns0 netns ns0 ~# ip netns exec ns0 ip link set lo up ~# ip netns exec ns0 ip link set vethns0 up ~# ip netns exec ns0 ip addr add 192.……

阅读全文

xfrm框架

1. 简介 IPsec协议帮助IP层建立安全可信的数据包传输通道。 当前已经有了如StrongSwan、OpenSwan等比较成熟的解决方案,而它们都使用了Linux内核中的XFRM框架进行报文接收发送. XFRM的正确读音是transform(转换), 这表示内核协议栈收到的IPsec报文需要经过转换才能还原为原始报文; 同样地,要发送的原始报文也需要转换为IPsec报文才能发送出去. 2. XFRM实例 IPsec中有两个重要概念: 安全关联(Security Association)和安全策略(Security Policy),这两类信息都需要存放在内核XFRM。 核XFRM使用netns_xfrm这个结构来组织这些信息,它也被称为xfrm instance(实例)。 从它的名字也可以看出来,这个实例是与network namespace相关的,每个命名空间都有这样的一个实例,实例间彼此独立。 所以同一台主机上的不同容器可以互不干扰地使用XFRM struct net { ...... #ifdef CONFIG_XFRM struct netns_xfrm xfrm; #endif ...... } 3. Netlink通道 上面提到了Security Association和Security Policy信息,这些信息一般是由用户态IPsec进程(eg. StrongSwan) 下发到内核XFRM的,这个下发的通道在network namespace初始化时创建。 static int __net_init xfrm_user_net_init(struct net *net) { struct sock *nlsk; struct netlink_kernel_cfg cfg = { .groups = XFRMNLGRP_MAX, .input = xfrm_netlink_rcv, }; nlsk = netlink_kernel_create(net, NETLINK_XFRM, &cfg); ...... return 0; } 这样,当用户下发IPsec配置时,内核便可以调用 xfrm_netlink_rcv() 来接收.……

阅读全文

oom killer

1. redis报错 MISCONF Redis is configured to save RDB snapshots, but is currently not able to persist on disk. Commands that may modify the data set are disabled. Please check Redis logs for details about the error. redis数据不能写入磁盘了 修正方式: 1.改redis-conf配置文件中的 stop-writes-on-bgsave-error 为 no,保证redis正常运行 2.修改/etc/sysctl.conf 中vm.overcommit_memory 的值为 1,再使用sysctl -p使修改生效,然后重启redis overcommit_memory=0, 表示内核将检查是否有足够的可用内存供应用进程使用;如果有足够的可用内存,内存申请允许;否则,内存申请失败,并把错误返回给应用进程。 overcommit_memory=1, 表示内核允许分配所有的物理内存,而不管当前的内存状态如何。 overcommit_memory=2, 表示内核允许分配超过所有物理内存和交换空间总和的内存 3.查看磁盘占用情况,检查磁盘是否写满 4.查看内存占用,检查是否配额充足,有可能进程被oom-killer干掉了 2. oom-killer Linux 内核有个机制叫OOM killer(Out Of Memory killer),该机制会监控那些占用内存过大, 尤其是瞬间占用内存很快的进程,然后防止内存耗尽而自动把该进程杀掉。 内核检测到系统内存不足、挑选并杀掉某个进程的过程可以参考内核源代码linux/mm/oom_kill.c, 当系统内存不足的时候,out_of_memory()被触发,然后调用select_bad_process()选择一个”bad”进程杀掉。 如何判断和选择一个”bad进程呢?linux选择”bad”进程是通过调用oom_badness(), 挑选的算法和想法都很简单很朴实:最bad的那个进程就是那个最占用内存的进程。 如何查看……

阅读全文

linux gcc编译

1. 常用编译命令选项 假设源程序文件名为test.c 1. 无选项编译链接 用法:#gcc test.c 作用:将test.c预处理、汇编、编译并链接形成可执行文件。这里未指定输出文件,默认输出为a.out。 2. 选项 -o 用法:#gcc test.c -o test 作用:将test.c预处理、汇编、编译并链接形成可执行文件test。-o选项用来指定输出文件的文件名。 3. 选项 -E 用法:#gcc -E test.c -o test.i 作用:将test.c预处理输出test.i文件。 4. 选项 -S 用法:#gcc -S test.i 作用:将预处理输出文件test.i汇编成test.s文件。 5. 选项 -c 用法:#gcc -c test.s 作用:将汇编输出文件test.s编译输出test.o文件。 6. 无选项链接 用法:#gcc test.o -o test 作用:将编译输出文件test.o链接成最终可执行文件test。 7. 选项-O 用法:#gcc -O1 test.c -o test 作用:使用编译优化级别1编译程序。级别为1~3,级别越大优化效果越好,但编译时间越长。 2. 多源文件的编译方法 如果有多个源文件,基本上有两种编译方法: [假设有两个源文件为test.c和testfun.c] 1. 多个文件一起编译 用法:#gcc testfun.c test.c -o test 作用:将testfun.c和test.c分别编译后链接成test可执行文件。 2. 分别编译各个源文件,之后对编译后输出的目标文件链接。 用法: #gcc -c testfun.……

阅读全文

linux coredump设置

1. core 在Linux下程序不寻常退出时,内核会在当前工作目录下生成一个core文件(是一个内存映像,同时加上调试信息,编译时需要加上 -g -Wall)。 使用gdb来查看core文件,可以指示出导致程序出错的代码所在文件和行数。 2. core文件的生成开关和大小限制 1.1 使用ulimit -c命令可查看core文件的生成开关 若结果为0,则表示关闭了此功能,不会生成core文件 1.2 使用ulimit -c filesize命令,可以限制core文件的大小(filesize的单位为kbyte) 如果生成的信息超过此大小,将会被裁剪,最终生成一个不完整的core文件或者根本就不生成。 如果生成被裁减的core文件,调试此core文件的时候,gdb也会提示错误。 用以下命令来表示core文件的大小不受限制. $ ulimit -c unlimited 用以下命令来阻止系统生成core文件: $ ulimit -c 0 备注:ulimit命令设置后只对一个终端有效,所以另起终端后需要重新设置。 3. 设置 Core Dump 的核心转储文件目录和命名规则 2.1 /proc/sys/kernel/core_uses_pid 可以控制产生的 core 文件的文件名中是否添加 pid 作为扩展 , 文件内容为1,表示添加pid作为扩展名,生成的core文件格式为core.xxxx;为0则表示生成的core文件同一命名为core。 $ echo "1" > /proc/sys/kernel/core_uses_pid 2.2 /proc/sys/kernel/core_pattern 可以设置格式化的 core 文件保存位置或文件名 $ echo "/corefile/core-%e-%p-%t" > /proc/sys/kernel/core_pattern 说明:将会控制所产生的 core 文件会存放到 /corefile 目录下,产生的文件名为 core- 命令名 -pid- 时间戳 以下是参数列表: %p - insert pid into filename 添加pid %u - insert current uid into filename 添加当前uid %g - insert current gid into filename 添加当前gid %s - insert signal that caused the coredump into the filename 添加导致产生core的信号 %t - insert UNIX time that the coredump occurred into filename 添加core文件生成时的unix时间 %h - insert hostname where the coredump happened into filename 添加主机名 %e - insert coredumping executable name into filename 添加命令名 2.……

阅读全文

rustdoc 范例

1. rust doc /// A human being is represented here pub struct Person { /// A person must have a name, no matter how much Juliet may hate it name: String, } impl Person { /// Returns a person with the name given them /// /// # Arguments /// /// * `name` - A string slice that holds the name of the person /// /// # Example /// /// ``` /// // You can have rust code between fences inside the comments /// // If you pass --test to Rustdoc, it will even test it for you!……

阅读全文

最近文章

分类

标签

友情链接

其它