摘要:
socket;组播;VLAN;
- Linux 中的 nmcli 命令 - 知乎 (zhihu.com)
- Linux实现VLAN - FromScratch - 博客园 (cnblogs.com)
- 深度解析Linux下VLAN功能的实现原理 - 知乎 (zhihu.com)
- 剖析Linux网络包接收过程:掌握数据如何被捕获和分发的全过程 (qq.com)
- Linux 划分Vlan的方法及配置 (xjx100.cn)
- linux网络协议栈(四)链路层 (5)vlan处理_vlan_dev_ioctl的过程_FSak47的博客-CSDN博客
- ***:组播编程_imr_ifindex_zhoujiaxq的博客-CSDN博客
- ***:加入一个多播组(最简单的情况)_如何加入组播组_Apeirion的博客-CSDN博客
- linux Packet socket (1)简单介绍 - mfrbuaa - 博客园 (cnblogs.com)
- 理解 Linux 网络栈:Linux 网络协议栈简单总结分析 - 知乎 (zhihu.com)
- linux下实现组播(socket)_linux 如何模拟发出组播数据_panamera12的博客-CSDN博客
- Socket的详细介绍_雾幻空的博客-CSDN博客
- ***:linux网络编程之-----多播(组播)编程_jmq_0000的博客-CSDN博客
- !!!组播2 good - bw_0927 - 博客园 (cnblogs.com)
*:应用程序可以通过读取套接字(socket:根据RFC793的定义:端口号拼接到IP地址就构成了套接字。)来获取其中的数据内容。Packet套接字提供的是L2的抓包能力,也叫raw socket(可以抓取数据链路层的报文),意思就是不经过操作系统tcp/ip协议栈处理的packet,抓上来的包须要自己处理tcp/ip的头部信息。
- socket_type参数为SOCK_RAW(对较低层协议,如IP或ICMP进行直接访问,它功能强大使用较为不便,常用于网络协议分析,检验新的网络协议实现)或SOCK_DGRAM(“无连接的套接字”数据报格式套接字:使用 UDP 协议,数据的发送和接收是同步的,有的教程也称“存在数据边界”),SOCK_STREAM(面向连接的套接字”使用了 TCP 协议,流格式套接字Stream Sockets也叫“面向连接的套接字”,传输的数据不存在数据边界)。区别是SOCK_RAW要求用户自己构造以太网首部,而SOCK_DGRAM则由内核来构造以太网首部。
- 套接字就是:一台主机的IP地址和端口号(端口并非赋予计算机的值,而是为区分程序中创建的套接字而分配给套接字的序号),套接字对就是互传信息两台主机的IP和端口号。在两台主机connect时,就是通过对应的套接字联系起来的。对客户来说:需要明确自己要连接的服务器IP和端口号,而自己的IP和端口号一般由内核默认了,会在连接后传给服务器。对服务器来说:需要明确自己监听的本机的端口就行,本机的IP可由宏INADDR_ANY经转换得到默认的IP给套接字结构。至于来自客户的IP和端口可以不用管,接收任何主机的连接。
- 常用的寄存器:PAR0~5(physical address register)---本地MAC地址
- 常用的寄存器:MAR0~7(multiple address register)---多播地址匹配
- 硬件网卡接收到网络包之后,通过 DMA 技术,将网络包放入 Ring Buffer。
- 硬件网卡通过中断通知 CPU 新的网络包的到来。网卡驱动程序会注册中断处理函数 ixgb_intr。
Linux内核网络收包过程函数调用分析
- 数据帧首先到达网卡的接收队列,分配RingBuffer
- DMA把数据搬运到网卡关联的内存
- 网卡向CPU发起硬中断,通知CPU有数据
- 调用驱动注册的硬中断处理函数
- 启动NAPI,触发软中断
1、在FDP1上抓取组播数据,CETC_NET通信使用组播:234.1.1.1;234.1.2.1;235.1.1.1;235.1.2.1。
[root@host3 fdop_log]# netstat -anutp |grep NET
udp 0 0 0.0.0.0:5101 0.0.0.0:* 3551/CETC_NET
udp 0 0 0.0.0.0:5102 0.0.0.0:* 3551/CETC_NET
udp 0 0 192.28.4.3:5105 0.0.0.0:* 3551/CETC_NET
udp 0 0 193.28.4.3:5106 0.0.0.0:* 3551/CETC_NET
udp 0 0 192.28.4.3:5109 0.0.0.0:* 3551/CETC_NET
udp 0 0 193.28.4.3:5110 0.0.0.0:* 3551/CETC_NET
udp 0 0 0.0.0.0:5111 0.0.0.0:* 3551/CETC_NET
udp 0 0 0.0.0.0:5112 0.0.0.0:* 3551/CETC_NET
[root@host3 fdop_log]# tcpdump -i any -nn port 5105...
IP 192.28.4.1.5105 > 234.1.1.1.5101: UDP, length 1015
IP 193.28.4.1.5106 > 234.1.2.1.5102: UDP, length 788
IP 192.28.4.1.5109 > 235.1.1.1.5111: UDP, length 114
IP 193.28.4.1.5110 > 235.1.2.1.5112: UDP, length 114
2、nmcli 是软件 NetworkManager 的提供的命令。使用 nmcli 命令时,必须确保 NetworkManager 为运行状态(nm 代表 NetworkManager,cli 代表 Command-Line)
- vconfig命令是vlan软件包的主命令,用来添加和移除VLAN interface。创建好后,就可以和实际网络设备一样,用ifconfig命令配置它。Linux中,Vlan设备建立在宿主设备的基础上,即该物理端口应该是trunk口(一个真实的物理网卡比如ethx,它可以承载多个VLAN的数据帧,因此它就是trunk端口),并指定一个vlan_id。Linux中vlan的实现可知,发往vlan设备的数据包都被打上vlan_head,而vlan设备接收到的数据包都默认为有vlan_head,并将其去除。这是符合trunk口的定义的。trunk端口,即配置为可通过多个vlan。
- 通过vconfig add命令可以创建一个vlan设备,该命令实际上是对/proc/net/vlan/config文件的ioctl操作,映射到内核中就是vlan.c中的vlan_ioctl_handler()函数,add命令最终调用register_vlan_device(*real_dev, vid)。最后把它注册进内核中的netdevice链表中,从此它对上层协议栈而言,就仿佛是一个实际的设备,和其它所有设备有平等的地位,可以用ifconfig配置它,也可以把它加入bridge等。Vlan设备对上层协议栈而言,和实际设备时平等的,所以它也会参与路由选择
# vconfig add eno1 222
ip a显示13: eno1.222@eno1
[root@host43 ~]# systemctl status NetworkManager
Active: active (running)
root@host43 ~]# nmcli c show //show是connection 的默认项,可以省略不写。
NAME UUID TYPE DEVICE
ens2 d052b26c-932a-430d-9be9-db38f5b0bdf4 ethernet ens2
Vlan ens2.101 4a165460-7ac5-c794-a6bb-42bbf780aad3 vlan ens2.101
Vlan ens2.102 8d67d5fe-a393-663b-97a0-b9c028b17987 vlan ens2.102
eno1 3047bdcb-d2a8-4c83-b37a-04716b8c497f ethernet eno1
enp8s0 774b4135-d3b2-4505-9a15-7dd37082d1bf ethernet enp8s0
virbr0 d61f83e1-4d17-485d-9fd7-d1fd15a30051 bridge virbr0
# ip -d link show ens2.102
11: ens2.102@ens2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default qlen 1000
link/ether 68:05:ca:1d:70:21 brd ff:ff:ff:ff:ff:ff promiscuity 0
vlan protocol 802.1Q id 102 <REORDER_HDR> addrgenmode eui64 numtxqueues 1 numrxqueues 1 gso_max_size 65536 gso_max_segs 65535
# cat /proc/net/vlan/config
VLAN Dev name | VLAN ID
Name-Type: VLAN_NAME_TYPE_RAW_PLUS_VID_NO_PAD
ens2.101 | 101 | ens2
ens2.102 | 102 | ens2
[root@host43 ~]# ls -al /proc/net/vlan/
总用量 0
dr-xr-xr-x 2 root root 0 9月 4 02:32 .
dr-xr-xr-x 7 root root 0 9月 4 02:32 ..
-rw------- 1 root root 0 9月 4 02:32 config
-rw------- 1 root root 0 9月 4 02:32 ens2.101
-rw------- 1 root root 0 9月 4 02:32 ens2.102
-rw------- 1 root root 0 9月 4 02:32 ens2.200
[root@host43 ~]# more /proc/net/vlan/ens2.101
ens2.101 VID: 101 REORDER_HDR: 1 dev->priv_flags: 1
total frames received 24650330
total bytes received 2030884444
Broadcast/Multicast Rcvd 0
total frames transmitted 29
total bytes transmitted 4122
Device: ens2
INGRESS priority mappings: 0:0 1:0 2:0 3:0 4:0 5:0 6:0 7:0
EGRESS priority mappings:
3、在某些场景中,Linux服务器(CentOS / RHEL)上的同一网卡分配来自不同VLAN的多个ip。可以通过启用VLAN标记接口来实现,但要实现这一点,首先必须确保交换机上添加多个vlan。
- 在CentOS 7 /RHEL 7 / CentOS 8 /RHEL 8系统上使用VLAN标记接口,必须加载内核模块8021q。
- 从交换机连接到服务器数据流量网卡的端口被配置为Trunk,通过映射多个vlan到它。
- 在Linux系统中,建立socket无法直接获取二层网络数据包中带VLAN ID的数据包,但是可以通过sockopt函数设置PACKET_AUXDATA选项,将VLAN信息带上,提取,然后组成802.1q协议包,从而完成802.1q协议VLAN数据包的获取。
- Linux 上的VLAN和Cisco/H3C上的VLAN不同,后者的VLAN是先有了LAN,再有V,也就是说是先有一个大的LAN,再划分为不同的VLAN,而 Linux则正好相反,由于Linux的Bridge设备是被创建出来的逻辑设备,因此Linux需要先创建VLAN,再创建一个Bridge关联到该 VLAN。
- 使用vconfig创建一个ethx.y的虚拟设备,就创建了一个 trunk,其中ethx就是trunk口,而y代表该trunk口连接的trunk链路可以承载的VLAN数据帧的id。
# lsmod | grep -i 8021q
8021q 33080 0
garp 14384 1 8021q
mrp 18542 1 8021q
# ip link add link ens2 name ens2.200 type vlan id 200
# ip link set ens2.200 up
# ip address add 192.168.31.222/24 dev ens2.200
# more /etc/sysconfig/network-scripts/ifcfg-ens2.102
DEVICE=ens2.102 <-----指定vlan为102
IPADDR0=197.168.2.43
PREFIX0=16 <-----组播的源IP(197.168.2.223)须和目的IP(197.168.2.43)在一个子网
VLAN=yes <-----yes
- Linux中,Vlan设备建立在宿主设备的基础上,即该物理端口应该是trunk口(承载多个vlan的port称为trunk口,它上面收发的数据包必须含有vlan_head,以识别该包是属于哪个vlan的)
- Linux中vlan的实现可知,发往vlan设备的数据包都被打上vlan_head,而vlan设备接收到的数据包都默认为有vlan_head,并将其去除。这是符合trunk口的定义的。
4、读取配置lineap="197.168.2.43:233.1.21.1:56061" protocola="MCAST" ,将197.168.2.43对应的网卡ens2.102(vlan 102)加入到组播组233.1.21.1
- 特殊的组播地址224.0.0.1(IGMP_ALL_HOSTS),它标识子网中的所有主机,同一个子网内具有组播功能的主机都属于这个组。IGMP_ALL_HOSTS组的sources为NULL,sfmode为MCAST_EXCLUDE(过滤掉sources中列出的所有源),所以结果是不过滤任何组播源。
- 组播IP地址是如何被映射成组播mac地址的。一个mac地址总共有6字节,48位,被分成两段:前3字节和后3字节,前3字节用于标识网卡的制造厂商,其中第40位(第一字节的最低位)用于标识组播,所以在网卡的mac地址中必须置0,后3字节是厂商内部使用的序列号。一个组播IP地址映射成mac地址的规则是:前三字节强制置01:00:5E,后3字节中,第23位置0,0-22位放入IP地址的0-23位。
- 网卡ens2.102的IP地址与CETC/public_data/adsbdata.xml配置中的IP必须完全一样,用于通过IP选择对应网卡ens2.102加入组播组。
root@host43 bin]# ./CETC_ADSRCV
x11s_lock2continue(): fcntl(F_SETLK) successed
hostid=43, gLogicType = SFP
==LOCAL========[43] [1] [CENTER] [1] [ACC]
start read [adsbdata.xml] [/home/atc/config/CETC/public_data/adsbdata.xml] !
recordcount ret = 1
DataCount = 1
lognum=1 name=ADS period=1188692064. prot=MCAST, host=43,addr=197.168.2.43:233.1.21.1:56061,iomode=0,rqs=0; prot=MCAST, host=44,addr=8301,iomode=0,rqs=0;
end free 2 <adsbdata.xml>!
1380| init_readcfg_fusing
start read [localpart_vsp.xml] [/home/atc/config/CETC/public_data/localpart_vsp.xml] !
recordcount ret = 225
DataCount = 224
- 莱斯CETC_ADSRCV程序加了包过滤条件,要求组播报文中的源IP需要与加入组播组的网卡IP在同一个网段。网卡ens2.102的IP网段须是197.168.2.0/24,与197.168.2.223.43987 > 233.1.21.1.56061组播源IP一个网段(测试,197.168.8.0/16子网掩码16也行,因为网络地址197.168与组播数据报中197.168.2.223的前16为网络地址一致)。
- 川大trackasmla.linux程序是在配置文件trackasmla.ini指定了组播的源IP:[send]key=196.168.2.223。
trackasmla.ini
[recv0]
ip=196.168.2.52
#ip=192.168.31.247
port=56060
multip=233.1.21.1
mla=MLA0 MLA1
[MLA0]
key=196.168.2.223 //利用该IP对组播源进行过滤
mid=3
cid=0
--->假如eth2没有IP:196.168.2.52,failed提示No such device,即从IP:196.168.2.52未能对应到DEVICE。
[atc@FSDP-2 bin]$ ./trackasmla.linux
--------recv count is 2
recv0: 196.168.2.52:56060 233.1.21.1
Send:168.192.11.255:5004
MLA:196.168.2.223 3 0
MLA:196.168.2.224 3 1
Init to Group:233.1.21.1:56060
bind addr failed: 98:Address already in use
[UUdpSocket] join to multcast 233.1.21.1 interface 196.168.2.52
Join to multi group failed: 19:No such device
--->eth2随便设置个IP:1.1.1.1,trackasmla.ini中[recv0]ip=1.1.1.1改成一样,就能通过该IP找到网卡eth2,并加入组播组,使用key=196.168.2.223对组播源进行过滤,ADS-B信号OK!
[root@FSDP-2 ~]# netstat -ang
IPv6/IPv4 Group Memberships
Interface RefCnt Group
eth2 1 233.1.21.1
- 测试现象:按以上组播输出正常后,ip addr del 1.1.1.1/24 dev eth2也正常,说明是网卡eth2加入组播组,不是IP。网卡eth2加入组播组后tcpdump -i eth2 port 56060才能抓到包,否则组播包二层都到不了该网卡,所以抓不到包。
- 川大也用了sfmode和sfcount是过滤参数,也就是说,eth2网络设备接口虽然加入了某个组播组,但对某些主机向该组发的数据报不接收,或者只接收某个主机发向该组的数据报,这就要对组播源进行过滤:eth2网卡必须有与组播源IP同一子网的IP地址(例如196.168.2.0/24,196.168.3.0/16都行,ip addr del 196.168.3.52/16 dev eth2后虽然能抓到包,但是trackasmla.linux不输出信号了,因为sfmode设置只接收源地址是同网段的组播包,tcpdump不涉及sfmode)。
- 二所afp程序不管源IP:在afp.ini指定网卡eth4加入组播[SYSTEM]DI_A_INTERFACE=eth4。
[root@SDFP1 sfp]# vim afp.ini //二所
[SYSTEM]
DI_A_INTERFACE=eth4
DI_B_INTERFACE=eth5
[root@SDFP1 sfp]# ../../../bin/afp
[root@SDFP1 ~]# netstat -ang |grep 233.1.21.1
eth4 2 233.1.21.1
eth5 2 233.1.21.1
5、组播程序设计使用setsockopt()函数和getsockopt()函数来实现,组播的选项是IP层的。加入或者退出一个组播组,通过选项IP_ADD_MEMBERSHIP和IP_DROP_MEMBERSHIP,对一个结构struct ip_mreq类型的变量进行控制。(setsockopt - TreckWiki)
ip_mreq是一个旧的数据结构,但目前仍然可用,ip_mreqn是从Linux 2.2之后可用的新的数据结构,相比ip_mreq,其多了一个imr_ifindex
struct ip_mreqn
{
struct in_addr imr_multiaddr; /*加入或者退出的组播组IP地址*/
struct in_addr imr_address; /*本机需要加入或者退出组播组的网络接口IP地址,对应ip_mreq旧结构是imr_interface*/
//设置加入多播组的的网卡ip, 注意这里并不表示socket同该网卡绑定,该socket仍然能够接收到不是该网卡的数据包,该设置仅仅表示该ip对应的网卡能够接收对应多播组的数据包
int imr_ifindex; /*设置加入多播组的网卡的index,该设置项优先级高于上边的网卡ip,接口索引设为0表示任何接口*/
};
//使用如下函数可以将网卡名称(ifconfig查看)转换为对应的index,用于填充imr_ifindex域,也可以将imr_ifindex设置为0表示使用默认网卡
//每个网络设备除了可以用name的方式表示以外,还可以用ifindex(interface index)表示,当网络设备注册时调用dev_new_index( )获取
unsigned int if_nametoindex (const char *__ifname)
imr_multiaddr.s_addr=inet_addr("225.0.0.0");//组播地址
imr_address.s_addr=INADDR_ANY;//INADDR_ANY 即 0.0.0.0 ,表示本机的所有网卡地址
imr_ifindex= if_nametoindex("ens33"); //物理地址
/* 把本机加入组播地址,即本机网卡作为组播成员,只有加入组才能收到组播消息 */
setsockopt(sockfd, IPPROTO_IP, IP_ADD_MEMBERSHIP , &mreq,sizeof (struct ip_mreq)
struct sockaddr_in self;
self.sin_family=AF_INET;
self.sin_addr.s_addr=INADDR_ANY;//本地地址 htonl(INADDR_ANY)
self.sin_port=htons(PORT);//h表示host,n表示network,l表示32位长整数,s表示16位短整数:本地字节序---->网络字节序(port)
/* 绑定自己的端口和IP信息到socket上,bind系统调用的作用就是为一个本地套接口指定发送源地址和接收地址(即把一个本地套接口绑定在一个本地网络设备接口上)*/
bind(socfd,(struct sockaddr *)(&self),sizeof(self))
bind函数用于将套接字文件描述符和网络端口号绑定起来,其第二个参数是含ip+port的一个结构体
- bind之后,必须要有 setsockopt(fd, SOL_IP, IP_ADD_MEMBERSHIP, &mreq,sizeof(mreq))该动作,因为bind()操作对于组播,它会把saddr置为0,此时系统就无从下手,不知道选择哪个网卡来接收组播数据。
- 对于接收方socket,如果我们将其bind(239.255.1.2:8888),我们就只能接收发往地址为239.255.1.2,端口为8888的组播数据;而不能接收到地址为239.255.1.3端口为8888的组播数据,即使IP_ADD_MEMBERSHI加入了该组播地址。
- 同样,对于组播数据的发送方,如果也调用了bind()操作,会导致saddr = 0; 这会使用系统默认的网卡发送组播数据。如果需要指定某个网卡来发送数据,需要setsockopt(sock, IPPROTO_IP, IP_MULTICAST_IF, &addr, sizeof(addr))来设置saddr.
- bind() 函数将套接字与特定的 IP 地址和端口绑定起来,只有这样,流经该 IP 地址和端口的数据才能交给套接字处理。IP、MAC和端口号——网络通信中确认身份信息的三要素
- 一台主机上可能有多块网卡,接入多个不同的子网,imr_address/imr_interface参数就是指定一个特定的设备接口,告诉协议栈只想在这个设备所在的子网中加入某个组播组。有了这两个参数,协议栈就能知道:在哪个网络设备接口上加入哪个组播组。为了简单起见,我们的程序中直接写明了IP地址:在172.16.48.2所在的设备接口上加入组播组224.0.1.1。
- IP_MULTICAST_IF是一个用于确定提交组播报文的接口,它的参数也是struct ip_mreq,通过该参数指定发送组播报文所使用的本地IP地址和本地网络设备接口的索引号,用于发送组播数据报,这两个值确定后放在套接字的结构体struct inet_sock的成员mc_addr和mc_index中,以备发送组播数据报时查询。
- IP_MULTICAST_LOOP使组播报文环路有效或无效,如果环路有效,则在发送组播报文的时候,会给环回接口也发一份。该值存放在套接字的结构体struct inet_sock的成员mc_loop中。
- 绑定/选择不仅基于调用,而且还基于给定的 interface 接口,首先,从
ifconfig
获得的列表中想要的接口 name (例如eth0
).然后,从接口名称中使用ioctl(SIOCGIFINDEX,...)
获得接口 index ,然后根据接口索引绑定
到该接口。
6、内核中用结构体struct net_device标识一个网络设备接口,该结构体有一个成员指针ip_ptr,它是留给IPv4协议用于填充协议相关的一些数据的。IPv4协议的模块将其指向一个结构体struct in_device,该结构体含有很多协议相关的数据,比如配置在这个网络设备接口上的所有的IPv4的地址,该网络设备接口接受的组播地址等。这里关注mc_list成员,这是关于组播的一个最为关键的数据结构。mc_list是一个链表,链表的一个结点代表一个组播地址(也就是一个多播组的组号),代表这个网络设备接口已经加入了这个组播组,需要接收来自这个组的数据报。下面是该节点的结构体定义:
struct ip_mc_list
{
struct in_device *interface;
unsigned long multiaddr;
struct ip_sf_list *sources;
struct ip_sf_list *tomb;
unsigned int sfmode;
unsigned long sfcount[2];
struct ip_mc_list *next;
struct timer_list timer;
int users;
atomic_t refcnt;
spinlock_t lock;
char tm_running;
char reporter;
char unsolicit_count;
char loaded;
unsigned char gsquery;
unsigned char crcount;
};
- multiaddr就是组播地址,sources和tomb是关于组播源地址的一个列表,
- sfmode和sfcount是过滤参数,也就是说,该网络设备接口虽然加入了某个组播组,但对某些主机向该组发的数据报不接收,或者只接收某个主机发向该组的数据报,这就要对组播源进行过滤。
- sfmode是过滤模式,取值为MCAST_INCLUDE或MCAST_EXCLUDE,分别表示只接收sflist所列出的那些源的多播数据报,和不接收sflist所列出的那些源的多播数据报。
- 与普通的UDP数据报接收相比,组播多一个过滤检查,即在套接字结构体的成员mc_list中找到与该数据报所属组对应的ip_mc_socklist项,查看它的过滤配置,确认该数据报的源地址是否在过滤列表中。如果不在,则把数据放到该socket的接收队列中,完成组播数据报的接收。