kernel 5.15 version tu firewall using netfilter hook
사용자가 설정의한 룰 설정 파일을 기반으로 리눅스 방화벽을 구성해주는 모듈입니다.
linux에서 사용되는 방화벽 iptables는 netfilter hook의 알람을 받아 패킷의 처리를 진행한다.
이처럼 tufirewall 또한 kernel module로서 동작하며 netfilter hook(NF_INET_LOCAL_IN, NF_INET_LOCAL_OUT)의 알람을 받아 패킷의 처리를 진행한다.
NF_INET_LOCAL_IN : 패킷이 호스트내부로 전송
NF_INET_LOCAL_OUT : 호스트에서 외부로 패킷이 전송
패킷의 기본적인 필터는 아래의 구조체를 기반으로 진행한다.
struct rule_data
{
char name[MAXINUM_RULE_NAME];
__u8 action; //NF_ACCEPT, NF_DROP
__u8 protocol;
__u8 when; //determin NF_INET_LOCAL_IN, NF_INET_LOCAL_OUT
__u32 ip;
__u16 port;
};
- name : 해당 rule의 이름
- action : 해당 rule에 대해서 DROP할지 ACCEPT할지 결정
- when : 해당 rule에 대해서 패킷이 LOCAL_IN인지 LOCAL_OUT인지 결정
- ip : 필터링하려는 IP, when에 따라서 source ip일지 dest ip일지 결정
- port : 필터링하려는 PORT, when에 따라서 호스트의 dport일지 외부주소의 dport일지 결정
- NF_INET_LOCAL_IN or NF_INET_LOCAL_OUT 이벤트 발생
- hooknum에 따른 핸들러 호출(packet_in_zone_handler, packet_out_zone_handler)
- list_for_each 매크로를 이용하여 사용자가 지정한 rule을 반복문 돌리면서 해당 패킷에 대해서 필터링 진행
- rule_match 함수를 통해 사용자의 rule과 패킷에 대해서 일치하는지 진행
일치한다면 TU_RULE_MATCH, 불일치 한다면 TU_RULE_NONE_MATCH 반환 - TU_RULE_MATCH에 대해 지정한 ACTION 수행
- 사용자가 정의한 Rule 파일 (tu.conf)을 기반으로 룰 정보를 파싱한다.
- Kernel Module은 Device를 생성하여 /dev 폴더에 위치
- 어플리케이션 프로그램은 tu.conf 파일을 기반으로 파싱된 룰을 IOCTL을 이용하여 Kernel Module로 데이터 전송
- 이때 전송단계는 모든 룰을 지우는 명령과, 룰 하나씩 전송 순으로이다.
사용가능한 필터는 ICMP, TCP, UDP이며 현재 버전은 테스트 케이스 생성뿐이다.
추후 웹페이지를 만들어 설정파일 생성 및 로드 예정
커널 크래시 발생할 수 있으니 테스트시 사용하지 않는 서버 또는 일회성 서버에서 테스트 바랍니다.
git clone https://github.com/txuna/tufirewall.git
cd tufirewall
sudo make
sudo insmod firewall.ko
IOCTL을 사용하여 현재 커널 모듈에 등록된 룰을 지우고 새로운 룰을 등록합니다.
cd tufirewall
vi tu.conf # 룰 정보 수정하면 됩니다.
cd ./service
gcc load_tufirewall.c -o load
sudo ./load # IOCTL을 사용하는 것이기에 관리자 권한이 필요합니다.
# custom config file
[rule]
WHEN = OUTPUT
ACTION = DROP
IP = 8.8.8.8
PROTOCOL = ICMP
NAME = GOOGLE_NAME_SERVER
[end]
[rule]
WHEN = OUTPUT
ACTION = DROP
IP = 1.1.1.1
PROTOCOL = ICMP
NAME = GOOGLE_NAME_SERVER
[end]
[rule]
WHEN = IN
ACTION = DROP
IP = 200.200.200.200
PROTOCOL = TCP
NAME = TEST_RULE
PORT = 50000
[end]
sudo dmesg
sudo lsmod | grep firewall
sudo rmmod firewall
Rule Match - IN ZONE
static int packet_in_zone_handler(void *priv, struct sk_buff *skb, const struct nf_hook_state *state)
{
struct list_head *pos = NULL;
struct rule_list *cur_rule = NULL;
struct sk_buff *sb = NULL;
if(!skb)
{
return NF_ACCEPT;
}
sb = skb;
list_for_each(pos, &tu_rules.list)
{
cur_rule = list_entry(pos, struct rule_list, list);
if(cur_rule->data->when == WHEN_IN_ZONE)
{
if(rule_match(WHEN_IN_ZONE, sb, cur_rule->data) == TU_RULE_MATCH)
{
if(cur_rule->data->action == NF_ACCEPT)
{
printk(KERN_INFO "[TU FIREWALL] [IN ZONE] ACCEPT RULE : %s\n", cur_rule->data->name);
}
else if(cur_rule->data->action == NF_DROP)
{
printk(KERN_INFO "[TU FIREWALL] [IN ZONE] DROP RULE : %s\n", cur_rule->data->name);
}
return cur_rule->data->action;
}
}
}
return NF_ACCEPT;
}
IOCTL - get rules from userspace
switch(cmd)
{
case ADD_RULE:
if(copy_from_user(&rule_arg, (struct rule_data *)arg, sizeof(struct rule_data)))
{
printk(KERN_INFO "No Rule Data From Userspace\n");
break;
}
data = create_rule(rule_arg);
if(data == NULL)
{
printk(KERN_INFO "Invalid Received Rule From user space\n");
break;
}
push_rule(&tu_rules.list, data);
printk(KERN_INFO "Received Rule from user space\n");
printk(KERN_INFO "RULE NAME : %s\n", rule_arg.name);
break;
case DELETE_RULE:
if(copy_from_user(&value, (__u32*)arg, sizeof(value)))
{
printk(KERN_INFO "Error Delete Command\n");
break;
}
// delete all rules
del_all_rules(&tu_rules.list);
printk(KERN_INFO "Delete All Rules\n");
break;
default:
break;
}
Register Nerfilter Hook
tu_nf_local_in_ops = (struct nf_hook_ops *)kcalloc(1, sizeof(struct nf_hook_ops), GFP_KERNEL);
if(tu_nf_local_in_ops == NULL)
{
printk(KERN_INFO "kcalloc(): Failed Allocate nf_local_in_ops in kernelspace\n");
return -1;
}
tu_nf_local_in_ops->hook = (nf_hookfn*)packet_in_zone_handler;
tu_nf_local_in_ops->hooknum = NF_INET_LOCAL_IN;
tu_nf_local_in_ops->pf = NFPROTO_IPV4;
tu_nf_local_in_ops->priority = in_priority;