반응형

 

C language, Go(Golang), Netfilter, Netlink를 이용하여 Linux network을 제어하고 모니터링하는 방법을 알아보자~

 

 

개념 이해하기: Netfilter hooks into Linux networking packet flows

The following schematic shows packet flows through Linux networking:

 

From:  https://wiki.nftables.org/wiki-nftables/index.php/Netfilter_hooks

 

 


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

 

 

 

위 문서를 읽고 나서, 아래 예제를 테스트하면서 이해하기.

$  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

 

 

 

반응형

Kuberentes를 사용하면서 이해하기 어려운 부분이 CNI(Cluter Network Interface)이다.

실제로 각 CNI 마다 동작하는 방식과 구현이 다른데 CNI라고 퉁쳐서 표현하다보니 Kubernetes를 사용하는 운영자 입장에서는 CNI가 어떻게 IP Traffic(트래픽)을 처리하는지 알기 어렵다.

여러 종류의 CNI 중에서 내가 사용하고 있는 특정 CNI에 대해서 깊이 있게 설명하는 자료도 별로 없으니 스터디하기가 더욱 어렵다.

 

나의 일터에서는 주로 Openshift SDN을 사용하기 때문에 오늘은 시간을 내서 Openshift SDN을 깊게 파헤쳐보려 한다.

 

우선 아래 그림에 있는 빨간색 숫자를 따라 가면서 보자.

이 빨간 색 숫자의 순서대로 요청 패킷이 처리된다.

 

Openshift SDN 동작 방식

 

 

그림에 순서에 맞게 설명을 적었기 때문에 전반적인 설명은 필요 없을 것 같다.

단, 복잡한 처리가 있는 부분을 좀더 살펴 본다면,

 

(2) Netfilter를 이용하여 DNAT, SNAT 처리

Kubernetes Pod 정보를 조회해보면 Cluster IP를 볼 수 있다. 바로 그 Pod의 Cluster IP로 DNAT 처리되는 것이다.

즉, 10.10.12.208:8080 목적지 주소를 보고, 이 주소 값과 Mapping되는 Kubernetes Pod IP를 찾고 Netfilter를 이용하여 DNAT 처리한다.

Worker 1 Node에서 iptables 명령을 이용하여 Netfilter의 Chain Rule을 조회하면 실제로 아래와 같이 정보를 볼 수 있다.

이 Chain Rule대로 관련 Packet 조건을 찾고, 그 조건에 맞는 Netfilter Chain을 따라가면서 DNAT처리하는 것이다.

 

$  iptables -t nat -L KUBE-SERVICES -nv | grep 10.10.12.208
 1760  106K KUBE-FW-BAR7JSQD2GL6A2KT  tcp  --  *      *       0.0.0.0/0            10.10.12.208         /* almighty/almighty5:web loadbalancer IP */ tcp dpt:8080

… 중간 생략 …

$ iptables -t nat -L KUBE-SEP-ZBWV76PMI2XAVMCD -nv
 1949  117K DNAT       tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            /* almighty/almighty5:web */ tcp to:10.128.2.151:8080

—> kube-proxy가 실제 iptables 룰 생성시 사용한 명령은 아래와 같음.
KUBE-SEP-ZBWV76PMI2XAVMCD -p tcp -m comment --comment "almighty/almighty5:web" -m tcp -j DNAT --to-destination 10.128.2.151:8080

 

만약 Destination Pod가 다른 Worker Node에 있다면, 다른 Worker Node로 IP Packet을 보내기 위해 tun0 인터페이스를 이용하여야 한다. 이때 tun0의 IP Address로 SNAT 하도록 하는 Chain Rule 적용을 받는다.

 

Chain POSTROUTING (policy ACCEPT)
target     prot opt source               destination
OPENSHIFT-MASQUERADE  all  --  anywhere             anywhere             /* rules for masquerading OpenShift traffic */
KUBE-POSTROUTING  all  --  anywhere             anywhere             /* kubernetes postrouting rules */

Chain KUBE-POSTROUTING (1 references)
target     prot opt source               destination
RETURN     all  --  anywhere             anywhere             mark match ! 0x1/0x1
MARK       all  --  anywhere             anywhere             MARK xor 0x1
MASQUERADE  all  --  anywhere             anywhere             /* kubernetes service traffic requiring SNAT */ random-fully

 

 

(3) DST IP로 갈 수 있는 Routing Table을 조회하여 tun0로 Packet을 보냄

아마 대부분 Kubernetes 사용자가 헷갈려하는 부분일 것이다.

만약 Destination Pod가 같은 Worker Node에 있다면, 단순히 Netfilter가 처리한 대로 Packet Forward만 하면 된다.

그런데, Destination Pod가 다른 Worker Node에 있다면 관련있는 물리 Network Port를 찾아서 보내야 한다.

위 그림에서 Worker 1과 Worker 2의 tun0 간에는 ARP Packet이 브로드캐스팅되므로, 이미 Worker 1에서는 Destination Pod IP에 해당하는 MAC Address를 알고 있다. 

(참고: arp 명령으로 Worker 1 노드의 ARP Table 조회 가능)

따라서 일반적인 L2 Forward 처리와 동일하게 처리된다.

 

 

(4) ~ (5) Encapsulate, Decapsulate

일반적인 VxLAN 터널링을 사용하여 Worker Node간에 Pod의 메시지를 전송해준다.

VxLAN 터널링 때문에 tun0의 MTU는 1450 byte가 된다. (왜냐하면, 터널링 헤더 메시지 때문에 50 Byte만큼 tun0 포트의 MTU가 작아지게 된다)

 

 

나머지 절차는 모두 L2 Switching이기 때문에 딱히 설명할 것이 없다.

 

응답 메시지는 따로 설명하지 않았다.

왜냐하면,

L2 Switching 구간은 단순히 Source Address와 Destination Address를 반대로 뒤집으면 되고,

Netfilter가 SNAT 처리한 부분의 Connection Track 정보를 찾아서 원래의 주소값으로 치환해주면 되기 때문이다.

 

 

블로그 작성자: sejong.jeonjo@gmail.com

 

반응형

 

가끔 kubernetes에서 istio를 사용하다보면, Pod 내부의 iptables 구성 상태가 어떠한지 궁금할 때가 있다.

매번 여기저기 명령 찾아서 짜집기하고 있었는데, 이렇게 하니까 찾는 시간도 오래 걸리고, 나의 기억도 오래가지 않아서 여기에 메모를 하고 필요할 때마다 아래 명령을 copy & paste 해야겠다.

 

(이 명령은 Istio README.md에도 있으니까, 좀더 깊게 볼려면 Istio README.md에서 찾아보길~)

 

##
## pod의 container id 확인하기
##

$ ns=ns-a

$ podnm=ratings-v1-587d5cfb9-mxhxc

$ container_id=$(kubectl get pod -n ${ns} ${podnm} -o jsonpath="{.status.containerStatuses[?(@.name=='istio-proxy')].containerID}" | sed -n 's/docker:\/\/\(.*\)/\1/p')

$ echo $container_id
6dbeeb047da9149f0735943cd95885a156dbdc766b48145c40e7346ea7f6bcfb


##
## 어떤 worker node에서 ratings-v1 pod가 구동되어 있는지 확인하기
##

$ kubectl get -n ns-a pod -l app=ratings -o wide
NAME                         READY   STATUS    RESTARTS   AGE     IP               NODE      NOMINATED NODE   READINESS GATES
ratings-v1-587d5cfb9-mxhxc   2/2     Running   0          2d17h   10.244.91.198    node-73   <none>           <none>
ratings-v1-587d5cfb9-s6g6r   2/2     Running   0          2d17h   10.244.142.131   node-75   <none>           <none>


##
## ratings-v1 pod가 구동된 worker node에 ssh 접속하기
##

$ ssh root@node-73

##
## NOTE: worker node에 접속했으면, 위에서 알아냈던 container_id를 아래와 같이 설정하고
##       아래 명령 순서대로 실행한다.
##

[root@node-73 ~]$ container_id=6dbeeb047da9149f0735943cd95885a156dbdc766b48145c40e7346ea7f6bcfb

[root@node-73 ~]$ cpid=$(docker inspect --format '{{ .State.Pid }}' $container_id)


##
## Worker Node의 nsenter 명령을 이용하여 Container 내부의 iptables 정보를 조회한다.
##
[root@node-73 ~]$ nsenter -t $cpid -n iptables -L -t nat -n -v --line-numbers -x

Chain PREROUTING (policy ACCEPT 118222 packets, 7093320 bytes)
num      pkts      bytes target     prot opt in     out     source               destination
1      118224  7093440 ISTIO_INBOUND  tcp  --  *      *       0.0.0.0/0            0.0.0.0/0

Chain INPUT (policy ACCEPT 118224 packets, 7093440 bytes)
num      pkts      bytes target     prot opt in     out     source               destination

Chain OUTPUT (policy ACCEPT 10650 packets, 924376 bytes)
num      pkts      bytes target     prot opt in     out     source               destination
1         400    24000 ISTIO_OUTPUT  tcp  --  *      *       0.0.0.0/0            0.0.0.0/0

Chain POSTROUTING (policy ACCEPT 10650 packets, 924376 bytes)
num      pkts      bytes target     prot opt in     out     source               destination

Chain ISTIO_INBOUND (1 references)
num      pkts      bytes target     prot opt in     out     source               destination
1           0        0 RETURN     tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpt:15008
2           0        0 RETURN     tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpt:22
3           0        0 RETURN     tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpt:15090
4      118222  7093320 RETURN     tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpt:15021
5           0        0 RETURN     tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpt:15020
6           2      120 ISTIO_IN_REDIRECT  tcp  --  *      *       0.0.0.0/0            0.0.0.0/0

Chain ISTIO_IN_REDIRECT (3 references)
num      pkts      bytes target     prot opt in     out     source               destination
1           2      120 REDIRECT   tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            redir ports 15006

Chain ISTIO_OUTPUT (1 references)
num      pkts      bytes target     prot opt in     out     source               destination
1           3      180 RETURN     all  --  *      lo      127.0.0.6            0.0.0.0/0
2           0        0 ISTIO_IN_REDIRECT  all  --  *      lo      0.0.0.0/0           !127.0.0.1            owner UID match 1337
3           0        0 RETURN     all  --  *      lo      0.0.0.0/0            0.0.0.0/0            ! owner UID match 1337
4         397    23820 RETURN     all  --  *      *       0.0.0.0/0            0.0.0.0/0            owner UID match 1337
5           0        0 ISTIO_IN_REDIRECT  all  --  *      lo      0.0.0.0/0           !127.0.0.1            owner GID match 1337
6           0        0 RETURN     all  --  *      lo      0.0.0.0/0            0.0.0.0/0            ! owner GID match 1337
7           0        0 RETURN     all  --  *      *       0.0.0.0/0            0.0.0.0/0            owner GID match 1337
8           0        0 RETURN     all  --  *      *       0.0.0.0/0            127.0.0.1
9           0        0 ISTIO_REDIRECT  all  --  *      *       0.0.0.0/0            0.0.0.0/0

Chain ISTIO_REDIRECT (1 references)
num      pkts      bytes target     prot opt in     out     source               destination
1           0        0 REDIRECT   tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            redir ports 15001

[root@node-73 ~]$

 

 

위 iptables 구성에 관한 이론 및 개념 설명은 아래 blog를 참고하길~

 

 

iptables and netfilter

iptables의 Chain(Rule) & Table 구조 iptables는 다수의 Chain(예: PREROUTING, INPUT, OUTPUT, POSTROUTING, FORWARD)과 그 Chain에 정의된 Rule, 그리고 각 Chain에는 다수의 Table(raw, mangle, nat, filter,..

andrewpage.tistory.com

 

'kubernetes' 카테고리의 다른 글

Kubernetes Metrics Server 설치  (0) 2021.11.26
[TODO] Read - Istio Memo  (0) 2021.11.23
Kubernetes imagePullSecret 설정  (0) 2021.11.19
kubectl 명령 예제 & JSON 포맷으로 출력하기  (0) 2021.11.17
Setup Kubernetes Using Ansible  (0) 2021.11.15
반응형

 

iptables의 Chain(Rule) & Table 구조 

iptables는 다수의 Chain(예: PREROUTING, INPUT, OUTPUT, POSTROUTING, FORWARD)과 그 Chain에 정의된 Rule, 그리고 각 Chain에는 다수의 Table(raw, mangle, nat, filter, security)이 포함되어 있다.

 

Iptables의 Chain 종류와 Table 종류

 

 

IP Packet이 유입된 이후에 아래 그림과 같이 패킷의 L2, L3, L4 Header를 조작하거나, Header의 내용을 보면서 IP Packet을 다음 Chain에서 처리하게 할지, 아니면 Client(Sender)로 되돌려 보낼지를 처리한다.

 

IPTables Chains Order Scheme&nbsp; (from&nbsp; https://jimmysong.io/en/blog/sidecar-injection-iptables-and-traffic-routing/)

 

위 그림을 볼 때, 주의할 점은 노란색 메모장 같은 라벨이 붙은 table에서는 IP packet을 조작한다는 것이다. 그리고 filter 같은 table에서는 IP packet을 열람만하고 조작하지 않는다.

 

그리고, 아래 그림은  istio의 envoy proxy container와 app container가 어떻게 IP Traffic을 전달하는지 보여준다. 이때도 역시 iptables가 IP traffic의 전달에 관여한다.

 

image from&nbsp; https://jimmysong.io/en/blog/sidecar-injection-iptables-and-traffic-routing/

 

 

image from&nbsp; https://jimmysong.io/en/blog/sidecar-injection-iptables-and-traffic-routing/

 

 

Packet Flow in Netfilter and General Networking&nbsp; (from wikipedia.org/wiki/Netfilter)

 

 

 

iptables 명령 기본 표기법

iptables [-t table] [action] [chain] [pattern] [-j target] 

  • [table] 크게 nat와 filter로 나누며 기본값은 filter이다. 
  • [action] 전체 체인에 대한 정책을 지정 / -A , -L , -D , -F 등 대문자 옵션이 해당한다. 
  • [chain] 일반적 필터링에 속하는 INPUT.OUTPUT.FORWARD가 있으며 NAT테이블에는 POSTROUTING / PREROUTING / OUTPUT 이 있다. 
  • [pattern] 세부규칙을 지정하는 것으로 소문자 옵션 -s / -p /-d 등이 이에 해당한다. 
  • [target] 정책을 지정하는 것이며 DROP / ACCEPT / LOG 등등이 있다. 

 

iptables 명령 예시

Example - A

$ iptables -t filter
           -A INPUT  # 들어오는 패킷에 대한 필터링 
           -i eth0   # eth0 네트워크 아답터로 들어오는 패킷들이 대상임
           -p ICMP   # 프로토콜이 ICMP이면
           -j DROP   # 위 조건을 만족하는 패킷을 소멸(삭제)

 

Example - B

$ iptables -t filter
           -A INPUT       # 들어오는 패킷에 대한 필터링
           -i eth0        # eth0 네트워크 아답터로 들어오는 패킷들이 대상임
           -o eth0        # eth0 네트워크 아답터로 나가는는(output) 패킷들이 대상임
           -p TCP         # 프로토콜이 TCP이면
           --dport 22     # destination port가 22번이면
           --sport 39321  # source port가 39321번이면 
           -j ACCEPT      # 위 조건을 만족하는 패킷을 Accept

 

 

 

iptables 사용하기

iptables는 사용하는 방법은 크게 두가지가 있다.  

  • 첫번째는 전체 체인에 대한 설정이고,
  • 두번째는 각 체인에 대한 규칙을 사용하는 방법이다.  

체인에 대한 동작설정은 대문자 옵션을 사용하고 체인에 대한 세부규칙은 소문자 옵션을 사용한다. 

 

체인 전체옵션 설명 

-N 새로운 체인을 만든다 
-X 비어있는 체인을 제거한다 
-P 체인의 정책을 설정한다 
-L 현재 체인의 정책을 보여준다 
-F 체인의 규칙을 제거한다 
*체인 동작옵션 설명 (내부규칙을 뜻함) 
-A 체인에 새로운 규칙을 추가한다. 해당체인에 마지막규칙으로 등록된다. 
-t 가 filter 인 경우 INPUT, FORWARD 사용가능 
-t 가 nat 인 경우 POSTROUTING , PREROUTING 사용가능 
-I 체인에 규칙을 맨 첫부분에 등록한다. 
-R 체인의 규칙을 교환한다. 
-D 체인의 규칙을 제거한다 

 

 

 

iptables 세부 옵션

-s / -d 출발지('-s' , '--source') / 도착지 ('-d' , '--destination')를 뜻함 
$ iptables -A INPUT -s 192.168.10.0/24 -j DROP   # 192.168.10.0/24 네크워크에서 들어오는 패킷들을 모두 DROP한다. 
                                                 # -j 옵션에 대해 ACCEPT | DROP | DENY | REDIRECT 등등 설정 

$ iptables -A INPUT -s 192.168.10.20 -j DENY     # 192.168.10.20으로 들어오는 패킷에 대해서 거부한다.

 

-p (프로토콜의 지정) 
     (예) -p ! TCP      # TCP 프로토콜이 아닌경우를 뜻함 

-i (--in-interface) 패킷이 들어오는 인터페이스를 지정 
     INPUT / FORWARD 체인에서 사용 
     -t nat 이면 PREROUTING에서만 사용 가능 

-o ('--out-interface') 패킷이 나가는 인터페이스를 지정 
     OUTPUT / FORWARD 체인에서 사용 
     -t nat 이면 POSTROUTING에서만 사용 가능 

-t (--table) 테이블 선택의 의미 
     -t filter / -t nat / -t mangle 세가지 선택을 할수 있다. 

 

 

 

iptables 의 추가 옵션 설정하기 

-p 같은 프로토콜 관련 옵션들의 기능에 대한 세부적인 추가 옵션을 제공한다. 
--sport 발신지에서의 하나의 포트나 포트범위를 지정한다. 
--dport 도착지에서의 포트를 지정한다. 
--tcp-flags 플래그를 지정하는 옵션이다. 첫번째는 검사하고자 하는 지시자 리스트 . 
두번째는 지시자에 어떻게 할것인가를 설정 
$ iptables -A INPUT -p tcp --tcp-flag ALL SYN<ACK -j DENY  # 모든플래그를 검사(ALL의 의미는 SYS,ACK,FIN,RST,URG,PSH를 의미)SYN 과 ACK만 거부한다.

 

 

 

정책(Rule) 확인법

ping test를 이용하여 traffic policy를 확인할 수 있다.

$ ping localhost   # 127.0.0.1로 핑 보내기 
## 정상적으로 ping packet이 전송되는 것을 확인한다.

$ iptable -A INPUT -s 127.0.0.1 -p icmp -j DROP   # 출발지가 127.0.0.1 이며 프로토콜 icmp 사용해 들어오는 패킷을 DROP 한다. 

$ ping localhost 
## DROP 정책이 정상적으로 적용되었다면, ping이 되지 않을 것이다.

 

 

 

정책(Rule) 지우기

우선, 지우고자 하는 정책을 확인하고, -D 옵션을 이용하여 해당 정책을 지운다.

$ iptables -L --line   # 정책 순서대로 리스트 번호가 설정되어 보여짐

## 개별 정책 지우기 (-D) 
## iptables -D [해당정책] [리스트번호] 
$ iptables -t nat -D PREROUTING 2 

## 전체 정책 초기화 (-F) 
$ iptables -F INPUT    # INPUT체인에 부여된 정책 모두 제거

 

 

 

iptables rule 저장하고 불러오기

방법 A : 수작업 복구

원하는 방화벽 체인을 설정해 놓은후 그 설정을 저장하여 설정된 내용을 불러 올수 있다. 

## iptables-save > [파일명] 
$ iptables-save > my-save.txt 

## 저장한 내용 화면에 출력하기 
$ iptables-save 

## iptables-save로 저장한 체인을 복구 명령: iptables-restore 
$ iptables-restore < my-save.txt

 

방법 B :  자동 복구 (부팅 시 백업된 Rule 적용)

## root 사용자로 변경
$  sudo -s

## 자동 복구를 위한 관련 SW 패키지 설치
$  apt install iptables-persistent

## 아래와 같이 /etc/iptables 폴더가 생성되었는지 확인
$  ls -al /etc/iptables/

## netfilter-persistent 서비스 상태를 확인
$  systemctl status netfilter-persistent

$  systemctl is-enabled netfilter-persistent


## 테스트를 위해 아래와 같이 Rule을 추가해보고, OS Reboot해본다.
$  iptables -A POSTROUTING -t nat -o br-ex -s 172.16.0.0/16 -j MASQUERADE

##
## OS Reboot 완료 후, iptables 명령으로 netfilter rule이 잘 복구되었는지 확인한다.
##
$ iptables-save | grep 172.16.0.0

 

## 아래 명령과 같이 'save' 서브 명령이 있는데,
## 지금까지 테스트해본 결과를 보면, 'save' 명령을 수행하지 않아도
## 그때그때 netfilter rule의 변화를 /etc/iptables 폴더에 반영하는 것 같다.
## (2023년 6월 기준으로 이렇게 동작하지만, 나중에 또 어떻게 바뀌지 모르니까 참고만 할 것 !)
$ netfilter-persistent save

 

 

 

 


 

Somthing to read

 

https://iximiuz.com/en/posts/laymans-iptables-101/

이  Web Docs가 그림을 이용하여 다양한 상황을 잘 Illustration하고 있다. 시간 날 때마다 읽어서 기억을 유지하면 좋을 듯 :)

 

Illustrated introduction to Linux iptables - Ivan Velichko

What are iptables chains, rules, policies, and tables? Describe iptables in layman's terms.

iximiuz.com

image from&nbsp;https://iximiuz.com/en/posts/laymans-iptables-101/

+ Recent posts