0%

Raw Socket L2 vs L3

记录Raw socket相关知识点,以及数据链路层数据访问

  • Layer 2: MAC & LLC
  • Layer 3: Network

OSI Model

OSI七层模型如下

1556452439780

  • Layer2: MAC & LLC,数据类型 Frame
  • Layer3: Network,数据类型 Packet

Raw Socket

每一层级网络数据都由两部分组成:HeaderPayloadTCP报文如下

1556455755725

通常使用的流式套接字SOCK_STREAM和数据包式套接字SOCK_DGRAM,已经由系统提供的协议栈处理过,应用只能处理Payload部分,Header部分已被处理过。而当需要处理Header部分时,需要使用raw socket,它位于系统核心,可以处理Header部分,而raw socket根据能力不同分为L2L3,部分资料摘录如下:

  1. non-Raw socket

    you can just determine Transport Layer Payload. i.e it is OS task to create Transport,Network and Data Link layer headers.

  2. Raw socket

    you can determine every section of packet,either header or payload. Please note that raw socket is a general word. I categorize raw socket into: Network Socket andd Data-Link Socket (or alternativly L3 Socket and L2 Socket)

Create Raw Socket

要创建套接字,必须知道套接字族套接字类型协议三个方面

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>

int socket(int domain, int type, int protocol);

内核中套接字是一层一层进行抽象展示的,把共性的东西抽取出来,这样对外提供的接口可以尽量的统一。内核中把套接字的定义会抽象出来展示,如 struct sock->struct inet_sock->struct tcp_sock 从抽象到具体。Socket函数中的三个参数其实就是把抽象的socket具体化的条件,domain参数决定了图中所示的第二层通信域,type决定了第三层的通信模式,protocol决定了第四层真正的通信协议。

raw socket

使用 AF_INET,用户程序无法获得链路层数据,也即,以太网头部。简单来说,使用 AF_INET,是面向IP层的原始套接字;使用 AF_PACKET,是面向链路层的套接字。

$ man socket
$ man 7 socket
$ man 7 ip
$ man 7 packet

L3 Socket – Network Socket

In L3 Socket you can determine header and payload of packet in network layer. For example if network layer protocol is IPv4, you can determine IPv4 header and payload. Thus you can set transport layer header/payload, ICMP header/payload, Routing Protocols headder/payload.

创建方式

socket(AF_INET, SOCK_RAW, protocol)

在头文件netinet/in.h中定义了系统中该字段目前能取的值,注意:有些系统中不一定实现了netinet/in.h中的所有协议。源代码的linux/in.h中和netinet/in.h中的内容一样。常用的如下

  • IPPROTO_IP(0): 用于接收任何的 IP 数据包。其中的校验和和协议分析由程序自己完成
  • IPPROTO_RAW(255): 只能用来发送 IP 包,而不能接收任何的数据。发送的数据需要自己填充 IP 包头,并且自己计算校验和
  • 其他像IPPROTO_TCP(6)这种非 0、非 255 的协议,当操作系统内核碰到 IP 头中 protocol 域和创建 socket 所使用参数 protocol 相同的 IP 包,就会交给这个raw socket来处理,因此,一般来说,要想接收什么样的数据包,就应该在参数 protocol 里来指定相应的协议。当内核向此 raw socket 交付数据包的时候,是包括整个 IP 头的,并且已经是重组好的 IP 包

用这种方式可以得到原始的 IP 包,然后就可以自定义 IP 所承载的具体协议类型,如TCPUDPICMP,并手动对每种承载在 IP 协议之上的报文进行填充。

注意其中一个参数IP_HDRINCL,允许用户构造 IP 头

const int on =1;
if (setsockopt (sockfd, IPPROTO_IP, IP_HDRINCL, &on, sizeof(on)) < 0) {
    printf("setsockopt error!\n");
}

L3 原始套接字处理的只是 IP 层及其以上的数据,可以处理IP HeaderTCP/UDP Header,比如实现 SYN FLOOD 攻击、处理 PING 报文等。当需要操作更底层的数据的时候,就需要采用其他的方式。

In L2 Socket you can set header and payload of packet in data link layer, i.e everything in packet. Thus you do everything done with L3 Socket + determine ARP header/payload, PPP header/payload, PPPOE header/payload , …. .

为了实现直接从链路层收发数据帧,用到原始套接字的如下形式

socket(PF_PACKET, type, protocol)
  • type可取SOCK_RAWSOCK_DGRAM。它们两个都使用一种与设备无关的标准物理层地址结构struct sockaddr_ll,但具体操作的报文格式不同:
    • SOCK_RAW:直接向网络硬件驱动程序发送或接收没有任何处理的完整数据报文,包括物理帧的帧头
    • SOCK_DGRAM:这种类型的套接字对于收到的数据报文的物理帧帧头会被系统自动去掉,不包括物理帧的帧头,然后再将其往协议栈上层传递。发送时根据sockaddr_ll中的目的地址信息添加MAC
  • protocol,链路层协议,linux/if_ether.h,常用如下:
    • ETH_P_IP只接收发往目的 MAC 是本机的 IP 类型的数据帧
    • ETH_P_ARP只接收发往目的 MAC 是本机的 ARP 类型的数据帧
    • ETH_P_RARP只接受发往目的 MAC 是本机的 RARP 类型的数据帧
    • ETH_P_ALL接收发往目的 MAC 是本机的所有类型 (ip,arp,rarp) 的数据帧,同时还可以接收从本机发出去的所有数据帧。在混杂模式 /Monitor 打开的情况下,还会接收到发往目的 MAC 为非本地硬件地址的数据帧

发送数据的时候需要自己组织整个以太网数据帧。和地址相关的结构体就不能再用前面的struct sockaddr_in了,而是struct sockaddr_ll,头文件linux/if_packet.h,如下:

struct sockaddr_ll{
    unsigned short sll_family; /* 总是 AF_PACKET */
    unsigned short sll_protocol; /* 物理层的协议 */
    int sll_ifindex; /* 接口号 */
    unsigned short sll_hatype; /* 报头类型 */
    unsigned char sll_pkttype; /* 分组类型 */
    unsigned char sll_halen; /* 地址长度 */
    unsigned char sll_addr[8]; /* 物理层地址 */
};
  • sll_family,使用AF_PACKET

  • sll_protocol,见头文件linux/if_ether.h,例如ETH_P_ALL

  • sll_ifindex,设置为0表示处理所有interface,单网卡设备无所谓,示例代码如下

    struct  sockaddr_ll  sll;
    struct ifreq ifr;
    
    strcpy(ifr.ifr_name, "eth0");
    ioctl(sockfd, SIOCGIFINDEX, &ifr);
    sll.sll_ifindex = ifr.ifr_ifindex;
  • sll_hatype,ARP 硬件地址类型,定义在linux/if_arp.h中,例如

    • ARPHRD_ETHER以太网
    • ARPHRD_IEEE80211 IEEE 802.11
    • ARPHRD_IEEE80211_PRISM IEEE 802.11 + Prism2 header
    • ARPHRD_IEEE80211_RADIOTAP IEEE 802.11 + radiotap header Monitor 模式抓下来的类型
  • sll_pkttype,分组类型,定义在linux/if_packet.h,例如

      3 #define PACKET_HOST     0       /* To us        */
      4 #define PACKET_BROADCAST    1       /* To all       */
      5 #define PACKET_MULTICAST    2       /* To group     */
      6 #define PACKET_OTHERHOST    3       /* To someone else  */
      7 #define PACKET_OUTGOING     4       /* Outgoing of any type */
      8 #define PACKET_LOOPBACK     5       /* MC/BRD frame looped back */
      9 #define PACKET_USER     6       /* To user space    */
     10 #define PACKET_KERNEL       7       /* To kernel space  */
  • sll_addrsll_halen指示物理层(如以太网,802.3,802.4 或 802.5 等)地址及其长度,严格依赖于具体的硬件设备。类似于获取接口索引sll_ifindex,要获取接口的物理地址,可以采用如下代码

    struct ifreq ifr;
    
    strcpy(ifr.ifr_name, "eth0");
    ioctl(sockfd, SIOCGIFHWADDR, &ifr);

默认情况下,从任何接口收到的符合指定协议的所有数据报文都会被传送到原始 PACKET 套接字口,而使用bind系统调用并以一个sochddr_ll结构体对象将 PACKET 套接字与某个网络接口相绑定,就可使我们的 PACKET 原始套接字只接收指定接口的数据报文。示例代码如下

struct sockaddr_ll sll;
struct ifreq ifr;

/* find the interface index */
memset( &ifr, 0, sizeof( ifr ) );
strncpy( ifr.ifr_name, iface, sizeof( ifr.ifr_name ) - 1 );

if( ioctl( fd, SIOCGIFINDEX, &ifr ) < 0 )
{
    printf("Interface %s: \n", iface);
    perror( "ioctl(SIOCGIFINDEX) failed" );
    return( 1 );
}

memset( &sll, 0, sizeof( sll ) );
sll.sll_family   = AF_PACKET;
sll.sll_ifindex  = ifr.ifr_ifindex;
//sll.sll_ifindex  = if_nametoindex(iface);
sll.sll_protocol = htons( ETH_P_ALL );

/* bind the raw socket to the interface */

if( bind( fd, (struct sockaddr *) &sll, sizeof( sll ) ) < 0 )
{
    printf("Interface %s: \n", iface);
    perror( "bind(ETH_P_ALL) failed" );
    return( 1 );
}

L2 Socket 可以处理链路层数据,可以处理MAC Header,如果创建这个原始套接字时指定了非 0 的协议参数,那么接收到的数据报协议字段必须匹配该值。如果这个套接字已由bind调用绑定了某个 IP 地址,那么接收到的数据报的目的地址必须匹配这个绑定地址。

SOCK_PACKET

老的内核Linux 2.0接口提供的接口用于处理数据链路层数据,具体信息参看libpcap/pcap-linux.c

socket(PF_INET, SOCK_PACKET, htons(ETH_P_ALL))

如果系统已经提供PF_PACKET支持,不建议使用SOCK_PACKET

Programming Example

  • socket(AF_INET,RAW_SOCKET,…) means L3 socket , Network Layer Protocol = IPv4
  • socket(AF_IPX,RAW_SOCKET,…) means L3 socket , Network Layer Protocol = IPX
  • socket(AF_INET6,RAW_SOCKET,…) means L3 socket , Network Layer Protocol=IPv6
  • socket(AF_PACKET,RAW_SOCKET,…) means L2 socket , Data-link Layer Protocol= Ethernet
  • socket(AF_PACKET,SOCK_DGRAM,…) means L2 socket , Data-link Layer Protocol= Ethernet without MAC Header
  • socket(PF_INET, SOCK_PACKET, htons(ETH_P_ALL)) means L2 socket , Data-link Layer Protocol= Ethernet

Ref

  1. Is the Extra Layer Better? Layer 2 Versus Layer 3 Networking
  2. what is RAW socket in socket programming
  3. 原始套接字 SOCK_RAW
  4. Linux 网络编程:原始套接字 SOCK_RAW
  5. IPPROTO_IP , IPPROTO_RAW
  6. Difference between PF_INET sockets and PF_PACKET sockets ?
  7. 原始套接字和数据链路层访问
  8. OSI Model
  9. Sending raw Ethernet packets from a specific interface in C on Linux
  10. PACKET(7) Linux Programmer’s Manual
  11. 信息安全课程9:raw socket编程
  12. A Guide to Using Raw Sockets