뜸부기와 공작새

[Linux] 패킷 Flow 분석 - 수신 본문

Linux

[Linux] 패킷 Flow 분석 - 수신

성씨 2020. 3. 8. 16:54

수신부 인터럽트와 패킷처리

최초에 NIC에서 패킷을 수신하면

커널의 메모리 영역에 존재하는 rx_ring에 수신한 패킷 정보를 밀어넣는다

이후에 CPU에게 인터럽트를 걸고 새로운 패킷이 왔다는 것을

정보를 밀어넣고 CPU에게 인터럽트를 요청한다

인터럽트 신호를 받은 CPU는 
커널 인터럽트 핸들러를 수행한다 (do_IRQ() 호출)

irq핸들러는 인터럽트 번호를 보고
드라이버 인터럽트 핸들러를 호출한다


드라이버 인터럽트 핸들러 함수는 (napi_schedule())
소프트웨어 인터럽트(softirq)를 요청하는 일을 수행하는데
softirq핸들러 함수가 바로 net_rx_action() 이다

 

 

[L2 Layer]

net_rx_action() 함수는

드라이버의 rx_ring에 존재하는 패킷 정보를 상위 레이어로 전달한다

이때 rx_ring에 존재했던 패킷 정보는 sk_buff로 이동한다

이후 드라이버의 poll()함수를 호출하고 poll()함수는 netif_receive_skb()함수를 호출한다

이어서 netif_receive_skb() 함수는 올려보내는 패킷의 종류를 확인하고 (IPv4인지, IPv6 인지, arp인지)

패킷의 종류를 확인했으면 IP(Network) Layer에 패킷 정보를 올릴 때 이에 매칭되는 함수를 호출한다

패킷 종류가 IPv4인 경우 - ip_rcv()

패킷 종류가 IPv6인 경우 - ipv6_rcv()

패킷 종류가 arp인 경우 - arp_rcv()

 

[L3 Layer]

현재 전달받은 패킷의 종류가 IPv4라고 가정했을 때 Network Layer에서는 ip_rcv()가 호출된다.

ip_rcv() 함수는 패킷 오류체크 작업을 수행한 후 ip_rcv_finish() 함수를 호출한다

 

ip_rcv_finish() 함수는

전달받은 패킷의 라우팅 정보를 검색해서 내가 처리해야 할 패킷이면

ip_local_deliver() 함수를 호출하고

해당 함수에서 fragment 패킷인지를 검사한다
(fragment 패킷이면 조립하고, 아닌 경우는 조립하지 않는다)

위의 작업이 완료되면 ip_local_deliver_finish() 함수를 호출한다

 

ip_local_deliver_finish() 함수는

패킷의 IP헤더를 제거하고, 패킷으로 확인한 프로토콜에 해당하는 net_protocol 구조체에 매칭된
handler() 함수를 호출한다 (IPv4 TCP 프로토콜인 경우 - tcp_v4_rcv() 함수 호출)

 

*참고 net_protocol 구조체에 매칭된 handler() 함수 (리눅스 커널 소스코드 github에서 가져옴)

/* thinking of making this const? Don't.
 * early_demux can change based on sysctl.
 */
static struct net_protocol tcp_protocol = {
	.early_demux	=	tcp_v4_early_demux,
	.early_demux_handler =  tcp_v4_early_demux,
	.handler	=	tcp_v4_rcv,		// 요오기 함수를 호출
	.err_handler	=	tcp_v4_err,
	.no_policy	=	1,
	.netns_ok	=	1,
	.icmp_strict_tag_validation = 1,
};

 

 

[L4 Layer]

tcp_v4_rcv() 함수는

  • 전달받은 패킷이 올바른지 검사
    • 패킷이 유효한 TCP 헤더를 가지고 있는지 검사 (pskb_may_pull() 함수)
      ex) checksum 계산, 헤더 크기
  • 수신된 패킷에 할당된 socket을 찾는다
    • 할당된 socket이 없는 경우 패킷 Drop
  •  __inet_lookup_skb() 함수를 호출해서 TCP 연결 해시 테이블에서 패킷이 속하는 소켓을 찾는다
    소켓을 찾게 되면 TCP 패킷에 해당하는 소켓을 시작으로 tcp_sock, socket구조체를
    줄줄이 얻어낼 수 있다
    (소켓을 못찾게 되면 Drop)
  • TCP_TIME_WAIT을 확인한다
    • 만약 늦게 도착한 패킷인 것 확인되면 Drop
  • 실제 프로토콜 처리를 수행하는 함수를 호출한다 (tcp_v4_do_rcv())


tcp_v4_do_rcv() 함수는 실제 프로토콜 작업을 수행한다.

패킷의 정보가 ESTABLISHED 상태인 경우
(Handshaking이 완료되어 상호간 연결된 상태)
tcp_rcv_established()함수를 호출해서 수신 처리를 진행한다

 

tcp_rcv_established() 함수는

  • ACK 전송이 필요한 패킷이라면 ACK를 전송해준다 (tcp_ack_snd_check())
  • 버퍼 공간을 새로 할당하고 데이터 패킷을 새로운 소켓 버퍼에 추가 (tcp_data_queue())
    • 이 새로만든 소켓 버퍼는 Application에서 패킷 Read 시스템 콜이 발생할때만
      (TCP의 경우 tcp_recvmsg() 함수 호출) 버퍼의 데이터를 가져가 처리한다
  • 새로운 데이터 패킷을 전송할 수 있으면 전송 (tcp_data_snd_check())

 

[L7 Layer]

L7 App에서 read(), recvfrom()과 같은 시스템 콜 함수를 호출하면

sys_recv() -> sys_recvfrom() 순서로 시스템 콜 함수가 호출되고

sys_recvfrom() 함수에서 inet_stream_ops? proto_ops? 구조체에 프로토콜별로 바라보고 있는

콜백함수를 호출한다 (TCP 프로토콜의 경우에는 tcp_sendmsg() 함수 호출 / UDP의 경우 udp_sendmsg() 함수 호출)

 

tcp_sendmsg() 함수는

tcp_rcv_established() 함수에서 복사한 소켓버퍼에 있는 패킷 정보들을

Application의 유저 버퍼로 지정된 크기만큼 복사를 수행한다

'Linux' 카테고리의 다른 글

[Linux] 패킷 Flow 분석 - 송신  (0) 2020.03.08