前两天,老师安排我做一个在数据链路层转发数据的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 <stdio.h>
#include <string.h>
#include <unistd.h>
#include <ctype.h>
#include <stdlib.h>
#include <net/if.h>
#include <netpacket/packet.h>
#include <net/ethernet.h>
#include <arpa/inet.h>
#include <sys/select.h>
#include <sys/ioctl.h>
#include <errno.h>
#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_
网上查了下资料并结合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 <stdio.h>
#include <string.h>
#include <unistd.h>
#include <ctype.h>
#include <stdlib.h>
#include <net/if.h>
#include <netpacket/packet.h>
#include <net/ethernet.h>
#include <arpa/inet.h>
#include <sys/select.h>
#include <sys/ioctl.h>
#include <errno.h>
#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_