반응형
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
Linux Networking From Go
Manipulating network interfaces, firewalling, and forwarding from Go.
tomdnetto.net
https://tomdnetto.net/post/advanced_nftables_with_go
Advanced NFTables With Go
NFTables like your mama taught you.
tomdnetto.net
'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 |