linux下簡單的原始套接字通訊
阿新 • • 發佈:2019-02-12
原始套接字程式設計分析
與普通socket套接字程式設計,區別在於,原始套接字可以自行組裝資料包(偽裝本地 IP,本地 MAC),可以接收本機網絡卡上所有的資料幀(資料包)。另外,必須在管理員許可權下才能使用原始套接字。
通常情況下所接觸到的套接字(Socket)為兩類:
- 流式套接字(SOCK_STREAM):一種面向連線的Socket,針對於面向連線的TCP 服務應用;
- 資料報式套接字(SOCK_DGRAM):一種無連線的Socket,對應於無連線的UDP 服務應用。
而原始套接字(SOCK_RAW)與標準套接字(SOCK_STREAM、SOCK_DGRAM)的區別在於原始套接字直接置“根”於作業系統網路核心(Network Core),而 SOCK_STREAM、SOCK_DGRAM 則“懸浮”於 TCP 和 UDP 協議的外圍,如圖,
流式套接字只能收發 TCP 協議的資料,資料報套接字只能收發 UDP 協議的資料,原始套接字可以收發沒經過核心協議棧的資料包
原始套接字程式設計
int socket ( int family, int type, int protocol );
- family:協議簇 寫 PF_PACKET(AF_PACKET)
- type: 套接字類,寫 SOCK_RAW
protocol:協議類別,指定可以接收或傳送的資料包型別,不能寫 “0”,取值如下,注意,傳參時需要用 htons() 進行位元組序轉換。
ETH_P_IP:IPV4資料包
ETH_P_ARP:ARP資料包
ETH_P_ALL:任何協議型別的資料包返回值:
成功( >0 ):套接字,這裡為鏈路層的套接字
失敗( <0 ):出錯
原始套接字demo
下面主要通過PF_PACKET和SOCK_RAW,實現傳送和接收自定義type乙太網資料包。
乙太網的偵結構如下:
------------------------------------------------------
| 目的地址 | 源地址 | 型別 | 資料 |
------------------------------------------------------
| 6 byte | 6 byte | 2 byte | 46~1500 byte |
/**
* recv.c
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <unistd.h>
#include <netdb.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/ip_icmp.h>
#include <arpa/inet.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <linux/if_ether.h>
#include <bits/ioctls.h>
#include <linux/if_packet.h>
#include <net/ethernet.h>
#include <errno.h>
#define ETH_P_NP 0x0807
#define IFRNAME0 "eth0"
#define IFRNAME2 "eth2"
unsigned char dest_mac[6] = {0};
int main(int argc,char **argv)
{
int i ,datalen;
int sd ;
unsigned char data[IP_MAXPACKET] = {0};
unsigned char *buf = NULL;
struct sockaddr_ll device;
struct ifreq ifr;
socklen_t sll_len = sizeof(struct sockaddr_ll);
bzero(&device,sizeof(struct sockaddr_ll));
bzero(&ifr,sizeof(struct ifreq));
if((sd = socket(PF_PACKET,SOCK_DGRAM,htons(ETH_P_ALL))) < 0) {
perror("socket() failed to get socket descriptor for using ioctl()");
exit(EXIT_FAILURE);
}
memcpy(ifr.ifr_name,IFRNAME2,sizeof(struct ifreq));
if(ioctl(sd,SIOCGIFHWADDR,&ifr) < 0) {
perror("ioctl() failed to get source MAC address");
return (EXIT_FAILURE);
}
close(sd);
memcpy(dest_mac,ifr.ifr_hwaddr.sa_data,6);
memcpy(device.sll_addr,dest_mac,6);
device.sll_ifindex = ifr.ifr_ifindex;
device.sll_family = PF_PACKET;
device.sll_halen = htons(6);
device.sll_protocol = htons(ETH_P_NP);
if((sd = socket (PF_PACKET,SOCK_RAW,htons(ETH_P_NP))) < 0) {
perror("socket() failed");
exit(EXIT_FAILURE);
}
datalen = recvfrom(sd,data,1024,0,(struct sockaddr *)&device,&sll_len);
if (datalen < 0) { printf("error\n"); exit(-1); }
buf = data + 14;
printf("ip data : %s\n",buf);
return 0;
}
/**
* send.c
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <unistd.h>
#include <netdb.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/ip_icmp.h>
#include <arpa/inet.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <linux/if_ether.h>
#include <bits/ioctls.h>
#include <linux/if_packet.h>
#include <net/ethernet.h>
#include <errno.h>
#define ETH_P_NP 0x0807
#define IFRNAME0 "eth0"
#define IFRNAME1 "eth1"
unsigned char source_mac[6] = {0};
unsigned char dest_mac[6] = {0};
int main(int argc,char **argv)
{
int i ,datalen ,frame_length;
int sd ,bytes;
uint8_t data[IP_MAXPACKET];
uint8_t ether_frame[IP_MAXPACKET];
struct sockaddr_ll device;
struct ifreq ifr;
bzero(&device,sizeof(struct sockaddr_ll));
bzero(&ifr,sizeof(struct ifreq));
if((sd = socket(PF_PACKET,SOCK_DGRAM,htons(ETH_P_ALL))) < 0) {
perror("socket() failed to get socket descriptor for using ioctl()");
exit(EXIT_FAILURE);
}
memcpy(ifr.ifr_name,IFRNAME1,sizeof(struct ifreq));
if(ioctl(sd,SIOCGIFHWADDR,&ifr) < 0) {
perror("ioctl() failed to get source MAC address");
return (EXIT_FAILURE);
}
close(sd);
//copy source MAC address
memcpy(source_mac,ifr.ifr_hwaddr.sa_data,6);
memcpy(device.sll_addr,source_mac,6);
if((device.sll_ifindex = if_nametoindex(IFRNAME1)) == 0) {
perror("if_nametoindex() failed to obtain interface index");
exit(EXIT_FAILURE);
}
// /*or*/ device.sll_addr = ifr.ifr_ifindex;
device.sll_family = PF_PACKET;
device.sll_halen = htons(6);
//device.sll_protocol = htons(ETH_P_NP);
// ifconfig eth2 /*00:0c:29:78:65:12*/
dest_mac[0] = 0x00;
dest_mac[1] = 0x0c;
dest_mac[2] = 0x29;
dest_mac[3] = 0x78;
dest_mac[4] = 0x65;
dest_mac[5] = 0x12;
datalen = 9;
data[0] = 'h';
data[1] = 'e';
data[2] = 'l';
data[3] = 'l';
data[4] = 'o';
data[5] = ' ';
data[6] = 'z';
data[7] = 'o';
data[8] = 'u';
frame_length = 6 + 6 + 2 + datalen;
/* 如果希望方便的話,可以嘗試使用libnet */
memcpy(ether_frame,dest_mac,6);
memcpy(ether_frame + 6,source_mac,6);
ether_frame[12] = ETH_P_NP / 256;
ether_frame[13] = ETH_P_NP % 256;
memcpy(ether_frame + 14,data,datalen);
//one question : Here third parameters make me feel confused
//if((sd = socket (PF_PACKET,SOCK_RAW,htons(ETH_P_NP))) < 0)
if((sd = socket (PF_PACKET,SOCK_RAW,htons(ETH_P_ALL))) < 0) {
perror("socket() failed");
exit(EXIT_FAILURE);
}
while(1) {
if ((bytes = sendto (sd, ether_frame, frame_length, 0, (struct sockaddr *)&device, sizeof (device))) <= 0) {
perror("sendto() failed");
exit(EXIT_FAILURE);
}
sleep(4);
}
close(sd);
return 0;
}
其他型別的原始套接字,可參考以下的博文