libnetfilter_queue缺乏文档,看着libnetfilter_queue自带的例子弄的。现在写不出有头有尾的文章了,就贴贴代码(还没注释),提几个我记得的几个注意点就不写了,见谅。
程序功能, 将输出端目的地为 220.181.37.55 的包,都改为目的地为 64.233.189.104,输入段反之,达到DNAT的一小半功能,完整的NAT要做状态记录的。
注意点 :
1 - 2.6.23 的内核有BUG, nfq_unbind_pf 返回值不正确,见 : http://article.gmane.org/gmane.comp.security.firewalls.netfilter.general/33573
2 - TCP 要做正确的checksum重新计算,否则包发不出去。UDP也是,不过这段程序里没写,如果你有兴趣就练练手吧。
3 - iptables 的 QUEUE target 内核模块不返回 XT_CONTINUE/IPT_CONTINUE 的——NF_QUEUE 是一个netfilter的机制,不是x_tables的——所以如果要在QUEUE之后继续遍历 iptables 的规则就需要点小技巧,技巧在下面的loader.sh程序里展示了:通过在mangle表里的mark来判断是第一次进入QUEUE还是第二次。而且规 则是放在最前的,保证不影响后面的mark和用户态的traffict control程序。
就这些了,不写了,贴代码。
Makefile :
: nf_queue_test.c
gcc $() -lnetfilter_queue $ -o
:
rm -f nf_queue_test
nf_queue_test.c :
__LITTLE_ENDIAN
() /
(( *)&addr)[0], /
(( *)&addr)[1], /
(( *)&addr)[2], /
(( *)&addr)[3]
() /
(( *)&addr)[3], /
(( *)&addr)[2], /
(( *)&addr)[1], /
(( *)&addr)[0]
( , *, ){
= init;
( count > 1 ) {
sum += ntohs(* (*) addr);
addr += 2;
count -= 2;
}
( count > 0 )
sum += * ( *) addr;
(sum>>16)
sum = (sum & 0xffff) + (sum >> 16);
()~sum;
}
( * ){
checksum(0, (*)iphdrp, iphdrp->ihl<<2);
}
( * ){
iphdrp->check = 0;
iphdrp->check = htons(checksum(0, (*)iphdrp, iphdrp->ihl<<2));
}
( * , * ){
= ntohs(iphdrp->tot_len) - (iphdrp->ihl<<2);
= 0;
cksum += ntohs((iphdrp->saddr >> 16) & 0x0000ffff);
cksum += ntohs(iphdrp->saddr & 0x0000ffff);
cksum += ntohs((iphdrp->daddr >> 16) & 0x0000ffff);
cksum += ntohs(iphdrp->daddr & 0x0000ffff);
cksum += iphdrp->protocol & 0x00ff;
cksum += tcplen;
checksum(cksum, (*)tcphdrp, tcplen);
}
( * ){
* =
( *)((*)iphdrp + (iphdrp->ihl<<2));
tcp_checksum2(iphdrp, tcphdrp);
}
( * , * ){
tcphdrp->check = 0;
tcphdrp->check = htons(tcp_checksum2(iphdrp, tcphdrp));
}
( * ){
* =
( *)((*)iphdrp + (iphdrp->ihl<<2));
set_tcp_checksum2(iphdrp, tcphdrp);
}
( *, *,
*, *){
()nfmsg;
()data;
= 0;
*;
* = ;
;
ph = nfq_get_msg_packet_hdr(nfa);
(ph){
id = ntohl(ph->packet_id);
}
pdata_len = nfq_get_payload(nfa, (**)&pdata);
(pdata_len == -1){
pdata_len = 0;
}
* = ( *)pdata;
printf(,
pdata_len,
iphdrp->ihl<<2,
IPQUAD(iphdrp->saddr));
printf(,
IPQUAD(iphdrp->daddr),
getprotobynumber(iphdrp->protocol)->p_name);
printf(, ip_checksum(iphdrp));
(iphdrp->protocol == IPPROTO_TCP){
printf(, tcp_checksum1(iphdrp));
}
(iphdrp->daddr == inet_addr(TO)){
printf();
iphdrp->daddr = inet_addr(DNAT_TO);
set_ip_checksum(iphdrp);
(iphdrp->protocol == IPPROTO_TCP){
set_tcp_checksum1(iphdrp);
printf(,
ip_checksum(iphdrp), tcp_checksum1(iphdrp));
}
}
(iphdrp->saddr == inet_addr(DNAT_TO)){
iphdrp->saddr = inet_addr(TO);
printf();
set_ip_checksum(iphdrp);
(iphdrp->protocol == IPPROTO_TCP){
set_tcp_checksum1(iphdrp);
printf(,
ip_checksum(iphdrp), tcp_checksum1(iphdrp));
}
}
printf();
nfq_set_verdict_mark(qh, id, NF_REPEAT, 1,
()pdata_len, pdata);
}
( , **){
*;
*;
*;
;
;
[4096];
h = nfq_open();
(!h) {
exit(1);
}
(nfq_unbind_pf(h, AF_INET) < 0){
exit(1);
}
(nfq_bind_pf(h, AF_INET) < 0) {
exit(1);
}
= 0;
(argc == 2){
qid = atoi(argv[1]);
}
printf(, qid);
qh = nfq_create_queue(h, qid, &cb, );
(!qh) {
exit(1);
}
(nfq_set_mode(qh, NFQNL_COPY_PACKET, 0xffff) < 0) {
exit(1);
}
nh = nfq_nfnlh(h);
fd = nfnl_fd(nh);
((rv = recv(fd, buf, (buf), 0)) && rv >= 0) {
nfq_handle_packet(h, buf, rv);
}
nfq_destroy_queue(qh);
nfq_close(h);
exit(0);
}
load.sh :
=mangle
(){
-n removing chain...
{
sudo /sbin/iptables -t ${} -D PREROUTING -j NF_QUEUE_CHAIN
sudo /sbin/iptables -t ${} -D OUTPUT -j NF_QUEUE_CHAIN
sudo /sbin/iptables -t ${} -F NF_QUEUE_CHAIN
sudo /sbin/iptables -t ${} -X NF_QUEUE_CHAIN
} &>/dev/null
done
}
(){
-n creating chain...
sudo /sbin/iptables -t ${} -N NF_QUEUE_CHAIN
sudo /sbin/iptables -t ${} -A NF_QUEUE_CHAIN -m mark --mark 0 -j NFQUEUE --queue-num 8010
sudo /sbin/iptables -t ${} -A NF_QUEUE_CHAIN -j MARK --set-mark 0
sudo /sbin/iptables -t ${} -I OUTPUT -j NF_QUEUE_CHAIN
sudo /sbin/iptables -t ${} -I PREROUTING -j NF_QUEUE_CHAIN
done
}
(){
remove_chain
1
}
on_iqh INT QUIT HUP
remove_chain
create_chain
sudo ./nf_queue_test 8010
remove_chain
运行loader.sh就好了,如果你没有sudo,那就改改脚本自己用root运行吧
要看效果,就ping 220.181.37.55,然后连 220.181.37.55 的80 端口 试试,并且注意程序的输出。
PS : 220.181.37.55 是 baidu 的一个服务器的IP, 另外一个IP是 google 的。