Devin.KR

NAT 변환표 읽기

75분 안팎

학습 목표

제공된 변환표로 요청·응답의 출발지·목적지 주소와 포트를 출력합니다.

개념

왜 변환표를 읽는가

학교 학생 두 명이 가상 외부 웹 서버에 접속했는데 서버 로그에는 같은 주소가 보입니다. 이때 같은 사람이 두 번 요청했다고 결론 내리면 추적이 틀립니다. 경계 라우터가 출발지 주소를 합쳐 보이게 만들 수 있기 때문입니다. 이번 목표는 주어진 변환표 한 행을 이용해 네 관측 지점의 주소와 포트를 정확히 적는 것입니다. 표 계산의 성공과 실제 패킷 전달 성공은 따로 보고합니다.

이 모듈에서 사용하는 자료

브라우저 실습은 표준 입력 JSON을 읽는 Python 계산입니다. 로컬 실습은 m05 미션 solution을 그대로 포함한 압축 파일에서 시작합니다. 맥에서는 python3 test_edge.py와 python3 test_nat_rule.py를 실행하며 Linux VM에서는 sudo bash check.sh를 사용합니다. 실제 네임스페이스 검사는 external 대기입니다. README의 도구 목록과 소유 망 정리 절차를 읽고 호스트 인터페이스를 실습 브리지에 연결하지 않습니다.

주소와 포트의 역할

TCP 흐름은 프로토콜과 출발지 IP·포트, 목적지 IP·포트를 함께 보아 구분합니다. 학생 주소 10.20.10.111과 교직원 주소 10.20.20.11이 같은 포트 번호를 쓰더라도 내부 끝점은 다릅니다. 외부에서 두 출발지 주소가 198.51.100.1로 합쳐지면 포트도 구분되도록 대응 관계를 유지해야 합니다. 포트는 사람의 식별자가 아니라 해당 시점의 연결을 구분하는 정보입니다.

SNAT와 포트 변환

SNAT는 출발지 주소 변환을 말합니다. 여러 내부 연결을 한 외부 주소로 공유할 때 전송 포트까지 활용하는 변환을 NAPT 또는 PAT라고 부릅니다. 내부 포트가 언제나 바뀌는 것은 아닙니다. 충돌 없이 보존되기도 하고 새 번호를 받기도 합니다. 이 계산 문제는 운영체제의 포트 선택을 구현하지 않고 이미 결정되어 제공된 external_port를 그대로 사용합니다.

네 줄의 의미

inside-request는 변환 전 내부 요청, outside-request는 변환 후 외부 요청입니다. outside-reply는 외부 서버가 변환된 끝점으로 보내는 응답이고 inside-reply는 경계가 원래 학생 끝점으로 복원한 응답입니다. 요청에서 바뀐 출발지가 응답에서는 복원할 목적지가 됩니다. 서버의 주소와 서비스 포트는 이 SNAT 예제에서 그대로 유지됩니다. 방향만 뒤집는 계산으로는 inside-reply를 정확히 만들 수 없습니다.

계산 예제를 끝점으로 읽기

내부 10.20.10.111:41001이 서버 198.51.100.2:8080에 요청하고 외부 대응이 198.51.100.1:51001이라면 서버는 .1:51001을 상대 끝점으로 봅니다. 응답의 출발지는 .2:8080이고 외부 목적지는 .1:51001입니다. 경계 안으로 돌아오는 목적지는 .111:41001입니다. 네 줄을 종이에 먼저 써 보고 브라우저 결과와 비교하면 요청 방향과 응답 방향을 혼동한 위치를 찾을 수 있습니다.

입력 계약을 확인하기

입력 객체에는 internal_ip, internal_port, external_ip, external_port, server_ip, server_port가 있습니다. IP는 유효한 IPv4 문자열이고 포트는 1부터 65535까지의 정수입니다. 잘못된 JSON, 빠진 키, IPv6 주소, 범위를 벗어난 포트, 숫자처럼 보이는 문자열은 INVALID 한 줄로 처리합니다. 입력의 공백과 줄바꿈은 JSON 파서가 처리합니다. 빈 입력도 오류 입력에 포함합니다.

출력 계약을 고정하기

각 줄은 관측 이름, 출발지 IP:포트, 목적지 IP:포트를 공백 하나로 나눕니다. 화살표나 설명 문장을 출력에 추가하지 않습니다. 순서는 내부 요청, 외부 요청, 외부 응답, 내부 응답입니다. 끝에 줄바꿈을 붙입니다. 사설 주소가 보이는 내부 줄을 외부 주소로 덮어쓰면 원래 단말을 찾을 근거가 사라집니다. 계산 변수는 내부 끝점과 외부 끝점을 따로 보관합니다.

상태는 영구 주소표가 아닙니다

실제 경계의 연결 추적 상태에는 수명이 있습니다. 연결 종료와 유휴 시간, 장비 재기동에 따라 대응이 사라질 수 있으므로 오래된 표를 현재 응답에 그대로 적용하지 않습니다. 이번 입력은 한 흐름에 대한 유효한 대응이 존재한다고 가정합니다. 학습자는 그 가정을 제출물에 적습니다. 상태 생성과 만료 알고리즘까지 구현했다고 보고하지 않습니다.

서버 로그와 연결할 때

외부 서버 로그의 .1만으로 원래 학생을 결정하지 않습니다. 프로토콜, 변환 후 포트, 서버 끝점, 관측 시간을 포함해 경계 기록과 대조합니다. 외부 포트가 나중에 재사용될 수 있으므로 시간 없는 매핑 목록은 사건을 잘못 묶을 수 있습니다. 브라우저 계산에는 시간 필드가 없지만 실제 미션 nat.csv와 pcap을 함께 보관해 어느 실행의 변환인지 구분합니다.

계산이 알려주지 않는 것

네 줄이 맞아도 경로, ARP, 방화벽, 웹 프로세스가 정상이라는 증거는 없습니다. 표는 주소 변환의 논리적 대응만 보여 줍니다. 서버에 요청이 실제 도착했는지, 응답이 학생에게 실제 돌아왔는지는 다음 레슨의 격리망 검사로 확인합니다. INVALID 출력 역시 네트워크 장애가 아니라 제공한 자료가 입력 계약을 만족하지 못했다는 뜻입니다.

흔한 구현 오류

KeyError는 필요한 키를 읽기 전에 검사하지 않았다는 단서입니다. ValueError는 주소나 JSON 문자열이 해석되지 않았다는 단서일 수 있습니다. 오류를 숨기기 위해 모든 주소를 .1로 고정하지 않습니다. 먼저 원본 입력을 보존하고 어느 필드가 계약과 다른지 확인합니다. bool 값은 Python에서 정수처럼 취급될 수 있으므로 type(value) is int로 포트의 자료형을 확인합니다.

경계 사례로 자신을 검사하기

내부와 외부 포트가 같은 시험은 포트를 무조건 증가시키는 구현을 잡습니다. 두 포트가 다른 시험은 외부 포트를 무시하는 구현을 잡습니다. 포트 1과 65535는 유효한 양 끝이고 0은 무효입니다. 정답 한 사례를 복사한 프로그램은 주소가 다른 시험을 통과하지 못합니다. 각 시험에서 어느 입력 조건과 출력 줄을 확인하는지 말로 설명한 뒤 제출합니다.

이후 미션으로 가져갈 습관

외부 주소를 공인 인터넷 사용 가능 주소라고 부르지 않습니다. 이 트랙의 198.51.100.0/24는 문서용 가상 망입니다. 변환표의 줄마다 관측 위치와 방향을 먼저 적는 습관을 가져갑니다. 미션에서 실측 포트가 예제 숫자와 다르면 실제 관측값을 남기고 연결 대응을 다시 읽습니다. 정해진 숫자를 맞추려고 캡처 파일을 편집하거나 예측을 실측 열에 쓰지 않습니다.

개념 참고: RFC 3022의 주소·포트 변환 구분을 참고했습니다.

따라하기

요청과 응답 네 줄 계산

다음은 단일 대응표의 계산이며 실제 패킷이 아닙니다.

inside='10.20.10.111:41001'
outside='198.51.100.1:51001'
server='198.51.100.2:8080'
print('inside-request',inside,server)
print('outside-request',outside,server)
print('outside-reply',server,outside)
print('inside-reply',server,inside)

실행 결과

inside-request 10.20.10.111:41001 198.51.100.2:8080
outside-request 198.51.100.1:51001 198.51.100.2:8080
outside-reply 198.51.100.2:8080 198.51.100.1:51001
inside-reply 198.51.100.2:8080 10.20.10.111:41001

입력 계약 구현

json.loads와 ipaddress로 값을 검증한 뒤 내부·외부·서버 끝점을 만듭니다. 유효 입력은 네 줄, 잘못된 입력은 INVALID 한 줄입니다.

보존과 변경 모두 시험

브라우저 실습에서 포트가 같은 경우와 다른 경우, 최소·최대 포트 및 오류 입력을 비교합니다. 정답 코드는 접힌 모범 답안으로 확인합니다.

확인 문제

실습

표준 입력 JSON의 internal_ip/internal_port, external_ip/external_port, server_ip/server_port로 네 관측 줄을 출력합니다. 형식은 본문 계약과 첫 따라하기를 따릅니다. 주소는 IPv4, 포트는 1~65535 정수입니다. 잘못된 입력은 INVALID입니다. 실제 NAT 포트 배정은 구현하지 않습니다.

모범 답안
import sys,json,ipaddress
try:
 c=json.loads(sys.stdin.read())
 for k in ['internal_ip','external_ip','server_ip']:
  if type(c[k]) is not str or ipaddress.ip_address(c[k]).version != 4: raise ValueError()
 for k in ['internal_port','external_port','server_port']:
  if type(c[k]) is not int or not 1 <= c[k] <= 65535: raise ValueError()
 a=f"{c['internal_ip']}:{c['internal_port']}"
 b=f"{c['external_ip']}:{c['external_port']}"
 s=f"{c['server_ip']}:{c['server_port']}"
 print('inside-request',a,s)
 print('outside-request',b,s)
 print('outside-reply',s,b)
 print('inside-reply',s,a)
except (ValueError,KeyError,TypeError):
 print('INVALID')

더 읽기

면접 질문

  • NAT 전후 패킷 주소의 차이를 설명합니다.