摘要:

socket;组播;VLAN;

*:应用程序可以通过读取套接字(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

莱斯ATC笔记5_IP

  • 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和端口号——网络通信中确认身份信息的三要素

莱斯ATC笔记5_IP_02

  •  一台主机上可能有多块网卡,接入多个不同的子网,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的接收队列中,完成组播数据报的接收。