PF_PACKET 设备层编程接口

15 篇文章 0 订阅
 

http://bbs.openlab.net.cn/forums/threads/167.aspx

定义:
#include "/usr/include/sys/socket.h"
#include "/usr/includ/sys/if_packet.h"

packet_socket = socket(PF_PACKET, socket_type, protocol);

描述:
packet socket用于从设备驱动层接收或发送原始数据包,可用于用户在
物理层以上构建自己的通信协议.

socket_type 可为: SOCK_RAW/或SOCK_DGRAM.其中SOCK_RAW可用于发送
原始数据包,此时可自定义数据链路层头部;SOCK_DGRAM可用于在数据链
路层以上构建包.结构sockaddr_ll中会用到链路层头部信息.协议为IE-
EE 802.3 协议号的网络序列(可参考文件--它包含了
可接受的协议号定义).所有到达的属于已定义物理层协议的包先通过p-
acket socket到达已在内核实现的链路层协议处理层.

仅有特权进程或有CAP_NET_RAW属性的进程才能打开packet套接口.

如果用SOCK_RAW,则数据包将直接通过设备驱动程序不加任何改变地发送
出去.这就要求用户程序必须了解物理层头部结构,并适当地构建包,此时
地址解析将用到标准sockaddr_ll结构.SOCK_RAW很象用于2.0版核心老的
SOCK_PACKET,但他们并不完全一致.

SOCK_DGRAM建立在更高层.在接受包时,物理头将在到达用户前被去掉;而
在发包时,物理头部将在发送前被自动添加.

默认地所有的包都从packet socket层接收.当仅接收从特定界面来的包时
将使用bind来绑定由sockaddr_ll地址结构指定的接口.

为发送SOCK_RAW包,用户必须提供空间并构建包括物理头部在内的完整的数
据包.此包将不仅任何改变地加入网卡驱动程序发送队列,而网卡将由目的
地址确认.对于SOCK_DGRAM包,其头部将在包被加入发送队列前由系统根据
地址结构(sockaddr_ll)信息自动填写.

地址结构:
sockaddr_ll为设备无关的物理层地址结构.

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_protocol为在sys/if_ether.h中定义的标准以太协议好的网络序列.
sll_pkttype为包类型.可用的有PACKET_HOST类型用于本机地址的包;PAC-
KET_BROADCAST类型用于物理广播;PACKET_MULTICAST类型用于物理
组播;PACKET_OTHERHOST用于在网卡混杂模式下从别的主机通信上接
收包;PACKET_OUTGOING类型用于从本机packet socket发出的包.
sll_halen和sll_addr为物理地址及其长度.

组播和混杂模式的支持:
Linux2.2支持一种建立在packet socket上的新方法来配置组播和混杂模式.
他调用setsockopt来工作,其工作建立在SOL_PACKET packet socket之上,其
选项为PACKET_ADD_MEMBERSHIP或PACKET_DROP_MEMBERSHIP.底层结构为:

struct packet_mreq
{
intmr_ifindex; /* 接口编号 */
unsigned shortmr_type; /* mreq 类型 */
unsigned shortmr_alen; /* 地址长度 */
unsigned charmr_address[8]; /* 物理地址 */
};

mr_interfac包含接口索引,他指出了谁将要被改变.
mr_type有:PACKET_MR_MULTICAST用于绑定套接口和由mr_address指定的物理
组播地址;PACKET_MR_PROMISC 用于激活混杂模式以接受所有网络包;
PACKET_DROP_MEMBERSHIP用于撤销绑订或重置.

输入输出控制:
输入输出控制可调用ioct:
ioctl(tcp_socket, ioctl_type, value_ptr);

SIOCGSTAMP 返回一个标准timeval结构,则在须精确时间记录时很有用.
FIOCSETOWN 和 SIOCSPGRP 用于在进程在异步通信结束时发送SIGIO信号,其参
数为pid_t类型.
FIOCGETOWN 和 SIOCGPGRP 用于得到当前接收到SIGIO信号的进程组,当没有设
置时返回0,参数类型为pid_t.
出错处理:
无出错处理机制.

兼容性:
Linux 2.0仅支持SOCK_RAW它使用老的结构:
struct sockaddr_pkt
{
unsigned short spkt_family;
unsigned char spkt_device[14];
unsigned short spkt_protocol;
};

spkt_family包含设备类型.
spkt_protocol为IEEE 802.3标准协议.
spkt_device为设备名,如"eth0";

出错类型:
ENETDOWN 接口未工作.

ENOTCONN 没有接口地址.

ENODEV 未知的设备或接口名.

EMSGSIZE 包太大.

ENOBUFS 没有足够的内存来存放接收的包.

EFAULT 错误的内存地址.

EINVAL 参数错.

ENXIO 接口地址包含不合法接口索引.

EPERM 无打开packet socket接口权用户.

EADDRNOTAVAIL 未知组播地址.oup address passed.

ENOENT 未接收到包.

翻译太匆忙且小第水平太差望大家见谅. by cloud ---- 1999/12/18

 

 

 

以下转自:http://waret.iteye.com/blog/743967

链路层套接字PF_PACKET简介

 

在linux环境中要从链路层(MAC)直接收发数据帧,可以通过libpcap与libnet两个动态库来分别完成收与发的工作。虽然它已被广泛使用,但在要求进行跨平台移植的软件中使用仍然有很多弊端。

这里介绍一种更为直接地、无须安装其它库的从MAC层收发数据帧的方式,即通过定义链路层的套接字来完成。

Packet套接字用于在MAC层上收发原始数据帧,这样就允许用户在用户空间完成MAC之上各个层次的实现。给无论是进行开发还是测试的人们带来了极大的便利性。

Packet套接字的定义方式与传送层的套接字定义类似,这个套接字的打开需要用户有root权限,如下:

Cpp代码 复制代码  收藏代码
  1. packet_socket=socket(PF_PACKET,int socket_type,int protocol);  
packet_socket=socket(PF_PACKET,int socket_type,int protocol);

 

其中socket_type有两种类型,一种为SOCK_RAW,它是包含了MAC层头部信息的原始分组,当然这种类型的套接字在发送的时候需要自己加上一个MAC头部(其类型定义在linux/if_ether.h中,ethhdr),另一种是SOCK_DGRAM类型,它是已经进行了MAC层头部处理的,即收上的帧已经去掉了头部,而发送时也无须用户添加头部字段。

Protocol是指其送交的上层的协议号,如IP为0x0800,当其为htons(ETH_P_ALL) (其宏定义为0)时表示收发所有的协议。

创建好套接字后,就可以通过与UDP一样的recvfrom与sendto函数进行数据的收发,其目的地址结构为sockaddr_ll,这与传送层的地址结构定义是不一样的,其长度为20字节(在TCP/IP的链路层地址中使用了18字节),而传送层的地址结构长度为16字节。

Sockaddr_ll结构如下:

Cpp代码 复制代码  收藏代码
  1. struct sockaddr_ll{   
  2.     unsigned short sll_family; /* 总是 AF_PACKET */  
  3.     unsigned short sll_protocol; /* 物理层的协议 */  
  4.     int sll_ifindex; /* 接口号 */  
  5.     unsigned short sll_hatype; /* 报头类型 */  
  6.     unsigned char sll_pkttype; /* 分组类型 */  
  7.     unsigned char sll_halen; /* 地址长度 */  
  8.     unsigned char sll_addr[8]; /* 物理层地址 */  
  9. };  
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_protocol 是在 linux/if_ether.h 头文件中定义的按网络层排序的标准的以太桢协议类型。sll_ifindex 是接口的索引号(参见netdevice(2));0 匹配所有的接口(当然只有合法的才用于绑定)。 sll_hatype 是在 linux/if_arp.h 中定义的 ARP 硬件地址类型。 sll_pkttype 包含分组类型。有效的分组类型是:目标地址是本地主机的分组用的 PACKET_HOST,物理层广播分组用的 PACKET_BROADCAST ,发送到一个物理层多路广播地址的分组用的 PACKET_MULTICAST,在混杂(promiscuous)模式下的设备驱动器发向其他主机的分组用的 PACKET_OTHERHOST,本源于本地主机的分组被环回到分组套接口用的 PACKET_OUTGOING。这些类型只对接收到的分组有意义。sll_addr 和 sll_halen 包括物理层(例如 IEEE 802.3)地址和地址长度。精确的解释依赖于设备。(本段引于packet的用户手册)


当在多个网络接口的主机上使用这个套接字时,若要指定接收或发送的接口时可以使用bind进行绑定,这与TCP套接字的操作一样,但其内涵并不相同。绑定时将根据地址结构中的sll_protocal和sll_ifindex分别绑定收发的协议号和接口索引号,接口索引号sll_ifindex为0时表示使用有效的所有接口。接口的sll_ifindex值可以通过ioctl获得,如下面是获得名字为“eth0”的接口的索引号:

Cpp代码 复制代码  收藏代码
  1. strcpy(ifr.ifr_name,"eth0");   
  2. ioctl(fd_packet,SIOCGIFINDEX,&ifr);  
strcpy(ifr.ifr_name,"eth0");
ioctl(fd_packet,SIOCGIFINDEX,&ifr);

 

取得的值保存在ifr结构体的ifr_ifindex中,ifr结构类型为“struct ifreq”。


BTW,要获得接口的物理地址同样使用ioctl可以得到:

Cpp代码 复制代码  收藏代码
  1. ioctl(fd_packet,SIOCGIFHWADDR,&ifr);  
ioctl(fd_packet,SIOCGIFHWADDR,&ifr);

 

以数据形式保存在ifr的ifr_hwaddr.sa_data中。


另外需要注意的是,在调用recvfrom函数时返回的地址长度信息为18字节,原因是在sockaddr_ll结构中的sll_addr[8]为8字节,MAC地址只使用了其中的前6字节。在用sendto发送时需要将目的地址结构体强制转换为struct sockaddr 类型,而且指定的长度必须为20字节,而不能是18或其它值。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值