C language, Go(Golang), Netfilter, Netlink를 이용하여 Linux network을 제어하고 모니터링하는 방법을 알아보자~
개념 이해하기: Netfilter hooks into Linux networking packet flows
The following schematic shows packet flows through Linux networking:
Linux Netfilter + C example code
참고 문서: https://pr0gr4m.github.io/linux/kernel/netfilter/
이론 설명과 함께 잘 동작하는 예시가 있어서 쉽게 이해할 수 있다.
아래는 위 블로그의 끝 부분에 있는 HTTP Traffic만 선별하여 Drop하는 예제 코드인데,
그냥 이 예제 코드만 봐도 이해할 수 있을 것이다.
http_netfilter.c
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/netfilter.h>
#include <linux/netfilter_ipv4.h>
#include <linux/ip.h>
#include <linux/tcp.h>
#include <linux/netdevice.h>
static unsigned int hook_http(void *priv,
struct sk_buff *skb, const struct nf_hook_state *state)
{
struct iphdr *iph;
struct tcphdr *th;
char *data = NULL;
int length = 0;
if (!skb)
return NF_ACCEPT;
iph = ip_hdr(skb);
if (iph->protocol == IPPROTO_TCP) {
th = tcp_hdr(skb);
length = skb->len - iph->ihl * 4 - th->doff * 4;
if (length <= 4)
return NF_ACCEPT;
data = kzalloc(length, GFP_KERNEL);
memcpy(data, (unsigned char *)th + th->doff * 4, length);
if (strstr(data, "HTTP") != NULL) {
printk("[Kernel:%s] HTTP Detected\n", __func__);
kfree(data);
return NF_DROP;
}
kfree(data);
}
return NF_ACCEPT;
}
static struct nf_hook_ops *nf_ops = NULL;
static int __init nfilter_init(void)
{
nf_ops = (struct nf_hook_ops *)kzalloc(sizeof(struct nf_hook_ops), GFP_KERNEL);
nf_ops->hook = (nf_hookfn *)hook_http;
nf_ops->pf = PF_INET;
nf_ops->hooknum = NF_INET_LOCAL_IN;
nf_ops->priority = NF_IP_PRI_FIRST;
nf_register_net_hook(&init_net, nf_ops);
printk("[Kernel:%s] NFilter Init\n", __func__);
return 0;
}
static void __exit nfilter_exit(void)
{
nf_unregister_net_hook(&init_net, nf_ops);
kfree(nf_ops);
printk("[Kernel:%s] NFilter Exit\n", __func__);
}
module_init(nfilter_init);
module_exit(nfilter_exit);
MODULE_LICENSE("GPL");
Makefile
obj-m += http_netfilter.o
KDIR := /lib/modules/$(shell uname -r)/build
default:
$(MAKE) -C $(KDIR) M=$(PWD) modules
CC := gcc
%.c%:
${CC} -o $@ $^
clean:
$(MAKE) -C $(KDIR) M=$(PWD) clean
rm -f ${TARGETS}
Build & Test
##
## HTTP Server 장비에서 아래 명령을 수행.
##
$ make
$ sudo insmod http_netfilter.ko
$
---
##
## 다른 PC에서 아래와 같이 HTTP Traffic을 발생시켜본다.
##
$ curl -v http://my-test.server.domain/
* Trying my-test.server.domain:80...
* Connected to my-test.server.domain port 80 (#0)
> GET / HTTP/1.1
> Host: my-test.server.domain
> User-Agent: curl/7.77.0
> Accept: */*
> ##
## TCP Session만 수립될 뿐,
## 실제 HTTP Response 패킷을 받지 못해서 이 상태로 계속 남아있다가 Timed out 처리된다.
##
* Recv failure: Operation timed out
* Closing connection 0
curl: (56) Recv failure: Operation timed out
$
---
##
## HTTP Server 장비에서 아래 명령을 수행.
##
$ dmesg --color --follow
... 중간 생략 ..
[264707.035282] [Kernel:hook_http] HTTP Detected
[264711.387549] [Kernel:hook_http] HTTP Detected
... 중간 생략 ..
Netlink for C language
Wikipedia
Netlink Protocol Library Suite (libnl)
Core Library Developer's Guide (libnl)
Routing Library Developer's Guide (libnl-route)
Example Collection
- https://medium.com/thg-tech-blog/on-linux-netlink-d7af1987f89d
- https://olegkutkov.me/2018/02/14/monitoring-linux-networking-state-using-netlink/
위 문서를 읽고 나서, 아래 예제를 테스트하면서 이해하기.
$ cat detect_chg_event.c
/**
* How to build
* $ gcc -o detect_chg_event detect_chg_event.c
* How to run
* $ ./detect_chg_event
*/
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <ifaddrs.h>
#include <net/if.h>
#include <netdb.h>
#include <netinet/in.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
static int
create_sock (const char *nic)
{
struct sockaddr_nl addr;
int sock;
memset (&addr, 0, sizeof (addr));
addr.nl_family = AF_NETLINK;
addr.nl_groups = RTMGRP_LINK | RTMGRP_IPV4_IFADDR | RTMGRP_IPV6_IFADDR;
sock = socket (PF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
if (sock < 0)
{
fprintf (stderr, "failed to open NETLINK_ROUTE socket for %s - %s(%d)",
nic, strerror (errno), errno);
return -1;
}
if (bind (sock, (struct sockaddr *)&addr, sizeof(addr)) < 0)
{
fprintf (stderr, "failed to bind NETLINK_ROUTE socket for %s - %s(%d)",
nic, strerror (errno), errno);
close (sock);
return -1;
}
return sock;
}
/**
* NOTE: Cheeck if NIC status is changed
*/
static int
ip_changed (int sock,
const char *nic)
{
struct nlmsghdr *nlh;
char buffer[4096];
int len;
int idx;
int found;
len = recv (sock, buffer, sizeof (buffer), 0);
if (len <= 0)
{
fprintf (stderr, "NETLINK_ROUTE socket recv() failedn");
return -1;
}
printf("\n %s %s(%d) Receive message from raw socket \n",
__func__, __FILE__, __LINE__);
found = 0;
idx = if_nametoindex (nic);
printf("\n %s %s(%d) Index of %s: %d \n",
__func__, __FILE__, __LINE__, nic, idx);
for ( nlh = (struct nlmsghdr *) buffer;
NLMSG_OK (nlh, len);
nlh = NLMSG_NEXT (nlh, len))
{
if (nlh->nlmsg_type == NLMSG_DONE)
break;
if (nlh->nlmsg_type == NLMSG_ERROR)
continue;
if (!(NLMSG_OK (nlh, len)))
continue;
printf("\n %s %s(%d) Netlink MSG Type: %d\n",
__func__, __FILE__, __LINE__, nlh->nlmsg_type);
/*
* NOTE:
* RTM_NEWADDR, RTM_NEWLINK 에 관한 정의는 rtnetlink.h 파일에서 확인할 수 있다.
* - /usr/include/linux/rtnetlink.h
*/
switch (nlh->nlmsg_type)
{
case RTM_NEWADDR:
{
struct ifaddrmsg *ifa = (struct ifaddrmsg *)NLMSG_DATA (nlh);
if (ifa->ifa_index == idx)
found = 1;
}
break;
case RTM_NEWLINK:
{
struct ifinfomsg *ifi = (struct ifinfomsg *)NLMSG_DATA (nlh);
if (ifi->ifi_index == idx)
found = 1;
}
break;
default:
break;
}
}
return found;
}
static int
get_nic_addr ( const char *nic,
struct ifaddrs *ifaddr,
int wanted_family,
char *host,
int host_len,
int *active)
{
struct ifaddrs *ifa;
for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next)
{
int family;
int s;
if (ifa->ifa_addr == NULL)
continue;
if (strcmp (ifa->ifa_name, nic))
continue;
/* Skip unwanted families. */
family = ifa->ifa_addr->sa_family;
if (family != wanted_family)
continue;
*active = (ifa->ifa_flags & IFF_RUNNING) ? 1 : 0;
s = getnameinfo ( ifa->ifa_addr,
family == AF_INET ? sizeof (struct sockaddr_in) :
sizeof (struct sockaddr_in6),
host,
host_len,
NULL,
0,
NI_NUMERICHOST);
if (s != 0)
{
fprintf (stderr, "failed to getnameinfo() for '%s - %s(%d)",
ifa->ifa_name, strerror (errno), errno);
continue;
}
/* Get the address of only the first network interface card. */
return 1;
}
return 0;
}
static void
print_ip (const char *nic)
{
struct ifaddrs *ifaddr;
char addr[NI_MAXHOST];
int active;
printf("\n %s(%d) nic: %s \n",
__FILE__, __LINE__, nic);
if (getifaddrs (&ifaddr) == -1)
{
fprintf (stderr, "failed to getifaddrs() - %s(%d)", strerror (errno), errno);
return;
}
// NOTE: IPv4
if (!get_nic_addr (nic, ifaddr, AF_INET, addr, sizeof (addr), &active))
{
// If IPv4 configuration is not available,
// then try to get the Ipv6 configuration.
printf("\n %s(%d) nic: %s addr: %s active: %d \n",
__FILE__, __LINE__, nic, addr, active);
// NOTE: IPv6
if (!get_nic_addr (nic, ifaddr, AF_INET6, addr, sizeof (addr), &active))
{
// Nothing to do.
strcpy (addr, "127.0.0.1");
active = 0;
}
} else {
printf("\n %s(%d) nic: %s addr: %s active: %d \n",
__FILE__, __LINE__, nic, addr, active);
}
freeifaddrs (ifaddr);
printf("\n %s %s(%d) %s is %s (link %s) \n",
__func__, __FILE__, __LINE__,
nic, addr, active ? "active" : "inactive");
}
int
main (void)
{
// FIXME: enp7s0 --> my machine's network interface name
char *nic = "enp7s0";
int sock;
print_ip (nic);
sock = create_sock (nic);
if (sock < 0)
return -1;
while (1)
{
int ret;
ret = ip_changed (sock, nic);
if (ret < 0)
return -1;
if (ret)
print_ip (nic);
printf("\n\n %s %s(%d) END OF LOOP \n\n\n",
__func__, __FILE__, __LINE__);
}
close (sock);
return 0;
}
$
$ gcc -o detect_chg_event detect_chg_event.c
$ ./detect_chg_event
ip_changed detect_chg_event.c(73) Receive message from raw socket
ip_changed detect_chg_event.c(79) Index of enp7s0: 2
ip_changed detect_chg_event.c(93) Netlink MSG Type: 16
detect_chg_event.c(181) nic: enp7s0
detect_chg_event.c(205) nic: enp7s0 addr: 10.1.4.51 active: 1
print_ip detect_chg_event.c(211) enp7s0 is 10.1.4.51 (link active)
main detect_chg_event.c(239) END OF LOOP
...
...
위 예제에서 detect_chg_event 명령을 실행시켜 놓고, 아래와 같이 명령을 실행해본다.
$ ifconfig enp7s0 mtu 1501
$ ifconfig enp7s0 mtu 1500
$ ifconfig enp7s0 down
$ ifconfig enp7s0 up
detect_chg_event 예제 앱이 enp7s0 장치의 상태 변화를 감지해서 터미널에 감지한 내용을 출력해줄 것이다.
Netlink library for go
https://github.com/vishvananda/netlink
https://tomdnetto.net/post/linux_networking_from_go_nftables
https://tomdnetto.net/post/advanced_nftables_with_go
'Network' 카테고리의 다른 글
tcpdump 명령으로 tcp syn, rst 패킷 확인 (0) | 2023.07.25 |
---|---|
10G L3 Switch 장비 추천 (NEXTU 3424GL3-10G) (3) | 2023.04.13 |
CISCO ACI(Application Centric Infra), APIC and Kubernetes (0) | 2022.07.19 |
GNS3, CISCO Switch 명령 모음 및 동영상 강의 (0) | 2022.07.02 |
GNS3 사용을 위한 CISCO IOS 구하는 방법 (0) | 2022.07.01 |