Linux Kernel Layer 4 Network
해당 포스트는 리눅스 커널의 Layer 4 계층에 대하여 설명합니다.
Socket
네트워크 프로그래밍 시 업계 표준 인터페이스는 BSD에서 시작된 소켓이다.
해당 포스트는 유저 스페이스의 소켓 프로그래밍에 이미 숙달되어 있다고 가정 하에 작성되어 있다.
현재 리눅스에서 지원하고 있는 소켓 타입은 다음과 같다.
- SOCK_STREAM : TCP와 같이 신뢰할 수 있는 바이트 스트림 통신 채널을 제공한다.
- SOCK_DGRAM : UDP와 같이 메시지 교환을 위한 통신 채널을 제공한다.
- SOCK_RAW : IP 계층에 직접 접근하여, 전송 계층 형식 없이 트래픽을 송수신할 수 있는 채널을 제공한다.
- SOCK_RDM : 클러스터 애플리케이션에서 TIPC 통신을 할 때 사용된다.
- SOCK_SEQPACEKT : SOCK_STREAM과 유사한 연결 지향 소켓으로, 저장 영역을 따로 관리할 수 있다.
- SOCK_DCCP : DCCP 프로토콜에 사용되는 소켓 타입이다.
- SOCK_PACKET : Layer 2 디바이스 드라이버에 특정 AF_INET에 속하지 않고 RAW Data를 송수신할 수 있는 채널을 제공한다.
socket API에 해당하는 syscall은 include/linux/syscalls.h 에서 확인할 수 있으며, 해당 함수들의 정의는 net/socket.c에서 확인할 수 있다.
소켓 생성
커널에서 소켓을 표현하는 두 개의 구조체로 struct socket
과 struct sock
가 있다.
전자는 사용자 공간에 인터페이스를 제공하며, 후자는 L3 레이어에 인터페이스를 제공한다.
socket 구조체의 정의는 다음과 같다.
/**
* struct socket - general BSD socket
* @state: socket state (%SS_CONNECTED, etc)
* @type: socket type (%SOCK_STREAM, etc)
* @flags: socket flags (%SOCK_NOSPACE, etc)
* @ops: protocol specific socket operations
* @file: File back pointer for gc
* @sk: internal networking protocol agnostic socket representation
* @wq: wait queue for several uses
*/
struct socket {
socket_state state;
short type;
unsigned long flags;
struct file *file;
struct sock *sk;
const struct proto_ops *ops;
struct socket_wq wq;
};
- state : 소켓의 state를 나타낸다. 설정될 수 있는 상태는 include/uapi/linux/net.h에서 확인할 수 있다.
- type : 위에 작성된 SOCK_STREAM과 같은 소켓 타입이다.
- flags : 소켓 플래그를 나타낸다. 예를 들어, 소켓이 socket() 시스템콜이 아닌 방법으로 할당되면 TUN 장치에서는 SOCK_EXTERNALLY_ALLOCATED 플래그가 설정된다.
- sk : 소켓과 연결된 sock 객체를 나타낸다. socket 객체가 생성되면 연결된 sk 객체가 생성된다.
- ops : connect(), listen(), sendmsg() 등과 같은 오퍼레이션 콜백 객체이다. 해당 객체는 사용자 공간의 인터페이스가 된다. 각 프로토콜은 요구사항에 따라 자신ㅇ만의 proto_ops 객체를 정의한다.
- wq : wait queue 객체이다.
sock 구조체의 정의는 다음과 같다.
/**
* struct sock - network layer representation of sockets
* @__sk_common: shared layout with inet_timewait_sock
* @sk_shutdown: mask of %SEND_SHUTDOWN and/or %RCV_SHUTDOWN
* @sk_userlocks: %SO_SNDBUF and %SO_RCVBUF settings
* @sk_lock: synchronizer
* @sk_kern_sock: True if sock is using kernel lock classes
* @sk_rcvbuf: size of receive buffer in bytes
* @sk_wq: sock wait queue and async head
* @sk_rx_dst: receive input route used by early demux
* @sk_dst_cache: destination cache
* @sk_dst_pending_confirm: need to confirm neighbour
* @sk_policy: flow policy
* @sk_rx_skb_cache: cache copy of recently accessed RX skb
* @sk_receive_queue: incoming packets
* @sk_wmem_alloc: transmit queue bytes committed
* @sk_tsq_flags: TCP Small Queues flags
* @sk_write_queue: Packet sending queue
* @sk_omem_alloc: "o" is "option" or "other"
* @sk_wmem_queued: persistent queue size
* @sk_forward_alloc: space allocated forward
* @sk_napi_id: id of the last napi context to receive data for sk
* @sk_ll_usec: usecs to busypoll when there is no data
* @sk_allocation: allocation mode
* @sk_pacing_rate: Pacing rate (if supported by transport/packet scheduler)
* @sk_pacing_status: Pacing status (requested, handled by sch_fq)
* @sk_max_pacing_rate: Maximum pacing rate (%SO_MAX_PACING_RATE)
* @sk_sndbuf: size of send buffer in bytes
* @__sk_flags_offset: empty field used to determine location of bitfield
* @sk_padding: unused element for alignment
* @sk_no_check_tx: %SO_NO_CHECK setting, set checksum in TX packets
* @sk_no_check_rx: allow zero checksum in RX packets
* @sk_route_caps: route capabilities (e.g. %NETIF_F_TSO)
* @sk_route_nocaps: forbidden route capabilities (e.g NETIF_F_GSO_MASK)
* @sk_route_forced_caps: static, forced route capabilities
* (set in tcp_init_sock())
* @sk_gso_type: GSO type (e.g. %SKB_GSO_TCPV4)
* @sk_gso_max_size: Maximum GSO segment size to build
* @sk_gso_max_segs: Maximum number of GSO segments
* @sk_pacing_shift: scaling factor for TCP Small Queues
* @sk_lingertime: %SO_LINGER l_linger setting
* @sk_backlog: always used with the per-socket spinlock held
* @sk_callback_lock: used with the callbacks in the end of this struct
* @sk_error_queue: rarely used
* @sk_prot_creator: sk_prot of original sock creator (see ipv6_setsockopt,
* IPV6_ADDRFORM for instance)
* @sk_err: last error
* @sk_err_soft: errors that don't cause failure but are the cause of a
* persistent failure not just 'timed out'
* @sk_drops: raw/udp drops counter
* @sk_ack_backlog: current listen backlog
* @sk_max_ack_backlog: listen backlog set in listen()
* @sk_uid: user id of owner
* @sk_prefer_busy_poll: prefer busypolling over softirq processing
* @sk_busy_poll_budget: napi processing budget when busypolling
* @sk_priority: %SO_PRIORITY setting
* @sk_type: socket type (%SOCK_STREAM, etc)
* @sk_protocol: which protocol this socket belongs in this network family
* @sk_peer_pid: &struct pid for this socket's peer
* @sk_peer_cred: %SO_PEERCRED setting
* @sk_rcvlowat: %SO_RCVLOWAT setting
* @sk_rcvtimeo: %SO_RCVTIMEO setting
* @sk_sndtimeo: %SO_SNDTIMEO setting
* @sk_txhash: computed flow hash for use on transmit
* @sk_filter: socket filtering instructions
* @sk_timer: sock cleanup timer
* @sk_stamp: time stamp of last packet received
* @sk_stamp_seq: lock for accessing sk_stamp on 32 bit architectures only
* @sk_tsflags: SO_TIMESTAMPING socket options
* @sk_tskey: counter to disambiguate concurrent tstamp requests
* @sk_zckey: counter to order MSG_ZEROCOPY notifications
* @sk_socket: Identd and reporting IO signals
* @sk_user_data: RPC layer private data
* @sk_frag: cached page frag
* @sk_peek_off: current peek_offset value
* @sk_send_head: front of stuff to transmit
* @tcp_rtx_queue: TCP re-transmit queue [union with @sk_send_head]
* @sk_tx_skb_cache: cache copy of recently accessed TX skb
* @sk_security: used by security modules
* @sk_mark: generic packet mark
* @sk_cgrp_data: cgroup data for this cgroup
* @sk_memcg: this socket's memory cgroup association
* @sk_write_pending: a write to stream socket waits to start
* @sk_state_change: callback to indicate change in the state of the sock
* @sk_data_ready: callback to indicate there is data to be processed
* @sk_write_space: callback to indicate there is bf sending space available
* @sk_error_report: callback to indicate errors (e.g. %MSG_ERRQUEUE)
* @sk_backlog_rcv: callback to process the backlog
* @sk_validate_xmit_skb: ptr to an optional validate function
* @sk_destruct: called at sock freeing time, i.e. when all refcnt == 0
* @sk_reuseport_cb: reuseport group container
* @sk_bpf_storage: ptr to cache and control for bpf_sk_storage
* @sk_rcu: used during RCU grace period
* @sk_clockid: clockid used by time-based scheduling (SO_TXTIME)
* @sk_txtime_deadline_mode: set deadline mode for SO_TXTIME
* @sk_txtime_report_errors: set report errors mode for SO_TXTIME
* @sk_txtime_unused: unused txtime flags
*/
struct sock {
/*
* Now struct inet_timewait_sock also uses sock_common, so please just
* don't add nothing before this first member (__sk_common) --acme
*/
struct sock_common __sk_common;
#define sk_node __sk_common.skc_node
#define sk_nulls_node __sk_common.skc_nulls_node
#define sk_refcnt __sk_common.skc_refcnt
#define sk_tx_queue_mapping __sk_common.skc_tx_queue_mapping
#ifdef CONFIG_SOCK_RX_QUEUE_MAPPING
#define sk_rx_queue_mapping __sk_common.skc_rx_queue_mapping
#endif
#define sk_dontcopy_begin __sk_common.skc_dontcopy_begin
#define sk_dontcopy_end __sk_common.skc_dontcopy_end
#define sk_hash __sk_common.skc_hash
#define sk_portpair __sk_common.skc_portpair
#define sk_num __sk_common.skc_num
#define sk_dport __sk_common.skc_dport
#define sk_addrpair __sk_common.skc_addrpair
#define sk_daddr __sk_common.skc_daddr
#define sk_rcv_saddr __sk_common.skc_rcv_saddr
#define sk_family __sk_common.skc_family
#define sk_state __sk_common.skc_state
#define sk_reuse __sk_common.skc_reuse
#define sk_reuseport __sk_common.skc_reuseport
#define sk_ipv6only __sk_common.skc_ipv6only
#define sk_net_refcnt __sk_common.skc_net_refcnt
#define sk_bound_dev_if __sk_common.skc_bound_dev_if
#define sk_bind_node __sk_common.skc_bind_node
#define sk_prot __sk_common.skc_prot
#define sk_net __sk_common.skc_net
#define sk_v6_daddr __sk_common.skc_v6_daddr
#define sk_v6_rcv_saddr __sk_common.skc_v6_rcv_saddr
#define sk_cookie __sk_common.skc_cookie
#define sk_incoming_cpu __sk_common.skc_incoming_cpu
#define sk_flags __sk_common.skc_flags
#define sk_rxhash __sk_common.skc_rxhash
socket_lock_t sk_lock;
atomic_t sk_drops;
int sk_rcvlowat;
struct sk_buff_head sk_error_queue;
struct sk_buff *sk_rx_skb_cache;
struct sk_buff_head sk_receive_queue;
/*
* The backlog queue is special, it is always used with
* the per-socket spinlock held and requires low latency
* access. Therefore we special case it's implementation.
* Note : rmem_alloc is in this structure to fill a hole
* on 64bit arches, not because its logically part of
* backlog.
*/
struct {
atomic_t rmem_alloc;
int len;
struct sk_buff *head;
struct sk_buff *tail;
} sk_backlog;
#define sk_rmem_alloc sk_backlog.rmem_alloc
int sk_forward_alloc;
#ifdef CONFIG_NET_RX_BUSY_POLL
unsigned int sk_ll_usec;
/* ===== mostly read cache line ===== */
unsigned int sk_napi_id;
#endif
int sk_rcvbuf;
struct sk_filter __rcu *sk_filter;
union {
struct socket_wq __rcu *sk_wq;
/* private: */
struct socket_wq *sk_wq_raw;
/* public: */
};
#ifdef CONFIG_XFRM
struct xfrm_policy __rcu *sk_policy[2];
#endif
struct dst_entry *sk_rx_dst;
struct dst_entry __rcu *sk_dst_cache;
atomic_t sk_omem_alloc;
int sk_sndbuf;
/* ===== cache line for TX ===== */
int sk_wmem_queued;
refcount_t sk_wmem_alloc;
unsigned long sk_tsq_flags;
union {
struct sk_buff *sk_send_head;
struct rb_root tcp_rtx_queue;
};
struct sk_buff *sk_tx_skb_cache;
struct sk_buff_head sk_write_queue;
__s32 sk_peek_off;
int sk_write_pending;
__u32 sk_dst_pending_confirm;
u32 sk_pacing_status; /* see enum sk_pacing */
long sk_sndtimeo;
struct timer_list sk_timer;
__u32 sk_priority;
__u32 sk_mark;
unsigned long sk_pacing_rate; /* bytes per second */
unsigned long sk_max_pacing_rate;
struct page_frag sk_frag;
netdev_features_t sk_route_caps;
netdev_features_t sk_route_nocaps;
netdev_features_t sk_route_forced_caps;
int sk_gso_type;
unsigned int sk_gso_max_size;
gfp_t sk_allocation;
__u32 sk_txhash;
/*
* Because of non atomicity rules, all
* changes are protected by socket lock.
*/
u8 sk_padding : 1,
sk_kern_sock : 1,
sk_no_check_tx : 1,
sk_no_check_rx : 1,
sk_userlocks : 4;
u8 sk_pacing_shift;
u16 sk_type;
u16 sk_protocol;
u16 sk_gso_max_segs;
unsigned long sk_lingertime;
struct proto *sk_prot_creator;
rwlock_t sk_callback_lock;
int sk_err,
sk_err_soft;
u32 sk_ack_backlog;
u32 sk_max_ack_backlog;
kuid_t sk_uid;
#ifdef CONFIG_NET_RX_BUSY_POLL
u8 sk_prefer_busy_poll;
u16 sk_busy_poll_budget;
#endif
struct pid *sk_peer_pid;
const struct cred *sk_peer_cred;
long sk_rcvtimeo;
ktime_t sk_stamp;
#if BITS_PER_LONG==32
seqlock_t sk_stamp_seq;
#endif
u16 sk_tsflags;
u8 sk_shutdown;
u32 sk_tskey;
atomic_t sk_zckey;
u8 sk_clockid;
u8 sk_txtime_deadline_mode : 1,
sk_txtime_report_errors : 1,
sk_txtime_unused : 6;
struct socket *sk_socket;
void *sk_user_data;
#ifdef CONFIG_SECURITY
void *sk_security;
#endif
struct sock_cgroup_data sk_cgrp_data;
struct mem_cgroup *sk_memcg;
void (*sk_state_change)(struct sock *sk);
void (*sk_data_ready)(struct sock *sk);
void (*sk_write_space)(struct sock *sk);
void (*sk_error_report)(struct sock *sk);
int (*sk_backlog_rcv)(struct sock *sk,
struct sk_buff *skb);
#ifdef CONFIG_SOCK_VALIDATE_XMIT
struct sk_buff* (*sk_validate_xmit_skb)(struct sock *sk,
struct net_device *dev,
struct sk_buff *skb);
#endif
void (*sk_destruct)(struct sock *sk);
struct sock_reuseport __rcu *sk_reuseport_cb;
#ifdef CONFIG_BPF_SYSCALL
struct bpf_local_storage __rcu *sk_bpf_storage;
#endif
struct rcu_head sk_rcu;
};
주요 필드는 다음과 같다.
- sk_receive_queue : 수신 패킷에 대한 큐
- sk_rcvbuf : 바이트 단위의 수신 버퍼 크기
- sk_flags : SOCK_DEAD와 같은 소켓 플래그
- sk_sndbuf : 바이트 단위의 송신 버퍼 크기
- sk_write_queue : 송신 패킷에 대한 큐
- sk_no_check : 체크섬 플래그 비활성화
- sk_protocol : 프로토콜 식별자 (socket() 시스템 콜의 세 번째 매개변수)
- sk_type : SOCK_STREAM과 같은 소켓 타입
- sk_data_ready : 새로운 데이터가 도착했음을 소켓에 알리는 콜백
- sk_write_space : 데이터 전송을 진행하는 데 사용 가능한 여유 메모리를 가리키는 콜백
- socket_family : 연관된 socket 객체의 AF_INET과 같은 소켓 패밀리
- socket_type : 연관된 socket 객체의 SOCK_STREAM과 같은 소켓 타입
- protocol : 연관된 socket 객체의 프로토콜 식별자 (0, IPPROTO_TCP 등)
소켓을 생성하는 socket() 시스템콜의 정의는 다음과 같다.
SYSCALL_DEFINE3(socket, int, family, int, type, int, protocol)
{
return __sys_socket(family, type, protocol);
}
int __sys_socket(int family, int type, int protocol)
{
int retval;
struct socket *sock;
int flags;
/* Check the SOCK_* constants for consistency. */
BUILD_BUG_ON(SOCK_CLOEXEC != O_CLOEXEC);
BUILD_BUG_ON((SOCK_MAX | SOCK_TYPE_MASK) != SOCK_TYPE_MASK);
BUILD_BUG_ON(SOCK_CLOEXEC & SOCK_TYPE_MASK);
BUILD_BUG_ON(SOCK_NONBLOCK & SOCK_TYPE_MASK);
flags = type & ~SOCK_TYPE_MASK;
if (flags & ~(SOCK_CLOEXEC | SOCK_NONBLOCK))
return -EINVAL;
type &= SOCK_TYPE_MASK;
if (SOCK_NONBLOCK != O_NONBLOCK && (flags & SOCK_NONBLOCK))
flags = (flags & ~SOCK_NONBLOCK) | O_NONBLOCK;
retval = sock_create(family, type, protocol, &sock);
if (retval < 0)
return retval;
return sock_map_fd(sock, flags & (O_CLOEXEC | O_NONBLOCK));
}
sock_create()
함수는 __sock_create() 함수의 래퍼로, Address Family에 특화된 소켓 생성 함수를 호출한다.
예를 들어, IPv4의 경우 pf->create()
라인에서 inet_create()
함수를 호출한다.
sock_map_fd()
함수는 소켓과 연결된 파일 디스크립터를 반환한다.
유저 스페이스에서 데이터를 송신하거나 전송 계층에서 유저 스페이스 소켓으로 데이터를 수신하는 것은 다음과 같은 sendmsg()
와 recvmsg()
함수로 이루어진다.
int tcp_sendmsg(struct sock *sk, struct msghdr *msg, size_t size);
int tcp_recvmsg(struct sock *sk, struct msghdr *msg, size_t len, int nonblock,
int flags, int *addr_len);
int udp_sendmsg(struct sock *sk, struct msghdr *msg, size_t len);
static inline void udp_cmsg_recv(struct msghdr *msg, struct sock *sk,
struct sk_buff *skb);
int inet6_sendmsg(struct socket *sock, struct msghdr *msg, size_t size);
int inet6_recvmsg(struct socket *sock, struct msghdr *msg, size_t size,
int flags);
여기서 인자로 전달받는 msghdr 객체가 송수신할 데이터 영역을 포함한다.
/*
* As we do 4.4BSD message passing we use a 4.4BSD message passing
* system, not 4.3. Thus msg_accrights(len) are now missing. They
* belong in an obscure libc emulation or the bin.
*/
struct msghdr {
void *msg_name; /* ptr to socket address structure */
int msg_namelen; /* size of socket address structure */
struct iov_iter msg_iter; /* data */
/*
* Ancillary data. msg_control_user is the user buffer used for the
* recv* side when msg_control_is_user is set, msg_control is the kernel
* buffer used for all other cases.
*/
union {
void *msg_control;
void __user *msg_control_user;
};
bool msg_control_is_user : 1;
__kernel_size_t msg_controllen; /* ancillary data buffer length */
unsigned int msg_flags; /* flags on received message */
struct kiocb *msg_iocb; /* ptr to iocb for async requests */
};
- msg_name : 목적지 소켓 주소. msg_name를 sockaddr_in 구조체 포인터로 형변환하여 목적지 소켓을 구할 수 있다.
- msg_namelen : 주소의 길이
- msg_iter : 데이터 블록 이터레이터
- msg_control : 제어 정보
- msg_controllen : 제어 정보의 길이
- msg_flags : MSG_MORE과 같은 수신 메시지 플래그
- msg_iocb : 비동기 요청을 위한 iocb 포인터
커널이 처리할 수 있는 최대 제어 버퍼 길이는 소켓 당 sysctl_optmem_max 값(/proc/sys/net/core/optmem_max)으로 제한돼 있다.
UDP
UDP는 신뢰할 수 없는 메시지 지향 전송을 혼잡 제어 없이 제공한다.
UDP 헤더의 길이는 8바이트로, 커널에서의 정의는 다음과 같다.
struct udphdr {
__be16 source;
__be16 dest;
__be16 len;
__sum16 check;
};
- source : 출발지 포트 주소
- dest : 목적지 포트 주소
- len : UDP 헤더와 페이로드의 바이트 길이
- check : 패킷의 체크섬
UDP 초기화
UDP 초기화는 부팅 시 inet_init()
함수에서 다음과 같이 수행한다.
/* thinking of making this const? Don't.
* early_demux can change based on sysctl.
*/
static struct net_protocol udp_protocol = {
.early_demux = udp_v4_early_demux,
.early_demux_handler = udp_v4_early_demux,
.handler = udp_rcv,
.err_handler = udp_err,
.no_policy = 1,
.netns_ok = 1,
};
static int __init inet_init(void)
{
struct inet_protosw *q;
struct list_head *r;
int rc;
...
rc = proto_register(&udp_prot, 1);
if (rc)
goto out_unregister_tcp_proto;
...
if (inet_add_protocol(&udp_protocol, IPPROTO_UDP) < 0)
pr_crit("%s: Cannot add UDP protocol\n", __func__);
}
struct proto udp_prot = {
.name = "UDP",
.owner = THIS_MODULE,
.close = udp_lib_close,
.pre_connect = udp_pre_connect,
.connect = ip4_datagram_connect,
.disconnect = udp_disconnect,
.ioctl = udp_ioctl,
.init = udp_init_sock,
.destroy = udp_destroy_sock,
.setsockopt = udp_setsockopt,
.getsockopt = udp_getsockopt,
.sendmsg = udp_sendmsg,
.recvmsg = udp_recvmsg,
.sendpage = udp_sendpage,
.release_cb = ip4_datagram_release_cb,
.hash = udp_lib_hash,
.unhash = udp_lib_unhash,
.rehash = udp_v4_rehash,
.get_port = udp_v4_get_port,
#ifdef CONFIG_BPF_SYSCALL
.psock_update_sk_prot = udp_bpf_update_proto,
#endif
.memory_allocated = &udp_memory_allocated,
.sysctl_mem = sysctl_udp_mem,
.sysctl_wmem_offset = offsetof(struct net, ipv4.sysctl_udp_wmem_min),
.sysctl_rmem_offset = offsetof(struct net, ipv4.sysctl_udp_rmem_min),
.obj_size = sizeof(struct udp_sock),
.h.udp_table = &udp_table,
.diag_destroy = udp_abort,
};
udp_protocol
객체는 L3 레이어에서 UDP 패킷을 식별했을 경우 처리하기 위한 오퍼레이션을 정의한다.
udp_prot
객체는 유저 스페이스에서 UDP 소켓을 열거나 소켓 API를 사용했을 때 호출할 콜백을 정의한다.
예를 들어, UDP 소켓에서 setsockopt()
시스템 콜을 호출하면 udp_setsockopt()
콜백이 호출될 것이다.
UDP 패킷 송신
유저 스페이스에서 UDP 소켓을 통해 send()
, sendmsg()
, write()
와 같은 시스템 콜로 데이터를 송신한다.
이들 함수는 모두 커널에서 udp_sendmsg()
함수로 처리된다.
유저 스페이스 애플리케이션에서 데이터 영역을 포함하는 msghdr
객체를 만들고, 커널에 이 객체를 전달한다.
udp_sendmsg() 함수의 주요 내용은 다음과 같다.
int corkreq = up->corkflag || msg->msg_flags&MSG_MORE;
: UDP_CORK 소켓 옵션을 설정한다. CORK 옵션이 enable 되면 모든 데이터가 하나의 datagram에 누적되다가, CORK 옵션이 disable 될 때 패킷이 전송된다.if (len > 0xFFFF) return -EMSGSIZE;
: 온전성 검사를 수행한다. (패킷의 최대 길이는 65535보다 클 수 없다)if (up->pending) { ... }
: 소켓에 pending frames가 존재하는 경우 do_append_data 레이블로 바로 이동한다.if (usin) { ... daddr = usin->sin_addr.s_addr; dport = usin->sin_port; ... }
: 목적지의 IP 주소와 Port 주소를 구한다.if (msg->msg_controllen) { err = udp_cmsg_send(sk, msg, &ipc.gso_size); ... }
: 유저 스페이스에서 보낸 제어 정보를 처리한다. 예를 들어, UDP_SEGMENT 보조 데이터를 통하여 GSO 크기를 설정한다. msg 객체의 msg_controllen이 0이 아닌 경우 제어 정보를 포함한다.if (ipv4_is_multicast(daddr)) { ... }
: 목적지 주소가 멀티캐스트 주소일 경우 해당 처리를 수행한다.if (!rt) { struct net *net = sock_net(sk); ... fl4 = &fl4_stack; ... }
: 라우팅 항목이 NULL인 경우 라우팅 서브시스템 탐색을 수행한다.if (!corkreq) { skb = ip_make_skb(sk, fl4, getfrag, msg, ulen, ... }
: CORK가 설정되지 않은 경우 잠금 없이 빠른 전송 경로로 패킷을 전송한다. 즉, CORK가 설정되지 않으면 소켓 lock을 보유하지 않고 바로udp_send_skb()
함수를 호출하고, CORK가 설정되면lock_sock()
함수를 호출하여 소켓 lock을 보유한 후 패킷을 전송한다.err = ip_append_data(sk, fl4, getfrag, msg, ulen, ...);
: 아직 전송되지 않은 데이터를 버퍼링한다.if (err) udp_flush_pending_frames(sk);
: 함수 호출 실패 시 보류 중인 SKB를 비운다.else if (!corkreq) err = udp_push_pending_frames(sk);
: 실제 전송을 수행한다.
UDP 패킷 수신
UDP 패킷 핸들러인 udp_rcv()
함수 정의는 다음과 같다.
int udp_rcv(struct sk_buff *skb)
{
return __udp4_lib_rcv(skb, &udp_table, IPPROTO_UDP);
}
실제 동작을 수행하는 __udp4_lib_rcv() 함수의 주요 내용은 다음과 같다.
uh = udp_hdr(skb); ulen = ntohs(uh->len); saddr = ip_hdr(skb)->saddr; daddr = ip_hdr(skb)->daddr;
: UDP 헤더, 헤더 길이, 출발지와 목적지 주소를 SKB에서 추출한다.- 패킷 사이즈, 프로토콜 타입, 체크섬 등의 온전성 검사들을 수행한다.
sk = skb_steal_sock(skb, &refcounted); if (sk) { ... }
: SKB에 소켓이 설정되어 있는 경우, 해당 소켓을 steal한다. (SKB에 설정되어 있던 소켓을 반환받고, SKB의 sk에는 NULL을 설정한다.) steal에 성공한 경우udp_unicast_rcv_skb()
함수를 호출하여 빠르게 수신한다.if (rt->rt_flags & (RTCF_BROADCAST|RTCF_MULTICAST)) { return __udp4_lib_mcast_deliver(net, skb, uh, saddr, daddr, udptable, proto);}
: 멀티캐스트나 브로드캐스트의 경우 처리한다.sk = __udp4_lib_lookup_skb(skb, uh->source, uh->dest, udptable); if (sk) return udp_unicast_rcv_skb(sk, skb, uh);
: UDP 소켓 해시 테이블에서 탐색을 수행하여, 탐색 성공한 경우 해당 소켓을 통하여 수신한다.- IPSec 관련 정책 체크를 수행한다.
if (udp_lib_checksum_complete(skb)) { ... }
: 체크섬이 틀린 경우 패킷을 폐기한다.icmp_send(skb, ICMP_DEST_UNREACH, ICMP_PORT_UNREACH, 0);
: 수신할 소켓을 찾지 못한 경우 ICMP_PORT_UNREACH 에러 메시지를 회신한다.
TCP
TCP는 신뢰성 있는 연결 지향 전송을 혼잡 제어와 함께 제공한다.
TCP 기능은 크게 연결 관리, 데이터 송수신으로 구성된다.
커널에서의 TCP 헤더 정의는 다음과 같다.
struct tcphdr {
__be16 source;
__be16 dest;
__be32 seq;
__be32 ack_seq;
#if defined(__LITTLE_ENDIAN_BITFIELD)
__u16 res1:4,
doff:4,
fin:1,
syn:1,
rst:1,
psh:1,
ack:1,
urg:1,
ece:1,
cwr:1;
#elif defined(__BIG_ENDIAN_BITFIELD)
__u16 doff:4,
res1:4,
cwr:1,
ece:1,
urg:1,
ack:1,
psh:1,
rst:1,
syn:1,
fin:1;
#else
#error "Adjust your <asm/byteorder.h> defines"
#endif
__be16 window;
__sum16 check;
__be16 urg_ptr;
};
- source : 출발지 포트 주소
- dest : 목적지 포트 주소
- seq : sequence number (일련 번호)
- ack_seq : acknowledge number (확인 응답 번호). ACK 플래그가 설정되면 이 필드의 값은 수신자가 원하는 다음 sequence number이다.
- res1 : 예약 필드
- doff : 데이터 오프셋 필드로, 4바이트 배수의 TCP 헤더 크기를 나타낸다. 5(20바이트) ~ 15(60바이트) 값을 가질 수 있다.
- fin : 종단 중 하나가 연결 종료를 시도하는 플래그
- syn : 3-way handshake 수립 시 처음 사용하는 플래그
- rst : 현재 연결에 대해 의도되지 않은 분할 패킷이 도착하면 사용되는 reset 플래그
- psh : 데이터를 가능한 빨리 사용자 공간에 전달해야 함
- ack : TCP 헤더의 ACK 번호 값이 유효함을 의미하는 플래그
- urg : 긴급 포인터가 유효함을 의미하는 플래그
- ece : ECN 에코 플래그
- cwr : congestion window reduced 플래그
- window : 16비트의 TCP 수신 윈도우 크기
- check : TCP 헤더와 페이로드의 체크섬
- urg_ptr : urg 플래그가 설정되었을 때, 마지막 긴급 데이터 바이트(16비트)를 가리키는 일련 번호의 오프셋
TCP 초기화
TCP 초기화는 부팅 시 inet_init()
함수에서 다음과 같이 수행한다.
/* 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,
};
static int __init inet_init(void)
{
...
rc = proto_register(&tcp_prot, 1);
if (rc)
goto out;
...
if (inet_add_protocol(&tcp_protocol, IPPROTO_TCP) < 0)
pr_crit("%s: Cannot add TCP protocol\n", __func__);
...
}
struct proto tcp_prot = {
.name = "TCP",
.owner = THIS_MODULE,
.close = tcp_close,
.pre_connect = tcp_v4_pre_connect,
.connect = tcp_v4_connect,
.disconnect = tcp_disconnect,
.accept = inet_csk_accept,
.ioctl = tcp_ioctl,
.init = tcp_v4_init_sock,
.destroy = tcp_v4_destroy_sock,
.shutdown = tcp_shutdown,
.setsockopt = tcp_setsockopt,
.getsockopt = tcp_getsockopt,
.bpf_bypass_getsockopt = tcp_bpf_bypass_getsockopt,
.keepalive = tcp_set_keepalive,
.recvmsg = tcp_recvmsg,
.sendmsg = tcp_sendmsg,
.sendpage = tcp_sendpage,
.backlog_rcv = tcp_v4_do_rcv,
.release_cb = tcp_release_cb,
.hash = inet_hash,
.unhash = inet_unhash,
.get_port = inet_csk_get_port,
#ifdef CONFIG_BPF_SYSCALL
.psock_update_sk_prot = tcp_bpf_update_proto,
#endif
.enter_memory_pressure = tcp_enter_memory_pressure,
.leave_memory_pressure = tcp_leave_memory_pressure,
.stream_memory_free = tcp_stream_memory_free,
.sockets_allocated = &tcp_sockets_allocated,
.orphan_count = &tcp_orphan_count,
.memory_allocated = &tcp_memory_allocated,
.memory_pressure = &tcp_memory_pressure,
.sysctl_mem = sysctl_tcp_mem,
.sysctl_wmem_offset = offsetof(struct net, ipv4.sysctl_tcp_wmem),
.sysctl_rmem_offset = offsetof(struct net, ipv4.sysctl_tcp_rmem),
.max_header = MAX_TCP_HEADER,
.obj_size = sizeof(struct tcp_sock),
.slab_flags = SLAB_TYPESAFE_BY_RCU,
.twsk_prot = &tcp_timewait_sock_ops,
.rsk_prot = &tcp_request_sock_ops,
.h.hashinfo = &tcp_hashinfo,
.no_autobind = true,
.diag_destroy = tcp_abort,
};
tcp_protocol
객체와 tcp_prot
객체의 역할은 위의 UDP 연관 객체들과 같다.
tcp_prot
객체의 init 함수 포인터는 tcp_v4_init_sock()
함수로 정의해야 한다.
이 함수는 tcp_init_sock()
함수를 호출하여 타이머를 초기화하거나 소켓의 다양한 필드에 초기 값을 설정한다.
즉, 유저 스페이스에서 SOCK_STREAM 소켓을 생성하면 해당 함수가 호출되는데, 주요 작업은 다음과 같다.
- 소켓의 상태를 TCP_CLOSE로 설정한다.
tcp_init_xmit_timers()
함수를 호출하여 TCP 타이머를 초기화한다.- 소켓 송신 버퍼(sk_sndbuf)와 수신 버퍼(sk_rcvbuf)를 초기화한다.
sk_sndbuf
는sysctl_tcp_wmem[1]
으로 설정되며, 기본 값은 16,384 바이트이다.sk_rcvbuf
는sysctl_tcp_rmem[1]
으로 설정되며, 기본 값은 131,072 바이트이다. 이러한 기본 값은 tcp_init() 함수에서 설정되며,/proc/sys/net/ipv4/tcp_wmem
과/proc/sys/net/ipv4/tcp_rmem
에 써서 재설정할 수 있다.
TCP 타이머
TCP 타이머에 관련된 소스는 net/ipv4/tcp_timer.c에서 확인할 수 있다.
TCP에서 사용되는 네 가지 타이머는 다음과 같다.
- Retransmit timer : 지정된 시간 간격에 확인 응답이 없는 패킷의 재전송을 책임진다. 패킷이 손실되거나 손상되면 발생할 수 있다. 이 타이머는 각 세그먼트가 전송된 뒤 시작한다. 타이머가 만료되기 전에 ACK가 도착하면 타이머는 취소된다.
- Delayed ACK timer : ACK 패킷의 전송을 지연한다. TCP가 데이터를 받은 후에 바로 확인 응답을 할 필요가 없으면 설정된다.
- Keep Alive timer : 연결이 끊어졌는지 검사한다. 세션이 오랜 시간 유휴 상태에서 한 쪽이 종료되는 경우가 있다. 해당 타이머는 이런 상황을 탐지하고
tcp_send_active_reset()
함수를 호출하여 연결을 재설정한다. - Zero window probe timer : 수신 버퍼가 가득 차면 수신자는 제로 윈도우를 알리고 송신자는 전송을 중지한다. 수신자가 새로운 윈도우 크기를 가진 세그먼트를 전송하는데, 해당 세그먼트가 유실되면 송신자는 영원히 기다리게 될 것이다. 따라서, 송신자가 제로 윈도우를 받으면 해당 타이머 (지속 타이머)를 사용하여 윈도우 크기에 대한 수신자를 탐지한다. 윈도우 크기가 0이 아니면 지속 타이머는 중지된다.
TCP 연결 설정
TCP는 연결 설정과 해제 등의 이벤트에 따라 state를 갖게 된다.
해당 state는 sock 객체의 sk_state 멤버로 설정된다.
(정확하게는 sock 객체의 멤버인 struct sock_common 객체가 sk_state 멤버를 가지고 있다.)
설정 가능한 모든 state는 include/net/tcp_states.h에서 확인할 수 있다.
TCP 클라이언트와 TCP 서버 사이의 TCP 연결 수립을 위한 3-way handshake 과정은 다음과 같다.
- 클라이언트에서 서버에 SYN 요청을 송신한다. 클라이언트의 state는 TCP_SYN_SENT가 된다.
- TCP_LISTEN 상태의 서버가 SYN 요청을 수신하면 TCP_SYN_RECV state가 된다. 해당 상태에서 새로운 연결을 위한 요청 소켓을 생성하고, SYN ACK을 회신한다.
- SYN ACK을 수신한 클라이언트는 TCP_ESTABLISHED state가 된다. 해당 상태에서 서버에 ACK를 회신한다.
- 서버는 ACK을 수신하고, TCP_ESTABLISHED state가 된다. 요청 소켓을 자식 소켓으로 변경하고, 해당 소켓으로 데이터를 송수신할 수 있게 된다.
TCP 패킷 수신
네트워크 계층에서 TCP 패킷을 수신하는 핸들러는 tcp_v4_rcv() 함수다.
해당 함수의 정의는 다음과 같다.
/*
* From tcp_input.c
*/
int tcp_v4_rcv(struct sk_buff *skb)
{
struct net *net = dev_net(skb->dev);
struct sk_buff *skb_to_free;
int sdif = inet_sdif(skb);
int dif = inet_iif(skb);
const struct iphdr *iph;
const struct tcphdr *th;
bool refcounted;
struct sock *sk;
int ret;
if (skb->pkt_type != PACKET_HOST)
goto discard_it;
/* Count it even if it's bad */
__TCP_INC_STATS(net, TCP_MIB_INSEGS);
/* 패킷 크기가 TCP 헤더 크기보다 작으면 discard */
if (!pskb_may_pull(skb, sizeof(struct tcphdr)))
goto discard_it;
/* 헤더 파싱 */
th = (const struct tcphdr *)skb->data;
if (unlikely(th->doff < sizeof(struct tcphdr) / 4)) // 헤더의 값 검사
goto bad_packet;
if (!pskb_may_pull(skb, th->doff * 4))
goto discard_it;
/* An explanation is required here, I think.
* Packet length and doff are validated by header prediction,
* provided case of th->doff==0 is eliminated.
* So, we defer the checks. */
if (skb_checksum_init(skb, IPPROTO_TCP, inet_compute_pseudo))
goto csum_error;
th = (const struct tcphdr *)skb->data;
iph = ip_hdr(skb);
lookup:
/* 해당 소켓에 대한 탐색을 수행한다. SKB에 소켓이 설정되어 있으면 steal하고,
* 없다면 __inet_lookup() 함수를 호출한다. 해당 함수에서는 __inet_lookup_established()
* 함수를 호출하여 수립된 소켓 해시 테이블에서 먼저 탐색한다. 탐색이 실패하면
* __inet_lookup_listener() 함수를 호출하여 리스닝 소켓 해시 테이블을 탐색한다.
* 어떠한 소켓도 찾지 못하면 패킷은 폐기된다.
*/
sk = __inet_lookup_skb(&tcp_hashinfo, skb, __tcp_hdrlen(th), th->source,
th->dest, sdif, &refcounted);
if (!sk)
goto no_tcp_socket;
process:
/* 소켓 state가 TCP_TIME_WAIT이면 do_time_wait 레이블로 이동 */
if (sk->sk_state == TCP_TIME_WAIT)
goto do_time_wait;
/* 소켓 state가 TCP_NEW_SYN_RECV인 경우 */
if (sk->sk_state == TCP_NEW_SYN_RECV) {
struct request_sock *req = inet_reqsk(sk); // sock 객체를 request_sock으로 형변환
bool req_stolen = false;
struct sock *nsk;
sk = req->rsk_listener; // rsk_listener 가져옴
if (unlikely(tcp_v4_inbound_md5_hash(sk, skb, dif, sdif))) {
/* CONFIG_TCP_MD5SIG가 설정되어 있는 경우,
* BGP 세션에서 효율을 위해 다음 세 경우 패킷을 드랍한다.
* 1. 예상된 커넥션에 대해 MD5 해쉬가 존재하지 않은 경우
* 2. 예상하지 않은 커넥션에 대해 MD5 해쉬가 존재하는 경우
* 3. 존재하는 MD5 해쉬가 틀린 경우
*/
sk_drops_add(sk, skb);
reqsk_put(req);
goto discard_it;
}
if (tcp_checksum_complete(skb)) {
reqsk_put(req);
goto csum_error;
}
if (unlikely(sk->sk_state != TCP_LISTEN)) {
inet_csk_reqsk_queue_drop_and_put(sk, req);
goto lookup;
}
/* We own a reference on the listener, increase it again
* as we might lose it too soon.
*/
sock_hold(sk);
refcounted = true;
nsk = NULL;
if (!tcp_filter(sk, skb)) {
th = (const struct tcphdr *)skb->data;
iph = ip_hdr(skb);
tcp_v4_fill_cb(skb, iph, th);
nsk = tcp_check_req(sk, skb, req, false, &req_stolen);
}
if (!nsk) {
reqsk_put(req);
if (req_stolen) {
/* Another cpu got exclusive access to req
* and created a full blown socket.
* Try to feed this packet to this socket
* instead of discarding it.
*/
tcp_v4_restore_cb(skb);
sock_put(sk);
goto lookup;
}
goto discard_and_relse;
}
if (nsk == sk) {
reqsk_put(req);
tcp_v4_restore_cb(skb);
} else if (tcp_child_process(sk, nsk, skb)) {
tcp_v4_send_reset(nsk, skb);
goto discard_and_relse;
} else {
sock_put(sk);
return 0;
}
}
if (unlikely(iph->ttl < inet_sk(sk)->min_ttl)) {
__NET_INC_STATS(net, LINUX_MIB_TCPMINTTLDROP);
goto discard_and_relse;
}
if (!xfrm4_policy_check(sk, XFRM_POLICY_IN, skb))
goto discard_and_relse;
if (tcp_v4_inbound_md5_hash(sk, skb, dif, sdif))
goto discard_and_relse;
/* 넷필터 conntrack reset (nfct을 0으로 리셋) */
nf_reset_ct(skb);
if (tcp_filter(sk, skb))
goto discard_and_relse;
/* 헤더 파싱 */
th = (const struct tcphdr *)skb->data;
iph = ip_hdr(skb);
/* control block 관련 초기화 */
tcp_v4_fill_cb(skb, iph, th);
skb->dev = NULL;
/* state가 TCP_LISTEN이라면 tcp_v4_do_rcv() 호출 */
if (sk->sk_state == TCP_LISTEN) {
ret = tcp_v4_do_rcv(sk, skb);
goto put_and_return;
}
sk_incoming_cpu_update(sk);
bh_lock_sock_nested(sk);
tcp_segs_in(tcp_sk(sk), skb);
ret = 0;
/* sock_owned_by_user() 함수는 현재 소켓을 소유한 애플리케이션이
* 있다면 true를 반환하고, 어떠한 애플리케이션에서도 소켓을 소유하지 않다면
* false를 반환 */
if (!sock_owned_by_user(sk)) { // 소켓을 소유하는 애플리케이션이 없다면
skb_to_free = sk->sk_rx_skb_cache;
sk->sk_rx_skb_cache = NULL;
ret = tcp_v4_do_rcv(sk, skb);
} else { // 소켓을 소유하는 애플리케이션이 있다면
if (tcp_add_backlog(sk, skb)) // 현재 패킷을 수락할 수 없으므로 backlog에 추가
goto discard_and_relse;
skb_to_free = NULL;
}
bh_unlock_sock(sk);
if (skb_to_free)
__kfree_skb(skb_to_free);
put_and_return:
if (refcounted)
sock_put(sk);
return ret;
no_tcp_socket:
if (!xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb))
goto discard_it;
tcp_v4_fill_cb(skb, iph, th);
if (tcp_checksum_complete(skb)) {
csum_error:
__TCP_INC_STATS(net, TCP_MIB_CSUMERRORS);
bad_packet:
__TCP_INC_STATS(net, TCP_MIB_INERRS);
} else {
tcp_v4_send_reset(NULL, skb);
}
discard_it:
/* Discard frame. */
kfree_skb(skb);
return 0;
discard_and_relse:
sk_drops_add(sk, skb);
if (refcounted)
sock_put(sk);
goto discard_it;
do_time_wait:
if (!xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb)) {
inet_twsk_put(inet_twsk(sk));
goto discard_it;
}
tcp_v4_fill_cb(skb, iph, th);
if (tcp_checksum_complete(skb)) {
inet_twsk_put(inet_twsk(sk));
goto csum_error;
}
switch (tcp_timewait_state_process(inet_twsk(sk), skb, th)) {
case TCP_TW_SYN: {
struct sock *sk2 = inet_lookup_listener(dev_net(skb->dev),
&tcp_hashinfo, skb,
__tcp_hdrlen(th),
iph->saddr, th->source,
iph->daddr, th->dest,
inet_iif(skb),
sdif);
if (sk2) {
inet_twsk_deschedule_put(inet_twsk(sk));
sk = sk2;
tcp_v4_restore_cb(skb);
refcounted = false;
goto process;
}
}
/* to ACK */
fallthrough;
case TCP_TW_ACK:
tcp_v4_timewait_ack(sk, skb);
break;
case TCP_TW_RST:
tcp_v4_send_reset(sk, skb);
inet_twsk_deschedule_put(inet_twsk(sk));
goto discard_it;
case TCP_TW_SUCCESS:;
}
goto discard_it;
}
함수 내용에 대한 설명은 주석을 참고한다.
핵심 수신 루틴 함수인 tcp_v4_do_rcv()
함수의 정의는 다음과 같다.
/* The socket must have it's spinlock held when we get
* here, unless it is a TCP_LISTEN socket.
*
* We have a potential double-lock case here, so even when
* doing backlog processing we use the BH locking scheme.
* This is because we cannot sleep with the original spinlock
* held.
*/
int tcp_v4_do_rcv(struct sock *sk, struct sk_buff *skb)
{
struct sock *rsk;
if (sk->sk_state == TCP_ESTABLISHED) { /* Fast path */
struct dst_entry *dst = sk->sk_rx_dst;
sock_rps_save_rxhash(sk, skb);
sk_mark_napi_id(sk, skb);
if (dst) {
if (inet_sk(sk)->rx_dst_ifindex != skb->skb_iif ||
!INDIRECT_CALL_1(dst->ops->check, ipv4_dst_check,
dst, 0)) {
dst_release(dst);
sk->sk_rx_dst = NULL;
}
}
tcp_rcv_established(sk, skb);
return 0;
}
if (tcp_checksum_complete(skb))
goto csum_err;
if (sk->sk_state == TCP_LISTEN) {
struct sock *nsk = tcp_v4_cookie_check(sk, skb);
if (!nsk)
goto discard;
if (nsk != sk) {
if (tcp_child_process(sk, nsk, skb)) {
rsk = nsk;
goto reset;
}
return 0;
}
} else
sock_rps_save_rxhash(sk, skb);
if (tcp_rcv_state_process(sk, skb)) {
rsk = sk;
goto reset;
}
return 0;
reset:
tcp_v4_send_reset(rsk, skb);
discard:
kfree_skb(skb);
/* Be careful here. If this function gets more complicated and
* gcc suffers from register pressure on the x86, sk (in %ebx)
* might be destroyed here. This current version compiles correctly,
* but you have been warned.
*/
return 0;
csum_err:
TCP_INC_STATS(sock_net(sk), TCP_MIB_CSUMERRORS);
TCP_INC_STATS(sock_net(sk), TCP_MIB_INERRS);
goto discard;
}
- state가 TCP_ESTABLISHED라면 tcp_rcv_established()를 호출하여 처리한다.
- state가 TCP_LISTEN이라면 cookie_check 이 후 tcp_child_process()를 호출하여 처리한다.
- 그 외에는 tcp_rcv_state_process()를 호출하여 처리한다.
TCP 패킷 송신
UDP와 마찬가지로 TCP 소켓을 이용한 패킷 송신은 유저 스페이스에서 send(), sendto(), sendmsg(), write() 등의 시스템 콜로 수행된다.
이러한 시스템 콜은 결국 tcp_sendmsg()
함수로 처리된다.
이 함수는 유저 스페이스에서 커널로 페이로드를 복사하고 그것을 TCP 세그먼트로 전송한다.
해당 함수의 정의는 다음과 같다.
int tcp_sendmsg(struct sock *sk, struct msghdr *msg, size_t size)
{
int ret;
lock_sock(sk);
ret = tcp_sendmsg_locked(sk, msg, size);
release_sock(sk);
return ret;
}
주요 내용은 tcp_sendmsg_locked()
함수로 수행한다. 해당 함수의 정의는 다음과 같다.
int tcp_sendmsg_locked(struct sock *sk, struct msghdr *msg, size_t size)
{
struct tcp_sock *tp = tcp_sk(sk);
struct ubuf_info *uarg = NULL;
struct sk_buff *skb;
struct sockcm_cookie sockc;
int flags, err, copied = 0;
int mss_now = 0, size_goal, copied_syn = 0;
int process_backlog = 0;
bool zc = false;
long timeo;
flags = msg->msg_flags;
if (flags & MSG_ZEROCOPY && size && sock_flag(sk, SOCK_ZEROCOPY)) {
skb = tcp_write_queue_tail(sk);
uarg = msg_zerocopy_realloc(sk, size, skb_zcopy(skb));
if (!uarg) {
err = -ENOBUFS;
goto out_err;
}
zc = sk->sk_route_caps & NETIF_F_SG;
if (!zc)
uarg->zerocopy = 0;
}
if (unlikely(flags & MSG_FASTOPEN || inet_sk(sk)->defer_connect) &&
!tp->repair) {
err = tcp_sendmsg_fastopen(sk, msg, &copied_syn, size, uarg);
if (err == -EINPROGRESS && copied_syn > 0)
goto out;
else if (err)
goto out_err;
}
timeo = sock_sndtimeo(sk, flags & MSG_DONTWAIT);
tcp_rate_check_app_limited(sk); /* is sending application-limited? */
/* Wait for a connection to finish. One exception is TCP Fast Open
* (passive side) where data is allowed to be sent before a connection
* is fully established.
*/
if (((1 << sk->sk_state) & ~(TCPF_ESTABLISHED | TCPF_CLOSE_WAIT)) &&
!tcp_passive_fastopen(sk)) {
err = sk_stream_wait_connect(sk, &timeo);
if (err != 0)
goto do_error;
}
if (unlikely(tp->repair)) {
if (tp->repair_queue == TCP_RECV_QUEUE) {
copied = tcp_send_rcvq(sk, msg, size);
goto out_nopush;
}
err = -EINVAL;
if (tp->repair_queue == TCP_NO_QUEUE)
goto out_err;
/* 'common' sending to sendq */
}
sockcm_init(&sockc, sk);
if (msg->msg_controllen) {
err = sock_cmsg_send(sk, msg, &sockc);
if (unlikely(err)) {
err = -EINVAL;
goto out_err;
}
}
/* This should be in poll */
sk_clear_bit(SOCKWQ_ASYNC_NOSPACE, sk);
/* Ok commence sending. */
copied = 0;
restart:
mss_now = tcp_send_mss(sk, &size_goal, flags);
err = -EPIPE;
if (sk->sk_err || (sk->sk_shutdown & SEND_SHUTDOWN))
goto do_error;
while (msg_data_left(msg)) {
int copy = 0;
skb = tcp_write_queue_tail(sk);
if (skb)
copy = size_goal - skb->len;
if (copy <= 0 || !tcp_skb_can_collapse_to(skb)) {
bool first_skb;
new_segment:
if (!sk_stream_memory_free(sk))
goto wait_for_space;
if (unlikely(process_backlog >= 16)) {
process_backlog = 0;
if (sk_flush_backlog(sk))
goto restart;
}
first_skb = tcp_rtx_and_write_queues_empty(sk);
skb = sk_stream_alloc_skb(sk, 0, sk->sk_allocation,
first_skb);
if (!skb)
goto wait_for_space;
process_backlog++;
skb->ip_summed = CHECKSUM_PARTIAL;
skb_entail(sk, skb);
copy = size_goal;
/* All packets are restored as if they have
* already been sent. skb_mstamp_ns isn't set to
* avoid wrong rtt estimation.
*/
if (tp->repair)
TCP_SKB_CB(skb)->sacked |= TCPCB_REPAIRED;
}
/* Try to append data to the end of skb. */
if (copy > msg_data_left(msg))
copy = msg_data_left(msg);
/* Where to copy to? */
if (skb_availroom(skb) > 0 && !zc) {
/* We have some space in skb head. Superb! */
copy = min_t(int, copy, skb_availroom(skb));
err = skb_add_data_nocache(sk, skb, &msg->msg_iter, copy);
if (err)
goto do_fault;
} else if (!zc) {
bool merge = true;
int i = skb_shinfo(skb)->nr_frags;
struct page_frag *pfrag = sk_page_frag(sk);
if (!sk_page_frag_refill(sk, pfrag))
goto wait_for_space;
if (!skb_can_coalesce(skb, i, pfrag->page,
pfrag->offset)) {
if (i >= sysctl_max_skb_frags) {
tcp_mark_push(tp, skb);
goto new_segment;
}
merge = false;
}
copy = min_t(int, copy, pfrag->size - pfrag->offset);
if (!sk_wmem_schedule(sk, copy))
goto wait_for_space;
err = skb_copy_to_page_nocache(sk, &msg->msg_iter, skb,
pfrag->page,
pfrag->offset,
copy);
if (err)
goto do_error;
/* Update the skb. */
if (merge) {
skb_frag_size_add(&skb_shinfo(skb)->frags[i - 1], copy);
} else {
skb_fill_page_desc(skb, i, pfrag->page,
pfrag->offset, copy);
page_ref_inc(pfrag->page);
}
pfrag->offset += copy;
} else {
if (!sk_wmem_schedule(sk, copy))
goto wait_for_space;
err = skb_zerocopy_iter_stream(sk, skb, msg, copy, uarg);
if (err == -EMSGSIZE || err == -EEXIST) {
tcp_mark_push(tp, skb);
goto new_segment;
}
if (err < 0)
goto do_error;
copy = err;
}
if (!copied)
TCP_SKB_CB(skb)->tcp_flags &= ~TCPHDR_PSH;
WRITE_ONCE(tp->write_seq, tp->write_seq + copy);
TCP_SKB_CB(skb)->end_seq += copy;
tcp_skb_pcount_set(skb, 0);
copied += copy;
if (!msg_data_left(msg)) {
if (unlikely(flags & MSG_EOR))
TCP_SKB_CB(skb)->eor = 1;
goto out;
}
if (skb->len < size_goal || (flags & MSG_OOB) || unlikely(tp->repair))
continue;
if (forced_push(tp)) {
tcp_mark_push(tp, skb);
__tcp_push_pending_frames(sk, mss_now, TCP_NAGLE_PUSH);
} else if (skb == tcp_send_head(sk))
tcp_push_one(sk, mss_now);
continue;
wait_for_space:
set_bit(SOCK_NOSPACE, &sk->sk_socket->flags);
if (copied)
tcp_push(sk, flags & ~MSG_MORE, mss_now,
TCP_NAGLE_PUSH, size_goal);
err = sk_stream_wait_memory(sk, &timeo);
if (err != 0)
goto do_error;
mss_now = tcp_send_mss(sk, &size_goal, flags);
}
out:
if (copied) {
tcp_tx_timestamp(sk, sockc.tsflags);
tcp_push(sk, flags, mss_now, tp->nonagle, size_goal);
}
out_nopush:
net_zcopy_put(uarg);
return copied + copied_syn;
do_error:
skb = tcp_write_queue_tail(sk);
do_fault:
tcp_remove_empty_skb(sk, skb);
if (copied + copied_syn)
goto out;
out_err:
net_zcopy_put_abort(uarg, true);
err = sk_stream_error(sk, flags, err);
/* make sure we wake any epoll edge trigger waiter */
if (unlikely(tcp_rtx_and_write_queues_empty(sk) && err == -EAGAIN)) {
sk->sk_write_space(sk);
tcp_chrono_stop(sk, TCP_CHRONO_SNDBUF_LIMITED);
}
return err;
}
- 유저 스페이스에서 페이로드를 복사하여 SKB를 생성한다.
tcp_push()
계열 함수(tcp_push()
,tcp_push_one()
,__tcp_push_pending_frames()
등)로 전송이 시작된다.- 위 push 함수들은 결국
tcp_write_xmit()
함수를 호출한다. - tcp_write_xmit() 함수는 tcp_transmit_skb() 함수를 호출한다.
- 내부적으로
__tcp_transmit_skb()
함수를 호출하고, 해당 함수에서ip_queue_xmit()
콜백을 호출하게 된다.