일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- pyqt layout
- git 입문
- 프로그래밍 문제
- SWEA
- 3D Surface Area
- 도커
- tcp stack
- hackerrank
- 회문
- Queen's Attack
- 백준 알고리즘
- 백준
- 해커랭크
- 17609
- 커널 패킷 처리
- git 명령어
- pyqt tooltip
- pyqt button
- Queen's Attack II
- 두 문자열
- git
- 네트워크 스택
- 혁진이의 프로그램 검증
- 리눅스 커널
- Two Characters
- Python
- pyqt status bar
- pyqt menu bar
- PyQt
- 하이퍼바이저
- Today
- Total
뜸부기와 공작새
[Linux] 패킷 Flow 분석 - 수신 본문
수신부 인터럽트와 패킷처리
최초에 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 계산, 헤더 크기
- 패킷이 유효한 TCP 헤더를 가지고 있는지 검사 (pskb_may_pull() 함수)
- 수신된 패킷에 할당된 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() 함수 호출) 버퍼의 데이터를 가져가 처리한다
- 이 새로만든 소켓 버퍼는 Application에서 패킷 Read 시스템 콜이 발생할때만
- 새로운 데이터 패킷을 전송할 수 있으면 전송 (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 |
---|