1、建立raw socket的權限:只有root權限纔可以建立.linux
2、raw socket的用途:主要有三個方面redis
(1):經過raw socket來接收發向本機的ICMP,IGMP協議包,或者用來發送這些協議包.網絡
(2):接收發向本機但TCP/IP棧不可以處理的IP包:如今許多操做系統在實現網絡部分的時候,一般只實現了經常使用的幾種協議,如tcp,udp,icmp等,但象其它的如ospf,ggp等協議,操做系統每每沒有實現,若是本身有必要編寫位於其上的應用,就必須藉助raw socket來實現,這是由於操做系統遇到本身不可以處理的數據包(ip頭中的protocol所指定的上層協議不能處理)就將這個包交給協議對應的raw socket.數據結構
(3):用來發送一些本身制定源地址等特殊做用的IP包(本身寫IP頭,TCP頭等等),由於內核不能識別的協議、格式等將傳給原始套接字,所以,可使用原始套接字定義用戶本身的協議格式socket
3、socket的分類:根據網路的7層模型,socket能夠分爲3種tcp
(1):傳輸層socket-最經常使用的socket,非raw socketide
創建方式以下:函數
sockfd = socket(AF_INET,SOCK_STREAM/SOCK_DGRAM, protocol);ui
收發數據格式:用於TCP和UDP協議,發送和接收的數據都不包含UDP頭或TCP頭,使用ip地址+端口號做爲地址。
this
(2):網絡層socket - 網絡層 raw socket
創建方式以下:
sockfd = socket(PF_INET, SOCK_RAW, protocol);
收發數據格式:用於IP層,發送數據可使用setsockopt(sockfd, IPPROTO_IP, IP_HDRINCL, &flag, sizeof(int))指明是否由做者本身填充IP頭;接收的數據中包含IP頭。
第一個參數:PF_INET和AF_INET的區別-指定address family時通常設置爲AF_INET,即便用IP;指定protocal family時通常設置PF_INET。在頭文件中有以下定義 #define AF_INET 0
#define PF_INET AF_INET 因此AF_INET與PF_INET徹底同樣
第二個參數說明創建的是一個raw socket
第三個參數分三種狀況: (socket(AF_INET, SOCK_RAW, IPPROTO_TCP/IPPROTO_UDP/IPPROTO_ICMP)能夠同時接收都過傳輸層協議,也能夠自定義傳輸層的協議號)
a.參數protocol用來指明所要接收的協議號,若是是象IPPROTO_TCP(6)這
種非0,非255號的協議,則內核碰到ip頭中protocol域和建立socket所使用參數protocol相同的IP包,就會交給這個rawsocket來處理.
接收IP包:當內核向此raw socket交付數據包的時候,是包括整個IP頭的,而且已是重組好的IP包. 以下:
---------------------------------------------------------------
|ip header|tcp header(or x header)| data |
---------------------------------------------------------------
發送IP包時,默認不用包含IP包頭,只須要填充參數protocol所指定的相應的協議頭.也就是說,用sendto的時候,只須要構造這麼一個緩衝區就能夠了
--------------------------------------------------------------
|tcp header(or udp header or x header)| data |
--------------------------------------------------------------
發送IP包時,若是想本身也想親自處理IP頭,則須要IP_HDRINCL的socket選項.以下:int flag = 1;
setsockopt(sockfd, IPPROTO_IP, IP_HDRINCL, &flag, sizeof(int));這樣,發送時所要提供的緩衝區有成以下形式:
---------------------------------------------------------------
|ip header|tcp header(or x header)| data |
---------------------------------------------------------------
可是,即便是這種狀況,在發送IP包的時候.也不是填充ip頭的全部字段,而是應該將ip頭的id(identification)字段設置爲0,表示讓內核來處理這個字段.同時,內核自動完成ip頭的校驗和的計算,並隨後填充check字段.
b.若是protocol是IPPROTO_RAW(255),這個socket只能用來發送IP包,而不能接收任何數據.發送的數據須要本身填充IP包頭,而且本身計算校驗和.
c.若是protocol是IPPROTO_IP(0).在linux和sco unix上是不容許創建的.
網絡層raw socket的特色:該套接字能夠接收協議類型爲(icmp,igmp等)發往本機的ip數據包;不能收到非發往本地ip的數據包(ip軟過濾會丟棄這些不是發往本機ip的數據包);不能收到從本機發送出去的數據包;發送時須要本身組織tcp udp icmp等傳輸層協議頭部,能夠setsockopt來本身包裝ip頭部;接收的UDP和TCP協議號的數據不會傳給任何原始套接字接口,UDP和TCP協議的數據須要經過MAC層原始套接字來得到。
傳輸層和網絡層socket使用的收發地址:
struct sockaddr_in {
short sin_family;/*Addressfamily通常來講AF_INET(地址族)PF_INET(協議族)*/
unsigned short sin_port;/*Portnumber(必需要採用網絡數據格式,-htons()*/
struct in_addr sin_addr;/*Internetaddress*/
unsigned char sin_zero[8];/*Samesizeasstructsockaddr沒有實際意義,只是爲了跟SOCKADDR結構在內存中對齊*/
};
typedef struct in_addr {
unsigned long s_addr;
};
sockaddr_in addr_in;
memset((char*)&addr_in,0,sizeof(addr_in));
addr_in.sin_family = AF_INET;
addr_in.sin_port = htons(desPort);//能夠不須要
addr_in.sin_addr.s_addr=inet_addr("192.168.0.1");
//inet_pton(AF_INET,"192.168.0.1", &servaddr.sin_addr);
int addr_in_len=sizeof(addr_in);
sendto(sockfd,"a",1,0,(struct sockaddr *)&servaddr, sizeof(servaddr));
recvfrom(sockfd, buf, 2, 0, (struct sockaddr *)&servaddr, &addr_in_len);
收發時進行強制地址轉化。
(3):MAC層socket - raw socket,收發數據鏈路層數據
創建方式以下:
sockfd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_IP/ETH_P_ARP/ETH_P_ALL))
收發數據格式:發送和接收數據使用MAC地址,能夠在MAC層發送和接收任意的數據,若是有上層協議須要自行添加協議頭。
第三個參數:protocol協議類型一共有四個,意義以下
ETH_P_IP 0x800 只接收發往本機mac的ip類型的數據幀
ETH_P_ARP 0x806 只接受發往本機mac的arp類型的數據幀
ETH_P_ARP 0x8035 只接受發往本機mac的rarp類型的數據幀
ETH_P_ALL 0x3 接收發往本機mac的全部類型ip arp rarp的數據幀, 接收從本機發出的全部類型的數據幀.(混雜模式打開的狀況下,會接收到非發往本地mac的數據幀),
MAC接收和發送的數據類型:接收和發送都是數據幀。 數據幀格式以下:
---------------------------------------------------------------
|eth header| data |
---------------------------------------------------------------
即: ---------------------------------------------------------------
|MACdesAddr(6)|MACsrcAddr(6)|protocal(2)| data |
---------------------------------------------------------------
protocal爲以太網協議:0x0806
MAC層使用的收發地址:- 用以指定本機收發使用的網卡信息,而不是目的地址
sll_protocol 是在 linux/if_ether.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_protocol 是在 linux/if_ether.h 頭文件中定義的按網絡層排序的標準的以太楨協議類型。
sll_ifindex 是接口的索引號-0 匹配全部的接口(固然只有合法的才用於綁定)。接口索引號sll_ifindex爲0時表示使用有效的全部接口。接口的sll_ifindex值能夠經過ioctl得到,以下面是得到名字爲「eth0」的接口的索引號:strcpy(ifr.ifr_name,"eth0"); ioctl(fd_packet,SIOCGIFINDEX,&ifr);取得的值保存在ifr結構體的ifr_ifindex中,ifr結構類型爲「struct ifreq」
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)地址和地址長度。要得到接口的物理地址一樣使用ioctl能夠獲得,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或其它值。
收發數據時,只需指定如下三個字段就行。
struct sockaddr_ll sll;
memset((char *)&sll,0, sizeof(struct sockaddr_ll));
sll.sll_family = AF_PACKET;
sll.sll_protocol = htons(ETH_P_ALL);
sll.sll_ifindex = BspGetIfaceIndex (iRecvSocket,"eth1");
struct ifreq ifr;
memset(&ifr,0,sizeof(ifr));
strcpy(ifr.ifr_name,"eth0");
if( (int)BSP_ERROR == ioctl(sock_fd,SIOCGIFINDEX,&ifr))
{return BSP_ERROR;}
sll.sll_ifindex=ifr.ifr_ifindex;
int sll_len=sizeof(sll);
//收發時進行強制地址轉化。
sendto(sockfd,"a",1,0,(struct sockaddr *)&sll, sizeof(sll));
recvfrom(sockfd, buf, 2, 0,(struct sockaddr *)&sll, &sll_len);
若是recvfrom的接收地址爲NULL,表示從當前主機的全部網卡上接收。
MAC層socket的特色:能夠接收發往本地mac的數據幀;能夠接收從本機發送出去的數據幀(第3個參數須要設置爲ETH_P_ALL);能夠接收非發往本地mac的數據幀(網卡須要設置爲promisc混雜模式)。
(4):socket(AF_INET, SOCK_PACKET, htons(ETH_P_ALL)),用於抓包程序,過期了,不要用啊
4、被bind綁定的sock_fd和被connect鏈接的sock_fd
接收指定目的地址的數據:若是原始套接字bind綁定了一個地址,核心只將目的地址爲本機IP地址的數包傳遞給原始套接字,若是某個原始套接字沒有bind地址,核心就會把收到的全部IP數據包發給這個原始套接字.
接收指定源地址的數據:若是原始套接字調用了connect函數,則核心只將源地址爲connect鏈接的IP地址的IP數據包傳遞給這個原始套接字.
接收因此數據:若是原始套接字沒有調用bind和connect函數,則核心會將全部協議匹配的IP數據包傳遞給這個原始套接字.
5、socket的收發函數
原始套接字可使用也能夠不使用bind()函數綁定地址,由於進行發送和接收數據的時候可使sendto(),sendmsg()和函數recvfrom(),recvmsg()來發送和接收數據指定要發送和接收的目的地址的IP。可是使用bind只能接收到與該地址相同的網絡包。
當系統對socket進行了綁定的時候,發送和接收的函數可使用send()和recv()及read()和write()等不須要指定目的地址的函數。
6、 把網卡置爲混雜模式 - 接收eth上全部數據
在正常的狀況下,一個網絡接口在數據鏈路層應該只響應兩種數據幀:
a.與本身硬件地址相匹配的數據幀 ; b.發向全部機器的廣播數據幀
若是要網卡接收全部經過它的數據, 而不論是不是發給它的, 那麼必須把網卡置於混雜模式.
用 Raw Socket 實現代碼以下:
setsockopt(sock, IPPROTO_IP, IP_HDRINCL, (char*)&flag, sizeof(flag);//設置IP頭操做選項
bind(sockRaw, (PSOCKADDR)&addrLocal, sizeof(addrLocal); //把 sockRaw 綁定到本地網卡上
ioctlsocket(sockRaw, SIO_RCVALL, &dwValue); //讓 sockRaw 接受全部的數據
參數:flag 標誌是用來設置 IP 頭操做的, 也就是說要親自處理 IP 頭: bool flag = ture;
addrLocal 爲本地地址: SOCKADDR_IN addrLocal;
dwValue 爲輸入輸出參數, 爲 1 時執行, 0 時取消: DWORD dwValue = 1;
7、內核接收網絡數據後在rawsocket上處理原則
(1):對於UDP/TCP產生的IP數據包,內核不將它傳遞給任何網絡層的原始套接字,而只是將這些數據直接交給對應的傳輸層UDP/TCP數據socket處理句柄。因此,只能經過MAC層原始套接字將第3個參數指定爲htons(ETH_P_IP)來訪問TCP/UDP數據。
(2):對於ICMP和EGP等使用IP數據包承載數據但又在傳輸層之下的協議類型的IP數據包,內核不論是否已經有註冊了句柄來處理這些數據,都會將這些IP數據包複製一份傳遞給協議類型匹配的原始套接字.(網絡層套接字能截獲除TCP/UDP之外傳輸層協議號protocol相同的ip數據)
(3):對於不能識別協議類型的數據包,內核進行必要的校驗,而後會查看是否有類型匹配的原始套接字負責處理這些數據,若是有的話,就會將這些IP數據包複製一份傳遞給匹配的原始套接字,不然,內核將會丟棄這個IP數據包,並返回一個ICMP主機不可達的消息給源主機.
(4):若是原始套接字bind綁定了一個地址,核心只將目的地址爲本機IP地址的數包傳遞給原始套接字,若是某個原始套接字沒有bind地址,核心就會把收到的全部IP數據包發給這個原始套接字.
(5):若是原始套接字調用了connect函數,則核心只將源地址爲connect鏈接的IP地址的IP數據包傳遞給這個原始套接字.
(6):若是原始套接字沒有調用bind和connect函數,則核心會將全部協議匹配的IP數據包傳遞給這個原始套接字.
8、數據流動與數據被提交到socket句柄的時機
網卡首先收到了一個以太網數據幀。 首先,網卡對該數據幀進行硬過濾(根據網卡的模式不一樣會有不一樣的動做,若是設置了promisc混雜模式的話,則不作任何過濾直接交給上一層輸入例程,不然非本機mac或者廣播mac會被直接丟棄).若是成功的話,會進入ip輸入例程.可是在進入ip輸入例程以前,系統會檢查系統中是否有通socket(AF_PACKET, SOCK_RAW, ..)建立的套接字.若是有而且協議相符,系統就給每一個這樣的socket接收緩衝區發送一個數據幀拷貝。 而後,進入了ip輸入例程,ip層會對該數據包進行軟過濾,就是檢查校驗或者丟棄非本機ip或者廣播ip的數據包等,若是成功的話會進入傳輸層輸入例程.可是在交給傳輸層輸入例程以前,系統會檢查系統中是否有經過socket(AF_INET, SOCK_RAW, ..)建立的套接字.若是有的話而且協議相符,系統就給每一個這樣的socket接收緩衝區發送一個數據幀拷貝。 最後,進入傳輸層輸入例程。
9、例程
例子1:網絡層raw socket。下面的程序利用Raw Socket發送TCP報文,並徹底手工創建報頭:
int sendTcp(unsigned short desPort, unsigned long desIP)
{
WSADATA WSAData;
SOCKET sock;
SOCKADDR_IN addr_in;
IPHEADER ipHeader;
TCPHEADER tcpHeader;
PSDHEADER psdHeader;
char szSendBuf[MAX_LEN] = { 0 };
BOOL flag;
int rect, nTimeOver;
if (WSAStartup(MAKEWORD(2, 2), &WSAData) != 0)
{
printf("WSAStartup Error!\n");
return false;
}
if ((sock = WSASocket(AF_INET, SOCK_RAW, IPPROTO_RAW, NULL, 0,
WSA_FLAG_OVERLAPPED)) == INVALID_SOCKET)
{
printf("Socket Setup Error!\n");
return false;
}
flag = true;
if (setsockopt(sock, IPPROTO_IP, IP_HDRINCL, (char*) &flag, sizeof(flag))
==SOCKET_ERROR)
{
printf("setsockopt IP_HDRINCL error!\n");
return false;
}
nTimeOver = 1000;
if (setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, (char*) &nTimeOver, sizeof
(nTimeOver)) == SOCKET_ERROR)
{
printf("setsockopt SO_SNDTIMEO error!\n");
return false;
}
addr_in.sin_family = AF_INET;
addr_in.sin_port = htons(desPort);
addr_in.sin_addr.S_un.S_addr = inet_addr(desIP);
//填充IP報頭
ipHeader.h_verlen = (4 << 4 | sizeof(ipHeader) / sizeof(unsigned long));
// ipHeader.tos=0;
ipHeader.total_len = htons(sizeof(ipHeader) + sizeof(tcpHeader));
ipHeader.ident = 1;
ipHeader.frag_and_flags = 0;
ipHeader.ttl = 128;
ipHeader.proto = IPPROTO_TCP;
ipHeader.checksum = 0;
ipHeader.sourceIP = inet_addr("localhost");
ipHeader.destIP = desIP;
//填充TCP報頭
tcpHeader.th_dport = htons(desPort);
tcpHeader.th_sport = htons(SOURCE_PORT); //源端口號
tcpHeader.th_seq = htonl(0x12345678);
tcpHeader.th_ack = 0;
tcpHeader.th_lenres = (sizeof(tcpHeader) / 4 << 4 | 0);
tcpHeader.th_flag = 2; //標誌位探測,2是SYN
tcpHeader.th_win = htons(512);
tcpHeader.th_urp = 0;
tcpHeader.th_sum = 0;
psdHeader.saddr = ipHeader.sourceIP;
psdHeader.daddr = ipHeader.destIP;
psdHeader.mbz = 0;
psdHeader.ptcl = IPPROTO_TCP;
psdHeader.tcpl = htons(sizeof(tcpHeader));
//計算校驗和
memcpy(szSendBuf, &psdHeader, sizeof(psdHeader));
memcpy(szSendBuf + sizeof(psdHeader), &tcpHeader, sizeof(tcpHeader));
tcpHeader.th_sum = checksum((unsigned short*)szSendBuf, sizeof(psdHeader) + sizeof
(tcpHeader));
memcpy(szSendBuf, &ipHeader, sizeof(ipHeader));
memcpy(szSendBuf + sizeof(ipHeader), &tcpHeader, sizeof(tcpHeader));
memset(szSendBuf + sizeof(ipHeader) + sizeof(tcpHeader), 0, 4);
ipHeader.checksum = checksum((unsigned short*)szSendBuf, sizeof(ipHeader) + sizeof
(tcpHeader));
memcpy(szSendBuf, &ipHeader, sizeof(ipHeader));
rect = sendto(sock, szSendBuf, sizeof(ipHeader) + sizeof(tcpHeader), 0,
(struct sockaddr*) &addr_in, sizeof(addr_in));
if (rect == SOCKET_ERROR)
{
printf("send error!:%d\n", WSAGetLastError());
return false;
}
else
printf("send ok!\n");
closesocket(sock);
WSACleanup();
return rect;
}
例子2:MAC層raw socket
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h> // close()
#include <string.h> // strcpy, memset(), and memcpy()
#include <netdb.h> // struct addrinfo
#include <sys/types.h> // needed for socket(), uint8_t, uint16_t, uint32_t
#include <sys/socket.h> // needed for socket()
#include <netinet/in.h> // IPPROTO_ICMP, INET_ADDRSTRLEN
#include <netinet/ip.h> // struct ip and IP_MAXPACKET (which is 65535)
#include <netinet/ip_icmp.h> // struct icmp, ICMP_ECHO
#include <arpa/inet.h> // inet_pton() and inet_ntop()
#include <sys/ioctl.h> // macro ioctl is defined
#include <bits/ioctls.h> // defines values for argument "request" of ioctl.
#include <net/if.h> // struct ifreq
#include <linux/if_ether.h> // ETH_P_IP = 0x0800, ETH_P_IPV6 = 0x86DD
#include <linux/if_packet.h> // struct sockaddr_ll (see man 7 packet)
#include <net/ethernet.h>
#include <errno.h> // errno, perror()
#define ETH_P_DEAN 0x8874 //自定義的以太網協議type
int main (int argc, char **argv)
{
int i, datalen,frame_length, sd, bytes;
char *interface="eth1";;
uint8_t data[IP_MAXPACKET];
uint8_t src_mac[6];
uint8_t dst_mac[6];;
uint8_t ether_frame[IP_MAXPACKET];
struct sockaddr_ll device;
// Submit request for a socket descriptor to look up interface.
if ((sd = socket (PF_PACKET, SOCK_RAW, htons (ETH_P_ALL))) < 0) {//第一次建立socket是爲了獲取本地網卡信息
perror ("socket() failed to get socket descriptor for using ioctl() ");
exit (EXIT_FAILURE);
}
// Use ioctl() to look up interface name and get its MAC address.
struct ifreq ifr;
memset (&ifr, 0, sizeof (ifr));
snprintf (ifr.ifr_name, sizeof (ifr.ifr_name), "%s", interface);
if (ioctl (sd, SIOCGIFHWADDR, &ifr) < 0) {
perror ("ioctl() failed to get source MAC address ");
return (EXIT_FAILURE);
}
close (sd);
memcpy (src_mac, ifr.ifr_hwaddr.sa_data, 6); // Copy source MAC address.
memset (&device, 0, sizeof (device));
if ((device.sll_ifindex = if_nametoindex(interface)) == 0) {
perror ("if_nametoindex() failed to obtain interface index ");
exit (EXIT_FAILURE);
}
// Set destination MAC address: you need to fill these out
//設置目的網卡地址
dst_mac[0] = 0x10; dst_mac[1] = 0x78; dst_mac[2] = 0xd2;
dst_mac[3] = 0xc6; dst_mac[4] = 0x2f; dst_mac[5] = 0x89;
device.sll_family = AF_PACKET;
memcpy (device.sll_addr, src_mac, 6);
device.sll_halen = htons (6);
// 發送的data,可是抓包時看到最小數據長度爲46,以太網協議規定以太網幀數據域最小爲46字節,不足的自動補零處理
datalen = 12;
data[0] = 'h';data[1] = 'e';data[2] = 'l';data[3] = 'l';data[4] = 'o';data[5] = ' ';
data[6] = 'w';data[7] = 'o';data[8] = 'r';data[9] = 'l';data[10] = 'd';data[11] = '!';
memcpy (ether_frame, dst_mac, 6);
memcpy (ether_frame + 6, src_mac, 6);
ether_frame[12] = ETH_P_DEAN / 256;
ether_frame[13] = ETH_P_DEAN % 256;
memcpy (ether_frame + 14 , data, datalen);
frame_length = 6 + 6 + 2 + datalen;
if ((sd = socket (PF_PACKET, SOCK_RAW, htons (ETH_P_ALL))) < 0) {//建立正真發送的socket
perror ("socket() failed ");
exit (EXIT_FAILURE);
}
if ((bytes = sendto (sd, ether_frame, frame_length, 0, (struct sockaddr *) &device, sizeof (device))) <= 0) {
perror ("sendto() failed");
exit (EXIT_FAILURE);
}
close (sd);
return (EXIT_SUCCESS);
}
例子3:ARP地址解析
ARP協議幀:
以太網的ARP數據結構:
struct arppacket {
unsigned short ar_hrd; /*硬件類型*/
unsigned short ar_pro; /*協議類型*/
unsigned char ar_hln; /*硬件地址長度*/
unsigned char ar_pln; /*協議地址長度*/
unsigned short ar_op; /*ARP操做碼*/
unsigned char ar_sha[ETH_ALEN]; /*發送方MAC地址*/
unsigned char ar_sip[4]; /*發送方IP地址*/
unsigned char ar_tha[ETH_ALEN]; /*目的MAC地址*/
unsigned char ar_tip[4]; /*目的IP地址*/
};
ARP請求包的構建包含了以太網頭部部分、ARP頭部部分、ARP的數據部分。其中特別要注意目的以太網地址,因爲ARP的做用就是查找目的IP地 址的MAC地址,因此目的以太網地址是未知的。並且須要在整個以太網上查找其IP地址,因此目的以太網地址是一個全爲1的值,即爲 {0xFF,0xFF,0xFF ,0xFF ,0xFF ,0xFF}。
#include <sys/socket.h>
#include <sys/ioctl.h> /*ioctl 命令*/
#include <Linux/if_ether.h> /*ethhdr 結構*/
#include <net/if.h> /*ifreq 結構*/
#include <netinet/in.h> /*in_addr結構*/
#include <Linux/ip.h> /*iphdr 結構*/
#include <Linux/udp.h> /*udphdr 結構*/
#include <Linux/tcp.h> /*tcphdr 結構*/
struct arppacket {
unsigned short ar_hrd; /*硬件類型*/
unsigned short ar_pro; /*協議類型*/
unsigned char ar_hln; /*硬件地址長度*/
unsigned char ar_pln; /*協議地址長度*/
unsigned short ar_op; /*ARP操做碼*/
unsigned char ar_sha[ETH_ALEN]; /*發送方MAC地址*/
unsigned char ar_sip[4]; /*發送方IP地址*/
unsigned char ar_tha[ETH_ALEN]; /*目的MAC地址*/
unsigned char ar_tip[4]; /*目的IP地址*/
};
int main(int argc, char*argv[])
{
char ef[ETH_FRAME_LEN]; /*以太幀緩衝區*/
struct ethhdr*p_ethhdr; /*以太網頭部指針*/
char eth_dest[ETH_ALEN]={0xFF,0xFF,0xFF,0xFF,0xFF,0xFF}; /*目的以太網地址*/
char eth_source[ETH_ALEN]={0x00,0x0C,0x29,0x73,0x9D,0x15}; /*源以太網地址*/ char eth_dest[4]={0xFF,0xFF,0xFF,0xFF,0xFF,0xFF}; /*目的IP地址*/
int n;
int fd;
fd = socket(AF_INET, SOCK_PACKET, htons(0x0003)); /*fd是套接口的描述符*/
p_ethhdr = (struct ethhdr*)ef; /*使p_ethhdr指向以太網幀的幀頭*/
memcpy(p_ethhdr->h_dest, eth_dest, ETH_ALEN); /*複製目的以太網地址*/
memcpy(p_ethhdr->h_source, eth_source, ETH_ALEN); /*複製源以太網地址*/
p_ethhdr->h_proto = htons(0x0806); /*設置協議類型,以太網0x0806*/
struct arppacket*p_arp;
p_arp = ef + ETH_HLEN; /*定位ARP包地址*/
p_arp->ar_hrd = htons(0x1); /*arp硬件類型*/
p_arp->ar_pro = htons(0x0800); /*協議類型*/
p_arp->ar_hln = 6; /*硬件地址長度*/
p_arp->ar_pln = 4; /*IP地址長度*/
memcpy(p_arp->ar_sha, eth_source, ETH_ALEN); /*複製源以太網地址*/
(unsigned int*)p_arp->ar_sip = inet_addr("192.168.1.152"); /*源IP地址*/
memcpy(p_arp->ar_tha, eth_dest, ETH_ALEN); /*複製目的以太網地址*/
(unsigned int*)p_arp->ar_tip = inet_addr("192.168.1.1"); /*目的IP地址*/
/*發送ARP請求8次,間隔1s*/
int i = 0;
for(i=0;i<8;i++){
n = write(fd, ef, ETH_FRAME_LEN);/*發送*/
sleep(1); /*等待1s*/
}
close(fd);
return 0;
}
例子4. ARP徹底實現
獲取局域網ip-mac例子:
/***************************************************************************
* Copyright (C) 2007 by qzc * * qzc1998@126.com *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any laterversion.
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
***************************************************************************/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <stdio.h>
#include <sys/time.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <netdb.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <netinet/if_ether.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <net/if.h>
/* 獲得本機的mac地址和ip地址 */
int GetLocalMac ( const char *device,char *mac,char *ip ) {
int sockfd;
struct ifreq req;
struct sockaddr_in * sin;
if ( ( sockfd = socket ( PF_INET,SOCK_DGRAM,0 ) ) ==-1 ) {
fprintf ( stderr,"Sock Error:%s\n\a",strerror ( errno ) );
return ( -1 );
}
memset ( &req,0,sizeof ( req ) );
strcpy ( req.ifr_name,device );
if ( ioctl ( sockfd,SIOCGIFHWADDR, ( char * ) &req ) ==-1 ) {
fprintf ( stderr,"ioctl SIOCGIFHWADDR:%s\n\a",strerror ( errno ) );
close ( sockfd );
return ( -1 );
}
memcpy ( mac,req.ifr_hwaddr.sa_data,6 );
req.ifr_addr.sa_family = PF_INET;
if ( ioctl ( sockfd,SIOCGIFADDR, ( char * ) &req ) ==-1 ) {
fprintf ( stderr,"ioctl SIOCGIFADDR:%s\n\a",strerror ( errno ) );
close ( sockfd );
return ( -1 );
}
sin = ( struct sockaddr_in * ) &req.ifr_addr;
memcpy ( ip, ( char * ) &sin->sin_addr,4 );
return ( 0 );
}
char *mac_ntoa ( const unsigned char *mac ) {
static char buffer[18];
memset ( buffer,0,sizeof ( buffer ) );
sprintf ( buffer,"%02X:%02X:%02X:%02X:%02X:%02X",
mac[0],mac[1],mac[2],mac[3],mac[4],mac[5] );
return ( buffer );
}
/* 根據 RFC 0826 修改*/
typedef struct _Ether_pkg Ether_pkg;
struct _Ether_pkg { /* 前面是ethernet頭 */
unsigned char ether_dhost[6]; /* 目地硬件地址 */
unsigned char ether_shost[6]; /* 源硬件地址 */
unsigned short int ether_type; /* 網絡類型 */ /* 下面是arp協議 */
unsigned short int ar_hrd; /* 硬件地址格式 */
unsigned short int ar_pro; /* 協議地址格式 */
unsigned char ar_hln; /* 硬件地址長度(字節) */
unsigned char ar_pln; /* 協議地址長度(字節) */
unsigned short int ar_op; /* 操做代碼 */
unsigned char arp_sha[6]; /* 源硬件地址 */
unsigned char arp_spa[4]; /* 源協議地址 */
unsigned char arp_tha[6]; /* 目地硬件地址 */
unsigned char arp_tpa[4]; /* 目地協議地址 */
};
void parse_ether_package ( const Ether_pkg *pkg ) {
printf ( "源 IP=[%s] MAC=[%s]\n",inet_ntoa ( * ( struct in_addr * ) pkg->arp_spa ),mac_ntoa ( pkg->arp_sha ) );
printf ( "目地 IP=[%s] MAC=[%s]\n",inet_ntoa ( * ( struct in_addr * ) pkg->arp_tpa ),mac_ntoa ( pkg->arp_tha ) );
}
int sendpkg ( char * mac,char * broad_mac,char * ip,char * dest ) {
Ether_pkg pkg;
struct hostent *host =NULL;
struct sockaddr sa;
int sockfd,len;
char buffer[255];
unsigned char temp_ip[5];
memset ( ( char * ) &pkg,'\0',sizeof ( pkg ) ); /* 填充ethernet包文 */
memcpy ( ( char * ) pkg.ether_shost, ( char * ) mac,6 );
memcpy ( ( char * ) pkg.ether_dhost, ( char * ) broad_mac,6 );
pkg.ether_type = htons ( ETHERTYPE_ARP ); /* 下面填充arp包文 */
pkg.ar_hrd = htons ( ARPHRD_ETHER );
pkg.ar_pro = htons ( ETHERTYPE_IP );
pkg.ar_hln = 6;
pkg.ar_pln = 4;
pkg.ar_op = htons ( ARPOP_REQUEST );
memcpy ( ( char * ) pkg.arp_sha, ( char * ) mac,6 );
memcpy ( ( char * ) pkg.arp_spa, ( char * ) ip,4 );
memcpy ( ( char * ) pkg.arp_tha, ( char * ) broad_mac,6 );
fflush ( stdout );
memset ( temp_ip,0,sizeof ( temp_ip ) );
if ( inet_aton ( dest, ( struct in_addr * ) temp_ip ) ==0 ) {
if ( ( host = gethostbyname ( dest ) ) ==NULL ) {
fprintf ( stderr,"Fail! %s\n\a",hstrerror ( h_errno ) );
return ( -1 );
}
memcpy ( ( char * ) temp_ip,host->h_addr,4 );
}
memcpy ( ( char * ) pkg.arp_tpa, ( char * )temp_ip,4 ); /* 實際應該使用PF_PACKET */
if ( ( sockfd = socket ( PF_INET,SOCK_PACKET,htons ( ETH_P_ALL ) ) ) ==-1 ) {
fprintf ( stderr,"Socket Error:%s\n\a",strerror ( errno ) );
return ( 0 );
}
memset ( &sa,'\0',sizeof ( sa ) );
strcpy ( sa.sa_data,"eth0" );
len = sendto ( sockfd,&pkg,sizeof ( pkg ),0,&sa,sizeof ( sa ) );
if ( len != sizeof ( pkg ) ) {
fprintf ( stderr,"Sendto Error:%s\n\a",strerror ( errno ) );
close(sockfd);
return ( 0 );
}
Ether_pkg *parse;
parse = ( Ether_pkg * ) buffer;
fd_set readfds;
struct timeval tv;
while(1) {
tv.tv_sec = 0;
tv.tv_usec = 500000; //500毫秒
FD_ZERO ( &readfds );
FD_SET ( sockfd, &readfds );
len = select ( sockfd+1, &readfds, 0, 0, &tv );
if ( len>-1 ) {
if ( FD_ISSET ( sockfd,&readfds ) ) {
memset ( buffer,0,sizeof ( buffer ) );
len=recvfrom ( sockfd,buffer,sizeof ( buffer ),0,NULL,&len );
if ( ( ntohs ( parse->ether_type ) ==ETHERTYPE_ARP ) &&( ntohs ( parse->ar_op ) == ARPOP_REPLY ) ){
parse_ether_package ( parse );
}
}
}
break;
}
close(sockfd);
return 1;
}
int main ( int argc,char **argv ) {
struct timeval tvafter,tvpre;
struct timezone tz;
gettimeofday ( &tvpre , &tz );
unsigned char mac[7];
unsigned char ip[5];
char dest[16]={0};
unsigned char broad_mac[7]={0xff,0xff,0xff,0xff,0xff,0xff,0x00};
memset ( mac,0,sizeof ( mac ) );
memset ( ip,0,sizeof ( ip ) );
if ( GetLocalMac ( "eth0",mac,ip ) ==-1 )
return ( -1 );
printf ( "本地 Mac=[%s] Ip=[%s]\n", mac_ntoa ( mac ),
inet_ntoa ( * ( struct in_addr * ) ip ) );
sprintf ( dest,"255.255.255.255");
sendpkg ( mac,broad_mac,ip,dest );
gettimeofday ( &tvafter , &tz );
printf ( "\n程序執行完畢:%d毫秒\n", ( tvafter.tv_sec-tvpre.tv_sec ) *1000+ ( tvafter.tv_usec-tvpre.tv_usec ) /1000 );
return 0;
}