前两天,我做一个在数据链路层转发数据的linux下的小程序。老师蛮严肃,所以这个程序的具体应用我也不是特别清楚。不过它的大致功能是这样的:能够将ethx收到的所有数据从ethy接口转发出去,整个过程操作在数据链路层,这里ethx和ethy可能相同。
网上查了下资料并结合unp,了解到linux下访问数据链路层大致有两类方法。第一类方法是协议栈提供的操作接口,SOCK_PACKET接口或PF_PACKET接口;第二类方法是利用第三方提供的库,如libpcap或libnet等。
因为这个小程序的目标是简单易用,不可能让用户使用前都去装libpcap之类的库,所以我选择了第一类方法。第一类方法中的两个接口调用方法如下。
fd = socket(PF_INET, SOCK_PACKET, htons(ETH_P_ALL)); /* 这是较旧的方法,不推荐使用 */
fd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL)); /* 这是较新的方法 */
可以看出,这两个接口的使用方法与tcp或udp的接口的使用非常类似,当然socket函数的第三个参数用了“ htons(ETH_PALL)”,以前没见过这样的字序转换(tcp和udp中没见过,不知道这里为什么接口有点小不同)。PS:SOCK_RAW,表示返回原始包,即不剥去链路层帧头;而SOCK_DGRAM,则表示返回煮熟了的包(链路层帧头被剥掉了)。ETH_P_ALL抓取链路层的所有包(不管是arp包还是ip包),而ETH_P_IP则表示只抓取IP包。这些选项参数,请参考man文档(如man 7 packet).
程序的流程。解析命令行参数(如根据参数,判断从哪个网络接口读数据,又从哪个网络接口写数据)-->初始化网络接口(主要是bind接口和置接口为混杂模式)-->处理数据(打印并转发)。
为什么要bind网络接口呢?因为默认情况下,读或写操作操作的是所有网络接口,如果我们只对某个网络接口感兴趣,如eth0,我们就需要bind eth0。
置网络接口为混杂模式,能使我们抓取到所有经过该接口的数据包而不是通常情况下的只有目的地位为该接口的包。注意置网络接口为混杂模式,因为协议栈(IP层的筛选功能),并不会导致上层应用程序受到太大影响,当然这无疑增加了协议栈的负担。
下面是我的代码。
/**************nd_rdwt.c***************/
*author:bripengandre *
****************************************/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define MAX_IF_NAME_SIZE 20
#define MAX_IF_CNT 10
#define IF_CNT_STEP 2
#define RECV_BUF_SIZE 1600
#define SEND_BUF_SIZE 1600
enum _FD_TYPE
{
SOCKFD_RD = 0,
SOCKFD_WT = 1
};
typedef struct _if_config
{
char if_rdwt_name[MAX_IF_NAME_SIZE+1];
char if_rd_name[MAX_IF_NAME_SIZE+1];
char if_wt_name[MAX_IF_NAME_SIZE+1];
long cap_cnt;
char is_verbose;
char is_promiscuous;
}if_conf_t, *pif_conf_t;
static long cap_cnt;
static void init_if_conf(if_conf_t * if_conf);
static void process_cmd(if_conf_t * if_conf);
static int init_sockfd(if_conf_t *if_conf, int type);
static void process_recv_pkt(char *recv_buf, int recv_len, char **send_buf, int *send_len, if_conf_t *if_conf);
static void usage(char *exe_name);
static int get_if_index(int fd, char *if_name);
static int set_promiscuous(int fd, char *if_name);
static void dis_pkt(char *buf, int len);
static void print_char(char ch);
int main(int argc, char *argv[])
{
int op, recv_len, send_len;
if_conf_t if_conf;
int fd_rd, fd_wt, max_fd;
fd_set rd_set, wt_set;
char recv_buf[RECV_BUF_SIZE], send_buf[SEND_BUF_SIZE];
char *buf;
init_if_conf(&if_conf);
while( (op = getopt(argc, argv, "i:r:w:c:np")) != -1)
{
switch(op)
{
case 'i':
//printf("%d, %s\n", optind, argv[optind]);
strncpy(if_conf.if_rdwt_name, optarg, MAX_IF_NAME_SIZE);
//printf("%s\n", argv[optind]);
break;
case 'r':
strncpy(if_conf.if_rd_name, optarg, MAX_IF_NAME_SIZE);
break;
case 'w':
strncpy(if_conf.if_wt_name, optarg, MAX_IF_NAME_SIZE);
break;
case 'c':
if_conf.cap_cnt = atol(optarg);
break;
case 'n':
if_conf.is_verbose = 0;
break;
case 'p':
if_conf.is_promiscuous = 0;
break;
default:
usage(argv[0]);
exit(0);
break;
}
}
if(argc-optind >= 1)
{
usage(argv[0]);
exit(0);
}
process_cmd(&if_conf);
if( (fd_rd = init_sockfd(&if_conf, SOCKFD_RD)) < 0)
{
fprintf(stderr, "init_sockfd error!\n");
exit(1);
}
if(strncmp(if_conf.if_rd_name, if_conf.if_wt_name, MAX_IF_NAME_SIZE) != 0)
{
if( (fd_wt = init_sockfd(&if_conf, SOCKFD_WT)) < 0)
{
fprintf(stderr, "init_sockfd error!\n");
exit(1);
}
}
else
{
fd_wt = fd_rd;
}
printf("start to capture packts from %s, and then send them to %s\n",
if_conf.if_rd_name, if_conf.if_wt_name);
cap_cnt = 0;
max_fd = fd_rd+1;
while(1)
{
FD_ZERO(&rd_set);
FD_SET(fd_rd, &rd_set);
switch(select(max_fd, &rd_set, NULL, NULL, NULL))
{
case 0: /* time out , should not execute here */
break;
case -1: /* error */
if(errno == EINTR)
{
continue;
}
else
{
sleep(1); /* sleep 1 second to wait for resource */
}
break;
default: /* normal */
if(FD_ISSET(fd_rd, &rd_set))
{
memset(recv_buf, 0, sizeof(recv_buf)); /* not needed */
recv_len = read(fd_rd, recv_buf, sizeof(recv_buf));
if(recv_len < 0 )
{
if(errno == EINTR)
{
continue;
}
else
{
fprintf(stderr, "read error: %s\n", strerror(errno));
exit(1);
}
}
if(recv_len > 0)
{
send_len = SEND_BUF_SIZE;
buf = send_buf;
process_recv_pkt(recv_buf, recv_len, &buf, &send_len, &if_conf);
//memcpy(buf+send_len-12, "Hello World!", 12);
write(fd_wt, buf, send_len); /* should assure send successfully */
if(if_conf.cap_cnt != 0 && cap_cnt >= if_conf.cap_cnt)
{
goto MAIN_EXIT;
}
}
}
}
}
MAIN_EXIT:
return 0;
}
static void init_if_conf(if_conf_t * if_conf)
{
memset(if_conf->if_rdwt_name, 0, sizeof(if_conf->if_rdwt_name));
strncpy(if_conf->if_rd_name, "eth0", MAX_IF_NAME_SIZE);
strncpy(if_conf->if_wt_name, "eth0", MAX_IF_NAME_SIZE);
if_conf->cap_cnt = 0;
if_conf->is_verbose = 1;
if_conf->is_promiscuous = 0;
}
static void process_cmd(if_conf_t * if_conf)
{
if(if_conf->if_rdwt_name[0] != '\0')
{
strncpy(if_conf->if_rd_name, if_conf->if_rdwt_name, MAX_IF_NAME_SIZE);
strncpy(if_conf->if_wt_name, if_conf->if_rdwt_name, MAX_IF_NAME_SIZE);
}
}
static int init_sockfd(if_conf_t *if_conf, int type)
{
int sockfd;
struct sockaddr_ll sll;
char if_name[MAX_IF_NAME_SIZE+1];
if( (sockfd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_IP))) < 0)
{
fprintf(stderr, "socket error: %s\n", strerror(errno));
return (-1);
}
if(type == SOCKFD_RD)
{
strncpy(if_name, if_conf->if_rd_name, MAX_IF_NAME_SIZE);
}
else if(type == SOCKFD_WT)
{
strncpy(if_name, if_conf->if_wt_name, MAX_IF_NAME_SIZE);
}
memset(&sll, 0, sizeof(sll));
sll.sll_family = PF_PACKET;
if( (sll.sll_ifindex = get_if_index(sockfd, if_name)) == -1)
{
fprintf(stderr, "get_if_index of %s error!\n", if_name);
return (-1);
}
sll.sll_protocol = htons(ETH_P_IP);
if(bind(sockfd, (struct sockaddr *)&sll, sizeof(sll)) < 0)
{
fprintf(stderr, "bind error: %s\n", strerror(errno));
return (-1);
}
if(type == SOCKFD_RD && if_conf->is_promiscuous)
{
if(set_promiscuous(sockfd, if_name) == 0)
{
fprintf(stderr, "set %s promiscuous error!\n", if_name);
}
}
return sockfd;
}
static void process_recv_pkt(char *recv_buf, int recv_len, char **send_buf, int *send_len, if_conf_t *if_conf)
{
cap_cnt++;
if(if_conf->is_verbose)
{
printf("*****************packet %ld***********************\n", cap_cnt);
dis_pkt(recv_buf, recv_len);
printf("\n");
}
*send_buf = recv_buf;
*send_len = recv_len;
}
static void usage(char *exe_name)
{
if(exe_name == NULL)
{
return;
}
fprintf(stderr, "Usage: %s [-i eth0] [-r eth1] [-w eth2] [-c 1000][-n] [-p]\n", exe_name);
}
static int get_if_index(int fd, char *if_name)
{
struct ifreq ifr;
strncpy(ifr.ifr_name, if_name, MAX_IF_NAME_SIZE);
if(ioctl(fd, SIOCGIFINDEX, &ifr) < 0)
{
fprintf(stderr, "get_if_index::ioctl error: %s!\n", strerror(errno));
return (-1); /* ifr.if_index can't be -1?? */
}
return ifr.ifr_ifindex;
}
static int set_promiscuous(int fd, char *if_name)
{
struct ifreq ifr;
strncpy(ifr.ifr_name, if_name, MAX_IF_NAME_SIZE);
if(ioctl(fd, SIOCGIFFLAGS, &ifr) < 0)
{
fprintf(stderr, "set_promiscuous::ioctl get error: %s!\n", strerror(errno));
return 0;
}
ifr.ifr_flags |= IFF_PROMISC;
if(ioctl(fd, SIOCSIFFLAGS, &ifr) < 0)
{
fprintf(stderr, "set_promiscuous::ioctl set error: %s!\n", strerror(errno));
return 0;
}
return 1;
}
static void dis_pkt(char *buf, int len)
{
unsigned int idx;
unsigned char *ptr;
int is_interpret;
int tail_start, i, tail_len, space_cnt;
if(buf == NULL)
{
return;
}
idx = 0;
ptr = buf;
is_interpret = 0;
while(idx < len)
{
if(!is_interpret)
{
if((idx%16) == 0)
{
printf("%08x: ", idx);
}
printf("%02x ", ptr[idx]);
if((idx+1)%16 == 0)
{
idx++;
idx-=16;
is_interpret = 1;
continue;
}
idx++;
}
else
{
if((idx)%16 == 0)
{
printf(" ");
}
print_char(ptr[idx]);
if((idx+1)%16 == 0)
{
printf("\n");
is_interpret = 0;
}
idx++;
}
}
if(idx%16 != 0)
{
tail_len = idx%16;
tail_start = idx - tail_len;
space_cnt = (16-tail_len)*3;
for(i = 0; i < space_cnt; i++)
{
fputc(' ', stdout);
}
printf(" ");
for(; tail_start < idx; tail_start++)
{
print_char(ptr[tail_start]);
}
}
}
static void print_char(char ch)
{
if(isprint(ch))
{
fputc(ch, stdout);
}
else
{
fputc('.', stdout);
}
}
这个程序有个问题就是效率低。因为linux的PF_PACKET接口并不提供内核缓冲。可通过在用户层增加缓冲来缓解这个问题。还有个小问题是,因为网络终端读写的特殊性,单纯的read或write并不能保证每次接收或发送出的都是一个完整的包(tcp流读写是这样的,但链路层这个读写可能是根据帧结构的,即有记录边界,所以这个问题可能并不存在,有待了解)~
因为寝室要断网了,就写到这里了,大家将就着看^_^
阅读(2364) | 评论(0) | 转发(1) |