分类: LINUX
2009-08-01 15:35:28
linux socket()调用与arp报文发送
Linux提供最常用的网络通信应用程序开发接口--Berkerley套接字(Socket).它既适用于同一主机上进程间通信(IPC),又适用于不同主机上的进程间通信。套接字的设置通过socket调用完成:
int socket(int family,int type,int protocol);
其中family指通信域或协议族,Linux系统支持的网络协议族有PF_UNIX,PF_IPX,PF_PACKET等几十种;type为套接字类型,目前有SOCK_STREAM、SOCK_DGRAM、SOCK_RAW、SOCK_PACKET等;protocol是套接字所用的特定协议类型号.
Linux系统提供的基于数据链路层开发应用程序的接口集成在套接字中,它是通过创建packet类型的套接宇.使应用程序可直接在数据链路层接收或发送未被系统处理的原始的数据报文(如ARP报文),用户也可以使用packet类型的套接宇在物理层上定义自己特殊的网络协议。只有注册号为0的用户(超级用户)进程才能建立或打开用于访问网络低层的套接字.在Linux系统中,用以下三种方式创建的packet套接字可直接用于访问数据链路层:
(1)PF_INET协议族中SOCK_PACKEI类型的套接字
(2)PF_PACKET协议族中SOCK_RAW类型的套接字
(3)PF_PACKET协议族中SOCK_DGRAM类型的套接字
Linux 2.0中对数据链路层的操作主要使用SOCK_PACKET定义的packet套接字.初始化定义如下:
sockfd=socket(AF_INET,SOCK_PACKET,protocol);
其中,protocol用于决定套接字所使用的物理层协议(在IEEE802.3中定义).笔者在此选择常用的物理层协议ETH_P_IP(Internet协议).SOCK_PACKET使用一种比较老的sockaddr_pkt数据结构来设置网络接口。
在Linux 2 2中使用PF_PACKET代替SOCK_PACKET来定义packet套接字.这种套接字的初始化定义如下:
sockfd=socket(PF_PACKET,socket_type,protocol);
其中socket_type只能为SOCK_RAW或SOCK_DGRAM,protocol为物理层通信协议(同上)。SOCK_RAW和SOCK_DGRAM类型套接字使用一种与设备无关的标准物理层地址结构sockaddr_ll,但具体操作的报文格式不同。SOCK_RAW套接字直接向网络硬件驱动程序发送(或从网络硬件驱动程序接收)没有任何处理的完整数据报文(包括物理帧的帧头),这就要求程序员必须了解对应设备的物理帧帧头结构,才能正确地装载和分析报文。SOCK_DGRAM套接字收到的数据报文的物理帧帧头会被系统自动去掉,同样,在发送时.系统将会根据sockaddr_ll结构中的目的地址信息为数据报文舔加一个台适的物理帧帧头。
默认情况下.从任何接口收到的符合指定协议的所有数据报文都会被传送到packet套接字。使用bind系统调用以一个sochddr_l1地址结构将paccket套接字与某个网络接口相绑定,可使套接字只接收指定接口的
数据报文.socaddr_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]; /* 物理层地址 */
};
一、利用PF_lNET协议族中SOCK_PACKET类型的套接宇实现ARP
(1)建套接字
创建套接宇采用socket系统调用,格式如下:
sockfd=socket(PF_INET,SOCK_PACKET,htons(ETH_P_ARP));
(2)装载报文
对于SOCK_PACKET类型的套接字,以太网物理帧头应作为所发送报文一部分由程序员设置,物理帧头的格式定义如下:(in /usr/include/linux/if_ether.h)
92 struct ethhdr
93 {
94 unsigned char h_dest[ETH_ALEN]; /* destination eth addr */
95 unsigned char h_source[ETH_ALEN]; /* source ether addr */
96 unsigned short h_proto; /* packet type ID field */
97 };
实际发送的地址解析报文帧由以太网物理帧头与帧数据(ARP报文)共同组成,用结构体ARPPACKET表
示如下:
typedef struct {
struct ethhdr eth_header; //struct defined in linux/if_ether.h
ARPHDR arp_header;
}ARPPACKET;
上述报文结构的装载比较简单。对ARP部分,arp_header的设置如下:
ptk.arp_header.ar_hrd=htons(ARPHRD_ETHER); //ARPHRD_ETHER is defined in linux/if_arp.h
ptk.arp_header.ar_pro=htons(ETHERTYPE_IP);
ptk.arp_header.ar_hln=6;
ptk.arp_header.ar_pln=4;
ptk.arp_header.ar_op=htons(ARPOP_REQUEST);
pkt.arp_header.ar_sha[]、pkt.arp_header.ar_sip[]、pkt_arp_header.ar_tip[]分别填入本机的物理地址、ip地址和要解析的对方主机的ip地址.返回报文中pkt.arp_header.tha[]中的内容就是解析
得到的对方主机的物理地址。
对于以太网帧头部分,pkt.eth_header.h_dest[]为目的地址,即广播物理地址0xFFFFFF, pkt.eth_header.source[]为本机物理地址(同pkt.arp_header.ar_sha[]),
pkt.eth_header.h_proto赋值htons(ETHERTYPE_ARP)表示为地址解析类型报文。
ETHERTYPE_ARP与ETH_P_ARP的值都是0x0806,只是定义的文件不同。前者定义在net/ethernet.h,后者定义在linux/if_ether.h
(3)报文的发送与接收
在数据链路层发送/接收报文与在IP层发送/接收数据报文类似,分别用系统调用sendto()和recvfrom()
完成,只是要将配置好的含有目标地址的报文发往本地网络硬件而不是目标主机。相应的程序段如下:
struct sockaddr to,from;
int fromlen=0;
strcpy(to.sa_data,"eth0");
sendto(sockfd,pkt,sizeof(struct ARPPACKET),0,&to,sizeof(struct sockaddr));
recvfrom(sockfd,buf,PACKET_SIZE,0,&from,&fromlen);
其中buf为包含结构体ARPPACKET的字符型指针。
通过检验所接收到的ARP应答报文中arp_header.ar_op项是否为ARPOP_REPLY(ARP应答)同时arp_header.ar_tip是否为已知的对方主机的IP地址来判断所得到的解析地址是否正确.
二、利用PF_PACKET协议族中SOCK_RAW类型的套接字实现ARP
(1)创建套接字
sockfd=socket(PF_PACKET,SOCK_RAW,htons(ETH_P_ARP));
(2)装载报文
与SOCK_PACKET类型的套接字相同,SOCK_RAW类型的套接字对链路层操作时,也要求以太网物理帧头应作为所发送报文一部分由程序员设置.
(3)报文的发送与接收
SOCK_RAW类型套接字使用标准的物理层地址结构sockaddr_ll,所以,报文发送之前,应将套接字绑定到(使用bind()系统调用)配置好的本地物理地址结构my_etheraddr,同时还需配置目的物理地址结构broad_etheraddr.
示例如下:
struct aockaddr_ll my_etheraddr,broad_etheraddr;
my_etheraddr.sll_family=AF_PACKET;
my_etheraddr.sll_protocol=htons(ETH_P_ARP);
my_etheraddr.sll_ifindex=2; /*接口号2表示是eth0*/
my_etheraddr.sll_hatype=ARPHRD_ETHER;
my_etheraddr.sll_pkttype=PACKET_HOST;
my_etheraddr.sll_halen=ETH_ALEN;
my_etheraddr.sll_addr[8]中放入本主机的物理地址。
broad_etheraddr的配置除了sll_pkttype取PACKET_BROADCAST和sll_addr取广播物理地址(0xFFFFFF)外,其他选项与my_etheraddr配置相同。
绑定格式如下:
bind(sockfd,(struct sockaddr *)&my_etheraddr,sizeof(my_etheraddr));
发送与接收调用程序如下:
sendto(sockfd,buf,sizeof(struct ARPPACKET),0,
(struct sockaddr *)&broad_etheraddr,sizeof(broad_etheraddr));
recvform(sockfd,buf,PACKET_SIZE,&from,&fromlen);
三、利用PF_PACKET协议族中SOCK_DGRAM类型的套接字实现ARP
SOCK_DGRAM类型的套接字不要求程序员配置以太网帧头,所以所发送的报文只有数据区(ARP报文)部分,其它与SOCK_RAW类型的套接字相同。