CIDR — /24가 무엇을 뜻하고, 무엇을 뜻하지 않는가
목차
방화벽 규칙이나 라우팅 설정에는 이런 표기가 나온다.
10.0.0.0/24
192.168.1.0/16
0.0.0.0/0
슬래시 뒤의 숫자가 무엇을 정하는지, 그리고 이 표기가 정하지 않는 것이 무엇인지 정리한다. 마지막 항목이 실무에서 가장 자주 문제가 된다.
슬래시 뒤 숫자는 “앞에서 몇 비트가 고정인가”
IP 주소는 32비트다. 10.0.0.5는 사람이 읽기 쉽게 8비트씩 끊어 적은 것이다.
10.0.0.5 → 00001010 00000000 00000000 00000101
└─10─┘ └──0──┘ └──0──┘ └──5──┘
/24는 앞에서 24비트가 고정이라는 뜻이다. 나머지 8비트는 자유다.
10.0.0.0/24 → 00001010 00000000 00000000 ........
└────── 고정 24비트 ──────┘ └자유 8비트┘
자유 비트가 8개면 경우의 수는 2의 8제곱, 즉 256개다. 그래서 /24는 주소
256개를 가리킨다.
| 표기 | 고정 비트 | 자유 비트 | 주소 개수 |
|---|---|---|---|
/32 | 32 | 0 | 1개 (주소 하나) |
/24 | 24 | 8 | 256 |
/16 | 16 | 16 | 65,536 |
/8 | 8 | 24 | 16,777,216 |
/0 | 0 | 32 | 전부 |
숫자가 클수록 범위가 좁다. /32는 주소 하나를 딱 집는 것이고, /0은 모든
주소다.
뒷자리는 지워야 한다
10.1.2.3/8이라고 쓰면 어떻게 될까. /8이니 앞 8비트만 고정이고 나머지는 자유다.
즉 1.2.3 부분은 아무 의미가 없다.
10.1.2.3/8 이 실제로 가리키는 것 = 10.0.0.0/8
같은 범위를 두 가지로 적을 수 있다는 뜻이다. 사람에게는 같아 보여도 프로그램에는 다른 값이 될 수 있다. 두 표기를 그대로 비교하면 다르다고 나오고, 목록에 넣으면 중복으로 쌓인다.
그래서 CIDR을 다루는 코드는 대개 자유 비트를 0으로 지운 형태로 정규화한다. 이 작업을 하지 않으면 “같은 범위를 두 번 등록했는데 하나만 지워지는” 종류의 버그가 생긴다.
범위가 겹치면 좁은 쪽이 이긴다
규칙 두 개가 있다고 하자.
10.0.0.0/8 → 허용
10.1.2.0/24 → 차단
10.1.2.5는 둘 다에 속한다. /8은 10.으로 시작하는 모든 주소를 포함하고,
/24는 그보다 좁게 포함한다.
이때는 더 좁은 쪽이 이긴다. 고정 비트가 많은 쪽, 즉 더 구체적인 쪽이다. 이 방식을 최장 접두사 매칭(longest prefix match)이라고 부른다.
CIDR을 정의한 RFC 4632가 한 줄로 못박는다.
Forwarding in the Internet is done on a longest-match basis.
라우터가 경로를 고를 때 쓰는 방식이고, 커널의 주소 자료구조도 같은 규칙을 쓴다. 그래서 넓은 규칙을 깔아두고 그 안의 일부를 좁은 규칙으로 예외 처리하는 것이 가능하다.
주의할 점은 순서가 아니라 좁기로 정해진다는 것이다. 방화벽 규칙 중에는 위에서 아래로 읽으며 처음 맞는 것을 쓰는 방식도 있으므로, 쓰고 있는 도구가 어느 쪽인지 확인해야 한다.
비트로 비교한다
접두사 매칭은 문자열 비교가 아니다. 비트를 앞에서부터 훑는다.
찾는 주소: 10.1.2.5 → 00001010 00000001 ...
후보 /8: 10.0.0.0/8 → 00001010 ........ ← 앞 8비트 일치
후보 /24: 10.1.2.0/24→ 00001010 00000001 00000010 .... ← 앞 24비트 일치
앞에서부터 비트를 따라가며 가장 깊이 일치하는 것을 고른다. 그래서 앞쪽 비트가 먼저 와야 한다. 바이트 순서를 잘못 넣으면 엉뚱한 범위에 맞는다.
이 때문에 CIDR을 커널에 넘기는 코드는 주소를 네트워크 바이트 순서(큰 자리가 앞)로 맞춘다. 리틀엔디언 정수를 그대로 넘기면 비트가 뒤집혀 실려서, 테스트에서는 멀쩡해 보이다가 실제 매칭이 어긋난다.
CIDR은 포트를 모른다
여기가 실무에서 가장 자주 놓치는 부분이다.
CIDR은 주소 범위만 정한다. 포트도, 프로토콜도 담지 않는다. 그래서 방화벽 규칙에
10.0.0.0/24를 허용으로 넣으면 그 뜻은 이렇게 된다.
이 256개 주소의 모든 포트에, TCP든 UDP든 허용
의도가 “저 대역의 웹 서버에 접근”이었더라도 규칙은 SSH도, 데이터베이스도, 열려 있는 모든 것을 함께 연다.
| 표기 | 여는 범위 |
|---|---|
10.0.0.5:8080/tcp | 주소 하나, 포트 하나, 프로토콜 하나 |
10.0.0.5/32 | 주소 하나, 모든 포트, 모든 프로토콜 |
10.0.0.0/24 | 주소 256개, 모든 포트, 모든 프로토콜 |
/32조차 포트를 좁히지 않는다. 주소를 하나로 좁힌 것뿐이다.
그래서 정책을 쓸 때는 주소:포트 단위가 기본이고, CIDR은 예외로 두는 편이 안전 하다. 대역 전체를 열어야 하는 경우가 실제로 있지만, 그때는 그것이 의도라는 것을 분명히 하고 쓰는 것이 낫다.
한 가지 더 새기 쉬운 자리가 있다. 대역을 여는 규칙은 그 대역 안에 있는 나를 함께 연다. 오버레이 네트워크에서 자기 주소가 그 대역 안에 있는 노드가 흔한데, “다른 노드의 트래픽을 날라 주려고” 쓴 규칙이 자기 자신에게도 그대로 적용된다. 규칙을 쓴 사람은 남을 생각하며 썼는데 규칙은 자기도 포함한다.
이것을 “나만 빼고”로 좁히고 싶은 유혹이 생기는데, 그러면 규칙에 예외가 하나 생긴다. 예외가 하나 생기면 그 예외를 아는 사람과 모르는 사람이 갈리고, 사고는 대개 모르는 쪽에서 난다. 예외를 만드는 대신 여는 대역을 실제로 필요한 만큼만 좁히는 편이 낫다.
그리고 CIDR 규칙이 걸린 자리를 볼 때는 도구가 무엇을 보여주는지도 확인해야 한다. 방화벽 설정을 출력해 보면 대역 규칙 자체는 목록에 안 나오는 경우가 있다. 그 규칙은 “들어오는 것을 허용”하는 형태가 아니라 “지나가는 것을 허용”하는 형태로 다른 체인에 들어가기 때문이다. 목록이 비어 보인다고 아무것도 안 열린 것이 아니다.
자주 보는 대역
사설 주소는 셋이다 — 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16. 가정용 공유기가
쓰는 것이 마지막이다. 127.0.0.0/8은 루프백이고, 100.64.0.0/10은 통신사 NAT용으로
따로 예약돼 있어 사설 주소와 섞이지 않는다.
0.0.0.0/0을 방화벽 허용 규칙에 넣으면 아무것도 막지 않는 것과 같다.
그래서 무엇을 확인하나
규칙을 하나 쓸 때마다 세 가지를 스스로 물어보면 위의 함정을 대부분 피한다.
이 표기가 실제로 몇 개를 여는가. /24는 256개다. 자유 비트 수를 세면 나온다.
/32라서 안심하고 있다면, 주소 하나를 좁힌 것이지 포트를 좁힌 것이 아니라는 점을
같이 떠올려야 한다.
포트를 정하고 싶었는가. 그렇다면 CIDR은 맞는 도구가 아니다. 주소:포트를 쓰는 문법이 그 도구에 있는지 먼저 찾아본다. 없으면 대역을 여는 것 말고 방법이 없다는 뜻이고, 그건 알고 감수하는 것과 모르고 당하는 것이 다르다.
이 대역 안에 내가 있는가. 있으면 그 규칙은 나에게도 적용된다.
확인하지 못한 것도 하나 적어 둔다. 좁은 쪽이 이기는 것은 라우팅과 커널 자료구조의 규칙이지, 모든 방화벽 도구가 그렇게 동작한다는 보장은 아니다. 위에서 아래로 읽으며 처음 맞는 규칙을 쓰는 도구도 있다. 쓰고 있는 도구의 문서에서 이 부분을 직접 확인하는 편이 안전하다.
References
- Classless Inter-domain Routing (CIDR): The Internet Address Assignment and Aggregation Plan — https://www.rfc-editor.org/rfc/rfc4632.html
- Address Allocation for Private Internets — https://www.rfc-editor.org/rfc/rfc1918.html
- IANA-Reserved IPv4 Prefix for Shared Address Space — https://www.rfc-editor.org/rfc/rfc6598.html