지식

[Network] 네트워크 기초 이론 5강 — 개발자 입장에서 포트 번호 이해하기

네트워크 기초 이론 시리즈 정리 — 5강. 참고: 널널한 개발자 TV, 「개발자 입장에서 Port번호 이해하기」 (재생목록)

2강에서 포트 번호가 보는 자리에 따라 프로세스·서비스·물리 단자로 달리 읽힌다고 정리했다. 이번에는 그중 소프트웨어 개발자의 자리에서 포트 번호를 본다. 결론부터 말하면, 포트 번호는 소켓과 떼어놓고 설명할 수 없다.

포트 번호는 4계층의 식별자다

포트 번호는 전송 계층(4계층)의 식별자다. TCP·UDP 헤더에 출발지 포트와 목적지 포트가 16비트씩 들어간다.

그런데 4계층은 커널의 영역이다. 1강에서 정리했듯 TCP/IP는 애플리케이션 안이 아니라 운영체제 커널에 구현되어 있다. 반면 데이터를 실제로 소비하는 프로세스는 유저 스페이스에 있다.

그래서 질문이 생긴다. 커널이 받은 데이터가 어떻게 유저 스페이스의 그 프로세스까지 도달하는가? 이 물음의 답이 소켓이다.

개발자에게 포트 번호는 프로세스 식별자다

개발자 입장에서 포트 번호는 프로세스 식별자로 읽힌다. "8080번으로 들어온 요청은 내 서버 프로세스가 받는다"는 감각이다.

그리고 프로세스는 데이터를 소켓으로 받는다. 1강의 정의를 그대로 가져오면, 소켓은 커널에 있는 TCP/IP 구현을 유저 모드 애플리케이션이 붙잡기 위한 파일 형태의 인터페이스다.

즉 포트 번호와 소켓은 이렇게 이어진다.

포트 번호는 4계층의 식별자이고, 그 포트로 온 데이터를 유저 스페이스에서 받는 창구가 소켓이다.

데이터가 프로세스에 닿기까지

패킷 하나가 도착해서 프로세스의 손에 들어가기까지의 경로다.

   NIC (하드웨어)
     ↓  프레임 수신
   커널 — 2계층 : 목적지 MAC 확인

   커널 — 3계층 : 목적지 IP 확인

   커널 — 4계층 : 목적지 포트 번호 확인
     ↓            ← 여기서 "어느 소켓인가"가 결정된다
   소켓 (수신 버퍼)
     ↓  read() / recv()
   프로세스 (유저 스페이스)

핵심은 커널이 포트 번호를 보고 소켓을 고르는 지점이다. 이 과정을 역다중화(demultiplexing)라고 부른다. 한 호스트에 도착하는 수많은 데이터를 목적지별로 갈라 각자의 소켓에 넣는 일이다.

프로세스는 이 과정에 관여하지 않는다. 자기 소켓의 수신 버퍼에 데이터가 쌓이면 그것을 읽어갈 뿐이다.

이걸 가능하게 하는 것 — 커널의 소켓 바인딩 테이블

커널은 어떻게 "이 포트는 저 소켓"이라고 알까. 커널 내부에 소켓 바인딩 테이블이 있기 때문이다.

프로세스가 bind()를 호출하면 커널은 "이 소켓은 이 주소·이 포트를 쓴다"는 사실을 테이블에 등록한다. 그 뒤 데이터가 도착할 때마다 커널은 이 테이블을 조회해 넘길 소켓을 찾는다.

int fd = socket(AF_INET, SOCK_STREAM, 0);   // 소켓을 만들고
bind(fd, ...);                               // 주소·포트를 묶는다  ← 테이블에 등록
listen(fd, backlog);                         // 연결 요청을 받기 시작

여기서 두 가지를 구분해야 한다.

  • 리스닝 소켓 — 아직 상대가 정해지지 않은, 연결을 기다리는 소켓. (프로토콜, 로컬 IP, 로컬 포트)로 식별된다.
  • 연결된 소켓 — 통신 상대가 확정된 소켓. (프로토콜, 로컬 IP, 로컬 포트, 원격 IP, 원격 포트) 다섯 값으로 식별된다. 이 다섯 값 묶음을 흔히 5-튜플이라 부른다.

이 구분이 중요한 이유가 있다. 웹 서버가 80번 포트 하나로 수천 명을 동시에 상대할 수 있는 것은, 연결마다 서로 다른 소켓이 만들어지기 때문이다. 로컬 포트는 다 같은 80번이지만 원격 IP와 원격 포트가 다르므로 커널은 각 연결을 구별해 올바른 소켓에 넣을 수 있다.

그래서 "포트 번호 = 프로세스 식별자"는 편의상의 표현이고, 커널이 실제로 찾아내는 대상은 소켓이다. 프로세스가 그 소켓을 들고 있으니 결과적으로 프로세스를 가리키는 것처럼 보이는 것이다.

직접 확인해 보기

바인딩 상태는 명령으로 볼 수 있다.

ss -tlnp        # 리스닝 중인 TCP 소켓과 그것을 물고 있는 프로세스
ss -tnp         # 연결된 소켓들 (로컬·원격 주소가 모두 보인다)
netstat -ano    # Windows

출력에서 로컬 주소:포트 — 원격 주소:포트 — 프로세스가 한 줄로 묶여 나온다.

ss 출력은 무엇을 보여주는가

여기서 헷갈리기 쉬운 지점이 있다. 포트 번호는 4계층, 즉 커널의 개념이라고 했는데, ss는 그 옆에 프로세스를 나란히 보여준다. 프로세스는 유저 스페이스에 있다. 서로 다른 세계의 정보가 왜 한 줄에 같이 나올까.

답은 ss가 두 곳을 읽어서 합쳐 주기 때문이다. 그리고 그 이음매가 바로 소켓이다.

커널이 들고 있는 것

ss가 읽는 원본은 커널의 소켓 테이블이다(/proc/net/tcp, 요즘은 netlink sock_diag). 그 내용은 대략 이렇게 생겼다.

  sl  local_address rem_address   st  ...  inode
   0: 0100007F:1F90 00000000:0000 0A  ...  9601081
      └ 127.0.0.1   └ 포트         └ LISTEN
        (리틀 엔디언 16진수)  0x1F90 = 8080

주소, 포트, 상태, 그리고 inode 번호가 있다. 여기에 프로세스 이름은 없다. 포트는 처음부터 끝까지 커널 쪽 정보다.

프로세스가 들고 있는 것

반대로 프로세스 쪽을 열어 보면, 가지고 있는 것은 파일 디스크립터 하나뿐이다.

/proc/<pid>/fd/25 -> socket:[9601081]

fd 25번이라는 정수가 가리키는 대상이 inode 9601081짜리 커널 소켓이다. 커널 테이블에 있던 그 inode와 같은 번호다.

ss -p가 하는 일

 커널 소켓 테이블                 프로세스들의 fd 테이블
 포트 8080 → inode 9601081  ←→  pid 1234, fd 25 → socket:[9601081]
                └──────── inode로 이어붙인다 ────────┘

ss -p는 커널의 소켓 테이블을 훑고, 각 소켓의 inode를 프로세스들의 fd 목록과 대조해 "이 소켓을 누가 들고 있는지"를 찾아 붙인다. 포트가 유저 스페이스에서 나온 게 아니라, 출처가 다른 두 정보를 도구가 합쳐서 보여주는 것이다.

이것이 소켓의 정의 그 자체다

소켓은 커널에 있는 객체이고, 두 개의 얼굴을 가진다.

커널 쪽 얼굴 유저 스페이스 쪽 얼굴
주소·포트·상태·수신 버퍼 파일 디스크립터(정수)
커널이 도착한 데이터를 넣는 곳 프로세스가 read()로 꺼내는 손잡이

1강에서 소켓을 "유저 모드와 커널의 경계선에 걸쳐 있는 손잡이"라고 정리했는데, ss 출력에서 포트와 프로세스가 한 줄에 나오는 것 자체가 그 경계를 눈으로 보여주는 셈이다.

여기서 따라오는 사실들

  • **"포트가 열려 있다"**는 말의 실체는 커널 소켓 테이블에 그 포트로 bind + listen된 소켓이 등록되어 있다는 뜻이다. 프로세스가 죽으면 fd가 닫히고 커널이 소켓을 회수하면서 포트가 풀린다.
  • 프로세스 칸이 비어 있는 리스닝 소켓도 있다. systemd의 소켓 액티베이션이 그렇다. 커널에 리스닝 소켓만 등록해 두고, 연결이 들어오면 그때 서비스를 띄운다. 포트는 커널 것이고 프로세스는 나중에 붙는다는 것을 보여주는 사례다.
  • ss -p로 남의 프로세스까지 보려면 root 권한이 필요하다. 권한이 없으면 포트는 보이는데 프로세스 칸만 빈다. 이것도 두 정보의 출처가 다르다는 증거다.

정리

  • 포트 번호는 4계층의 식별자이고, 4계층은 커널의 영역이다.
  • 개발자 입장에서 포트 번호는 프로세스 식별자로 읽히고, 프로세스는 데이터를 소켓으로 받는다.
  • 포트로 도착한 데이터는 커널이 소켓을 골라 그 수신 버퍼에 넣고, 프로세스는 소켓에서 읽어간다.
  • 이것이 가능한 이유는 커널에 소켓 바인딩 테이블이 있기 때문이다. bind()가 그 등록 행위다.
  • 커널이 실제로 식별하는 대상은 프로세스가 아니라 소켓이다. 그래서 포트 하나로 여러 연결을 동시에 다룰 수 있다.
조회 0댓글 0

댓글

아직 댓글이 없습니다. 첫 댓글을 남겨보세요.