본문 바로가기

서브넷과 CIDR

프리픽스 길이로 네트워크의 크기를 정하는 CIDR 표기와 호스트 수 계산을 정리하고, 하나의 블록을 같은 크기의 서브넷으로 나누는 과정과 여러 블록을 하나의 경로로 묶는 집약을 ipaddress 모듈로 따라갑니다. 클라우드 VPC에서 예약 주소를 고려해 서브넷의 크기를 정하는 방법도 다룹니다.

앞 글에서 본 클래스 체계의 문제는 크기를 고를 수 없다는 것이었습니다. 호스트가 300대 필요한 조직에 C 클래스를 주면 모자라고, B 클래스를 주면 6만 개가 넘는 주소가 놀게 됩니다. 이 문제를 푼 것이 서브넷(subnet)과 CIDR(classless inter-domain routing)입니다. 필요한 만큼의 크기로 블록을 나누어 주고, 큰 블록을 다시 잘게 나누어 쓰는 것입니다.

CIDR 표기와 호스트 수

CIDR은 클래스에 얽매이지 않고 네트워크부의 비트 수를 자유롭게 정하는 방식으로, 1993년 RFC 1519로 도입되었습니다. 192.168.1.0/24는 앞의 24비트가 네트워크부라는 뜻이며, 서브넷 마스크 255.255.255.0과 같은 뜻을 더 짧게 적은 것입니다.

프리픽스서브넷 마스크장치에 줄 수 있는 주소쓰임의 예
/16255.255.0.065,534기업의 VPC 전체
/20255.255.240.04,094큰 서브넷
/24255.255.255.0254보통의 LAN
/26255.255.255.19262작은 서브넷
/28255.255.255.24014작은 DMZ
/30255.255.255.2522라우터 둘을 잇는 링크
/31255.255.255.2542(둘 다 사용)라우터 둘을 잇는 링크
/32255.255.255.2551호스트 하나를 가리키는 경로

프리픽스 길이가 nn이면 호스트부는 32−n32 - n비트이고, 그 가운데 호스트부가 모두 0인 네트워크 주소와 모두 1인 브로드캐스트 주소는 장치에 주지 않으므로 장치에 줄 수 있는 주소는 다음과 같습니다.

호스트 수=232−n−2\text{호스트 수} = 2^{32-n} - 2

예외도 있습니다. RFC 3021은 장치가 둘뿐인 지점 간 링크에서는 브로드캐스트가 필요 없으므로 /31의 두 주소를 모두 쓰도록 했고, /32는 주소 하나를 뜻하는 경로로 씁니다.

하나의 블록을 나누기

192.168.1.0/24를 같은 크기의 서브넷 네 개로 나누어 봅시다. 네 개를 구분하려면 2² = 4이므로 호스트부에서 2비트를 빌려 네트워크부를 26비트로 늘리면 됩니다.

CIDR SPLIT · TWO BORROWED BITS / FOUR EQUAL BLOCKS

호스트부 2비트를 빌리면 /24가 같은 크기의 /26 네 개로 나뉜다

192.168.1.0/24의 256개 주소를 00·01·10·11에 대응하는 네 구역으로 나눕니다. 각 /26은 전체 64개 주소이며 일반 브로드캐스트 서브넷의 호스트 할당 가능 수는 62개입니다.

/24 주소 공간을 네 개의 /26으로 나누기192.168.1.0/24의 256개 주소를 같은 크기인 64개 주소씩 네 구역으로 나눕니다. 각 구역은 기존 호스트부에서 가져온 두 비트 00, 01, 10, 11에 대응합니다.192.168.1.0/24 · 256개 주소00 · /26.0 ~ .6364개 주소01 · /26.64 ~ .12764개 주소10 · /26.128 ~ .19164개 주소11 · /26.192 ~ .25564개 주소
/24 주소 공간을 네 개의 /26으로 나누기192.168.1.0/24의 256개 주소를 같은 크기인 64개 주소씩 네 구역으로 나눕니다. 각 구역은 기존 호스트부에서 가져온 두 비트 00, 01, 10, 11에 대응합니다.192.168.1.0/24전체 256개 주소00 · /26.0 ~ .63전체 64개 주소01 · /26.64 ~ .127전체 64개 주소10 · /26.128 ~ .191전체 64개 주소11 · /26.192 ~ .255전체 64개 주소

주소 범위는 마지막 옥텟만 줄여 적었습니다. /26의 처음 주소는 네트워크, 마지막 주소는 브로드캐스트입니다. 첫 호스트를 게이트웨이로 쓰는 것은 예제의 관례입니다. /31·/32와 클라우드 예약 규칙은 별도로 확인합니다.

import ipaddress

net = ipaddress.ip_network("192.168.1.0/24")
for sub in net.subnets(new_prefix=26):              # 호스트부에서 2비트를 빌려 넷으로 나눈다
    print(f"{sub}: 호스트 {sub[1]}부터 {sub[-2]}까지, 브로드캐스트 {sub.broadcast_address}")
192.168.1.0/26: 호스트 192.168.1.1부터 192.168.1.62까지, 브로드캐스트 192.168.1.63
192.168.1.64/26: 호스트 192.168.1.65부터 192.168.1.126까지, 브로드캐스트 192.168.1.127
192.168.1.128/26: 호스트 192.168.1.129부터 192.168.1.190까지, 브로드캐스트 192.168.1.191
192.168.1.192/26: 호스트 192.168.1.193부터 192.168.1.254까지, 브로드캐스트 192.168.1.255

빌린 2비트가 00, 01, 10, 11일 때 마지막 바이트가 0, 64, 128, 192에서 시작하므로, 서브넷은 64개씩의 주소로 고르게 나뉩니다. 서브넷마다 첫 호스트 주소를 게이트웨이로 쓰는 경우가 많지만, 이는 관례일 뿐 CIDR이 정한 규칙은 아닙니다. subnets()는 서브넷을 하나씩 만들어 내므로 큰 블록을 잘게 나누어도 괜찮지만, list(net.hosts())처럼 호스트를 모두 목록으로 만들면 큰 블록이나 IPv6에서는 메모리와 시간이 지나치게 듭니다.

같은 서브넷인지, 하나로 묶이는지

두 주소가 같은 서브넷에 속하는지는 프리픽스 길이에 따라 달라집니다. 반대로 여러 서브넷을 하나의 큰 블록으로 묶는 것을 경로 집약(route aggregation) 또는 슈퍼넷팅(supernetting)이라고 합니다.

import ipaddress

def same_subnet(a, b, prefix):
    return ipaddress.ip_address(b) in ipaddress.ip_network(f"{a}/{prefix}", strict=False)

print("/24에서 .100과 .200:", same_subnet("192.168.1.100", "192.168.1.200", 24))
print("/25에서 .100과 .200:", same_subnet("192.168.1.100", "192.168.1.200", 25))

four = [ipaddress.ip_network(f"192.168.{i}.0/24") for i in range(4)]
print("네 블록을 묶으면:", list(ipaddress.collapse_addresses(four)))
two = [ipaddress.ip_network("192.168.1.0/24"), ipaddress.ip_network("192.168.2.0/24")]
print("1과 2를 묶으면:", list(ipaddress.collapse_addresses(two)))
/24에서 .100과 .200: True
/25에서 .100과 .200: False
네 블록을 묶으면: [IPv4Network('192.168.0.0/22')]
1과 2를 묶으면: [IPv4Network('192.168.1.0/24'), IPv4Network('192.168.2.0/24')]

/25는 마지막 바이트를 0부터 127까지와 128부터 255까지로 나누므로 .100과 .200은 다른 블록에 놓입니다. strict=False는 .100처럼 호스트 비트가 섞인 입력을 그 네트워크 주소로 바꾸어 준다는 뜻입니다.

192.168.0.0/24부터 192.168.3.0/24까지의 네 블록은 192.168.0.0/22 하나로 묶였지만, 192.168.1.0/24와 192.168.2.0/24는 이어져 있는데도 하나로 묶이지 않았습니다. 하나의 프리픽스로 나타내려면 블록들이 이어져 있을 뿐 아니라 그 프리픽스의 크기 단위로 시작 주소가 맞춰져 있어야 하는데, .1과 .2를 담는 /23은 없기 때문입니다(/23은 .0과 .1, 또는 .2와 .3을 묶습니다). 또 경로를 묶으려면 묶이는 블록들이 같은 방향으로 나가고 같은 정책으로 다루어져야 합니다. 경로를 묶으면 라우터가 기억할 경로가 줄어 라우팅 표가 작아집니다.

클라우드 VPC의 서브넷 정하기

VPC(virtual private cloud)는 클라우드 안에 만드는 나만의 가상 사설망으로, 만들 때 10.0.0.0/16 같은 주소 블록을 정하고 그 안을 다시 서브넷으로 나누어 씁니다. 그래서 AWS의 VPC를 설계할 때도 이 계산이 그대로 쓰입니다. AWS의 문서에 따르면 IPv4 서브넷의 크기는 /16부터 /28까지 고를 수 있고, 서브넷마다 첫 주소 네 개와 마지막 주소 하나, 모두 다섯 개를 AWS가 예약합니다. 첫 주소는 네트워크 주소, 다음은 VPC 라우터, 그다음은 DNS 서버, 그다음은 앞으로를 위한 예약이고, 마지막은 브로드캐스트 자리입니다. 그래서 /24 서브넷에서 쓸 수 있는 주소는 254개가 아니라 251개입니다.

설계할 때 따질 것이유
VPC의 블록을 넉넉하게 잡는다처음 정한 기본 블록의 크기는 나중에 바꿀 수 없다. 보조 블록을 더할 수는 있다
가용 영역마다 서브넷을 나눈다한 영역이 멈추어도 다른 영역에서 서비스를 이어 간다
공개, 내부, 데이터베이스 서브넷을 나눈다이름만으로는 격리되지 않으므로 라우팅 표와 보안 그룹을 함께 정한다
컨테이너가 쓸 주소를 미리 셈한다Kubernetes의 Pod마다 VPC 주소를 주는 구성에서는 주소가 금방 바닥난다
ipcalc 192.168.1.100/26                        # 네트워크, 첫 호스트, 마지막 호스트, 브로드캐스트, 호스트 수
docker network inspect bridge                  # Docker 브리지 네트워크의 서브넷과 게이트웨이(IPAM 항목)

192.168.1.100/26은 앞에서 나눈 네 블록 가운데 192.168.1.64/26에 속하므로, ipcalc는 이 블록의 첫 호스트 .65, 마지막 호스트 .126, 브로드캐스트 .127, 호스트 수 62를 알려 줍니다. 손으로 한 계산을 이렇게 도구로 맞춰 볼 수 있습니다. docker network inspect로 Docker가 쓰는 서브넷을 확인하는 이유는, 그 블록이 회사망이나 VPC의 블록과 겹치면 그쪽 주소로 가는 경로가 충돌해 접속이 되지 않기 때문입니다.

정리

  • CIDR은 프리픽스 길이로 네트워크부의 크기를 자유롭게 정하며, 장치에 줄 수 있는 주소는 보통 2^(32-n) - 2개입니다. /31과 /32는 예외입니다.
  • 서브넷팅은 호스트부의 비트를 빌려 하나의 블록을 같은 크기의 작은 블록들로 나누는 것입니다.
  • 두 주소가 같은 서브넷인지는 프리픽스 길이에 따라 달라지므로, 주소만으로는 판단할 수 없습니다.
  • 경로 집약은 이어져 있고 시작 주소가 프리픽스의 크기 단위에 맞춰진 블록들을 하나의 경로로 묶어 라우팅 표를 줄입니다.
  • 클라우드의 서브넷은 제공자가 예약하는 주소를 빼고 크기를 정해야 하며, AWS는 서브넷마다 다섯 개를 예약합니다.

연습 문제

1. 단말 50대와 게이트웨이 하나

보통의 IPv4 LAN 하나에 단말 50대와 게이트웨이 하나를 넣으려 합니다. 모두 주소를 하나씩 쓰고 다른 예약은 없을 때, /27과 /26 가운데 어느 것이 가장 작은 충분한 크기인가요? 같은 블록을 AWS의 서브넷으로 쓴다면 어떻게 되나요?

해설 보기

보통의 LAN에서는 주소가 51개 필요합니다. /27은 32 - 2 = 30개라 모자라고, /26은 64 - 2 = 62개라 충분합니다. AWS에서는 서브넷마다 다섯 개가 예약되므로 /27은 27개, /26은 59개를 쓸 수 있고, 단말 50대에 직접 관리하는 게이트웨이 서버를 하나 더 둔다면 51개가 필요하므로 역시 /26이 맞습니다. 다만 AWS의 VPC 라우터는 이미 예약된 다섯 개 가운데 하나이므로, 게이트웨이로 VPC 라우터를 쓴다면 그것을 따로 셀 필요가 없습니다.

2. 묶을 수 있는 블록

10.1.4.0/24, 10.1.5.0/24, 10.1.6.0/24, 10.1.7.0/24를 하나의 경로로 묶을 수 있나요? 묶을 수 있다면 어떤 프리픽스가 되나요? 10.1.5.0/24부터 10.1.8.0/24까지의 네 블록은 어떤가요?

해설 보기

세 번째 바이트 4, 5, 6, 7은 이진수로 00000100부터 00000111까지로, 앞의 6비트 000001이 같고 뒤의 2비트만 다릅니다. 그래서 앞의 16 + 6 = 22비트가 같은 10.1.4.0/22 하나로 묶입니다. 5부터 8까지는 00000101부터 00001000까지로, 8에서 앞의 비트가 바뀌어 하나의 /22에 담기지 않습니다. 이 네 블록은 10.1.5.0/24, 10.1.6.0/23, 10.1.8.0/24처럼 여러 경로로 나타내야 합니다. 블록이 이어져 있는 것만으로는 부족하고, 시작 주소가 프리픽스의 크기 단위에 맞춰져 있어야 하나로 묶을 수 있습니다.

3. 컨테이너가 주소를 다 썼다

AWS의 /24 서브넷에 Kubernetes 노드 10대를 두었는데, Pod마다 VPC 주소를 주는 네트워크 플러그인을 쓰고 노드마다 Pod를 최대 30개 띄웁니다. 이 서브넷의 주소로 감당할 수 있나요? 어떻게 해야 하나요?

해설 보기

노드 10대가 주소 10개를 쓰고, Pod가 최대 10 × 30 = 300개의 주소를 쓰므로 모두 310개가 필요합니다. /24 서브넷에서 쓸 수 있는 주소는 251개뿐이라 감당할 수 없고, 주소가 바닥나면 새 Pod가 시작하지 못합니다. 서브넷을 /22처럼 크게 잡거나, Pod용 서브넷을 따로 두거나, 보조 블록을 더해 주소를 늘려야 합니다. 처음 설계할 때 컨테이너가 쓸 주소까지 미리 셈해 두는 것이 가장 좋습니다.

다음 글 NAT와 DHCP에서는 사설 주소를 쓰는 장치들이 인터넷과 통신하게 해 주는 NAT와, 장치에 주소를 자동으로 나누어 주는 DHCP의 동작을 살펴봅니다.