在实际应用中需要服务端调用libpcap模拟接收TCP连接而不实际进行accept,会存在客户端发了SYN请求,TCP协议栈直接回复了RST导致客户端直接关闭了连接的情况。(一开始是准备使用TCPreplay进行TCP数据重放的,后面发现TCPreplay中的TCPliveplay只是对PF_PACKET类型的数据包进行简单的send(),不过可以基于它提供的框架完成TCP、IP校验和的计算)
libpcap的相关文档介绍如下:
Programming with pcap
通过查阅相关资料,得知想要截获并修改数据包内容,可以使用netfilter/iptables 技术,即设置自己的hook函数,通过hook函数来对相应的数据包执行具体的操作。
在数据包流经内核协议栈的整个过程中,在一些已预定义的关键点上PRE_ROUTING、LOCAL_IN、FORWARD、LOCAL_OUT和POST_ROUTING会根据数据包的协议簇PF_INET到这些关键点去查找是否注册有钩子函数。如果没有,则直接返回okfn函数指针所指向的函数继续走协议栈;如果有,则调用nf_hook_slow函数,从而进入到Netfilter框架中去进一步调用已注册在该过滤点下的钩子函数,再根据其返回值来确定是否继续执行由函数指针okfn所指向的函数。
netfilter相关的文档介绍如下:
我因为只需要过滤特定IP的回复,所以代码很简单
#include
#include
#include
#include /*PF_INET*/
#include /*NF_IP_PRE_FIRST*/
#include
#include
#include /*in_aton()*/
#include
#include
#define ETHALEN 14
#define NIPQUAD(addr) \
((unsigned char *)&addr)[0], \
((unsigned char *)&addr)[1], \
((unsigned char *)&addr)[2], \
((unsigned char *)&addr)[3]
static struct nf_hook_ops nfho;
unsigned int checksum(unsigned int hooknum,
struct sk_buff *__skb,
const struct net_device *in,
const struct net_device *out,
int (*okfn)(struct sk_buff *))
{
struct sk_buff *skb;
struct net_device *dev;
struct iphdr *iph;
struct tcphdr *tcph;
int tot_len;
int iph_len;
int tcph_len;
int ret;
skb = __skb;
if (skb == NULL)
return NF_ACCEPT;
iph = ip_hdr(skb);
if (iph == NULL)
return NF_ACCEPT;
tot_len = ntohs(iph->tot_len);
__be32 sip,dip;
if(skb){
struct sk_buff *sb = NULL;
sb = skb;
struct iphdr *iph;
iph = ip_hdr(sb);
sip = iph->saddr;
dip = iph->daddr;
//printk("My Packet for source address: %d.%d.%d.%d\n\
// My destination address: %d.%d.%d.%d\n ", NIPQUAD(sip), NIPQUAD(dip));
}
if (iph->daddr == in_aton("10.1.65.180"))
{
__be32 myip;
myip = iph->daddr;
printk("before drop packet\n");
printk("drop packet, address : %d.%d.%d.%d\n ", NIPQUAD(myip));
return NF_DROP;
}
return NF_ACCEPT;
out:
dev_put(dev);
return NF_DROP;
}
static int __init filter_init(void)
{
printk(KERN_INFO "hello, world!\n");
int ret;
nfho.hook = checksum;
nfho.pf = PF_INET;
//nfho.hooknum = NF_INET_POST_ROUTING;
nfho.hooknum = NF_INET_LOCAL_OUT;
nfho.priority = NF_IP_PRI_FIRST;
ret = nf_register_hook(&nfho);
if (ret < 0)
{
printk("%s\n", "can't modify skb hook!");
return ret;
}
return 0;
}
static void filter_fini(void)
{
printk(KERN_INFO "goodbye, cruel world\n");
nf_unregister_hook(&nfho);
}
module_init(filter_init);
module_exit(filter_fini);
MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("myslq");
MODULE_DESCRIPTION("A simple Hello World Module");
这样需要自己实现一个内核模块,编译完成后需要使用insmod hello.ko 加载模块,使用tail -f /vag/log/messags查看printk打印的日志,使用rmmod hello移除内核模块。
内核模块相关的文档如下:
linux内核驱动模块开发步骤及实例入门介绍
Makefile文件也是使用网上通用的版本
ifneq ($(KERNELRELEASE),)
#kbuild syntax. dependency relationshsip of files and target modules are listed here.
mymodule-objs := hello.o
obj-m := hello.o
else
PWD := $(shell pwd)
KVER ?= $(shell uname -r)
KDIR := /lib/modules/$(KVER)/build
all:
$(MAKE) -C $(KDIR) M=$(PWD)
clean:
rm -rf .*.cmd *.o *.mod.c *.ko .tmp_versions
endif
注意,开发内核模块的话,必须有对应内核版本的dev开发包。
[root@host-10-33-43-250 hook_tcp]# rpm -qa|grep kernel
kernel-devel-3.10.0-327.el7.x86_64
kernel-3.10.0-957.1.3.el7.x86_64
kernel-tools-libs-3.10.0-327.el7.x86_64
kernel-headers-3.10.0-327.el7.x86_64
kernel-3.10.0-327.el7.x86_64
kernel-tools-3.10.0-327.el7.x86_64
kernel-devel-3.10.0-862.14.4.el7.x86_64
再后来发现这样真的是杀鸡用牛刀,可以直接使用iptables过滤掉相应的数据包,这样就过滤了指定IP的RST连接,客户端可以调用connect()而不用担心接收到RST导致连接被关闭。
第一条添加规则,第二条在调用完成后删除规则
iptables -t filter -A OUTPUT -p tcp --tcp-flags RST RST --dst 10.1.65.180 -j DROP
iptables -t filter -D OUTPUT -p tcp --tcp-flags RST RST --dst 10.1.65.180 -j DROP
iptables的命令行介绍
iptables工具_过滤包—命令
觉得使用system调用iptables命令不美观的话,可以安装iptables-devel库,通过调用libiptc库的应用程序接口设置相应的参数。
Linux c/c++编程使用 libiptc库下发规则可以代替system调用 iptables 命令方法,可以满足程序中批量地创建连接所需的批量增加、删除iptables规则;
使用步骤确实比较繁琐,得注意iptables版本的问题,编译相关库需要“-lip4tc” “-lxtables”,头文件需要“libiptc/libiptc.h” “net/netfilter/nf_nat.h” “iptables.h”;
然而实验结果表明,当多进程高频率得操作iptables时,system 和 libiptc 均会出现 “Resource temporarily unavailable” 错误,建议进行返回值判定和多进程的加锁保护;
因为官方提醒ibiptc不会提供稳定的对外接口,并不建议开发者使用。
建议直接使用popen方式输入命令执行。
sprintf(cmd_append_rst_restrict, "iptables -t filter -A OUTPUT -p tcp --tcp-flags RST RST --dst %s -j DROP 2>/dev/null", input_dip);
#include
#include
//#include
#include
int execute_cmd(char *cmd)
{
FILE *fp;
if((fp = popen(cmd, "r")) == NULL)
{
perror("failed to popen\n");
exit(1);
}
do{
char* buf2 = (char *)malloc(PIPE_MAX_SIZE);
memset(buf2, 0, PIPE_MAX_SIZE);
while(fgets(buf2, PIPE_MAX_SIZE, fp) != NULL)
{
printf("%s", buf2);
}
printf("result: %s", buf2);
}while(0);
pclose(fp);
return 0;
}
IP报文头部信息
struct ip_addr{
unsigned char byte1;
unsigned char byte2;
unsigned char byte3;
unsigned char byte4;
};
typedef struct ipv4_hdr ipv4_hdr;
struct ipv4_hdr{
u_int8_t ip_hl:4;
u_int8_t ip_v:4;
u_int8_t ip_tos;
u_int16_t ip_len;
u_int16_t ip_id;
u_int16_t ip_off;
u_int8_t ip_ttl;
u_int8_t ip_p;
u_int16_t ip_sum;
input_addr ip_src, ip_dst;
};
在实际中进行TCP报文的重放中,发现存在IP包中设置了DF(don’t fragment )标记位,但是IP包总长度大于MTU的情况,如下图。(可能和抓包是从KVM虚拟化环境虚拟机中抓取的有关,虽然虚拟机以太网卡MTU也是1500,但是不会对大于MTU的包进行丢弃。)
此种情况下,需要手动对包含以太帧的数据进行IP分片。分片具体代码如下,date为指向以太帧的const u_char *类型指向, size_t len 为前述数据的长度:
if(!exceed_mtu_flag)
{
retcode = (int)send(sp->handle.fd, (void *)data, len, 0);
}
else
{
tcp_hdr *tcphdr;
ipv4_hdr *iphdr;
unsigned int size_ip;
unsigned int size_tcp;
unsigned int size_ip_payload;
u_int16_t ip_header_id = my_rand();
u_int16_t tcp_bytes_offset = 0;// ip_off 只有低13位表示偏移长度,但是长度不超过1500,don't worry.
u_int16_t frage_off = 0;//分片
unsigned int ip_payload_sent_size = 0;
//while(left_tcp_data_len > 1460)
u_int16_t frage_max_off = 0;
//tcp_data(max 1460) + tcp_header 20 + ip_header 20 <= mtu 1500, the len also have eth_header 14.
//printf("warning, the length of data is %d, exceed the MTU and would be dropped", len);
//ipv4_hdr *iphdr;
iphdr = (ipv4_hdr *)(data + SIZE_ETHERNET);
size_ip = iphdr->ip_hl << 2;
if (size_ip < 20) {
printf("ERROR: Invalid IP header length: %u bytes\n", size_ip);
return 0;
}
//tcphdr = (tcp_hdr *)(data + SIZE_ETHERNET + size_ip);
//th_off is the offset of tcp header before payload.And the value should multiple 4.
//size_tcp = tcphdr->th_off*4;
//if (size_tcp < 20) {
// printf("ERROR: Invalid TCP header length: %u bytes\n", size_tcp);
//return 0;
//}
/* payload = (u_char *)(sched[i].packet_ptr + SIZE_ETHERNET + size_ip + size_tcp); */
size_ip_payload = ntohs(iphdr->ip_len) - (size_ip);
if(size_ip_payload != len - SIZE_ETHERNET - size_ip)
{
printf("ERROR: Invalid length %d, real length %d\n", size_ip_payload, len - SIZE_ETHERNET - size_ip);
}
frage_max_off = size_ip_payload / 8;
//left_ip_payload_size = size_ip_payload;
//iphdr->ip_len > len;
//iphdr->ip_len = htons(1500);//IP 报文中的长度为除以太帧首部的其负载长度
//uint32_t tcpr_random(uint32_t *seed);
//The cast is unnecessary. Assignment, initialization, parameter passing, and return statements can assign values between any numeric types without a cast. The value is converted implicitly:rvalue type to lvalue value.But for easy understanding, I would write it explicitly. convert int to short int
//iphdr->ip_id = (u_int16_t)my_rand();
iphdr->ip_id = ip_header_id;
char *send_buf = (char *) malloc(len + 1);
if(send_buf == NULL) return FALSE;
memset(send_buf, 0, len + 1);
while(ip_payload_sent_size != size_ip_payload)
{
if(size_ip_payload - ip_payload_sent_size > 1514 - SIZE_ETHERNET - size_ip)
{
iphdr->ip_len = htons(1514 - SIZE_ETHERNET);
//iphdr->ip_off = 0;//以免影响后续的设置
iphdr->ip_off = frage_off;
iphdr->ip_off = htons(iphdr->ip_off | IP_MF);//#define IP_MF 0x2000U
//ip_payload_sent_size += 1514 - SIZE_ETHERNET - size_ip;
do_checksum_ip_header((u_char *) iphdr);
memcpy(send_buf, data, SIZE_ETHERNET + size_ip);//copy the head
memcpy(send_buf + SIZE_ETHERNET + size_ip, data + ip_payload_sent_size + SIZE_ETHERNET + size_ip, 1514 - SIZE_ETHERNET - size_ip);
//ip_payload_sent_size += 1514 - SIZE_ETHERNET - size_ip;
//send data
retcode = (int)send(sp->handle.fd, (void *)send_buf, 1514, 0);
ip_payload_sent_size += 1514 - SIZE_ETHERNET - size_ip;
}
else
{
iphdr->ip_len = htons(size_ip_payload - ip_payload_sent_size + size_ip);
iphdr->ip_off = htons(frage_off);
//ip_payload_sent_size = size_ip_payload;
do_checksum_ip_header((u_char *) iphdr);
memcpy(send_buf, data, SIZE_ETHERNET + size_ip);//copy the head
memcpy(send_buf + SIZE_ETHERNET + size_ip, data + ip_payload_sent_size + SIZE_ETHERNET + size_ip, size_ip_payload - ip_payload_sent_size);
retcode = (int)send(sp->handle.fd, (void *)send_buf, size_ip_payload - ip_payload_sent_size + SIZE_ETHERNET + size_ip, 0);
ip_payload_sent_size = size_ip_payload;
}
//fix IP header checksum
//iphdr->ip_off = tcp_bytes_offset / 8;
//retcode = (int)send(sp->handle.fd, (void *)data, len, 0);
frage_off += (1514 - SIZE_ETHERNET - size_ip)/8;
//left_tcp_data_len -= 1460;
memset(send_buf, 0, len + 1);
}
free(send_buf);
send_buf = NULL;
}
//retcode = (int)send(sp->handle.fd, (void *)data, len, 0);
printf("retcode %d, desc %s, no %d", retcode, strerror(errno), errno);
其中使用到的一些自定义函数如下。
/**
* Implementation of rand_r that is consistent across all platforms
* This algorithm is mentioned in the ISO C standard, here extended
* for 32 bits.
* @param: seed
* @return: random number
*/
uint32_t tcpr_random(uint32_t *seed)
{
unsigned int next = *seed;
int result;
next *= 1103515245;
next += 12345;
result = (unsigned int) (next / 65536) % 2048;
next *= 1103515245;
next += 12345;
result <<= 10;
result ^= (unsigned int) (next / 65536) % 1024;
next *= 1103515245;
next += 12345;
result <<= 10;
result ^= (unsigned int) (next / 65536) % 1024;
*seed = next;
return result;
}
static int my_rand(void)
{
struct timeval tv;
unsigned int my_seed = 0;
{
gettimeofday(&tv, ((void *)0));
my_seed = (unsigned int)tv.tv_sec ^ (unsigned int)tv.tv_usec;
}
return tcpr_random(&my_seed);
}
static int do_checksum_math_liveplay(u_int16_t *data, int len)
{
int sum = 0;
union {
u_int16_t s;
u_int8_t b[2];
} pad;
while (len > 1) {
sum += *data++;
len -= 2;
}
if (len == 1) {
pad.b[0] = *(u_int8_t *)data;
pad.b[1] = 0;
sum += pad.s;
}
return (sum);
}
static int do_checksum_ip_header(u_int8_t *data)
{
ipv4_hdr *ipv4;
int ip_hl;
volatile int sum = 0;
ipv4 = NULL;
ipv4 = (ipv4_hdr *)data;
ip_hl = ipv4->ip_hl << 2;
ipv4->ip_sum = 0;
sum = do_checksum_math_liveplay((u_int16_t *)data, ip_hl);
ipv4->ip_sum = CHECKSUM_CARRY(sum);
return 0;
}
参考
https://www.cnblogs.com/x_wukong/p/5923767.html