사전 netfilter
구현체

netfilter

gabury1고친 사람 github-actions[bot]

netfilter 는 리눅스 커널 안에서 패킷이 꼭 지나는 길목 몇 곳에 코드를 끼우게 해 주는 장치입니다. 끼워 둔 코드는 지나가는 패킷을 보고 통과시키거나 버리거나 주소를 바꿉니다. 리눅스에서 쓰는 방화벽 대부분이 이 위에서 돕니다. Docker 로 컨테이너의 포트를 열 때 패킷을 컨테이너로 돌려 보내는 것도 이 장치입니다.

쉽고 빠른 이해

netfilter 는 리눅스 서버에 드나드는 패킷을 커널 안에서 검사하게 해 줍니다. 「22번 포트로 오는 접속만 받고 나머지는 버린다」 같은 방화벽 규칙이 여기서 집행됩니다. 규칙을 적을 때 사람이 직접 만지는 것은 netfilter 가 아니라 iptables 나 nft 같은 명령입니다.

이게 없으면 방화벽을 만들 때마다 커널의 네트워크 코드를 직접 고쳐야 합니다. 패킷을 전부 일반 프로그램으로 넘겨 검사하면 패킷마다 복사가 오가서 느립니다.

  1. 커널이 패킷을 정해진 길목 몇 곳에 차례로 세웁니다
  2. 길목마다 끼워 둔 함수가 패킷을 보고 통과시킬지 버릴지 정합니다
  3. 오가는 연결을 기억해 두어 응답 패킷과 낯선 접속 시도를 가립니다

주소와 포트, 연결 상태로 가르는 일에 씁니다. 웹 요청의 경로나 쿠키 값으로 가르는 일은 맡지 않습니다. 그 일은 웹 서버 쪽 몫입니다.

모든 패킷이 길목을 지나므로 규칙이 길어지면 패킷마다 드는 시간이 늘어납니다. 연결을 기억하는 표가 가득 차면 새 접속이 버려집니다.

상세

이 절은 패킷이 커널 안에서 지나는 길을 먼저 그립니다. 그 길 어디에 netfilter 가 코드를 끼우는지 보고, 그 위에서 도는 연결 추적과 주소 변환을 차례로 봅니다. 끝으로 netfilter 가 맡지 않는 일과 치르는 값을 짚습니다.

커널 안의 패킷 길

커널은 하드웨어와 프로그램 사이에서 기계 전체를 관리하는 운영체제의 핵심 코드입니다. 네트워크 카드에 도착한 패킷은 곧바로 프로그램에 닿지 않습니다. 먼저 커널이 받습니다.

커널 안에서 패킷을 맡는 부분을 네트워크 스택이라고 합니다. 네트워크 스택은 패킷이 어디로 가야 하는지 정하고 알맞은 프로그램에 건넵니다.

길을 정할 때 보는 것은 패킷 맨 앞에 붙은 헤더입니다. 헤더는 이 패킷을 누가 누구에게 보냈는지 적은 꼬리표입니다. 프로그램은 소켓이라는 통로로 패킷의 내용을 받습니다. 그때 헤더는 이미 떼어진 뒤입니다.

IP(Internet Protocol)는 인터넷에서 패킷을 주소로 나르는 규칙입니다. 이 규칙은 기계마다 주소를 하나씩 매깁니다. 그 주소가 IP 주소입니다. 헤더에는 보낸 쪽과 받는 쪽의 IP 주소가 적힙니다.

포트는 한 기계 안에서 어느 프로그램이 패킷을 받을지 가르는 번호입니다. 웹 서버가 흔히 80번을 쓰는 식입니다. 헤더에는 보낸 쪽과 받는 쪽의 포트 번호도 적힙니다.

헤더에는 나르는 방식인 프로토콜도 적힙니다. 연결을 맺고 순서대로 나르는 TCP(Transmission Control Protocol)와 연결 없이 하나씩 던지는 UDP(User Datagram Protocol)가 대표입니다.

네트워크 스택 안에는 갈림길이 하나 있습니다. 받는 주소가 이 기계면 패킷은 기계 안의 프로그램으로 갑니다. 아니면 다른 기계로 넘겨 보냅니다. 이 결정을 라우팅 결정이라고 합니다.

훅

netfilter 의 뼈대는 훅(hook)입니다. 훅은 코드 중간에 「여기서 다른 코드를 불러 달라」고 미리 뚫어 둔 지점입니다. 네트워크 스택은 패킷을 처리하다 훅에 이르면 그 훅에 등록된 함수들을 차례로 부릅니다.

훅이 왜 필요한지는 없을 때를 떠올리면 보입니다. 방화벽을 만들려면 네트워크 스택 코드 한가운데에 검사 코드를 직접 넣어야 합니다. 주소를 바꾸는 기능을 만들 때는 다른 곳을 또 고쳐야 합니다. 기능이 늘 때마다 스택 코드가 뒤엉킵니다.

사용자 공간은 일반 프로그램이 도는 메모리 구역입니다. 패킷을 전부 이 구역의 프로그램으로 넘겨 검사하면 스택을 안 고쳐도 됩니다. 대신 패킷마다 커널에서 프로그램으로, 다시 커널로 복사가 오갑니다. 패킷 수만큼 이 비용이 쌓여 느립니다.

netfilter 는 훅을 정해진 몇 곳에 미리 뚫어 두었습니다. 방화벽이나 주소 변환을 만드는 쪽은 스택을 고치지 않습니다. 훅에 함수를 등록하기만 합니다.

이 함수들은 흔히 커널 모듈로 올라가 커널 안에서 돕니다. 커널 모듈은 재부팅 없이 커널에 넣었다 뺄 수 있는 코드 묶음입니다.

다섯 훅

IP 의 두 판인 IPv4 와 IPv6 패킷이 지나는 훅은 다섯입니다. 이름은 PREROUTING(라우팅 전) · INPUT(들어옴) · FORWARD(넘겨 보냄) · OUTPUT(나감) · POSTROUTING(라우팅 뒤)입니다. 어느 훅을 지나는지는 패킷이 어디서 와서 어디로 가는지에 달렸습니다. 아래 그림은 세 갈래 길을 한데 모은 것입니다.

flowchart TD
    PRE["PREROUTING · 막 들어온 패킷"] --> R{"라우팅 결정 · 받는 곳이 이 기계인가"}
    R -->|예| IN["INPUT"]
    IN --> P["이 기계의 프로그램"]
    R -->|아니오| FWD["FORWARD"]
    P -->|프로그램이 새로 보낼 때| OUT["OUTPUT"]
    OUT --> POST["POSTROUTING · 곧 나갈 패킷"]
    FWD --> POST

이 기계로 오는 패킷은 PREROUTING 과 INPUT 을 지나 프로그램에 닿습니다. 이 기계의 프로그램이 보내는 패킷은 OUTPUT 과 POSTROUTING 을 지나 나갑니다. 다른 기계로 넘겨 보내는 패킷은 PREROUTING · FORWARD · POSTROUTING 을 지납니다. 프로그램은 거치지 않습니다.

훅마다 패킷을 보는 때가 다르니 주로 맡는 일도 다릅니다.

훅 패킷이 지나는 때 주로 맡는 일
PREROUTING 들어온 직후 · 라우팅 결정 전 받는 주소 바꾸기
INPUT 이 기계의 프로그램에 건네기 직전 이 기계로 오는 접속 거르기
FORWARD 다른 기계로 넘겨 보낼 때 지나가는 패킷 거르기
OUTPUT 이 기계의 프로그램이 보낸 직후 나가는 접속 거르기
POSTROUTING 네트워크 카드로 나가기 직전 보내는 주소 바꾸기

표의 「주소 바꾸기」는 집 공유기가 하는 일입니다. 왜 바꾸는지는 아래 「주소 변환」 소절에서 봅니다. 이 소절에서는 바꾸는 때만 봅니다.

받는 주소는 라우팅 결정 전에 바꿔야 합니다. 그래야 바뀐 주소를 보고 길이 정해집니다. 보내는 주소는 반대로 길이 다 정해진 뒤, 나가기 직전에 바꿉니다.

판정

훅에 등록된 함수는 패킷을 본 뒤 판정을 하나 돌려줍니다. 판정은 이 패킷을 어떻게 할지 네트워크 스택에 알리는 값입니다. 자주 쓰는 판정은 셋입니다.

판정 뜻
NF_ACCEPT 통과. 다음 함수나 다음 단계로 넘긴다
NF_DROP 버림. 패킷이 여기서 사라진다
NF_QUEUE 사용자 공간의 프로그램에 넘겨 판정을 맡긴다

한 훅에 함수가 여럿 등록되면 우선순위 숫자가 작은 것부터 부릅니다. 앞 함수가 버린 패킷은 뒤 함수가 못 봅니다. 그래서 어느 기능이 먼저 패킷을 보는지가 우선순위로 정해집니다.

아래 그림은 우선순위 10 인 함수 A 와 20 인 함수 B 가 한 훅에 걸린 모습입니다. A 가 통과시킨 패킷만 B 에 닿습니다.

flowchart TD
    PK["훅에 이른 패킷"] --> A["함수 A · 우선순위 10"]
    A -->|NF_DROP| X["버림"]
    A -->|NF_QUEUE| Q["사용자 공간의 프로그램"]
    A -->|NF_ACCEPT| B["함수 B · 우선순위 20"]
    B -->|NF_ACCEPT| NEXT["다음 단계"]

연결 추적

훅 함수는 한 번에 패킷 하나만 봅니다. 그것만으로는 모자랄 때가 많습니다. 서버가 바깥 웹 서비스의 443번 포트로 접속했다고 해 봅시다. 돌아오는 응답은 바깥에서 들어오는 패킷이라 INPUT 을 지납니다. 패킷 하나만 봐서는 이것이 응답인지 낯선 접속 시도인지 가릴 수 없습니다.

연결 추적(connection tracking)이 이 문제를 풉니다. netfilter 는 오가는 연결마다 양쪽 주소와 포트, 프로토콜을 표 하나에 기록합니다. 실무에서는 이 기능을 흔히 conntrack 이라고 줄여 부릅니다.

패킷이 들어오면 conntrack 은 표를 찾아보고 패킷에 상태를 붙입니다.

상태 뜻
NEW 표에 없는 새 연결의 첫 패킷
ESTABLISHED 이미 기록된 연결에 속한 패킷
RELATED 기존 연결에 딸려 생긴 다른 패킷. 보낸 패킷이 길을 못 찾았다고 중간에서 나르던 기계가 돌려보내는 오류 알림이 그 예다
INVALID 어느 연결에도 맞지 않는 패킷

방화벽 규칙은 이 상태를 보고 판단할 수 있습니다. 「ESTABLISHED 는 통과」 한 줄이면 응답 패킷은 모두 들어옵니다. 낯선 접속에는 NEW 가 붙으므로 따로 거르면 됩니다. 이렇게 연결 상태를 보고 거르는 방화벽을 상태 기반 방화벽이라고 합니다.

UDP 에는 연결이라는 것이 없습니다. conntrack 은 같은 주소와 포트 짝으로 오간 요청과 응답을 연결 하나로 쳐서 기록합니다. 한동안 패킷이 안 오면 그 기록을 지웁니다.

주소 변환

NAT(Network Address Translation, 네트워크 주소 변환)는 패킷 헤더의 주소나 포트를 바꿔 쓰는 일입니다. 집 공유기가 하는 일이 그렇습니다. 집 안 기기는 192.168.0.10 처럼 집 안에서만 통하는 주소를 씁니다. 밖으로 나갈 때는 여러 대가 공유기의 공인 IP 주소 하나를 함께 씁니다.

받는 주소를 바꾸면 DNAT(Destination NAT), 보내는 주소를 바꾸면 SNAT(Source NAT)라고 부릅니다. 앞의 표대로 DNAT 는 주로 PREROUTING 에서, SNAT 는 POSTROUTING 에서 합니다.

보통 SNAT 는 바꿔 넣을 주소를 규칙에 직접 적습니다. 마스커레이딩(masquerading)은 SNAT 의 한 갈래로, 나가는 네트워크 카드의 주소를 그때그때 가져다 씁니다. 공유기처럼 공인 주소가 바뀔 수 있는 기계에서 씁니다.

NAT 는 연결 추적 위에서 돕니다. 주소를 어떻게 바꿀지는 연결의 첫 패킷에서 한 번만 정합니다. 그 결정은 conntrack 의 기록에 남습니다. 뒤따르는 패킷은 규칙을 다시 보지 않고 기록대로 바뀝니다. 응답 패킷은 기록을 거꾸로 적용받아 원래 주소로 돌아갑니다.

아래 그림은 집 공유기에서 이 과정을 따라간 것입니다.

sequenceDiagram
    participant D as 집 안 기기
    participant G as 공유기
    participant S as 인터넷 서버
    D->>G: 보내는 주소 192.168.0.10
    Note over G: 첫 패킷이라 규칙을 보고<br/>보내는 주소를 공인 주소로 바꾸기로 정함<br/>이 결정을 conntrack 에 기록
    G->>S: 보내는 주소 = 공유기의 공인 주소
    S-->>G: 응답 · 받는 주소 = 공유기의 공인 주소
    Note over G: 규칙은 다시 안 보고<br/>기록을 거꾸로 적용
    G-->>D: 받는 주소 192.168.0.10

컨테이너 호스트의 규칙

백엔드 개발자가 netfilter 를 만나는 곳은 대개 컨테이너입니다. Docker 로 컨테이너의 포트를 호스트에 열면 Docker 가 호스트에 DNAT 규칙을 씁니다. 호스트의 8080번 포트로 온 패킷은 받는 주소가 컨테이너의 주소와 포트로 바뀝니다. 컨테이너가 밖으로 보내는 패킷에는 마스커레이딩 규칙이 걸립니다.

쿠버네티스도 흔히 같은 방법을 씁니다. 쿠버네티스는 컨테이너 묶음인 파드를 여러 기계에 띄웁니다. 그리고 파드 여럿 앞에 대표 주소 하나를 둡니다. 기계마다 도는 kube-proxy 가 그 대표 주소로 온 패킷을 파드 가운데 하나로 보내는 DNAT 규칙을 씁니다.

네트워크 네임스페이스는 기계 한 대 안에 네트워크 환경을 여러 벌 따로 두는 커널 기능입니다. netfilter 의 규칙과 연결 추적 표는 이 네임스페이스마다 따로 있습니다. 그래서 컨테이너 안에서 본 규칙과 호스트의 규칙이 다릅니다. 포트를 연 규칙을 찾으려면 컨테이너가 아니라 호스트를 봐야 합니다.

헤더까지만 보는 검사

netfilter 의 판단은 기본으로 헤더를 재료로 합니다. 주소와 포트, 프로토콜, 연결 상태로 가릅니다. HTTP(HyperText Transfer Protocol) 요청의 경로나 쿠키 값으로 가르는 일은 맡지 않습니다. 그런 일은 웹 서버나 리버스 프록시가 맡습니다.

요청 하나는 여러 패킷에 나뉘어 옵니다. 패킷을 하나씩 세우는 훅으로는 요청 전체를 모아 읽기 어렵습니다. 그래서 내용을 보는 검사는 연결을 받아 요청을 끝까지 읽은 프로그램 쪽이 맡습니다.

패킷마다 치르는 값

netfilter 는 모든 패킷을 훅에 세웁니다. 훅에 걸린 규칙이 길수록 패킷 하나에 드는 시간이 늘어납니다. 규칙을 위에서부터 한 줄씩 맞춰 보는 방식이면, 규칙이 수천 줄일 때 패킷마다 비교도 수천 번 일어날 수 있습니다. 아래 맞물림 절의 iptables 가 이 방식입니다.

연결 추적 표에는 담을 수 있는 연결 수의 상한이 있습니다. 짧은 연결이 몰려 표가 가득 차면 새 연결의 패킷은 버려집니다. 서버 프로그램은 멀쩡한데 새 접속만 실패하는 장애가 이렇게 생깁니다. 상한은 커널 설정으로 늘릴 수 있습니다.

훅은 네트워크 스택 안에 있습니다. 패킷이 스택에 들어오기 전에는 netfilter 가 손을 못 댑니다. 초당 패킷 수가 아주 많은 공격을 막을 때는 더 앞에서 거르는 XDP(eXpress Data Path)를 쓰기도 합니다. XDP 는 네트워크 카드 드라이버가 패킷을 받자마자 작은 프로그램을 돌려 판정하는 장치입니다.

XDP 에서 돌리는 프로그램은 eBPF(extended Berkeley Packet Filter)로 짭니다. eBPF 는 커널이 미리 검사한 뒤 커널 안에서 돌려 주는 작은 프로그램입니다.

맞물림

netfilter 자체에는 사람이 규칙을 적는 명령이 없습니다. 사람은 iptables · nft 같은 별도 도구로 규칙을 넣습니다. 이 도구들은 사용자 공간에서 돌며 규칙을 만들어 커널에 넣습니다. 도구들도 같은 netfilter 프로젝트에서 나왔습니다.

이 절은 그중 셋인 iptables · nftables · conntrack 도구가 커널 쪽과 어떻게 붙는지 봅니다.

iptables

iptables 는 오래 쓰여 온 규칙 도구입니다. 사람이 명령으로 규칙을 적으면 iptables 가 그 규칙을 커널에 넘깁니다. 커널 쪽에서는 iptables 용 모듈이 훅에 등록되어 있다가 패킷마다 그 규칙을 맞춰 봅니다.

iptables 는 규칙을 체인에 담습니다. 체인은 차례로 맞춰 볼 규칙의 목록입니다. 기본 체인의 이름은 훅 이름과 같습니다. INPUT 체인의 규칙은 INPUT 훅에서 맞춰 봅니다.

체인은 다시 테이블에 담깁니다. 테이블은 하는 일로 체인을 나눈 묶음입니다. 패킷 거르기는 filter 테이블에, 주소 바꾸기는 nat 테이블에 적습니다. 정리하면 테이블 안에 체인이, 체인 안에 규칙이 들어갑니다.

같은 이름의 체인이 테이블마다 따로 있을 수 있습니다. 아래 그림의 OUTPUT 이 그렇습니다. 그림에는 자주 쓰는 체인만 넣었습니다.

flowchart TD
    subgraph F["filter 테이블 · 거르기"]
        FI["INPUT 체인"]
        FF["FORWARD 체인"]
        FO["OUTPUT 체인"]
    end
    subgraph N["nat 테이블 · 주소 바꾸기"]
        NP["PREROUTING 체인"]
        NO["OUTPUT 체인"]
        NPO["POSTROUTING 체인"]
    end
    FI --> R1["규칙 1"] --> R2["규칙 2"] --> R3["규칙 3"]

INPUT 체인 아래 매단 규칙 셋이 바로 아래 예시의 세 줄입니다. 예시는 SSH(Secure Shell) 접속과 이미 맺은 연결의 패킷만 받는 규칙입니다. SSH 는 서버에 원격으로 접속하는 프로토콜입니다. 22번 포트를 씁니다.

터미널
iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
iptables -A INPUT -p tcp --dport 22 -j ACCEPT
iptables -A INPUT -j DROP

-A INPUT 은 INPUT 체인 끝에 규칙을 붙이라는 뜻입니다. -j 뒤는 맞았을 때 할 일입니다. 패킷이 INPUT 훅에 이르면 이 세 줄을 위에서부터 맞춰 봅니다.

첫 줄은 conntrack 이 붙인 상태를 봅니다. 둘째 줄은 TCP 22번 포트인지 봅니다. 둘 다 안 맞은 패킷은 셋째 줄에서 버려집니다.

규칙 하나를 바꿔도 iptables 는 테이블 전체를 커널에서 읽어 와 고친 뒤 다시 써 넣습니다. 규칙이 수만 줄인 기계에서는 이 갱신이 느려집니다. IPv4 는 iptables, IPv6 는 ip6tables 로 도구가 갈려 같은 규칙을 두 번 적어야 합니다.

nftables

nftables 는 같은 프로젝트가 iptables 를 대신하려고 만든 후속 도구입니다. 명령은 nft 하나입니다. 커널 쪽에는 규칙을 작은 명령어 묶음으로 받아 실행하는 가상 머신이 있습니다.

netlink 는 사용자 공간의 프로그램과 커널이 메시지를 주고받는 통로입니다. nft 는 사람이 적은 규칙을 가상 머신의 명령어로 옮겨 netlink 로 커널에 넣습니다. 바뀐 규칙만 보내므로 테이블 전체를 다시 쓰지 않습니다.

nftables 에는 미리 정해 둔 체인이 없습니다. 사용자가 체인을 만들고 어느 훅에 어떤 우선순위로 걸지 직접 적습니다. 규칙 묶음 하나가 IPv4 와 IPv6 를 함께 다룰 수도 있습니다.

주소 수천 개를 막을 때도 규칙 수천 줄을 쓰지 않습니다. 집합(set) 하나에 주소를 담습니다. 규칙 한 줄이 그 집합에서 주소를 한 번에 찾습니다.

여러 배포판에서는 iptables 명령도 뒤에서 nftables 규칙을 만듭니다. 옛 명령을 쓰는 스크립트가 깨지지 않게 하려는 장치입니다. 대신 두 방식으로 만든 규칙이 한 기계에 섞이면 무엇이 먼저 도는지 헷갈리기 쉽습니다. 규칙을 살필 때 iptables 와 nft 를 둘 다 봐야 하는 것도 이 때문입니다.

아래 그림이 두 도구가 커널에 닿는 길을 모은 것입니다. 실선과 점선 어느 길로 들어온 규칙이든 결국 같은 훅에 걸립니다.

flowchart TD
    subgraph U["사용자 공간"]
        IPT["iptables 명령"]
        NFT["nft 명령"]
    end
    subgraph K["커널"]
        MOD["iptables 용 모듈"]
        VM["nftables 가상 머신"]
        HOOK["훅"]
    end
    IPT --> MOD
    IPT -.->|여러 배포판에서| VM
    NFT -->|netlink| VM
    MOD --> HOOK
    VM --> HOOK

conntrack 도구

연결 추적 표는 커널 안에 있습니다. conntrack 명령은 netlink 로 이 표를 읽고 지웁니다. 지금 어떤 연결이 기록되어 있는지, 연결마다 주소가 어떻게 바뀌었는지를 볼 수 있습니다.

NAT 규칙을 고칠 때 이 도구가 필요합니다. NAT 결정은 연결의 첫 패킷에서 정해져 기록에 남습니다. 그래서 규칙을 고쳐도 이미 기록된 연결은 옛 결정대로 계속 바뀝니다. 새 규칙을 바로 적용하려면 conntrack -D 로 해당 기록을 지워야 합니다.

UDP 처럼 같은 주소와 포트로 끊임없이 보내는 흐름에서 이 일이 자주 생깁니다. 패킷이 계속 오니 기록이 만료되지 않습니다. 그 사이 고친 규칙은 영영 안 먹는 것처럼 보입니다.

관련 항목

netfilter 에 규칙을 넣는 도구

iptables · ip6tables · nftables · conntrack · ipset · ufw · firewalld

netfilter 위에서 도는 기능

방화벽 · 상태 기반 방화벽 · 연결 추적 · NAT · 마스커레이딩 · 포트 포워딩 · 패킷 필터링

netfilter 가 들어 있는 커널과 그 구성 요소

Linux · 커널 · 커널 모듈 · 프로토콜 스택 · 라우팅 · 라우팅 테이블 · 네트워크 네임스페이스 · netlink · 사용자 공간 · 커널 공간

netfilter 규칙을 쓰는 제품

Docker · Kubernetes · kube-proxy · 컨테이너 런타임 · 파드

netfilter 대신 eBPF 로 패킷을 거르는 수단

XDP · eBPF · Cilium

netfilter 와 같은 일을 하는 다른 운영체제의 장치

pf · ipfw · Windows Filtering Platform

netfilter 가 판단에 쓰는 패킷 정보

패킷 · IP 주소 · 포트 · TCP · UDP · IPv4 · IPv6

다른 이름: 넷필터 · Netfilter