花幾天寫了個so easy的Linux包過濾防火墻,估計實際意義不是很大。防火墻包括用戶態執行程序和內核模塊,內核模塊完全可以用iptable代替。由于在編寫的過程一開始寫的是內核模塊所以就直接用上來。
代碼結構如下:
.├── kernelspace│ ├── Makefile│ ├── Makefile_netlink│ ├── modules.order│ ├── Module.symvers│ ├── netfilter.c│ ├── netfilter.h│ ├── netfilter.ko│ ├── netfilter.mod.c│ ├── netfilter.mod.o│ ├── netfilter.o│ ├── out.temp│ └── tags└── userspace ├── filter ├── filter_1 ├── filter.c ├── filter.c~ ├── load.sh └── tags由于在開發的過程中誤刪了filter源文件。后面再重新寫過,過程也是挺艱辛的。后來想想自己寫過一個rm命令吧,把原來的rm命令替換掉,或者還可以這樣子,自己寫一個命令,姑且叫delete命令吧。delete刪除數據可以恢復的,再或者,用git吧,git管理想誤刪可不是這么容易的。
ok,閑話少扯,上代碼上分析上開發過程。
先從內核模塊看起,也就是文件樹下的以kernelspace為根的文件。嗯~~文件挺多的,不過自己寫的就三個,Makefile,netfilter.c and netfilter.h
內核模塊采用linux的netfilter框架。
通俗的說,netfilter的架構就是在整個網絡流程的若干位置放置了一些檢測點(HOOK),而在每個檢測點上登記了一些處理函數進行處理(如包過濾,NAT等,甚至可以是 用戶自定義的功能)。
IP層的五個HOOK點如下:
[1]:NF_IP_PRE_ROUTING:剛剛進入網絡層的數據包通過此點(剛剛進行完版本號,校驗和等檢測), 目的地址轉換在此點進行;[2]:NF_IP_LOCAL_IN:經路由查找后,送往本機的通過此檢查點,INPUT包過濾在此點進行;[3]:NF_IP_FORWARD:要轉發的包通過此檢測點,FORWARD包過濾在此點進行;[4]:NF_IP_POST_ROUTING:所有馬上便要通過網絡設備出去的包通過此檢測點,內置的源地址轉換功能(包括地址偽裝)在此點進行;[5]:NF_IP_LOCAL_OUT:本機進程發出的包通過此檢測點,OUTPUT包過濾在此點進行。(摘自百度百科)
更多關注點在怎樣使用netfilter上。兩個函數
nf_register_hook ========> nf_unregister_hook。顧名思義,注冊鉤子,釋放鉤子。關鍵在于參數結構struct nf_hook_ops *reg的填充。函數和struct nf_hook_ops結構都可以在netfilter.h頭文件中找到。作者netfilter.h目錄為/usr/src/linux-head***/include/linux下找到。
struct nf_hook_ops { struct list_head list; /* User fills in from here down. */ nf_hookfn *hook; struct module *owner; u_int8_t pf; unsigned int hooknum; /* Hooks are ordered in ascending priority. */ int priority;};
關注更多的是這些成員的具體意義以及編程時候如何選擇這些成員。
nf_hookfn *hook 是你自己定義的回調函數。當有符合條件的數據包到來時候會調用。
hooknum為前面提到的IP層的五個hook點的取值
prority根據uapi/linux/netfiler_ipv4.h的定義,可以取以下值
enum nf_ip_hook_priorities { NF_IP_PRI_FIRST = INT_MIN, NF_IP_PRI_CONNTRACK_DEFRAG = -400, NF_IP_PRI_RAW = -300, NF_IP_PRI_SELINUX_FIRST = -225, NF_IP_PRI_CONNTRACK = -200, NF_IP_PRI_MANGLE = -150, NF_IP_PRI_NAT_DST = -100, NF_IP_PRI_FILTER = 0, NF_IP_PRI_SECURITY = 50, NF_IP_PRI_NAT_SRC = 100, NF_IP_PRI_SELINUX_LAST = 225, NF_IP_PRI_CONNTRACK_HELPER = 300, NF_IP_PRI_CONNTRACK_CONFIRM = INT_MAX, NF_IP_PRI_LAST = INT_MAX,};
pf根socket編寫時候類似,在此不多寫。
現在獻上內核模塊的代碼
1 /************************************************************************* 2 > File Name: netfilter.c 3 > Author: ICKelin 4 > Mail: 18277973721@sina.cn 5 > Created Time: 2015年02月27日 星期五 02時39分09秒 6 ************************************************************************/ 7 8 #include "netfilter.h" 9 10 #define _USER_SPACE_11 12 struct nf_hook_ops hook_in;13 struct nf_hook_ops hook_out;14 15 static int __init fire_init()16 {17 hook_in.hook = fire_hook_entry;18 hook_in.hooknum = NF_INET_LOCAL_IN;19 hook_in.pf = PF_INET;20 hook_in.priority = NF_IP_PRI_FIRST;21 22 nf_register_hook(&hook_in);23 return 0;24 }25 26 static void __exit fire_exit()27 {28 nf_unregister_hook(&hook_in);29 }30 31 //有數據包到來調用32 33 unsigned int fire_hook_entry(34 unsigned int hooknum,35 struct sk_buff *skb,36 const struct net_device *in,37 const struct net_device *out,38 int (*okfn)(struct sk_buff*)39 )40 {41 42 #ifdef _USER_SPACE_43 return NF_QUEUE;44 #endif45 46 struct iphdr *ip = ip_hdr(skb);47 struct tcphdr *tcp = tcp_hdr(skb);48 struct udphdr *udp = udp_hdr(skb);49 50 if(ip->protocol == 6)51 {52 printk("tcp連接:::: 源ip:%3d.%3d.%3d.%3d 目的ip %3d.%3d.%3d.%3d ", NET_TO_IP((ip->saddr)),NET_TO_IP((ip->daddr)));53 54 printk("源端口號 %6d 目的端口號 %6d",ntohs(tcp->source), ntohs(tcp->dest));55 56 if(ntohs(tcp->dest) == 80)57 {58 printk("狀態:隊列/n");59 return NF_QUEUE;60 }61 else62 printk("狀態:允許通過防火墻");63 return NF_ACCEPT;64 }65 else if(ip->protocol == 17)66 {67 printk("udp連接::: 源ip:%3d.%3d.%3d.%3d 目的ip %3d.%3d.%3d.%3d ", NET_TO_IP(ip->saddr), NET_TO_IP(ip->daddr));68 printk("源端口號 %d 目的端口號 %d 狀態:允許通過防火墻/n", ntohs(udp->source), ntohs(udp->dest));69 return NF_ACCEPT;70 }71 else if(ip->protocol ==1)72 {73 printk("icmp connect come/n");74 return NF_QUEUE;75 }76 else if(ip->protocol == 2)77 {78 printk("igmp conect come/n");79 return NF_ACCEPT;80 }81 return NF_ACCEPT;82 83 // printk("packet come/n");84 return NF_ACCEPT;85 }86 87 module_init(fire_init);88 module_exit(fire_exit);頭文件netfilter.h包含基本文件linux頭文件。在此也貼上,以便讀者進行探索時候可以找到對應的頭文件。
1 /************************************************************************* 2 > File Name: netfilter.h 3 > Author: ICKelin 4 > Mail: 18277973721@sina.cn 5 > Created Time: 2015年02月27日 星期五 02時39分24秒 6 ************************************************************************/ 7 8 #include <linux/in.h> 9 #include <linux/ip.h>10 #include <linux/tcp.h>11 #include <linux/udp.h>12 #include <linux/icmp.h>13 14 #include <linux/kernel.h>15 #include <linux/module.h>16 #include <linux/netdevice.h>17 #include <linux/init.h>18 #include <linux/skbuff.h>19 #include <linux/types.h>20 #include <linux/inet.h>21 #include <linux/netfilter_ipv4.h>22 /*23 * 防火墻初始化函數,供module_exit的參數使用24 * 內部調用鉤子注冊函數nf_register_hook.填充25 * struct nf_hook_ops結構26 * struct nf_hook_ops27 * {28 * struct list_head list;29 * nf_hookfn *hook;30 * struct module *owner;31 * u_int8_t pf;32 * unsigned int hooknum;33 * int priority;34 * }35 *36 * 詳細信息參考netfilter.h頭文件37 * nf_hookfd指定為fire_hook_entry作為回調函數38 *39 * */40 41 static int __init fire_init();42 43 /*44 * 防火墻退出函數,共module_init的參數使用45 * 填充struct nf_hook_ops結構46 *47 * */48 49 static void __exit fire_exit();50 51 /*52 * 防火墻鉤子回調。供給nf_register_hook函數的參數53 *54 * struct nf_hook_ops結構的55 * hook成員使用,用與注冊回調函數56 *57 * */58 59 unsigned int fire_hook_entry60 (61 unsigned int hooknum,62 struct sk_buff *skb,63 const struct net_device *in,64 const struct net_device *out,65 int (*okfn)(struct sk_buff*)66 );67 /*68 * 69 *70 * */71 72 #define NET_TO_IP(addr) /73 ((unsigned char*)&addr)[0],/74 ((unsigned char*)&addr)[1],/75 ((unsigned char*)&addr)[2],/76 ((unsigned char*)&addr)[3]內核模塊需要make
Makefile
obj-m := netfilter.o KERNELBUILD :=/lib/modules/$(shell uname -r)/build default: make -C $(KERNELBUILD) M=$(shell pwd) modules clean: rm -rf *.o *.ko *.mod.c .*.cmd *.markers *.order *.symvers .tmp_versions
內核模塊其實還是挺簡單的。如果編寫用戶層的包過濾防火墻的話沒有必要在內核模塊上花費太多功夫,以上內核模塊實現的功能用iptable都可以實現。至于協議解析部分,也不是三言兩語能寫的完。但是作者寫過利用原始套接字進行抓包的程序,不過正在準備筆試就沒有多大時間寫博客總結。讀者能看懂包解析部分的代碼的是沒什么問題的。
用戶空間模塊。用戶空間模塊采用的是netfilter_queue函數庫。原本找資料的時候看到ipq這個庫,不過后來到netfilter官網上找資料,ipq函數庫被取代來。
libnetfilter_queue is a userspace library providing an API to packets that have been queued by the kernel packet filter. It is is part of a system that deprecates the old ip_queue / libipq mechanism.
libnetfilter_queue has been previously known as libnfnetlink_queue.
用戶空間動起來也不難。關鍵是官網有api參考。看著官網的api再結合之前抓包的程序寫起來就easy了。
這部分誤刪過一次,我那個淚奔啊,rm命令害死人,原本注釋打得完美了,重寫一次就沒有打注釋的欲望了。忘見諒。
/************************************************************************* > File Name: filter.c > Author: ICKelin > Mail: 18277973721@sina.cn > Created Time: 2015年03月02日 星期一 01時04分38秒************************************************************************/#include <stdio.h>#include <stdlib.h>#include <unistd.h>#include <netdb.h>#include <string.h>#include <errno.h>#include <netinet/in.h>#include <arpa/inet.h>#include <asm/byteorder.h>#include <linux/netfilter.h>#include <libnetfilter_queue/libnetfilter_queue.h>#include <netinet/ip.h>#include <netinet/tcp.h>#include <netinet/udp.h>#define BUFF_SIZE 1024*10#define IP_SIZE 50#define AUTHOR "ICKelin"#define VERSION "v1.1"#define _DEBUG_#define error(msg) / {fprintf(stderr, "%s error with %s/n", msg, strerror(errno));exit(-1);}struct filter_info{ long from_ip; long to_ip; char *protocol_type;}filter;int parse_cmd(char *protocol_type, char *from, char *to);void fire_help();void fire_version();int get_port_by_service(char *service);static int cb(struct nfq_q_handle *qh, struct nfgenmsg *nfmsg,struct nfq_data *nfa, void *data){ int is_block = 0; struct nfqnl_msg_packet_hdr *msg = nfq_get_msg_packet_hdr(nfa); if(msg == NULL) error("nfqnl_msg_packet_hdr"); char *pdata; int n = nfq_get_payload(nfa, (char**)&pdata); struct iphdr *ip = (struct iphdr*)pdata; struct tcphdr *tcp; int block_port = get_port_by_service(filter.protocol_type); struct in_addr add; add.s_addr = ip->saddr; printf("%s/t", inet_ntoa(add)); add.s_addr = ip->daddr; printf("%s/t", inet_ntoa(add)); switch(ip->protocol) { //udp case 17: printf("UDP/t"); struct udphdr *udp = (struct udphdr*)(pdata + sizeof(struct iphdr)); printf("%d/t%d/t", ntohs(udp->source), ntohs(udp->dest)); printf("通過/n"); break; case 6: printf("TCP/t"); tcp = (struct tcphdr *)(pdata + sizeof(struct iphdr)); printf("%d/t%d/t", ntohs(tcp->source), ntohs(tcp->dest), block_port); if( (ntohs(tcp->source) == block_port||ntohs(tcp->dest) == block_port) && ntohl(ip->saddr) >=filter.from_ip && ntohl(ip->saddr)<=filter.to_ip) { //char out[1024]; //strcpy(out,(char*)tcp + tcp->doff*4); //out[strlen(out)-3] = '3'; //memcpy((char*)(tcp + tcp->doff*4), out, sizeof(out)); //printf("%s/n", (char*)tcp + tcp->doff*4); printf("攔截/n"); return nfq_set_verdict(qh,ntohl(msg->packet_id), NF_DROP,n,pdata); } else printf("通過/n"); break; case 1: printf("ICMP/t"); printf("無/t無/t"); printf("通過/n"); break; default: printf("un/t"); printf("通過/n"); break; } return nfq_set_verdict(qh,ntohl(msg->packet_id), NF_ACCEPT,n,pdata); }int main(int argc, char **argv){ char *protocol_type,*from, *to; char opt; int flag = 0; while((opt = getopt(argc, argv, "hvf:t:p:")) != EOF) { switch(opt) { case 'h': fire_help(); return 0; case 'v': fire_version(); return 0; case 'f': from = optarg; flag=flag|1; break; case 't': to = optarg; flag|=2; break; case 'p': protocol_type = optarg; flag|=4; break; default: fire_help(); break; } } if((flag^7) != 0) { fprintf(stderr, "command line options error/nyou should use /n/t-f begin ip you are going to block/n-t end ip you are going to block/n/t-p for the protocol or port you are going to block/n"); fprintf(stderr,"/tfor example:filter -f 192.168.15.* -t 192.16.120.* -p http/n"); fprintf(stderr,"more information use -h/n"); exit(-1); } parse_cmd(protocol_type, from, to); printf("/nfirewall setup successfully/n/n"); printf(" you filter information:/n"); printf("/tfrom:%s net byte order %ld/n", from, filter.from_ip); printf("/tto :%s net byte order %ld/n", to, filter.to_ip); printf("/tprotocol:%s/n/n", filter.protocol_type); printf("now let's firework for firewall/n/n"); printf("源ip/t/t目的ip/t/t協議/t源端口 目的端口 狀態/n"); struct nfq_handle *h; struct nfq_q_handle *qh; int fd; int rv; char buf[4096]; h = nfq_open(); if (!h) error("nfq_open"); nfq_unbind_pf(h, AF_INET); if (nfq_bind_pf(h, AF_INET) < 0) error("nfq_bind_pf"); qh = nfq_create_queue(h, 0, &cb, NULL); if (!qh) error("nfq_create_queue"); if (nfq_set_mode(qh, NFQNL_COPY_PACKET, 0xffff) < 0) error("nfq_set_mode"); fd = nfq_fd(h); while ((rv = recv(fd, buf, sizeof(buf), 0)) && rv >= 0) nfq_handle_packet(h, buf, rv); nfq_destroy_queue(qh); nfq_close(h); return 0;}void fire_help(){ printf("welcome to use my network filter firework/n"); printf("how to set your own match to filter packets:/n/n"); printf("/t-p/tfilter protocol,like http,ftp...maybe you want to use port instead/n"); printf("/t-f/tfilter ip from argument/n"); printf("/t-h/tshow help information/n"); printf("/t-v/tshow sortware information and the author information/n/n"); printf(" author:%s/n", AUTHOR); printf(" come form:CHINA/n"); printf(" email:18277973721@sina.cn/n"); printf(" version:%s/n/n",VERSION);}void fire_version(){}int parse_cmd(char *protocol_type, char *from, char *to){ char temp[IP_SIZE]; int index; if(strcasecmp(protocol_type, "http") == 0) filter.protocol_type = "http"; else if(strcasecmp(protocol_type, "ftp") == 0) filter.protocol_type = "ftp"; else if(strcasecmp(protocol_type, "smtp") == 0) filter.protocol_type = "smtp"; else { fprintf(stderr, "not support protocol./nversion %s only support http,ftp or smtp protocol/nmore information see -h option/n", VERSION); exit(-1); } while(*from) { if(*from != '.' && *from !='*' &&(*from<'0'||*from>'9')) { fprintf(stderr, "from ip address format error! format:###.###.##.#/nexample:192.168.*.*/nmore information use -h option/n"); exit(-1); } if(*from == '*') temp[index++] = '0'; else temp[index++] = *from; from++; } temp[index] = 0; filter.from_ip = ntohl(inet_addr(temp)); memset(temp, 0, sizeof(temp)); index = 0; while(*to) { if(*to != '.' && *to !='*' &&(*to<'0'||*to>'9')) { fprintf(stderr, "to ip address format error! format:###.###.##.#/nexample:192.168.*.*/nmore information use -h option"); exit(-1); } if(*to == '*') temp[index++] = '0'; else temp[index++] = *to; to++; } temp[index] = 0; filter.to_ip = ntohl(inet_addr(temp)); if(filter.from_ip > filter.to_ip) { fprintf(stderr, "hello guys, there is no ip between %s to %s/ni advice you to check your input/nmore information see -h option", from, to); exit(-1); } return 1;}int get_port_by_service(char *service){ if(strcasecmp(service, "HTTP") == 0) return 80; if(strcasecmp(service, "FTP") == 0) return 21; if(strcasecmp(service, "smtp") == 0) return 25; return 0;}關于命令行選項和ip地址驗證這塊不多說。netfilter_queue庫的使用參考鏈接:libnetfilter_quue,讀者參考頭文件和官方文檔探索相信能夠很快就能編寫自己的包過濾防火墻來。
至于其他功能,讀者可以發揮自己的想象力去搞。只要不違反法律,盡情的去玩吧。
新聞熱點
疑難解答