套接字分析
每个操作系统都必须提供网络子系统入口及API,Linux内核网络子系统提供的标准 POSIX 套接字API向用户提供接口。在 Linux 中传输层之上的一切都属于用户空间。
Linux 也遵循 Unix 范式(一切皆为文件),因此套接字也与文件相关联,使用统一的套接字 API 会令应用程序移植更容易,如下为套接字类型:
SOCK_STREAM
(流套接字):提供可靠的字节流通信信道,TCP 套接字就属于流套接字;SOCK_DGRAM
(数据报套接字):支持消息交换,数据报套接字提供的通信信道不可靠,因为数据包可能被丢弃、不按顺序到达或重复;SOCK_RAW
(原始套接字):直接访问 IP 层,支持使用协议无关的传输层格式收发数据流;SOCK_RDM
(可靠传输的消息):用于透明进程间通信(TIPC);SOCK_SEQPACKET
(顺序数据包流):类似于SOCK_STREAM
,也是面向连接的;SOCK_DCCP
:数据报拥塞控制协议是一种传输层协议,提供不可靠拥塞控制流。
套接字API提供一些方法:
socket()
:用于创建一个套接字sys_socket()
;
bind()
:将套接字与本地端口和IP地址关联sys_bind()
;
send()
:发送消息;
recv()
:接收消息;
listen()
:能够让套接字接收来自其他套接字的连接请求;
accept()
:接受套接字连接请求,仅适用于基于连接的套接字类型 SOCK_STREAM
/SOCK_SEQPACKET
;
connect()
:建立到对等套接字的连接。仅适用于基于连接的套接字类型(SOCK_STREAM
/SOCK_SEQPACKET
)以及无连接的套接字类型(SOCK_DGRAM
)。
在内核中,socket
它向用户空间提供一个接口,是由方法sys_socket()
创建。另一个sock
结构它向网络层(L3)提供一个接口,sock
位于网络层,是一个与协议无关的结构,所以它是一个协议不可知的结构。
socket.h
结构
sock.h
结构
struct sock {
struct sk_buff_head sk_receive_queue;
int sk_rcvbuf;
unsigned long sk_flags;
int sk_sndbuf;
struct sk_buff_head sk_write_queue;
. . .
unsigned int sk_shutdown : 2,
sk_no_check : 2,
sk_protocol : 8,
sk_type : 16;
. . .
void (*sk_data_ready)(struct sock *sk, int bytes);
void (*sk_write_space)(struct sock *sk);
};
//(include/net/sock.h)
sk_receive_queue
:输入数据包的队列。
sk_rcvbuf
:接收缓冲区的大小,以字节为单位。
sk_flags
:各种标志位,像 SOCK_DEAD
或者 SOCK _ DEAD
参见include/net/sock.h
中的sock_flags enum
定义。
sk_sndbuf
:发送缓冲区的大小,以字节为单位。
sk_write_queue
:输出数据包的队列。
sk_no_check
:禁用校验和标志。可以用 SO_NO_CHECK
套接字选项设置;
sk_protocol
:协议标识,根据socket()
系统调用的第三个参数(protocol)设置:IPPROTO_TCP
/IPPROTO_UDP
;
sk_type
:套接字的类型,如 SOCK_STREAM
或 SOCK_RAW
参见include/linux/net.h
中的enum sock_type
;
sk_data_ready
:通知套接字新数据已经到达的回调;
sk_write_space
:回调,表示有空闲内存可以进行数据传输。
系统调用socket()
返回值(sockfd)是一个文件描述符,应将其作为参数传递给这个套接字的后续调用。
从用户空间套接字发送数据或在用户空间套接字中接收来自传输层的数据,这些工作分配是通信内核中调用方法sendmsg()
/recvmsg()
来处理。它们会将一个msghdr
对象作为参数,这个msghdr
对象包含要发送或填充的数据块及其它参数。
msg_name
:目的套接字地址。为了得到目标套接字,通常将msg_name
不透明指针转换为struct sockaddr_in
指针。
msg_namelen
:地址的长度。
iovec
:数据块的向量。
msg_iovlen
:矢量iovec中的块数。
msg_control
:控制信息(又称辅助数据)。
msg_controllen
:控制信息的长度。
msg_flags
:收到消息的标志,如 MSG_MORE
。
用户数据包协议(UDP)
UDP 提供面向消息的不可靠传输,但没有拥塞控制功能。很多协议都使用 UDP,如用于 IP 网络传输音频和视频的实时传输协议(Real-time Transport Protocol,RTP),此类型容许一定的数据包丢弃。UDP 报头长 8
字节,具体内核源码如下:
1、UDP初始化操作
定义了udp_protocol
对象(net_protocol
对象)并用inet_add_protocol()
方法添加它。这将udp_protocol
对象设置为全局协议数组(inet_protos
)中的一个元素。
2、发送UDP数据包
从 UDP 用户空间套接字中发送数据,可使用系统调用:send()
、sendto()
、sendmsg()
和write()
。这些系统调用最终都会由内核中方法udp_sendmsg()
来处理。
int udp_sendmsg(struct sock *sk, struct msghdr *msg, size_t len)
{
struct inet_sock *inet = inet_sk(sk);
struct udp_sock *up = udp_sk(sk);
struct flowi4 fl4_stack;
struct flowi4 *fl4;
int ulen = len;
struct ipcm_cookie ipc;
struct rtable *rt = NULL;
int free = 0;
int connected = 0;
__be32 daddr, faddr, saddr;
__be16 dport;
u8 tos;
int err, is_udplite = IS_UDPLITE(sk);
// UDP数据包通常会被立即发送
int corkreq = up->corkflag || msg->msg_flags&MSG_MORE;
int (*getfrag)(void *, char *, int, int, int, struct sk_buff *);
struct sk_buff *skb;
struct ip_options_data opt_copy;
// 首先执行一些完整性检查
if (len > 0xFFFF)
return -EMSGSIZE;
/*
* Check the flags.
*/
if (msg->msg_flags & MSG_OOB) /* Mirror BSD error message compatibility */
return -EOPNOTSUPP;
ipc.opt = NULL;
ipc.tx_flags = 0;
ipc.ttl = 0;
ipc.tos = -1;
getfrag = is_udplite ? udplite_getfrag : ip_generic_getfrag;
fl4 = &inet->cork.fl.u.ip4;
if (up->pending) {
/*
* There are pending frames.
* The socket lock must be held while it's corked.
*/
lock_sock(sk);
if (likely(up->pending)) {
if (unlikely(up->pending != AF_INET)) {
release_sock(sk);
return -EINVAL;
}
goto do_append_data;
}
release_sock(sk);
}
ulen += sizeof(struct udphdr);
/*
* Get and verify the address.
*/
// 需要知道目标地址和目标端口,这样才能够创建使用方法 udp_send_skb 或 ip_append_data()
if (msg->msg_name) {
DECLARE_SOCKADDR(struct sockaddr_in *, usin, msg->msg_name);
if (msg->msg_namelen < sizeof(*usin))
return -EINVAL;
if (usin->sin_family != AF_INET) {
if (usin->sin_family != AF_UNSPEC)
return -EAFNOSUPPORT;
}
daddr = usin->sin_addr.s_addr;
dport = usin->sin_port;
if (dport == 0)
return -EINVAL;
} else {
if (sk->sk_state != TCP_ESTABLISHED)
return -EDESTADDRREQ;
daddr = inet->inet_daddr;
dport = inet->inet_dport;
/* Open fast path for connected socket.
Route will not be used, if at least one option is set.
*/
connected = 1;
}
ipc.sockc.tsflags = sk->sk_tsflags;
ipc.addr = inet->inet_saddr;
ipc.oif = sk->sk_bound_dev_if;
if (msg->msg_controllen) {
err = ip_cmsg_send(sk, msg, &ipc, sk->sk_family == AF_INET6);
if (unlikely(err)) {
kfree(ipc.opt);
return err;
}
if (ipc.opt)
free = 1;
connected = 0;
}
if (!ipc.opt) {
struct ip_options_rcu *inet_opt;
rcu_read_lock();
inet_opt = rcu_dereference(inet->inet_opt);
if (inet_opt) {
memcpy(&opt_copy, inet_opt,
sizeof(*inet_opt) + inet_opt->opt.optlen);
ipc.opt = &opt_copy.opt;
}
rcu_read_unlock();
}
saddr = ipc.addr;
ipc.addr = faddr = daddr;
sock_tx_timestamp(sk, ipc.sockc.tsflags, &ipc.tx_flags);
if (ipc.opt && ipc.opt->opt.srr) {
if (!daddr)
return -EINVAL;
faddr = ipc.opt->opt.faddr;
connected = 0;
}
tos = get_rttos(&ipc, inet);
if (sock_flag(sk, SOCK_LOCALROUTE) ||
(msg->msg_flags & MSG_DONTROUTE) ||
(ipc.opt && ipc.opt->opt.is_strictroute)) {
tos |= RTO_ONLINK;
connected = 0;
}
if (ipv4_is_multicast(daddr)) {
if (!ipc.oif)
ipc.oif = inet->mc_index;
if (!saddr)
saddr = inet->mc_addr;
connected = 0;
} else if (!ipc.oif)
ipc.oif = inet->uc_index;
if (connected)
rt = (struct rtable *)sk_dst_check(sk, 0);
// 如果路由选择条日为NULL,就必须执行路由选择查找
if (!rt) {
struct net *net = sock_net(sk);
__u8 flow_flags = inet_sk_flowi_flags(sk);
fl4 = &fl4_stack;
flowi4_init_output(fl4, ipc.oif, sk->sk_mark, tos,
RT_SCOPE_UNIVERSE, sk->sk_protocol,
flow_flags,
faddr, saddr, dport, inet->inet_sport,
sk->sk_uid);
security_sk_classify_flow(sk, flowi4_to_flowi(fl4));
rt = ip_route_output_flow(net, fl4, sk);
if (IS_ERR(rt)) {
err = PTR_ERR(rt);
rt = NULL;
if (err == -ENETUNREACH)
IP_INC_STATS(net, IPSTATS_MIB_OUTNOROUTES);
goto out;
}
err = -EACCES;
if ((rt->rt_flags & RTCF_BROADCAST) &&
!sock_flag(sk, SOCK_BROADCAST))
goto out;
if (connected)
sk_dst_set(sk, dst_clone(&rt->dst));
}
if (msg->msg_flags&MSG_CONFIRM)
goto do_confirm;
back_from_confirm:
saddr = fl4->saddr;
if (!ipc.addr)
daddr = ipc.addr = fl4->daddr;
/* Lockless fast path for the non-corking case. */
/*如果没有设置 corking 功能时,将不获取套接字锁,并调用 udp_send_skb(),
如果设置 corking 功能,就调用方法1ock_sock()来获取套接字锁,之后再发送数据包 */
if (!corkreq) {
skb = ip_make_skb(sk, fl4, getfrag, msg, ulen,
sizeof(struct udphdr), &ipc, &rt,
msg->msg_flags);
err = PTR_ERR(skb);
if (!IS_ERR_OR_NULL(skb))
err = udp_send_skb(skb, fl4);
goto out;
}
lock_sock(sk);
if (unlikely(up->pending)) {
/* The socket is already corked while preparing it. */
/* ... which is an evident application bug. --ANK */
release_sock(sk);
net_dbg_ratelimited("cork app bug 2\n");
err = -EINVAL;
goto out;
}
/*
* Now cork the socket to pend data.
*/
fl4 = &inet->cork.fl.u.ip4;
fl4->daddr = daddr;
fl4->saddr = saddr;
fl4->fl4_dport = dport;
fl4->fl4_sport = inet->inet_sport;
up->pending = AF_INET;
do_append_data:
up->len += ulen;
// 将数据加入缓冲区,但不立即传输它们。接收调用方法 udp_push_pending_rames 来完成传输工作。
err = ip_append_data(sk, fl4, getfrag, msg, ulen,
sizeof(struct udphdr), &ipc, &rt,
corkreq ? msg->msg_flags|MSG_MORE : msg->msg_flags);
if (err)
udp_flush_pending_frames(sk);
else if (!corkreq)
err = udp_push_pending_frames(sk);
else if (unlikely(skb_queue_empty(&sk->sk_write_queue)))
up->pending = 0;
release_sock(sk);
out:
ip_rt_put(rt);
if (free)
kfree(ipc.opt);
if (!err)
return len;
/*
* ENOBUFS = no kernel mem, SOCK_NOSPACE = no sndbuf space. Reporting
* ENOBUFS might not be good (it's not tunable per se), but otherwise
* we don't have a good statistic (IpOutDiscards but it can be too many
* things). We could add another new stat but at least for now that
* seems like overkill.
*/
if (err == -ENOBUFS || test_bit(SOCK_NOSPACE, &sk->sk_socket->flags)) {
UDP_INC_STATS(sock_net(sk),
UDP_MIB_SNDBUFERRORS, is_udplite);
}
return err;
do_confirm:
if (msg->msg_flags & MSG_PROBE)
dst_confirm_neigh(&rt->dst, &fl4->daddr);
if (!(msg->msg_flags&MSG_PROBE) || len)
goto back_from_confirm;
err = 0;
goto out;
}
EXPORT_SYMBOL(udp_sendmsg);
3、接收L3的UDP数据包
方法udp_rcv()
是负责接收来自 L3 的 UDP 数据包主处理程序:
/*
* All we need to do is get the socket, and then do a checksum.
*/
int __udp4_lib_rcv(struct sk_buff *skb, struct udp_table *udptable,
int proto)
{
struct sock *sk;
struct udphdr *uh;
unsigned short ulen;
struct rtable *rt = skb_rtable(skb);
__be32 saddr, daddr;
struct net *net = dev_net(skb->dev);
/*
* Validate the packet.
*/
if (!pskb_may_pull(skb, sizeof(struct udphdr)))
goto drop; /* No space for header. */
// 从SKB中取回UDP报头、报头长度以及源地址和目标地址
uh = udp_hdr(skb);
ulen = ntohs(uh->len);
saddr = ip_hdr(skb)->saddr;
daddr = ip_hdr(skb)->daddr;
if (ulen > skb->len)
goto short_packet;
if (proto == IPPROTO_UDP) {
/* UDP validates ulen. */
if (ulen < sizeof(*uh) || pskb_trim_rcsum(skb, ulen))
goto short_packet;
uh = udp_hdr(skb);
}
if (udp4_csum_init(skb, uh, proto))
goto csum_error;
sk = skb_steal_sock(skb);
if (sk) {
struct dst_entry *dst = skb_dst(skb);
int ret;
if (unlikely(sk->sk_rx_dst != dst))
udp_sk_rx_dst_set(sk, dst);
ret = udp_queue_rcv_skb(sk, skb);
sock_put(sk);
/* a return value > 0 means to resubmit the input, but
* it wants the return to be -protocol, or 0
*/
if (ret > 0)
return -ret;
return 0;
}
// 路过一些完整性检査,如果确保UDP报头不超过数据包长度以及
// 核实指定proto为UDP协议标识为IPPROTO_UDP.
// 如果数据包为广播或组播,调用方法 udp4_lib_mcast_deliver
if (rt->rt_flags & (RTCF_BROADCAST|RTCF_MULTICAST))
return __udp4_lib_mcast_deliver(net, skb, uh,
saddr, daddr, udptable, proto);
// 在 UDP 套接字散列表中查找
sk = __udp4_lib_lookup_skb(skb, uh->source, uh->dest, udptable);
if (sk) {
int ret;
if (inet_get_convert_csum(sk) && uh->check && !IS_UDPLITE(sk))
skb_checksum_try_convert(skb, IPPROTO_UDP, uh->check,
inet_compute_pseudo);
// 如果找到匹配,就调用 udp_queue_rcv_skb 对 SKB 做进一步处理
ret = udp_queue_rcv_skb(sk, skb);
/* a return value > 0 means to resubmit the input, but
* it wants the return to be -protocol, or 0
*/
if (ret > 0)
return -ret;
return 0;
}
if (!xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb))
goto drop;
nf_reset(skb);
/* No socket. Drop packet silently, if checksum is wrong */
// 没有匹配的套接字,如果检验和不对,就丢弃数据包
if (udp_lib_checksum_complete(skb))
goto csum_error;
__UDP_INC_STATS(net, UDP_MIB_NOPORTS, proto == IPPROTO_UDPLITE);
icmp_send(skb, ICMP_DEST_UNREACH, ICMP_PORT_UNREACH, 0);
/*
* Hmm. We got an UDP packet to a port to which we
* don't wanna listen. Ignore it.
*/
// 收到的UDP数据包将前往我们不想侦听的端口,因此将它忽略
kfree_skb(skb);
return 0;
short_packet:
net_dbg_ratelimited("UDP%s: short packet: From %pI4:%u %d/%d to %pI4:%u\n",
proto == IPPROTO_UDPLITE ? "Lite" : "",
&saddr, ntohs(uh->source),
ulen, skb->len,
&daddr, ntohs(uh->dest));
goto drop;
csum_error:
/*
* RFC1122: OK. Discards the bad packet silently (as far as
* the network is concerned, anyway) as per 4.1.3.4 (MUST).
*/
net_dbg_ratelimited("UDP%s: bad checksum. From %pI4:%u to %pI4:%u ulen %d\n",
proto == IPPROTO_UDPLITE ? "Lite" : "",
&saddr, ntohs(uh->source), &daddr, ntohs(uh->dest),
ulen);
__UDP_INC_STATS(net, UDP_MIB_CSUMERRORS, proto == IPPROTO_UDPLITE);
drop:
__UDP_INC_STATS(net, UDP_MIB_INERRORS, proto == IPPROTO_UDPLITE);
kfree_skb(skb);
return 0;
}
UDP 流程:
传输控制协议(TCP)
TCP 是 Internet 中最常用的传输协议,很多著名协议都基于 TCP。其中最著名的可能就是 HTTP ,但这里有必要提及其它一些著名协议,如 SSH、SMTP、SSL 等,不同于 UDP, TCP 提供面向连接的可靠传输,是通过使用序列号和确认来实现的。
TCP 内核具体报头 20 字节,不过在使用 TCP 选项时它最长可达 60 字节,具体如下:
1、TCP初始化操作
定义对象 tcp_protocol
(net_protocol
对象),使用 inet_add_protocol()
添加它,
2、TCP定时器及TCP套接字初始化操作
TCP 使用定时器有 4 个:重传定时器、延迟确认定时器、存活定时器、持续定时器。
重传定时器:负责重传在指定时间内未得到确认的数据包;
延迟确认定时器:推迟发送确认数据包;
存活定时器:检查连接是否断开;
零窗口探测定时器(持续定时器):缓冲区满后,接收方会通告零窗口,发送方将停止发送数据;
使用 TCP 套接字,用户空间应用程序必须创建一个SOCK_STREAM
套接字,且调用系统调用 socket()
,内核中由回调函数tcp_v4_init_sock()
来处理,实际完成工作由 tcp_init_sock()
完成。
(1)将套接字的状态设置为TCP_CLOSE
;
(2)调用方法tcp_init_xmit_timers()
来初始化TCP定时器;
(3)初始化套接字的发送缓冲区(sk_sndbuf
)和接收缓冲区(skrcvbuf);
(4)初始化无序队列和预备队列;初始化各种参数。
TCP 连接的建立和拆除及 TCP 连接的属性都被描述为状态机的状态,在给定时点,TCP 套接字将处于指定的任何一种状态。在TCP客户端和TCP服务器之间,使用三次握手建立TCP连接。
3、接收 L3 的TCP数据包
方法tcp_v4_rcv
是负责接收来自 L3 的 TCP 数据包的主处理程序,内核源码具体如下:
int tcp_v4_rcv(struct sk_buff *skb)
{
struct net *net = dev_net(skb->dev);
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头的长度
if (!pskb_may_pull(skb, sizeof(struct tcphdr)))
goto discard_it;
// 取得TCP首部
th = (const struct tcphdr *)skb->data;
// 检查 TCP 首部的长度和TCP首部中的doff字段是否匹配
if (unlikely(th->doff < sizeof(struct tcphdr) / 4))
goto bad_packet;
// 检査TCP首部到TCP数据之间的偏移是否越界
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);
/* This is tricky : We move IPCB at its correct location into TCP_SKB_CB()
* barrier() makes sure compiler wont play fool^Waliasing games.
*/
memmove(&TCP_SKB_CB(skb)->header.h4, IPCB(skb),
sizeof(struct inet_skb_parm));
barrier();
TCP_SKB_CB(skb)->seq = ntohl(th->seq);
TCP_SKB_CB(skb)->end_seq = (TCP_SKB_CB(skb)->seq + th->syn + th->fin +
skb->len - th->doff * 4);
TCP_SKB_CB(skb)->ack_seq = ntohl(th->ack_seq);
TCP_SKB_CB(skb)->tcp_flags = tcp_flag_byte(th);
TCP_SKB_CB(skb)->tcp_tw_isn = 0;
TCP_SKB_CB(skb)->ip_dsfield = ipv4_get_dsfield(iph);
TCP_SKB_CB(skb)->sacked = 0;
lookup:
// 如果查找不到匹配的 sock,则直接丢弃数据包
sk = __inet_lookup_skb(&tcp_hashinfo, skb, __tcp_hdrlen(th), th->source,
th->dest, &refcounted);
if (!sk)
goto no_tcp_socket;
// 检查sock是否处于半关闭状态
process:
if (sk->sk_state == TCP_TIME_WAIT)
goto do_time_wait;
if (sk->sk_state == TCP_NEW_SYN_RECV) {
struct request_sock *req = inet_reqsk(sk);
struct sock *nsk;
sk = req->rsk_listener;
if (unlikely(tcp_v4_inbound_md5_hash(sk, skb))) {
sk_drops_add(sk, skb);
reqsk_put(req);
goto discard_it;
}
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 = tcp_check_req(sk, skb, req, false);
if (!nsk) {
reqsk_put(req);
goto discard_and_relse;
}
if (nsk == sk) {
reqsk_put(req);
} 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))
goto discard_and_relse;
nf_reset(skb);
if (tcp_filter(sk, skb))
goto discard_and_relse;
th = (const struct tcphdr *)skb->data;
iph = ip_hdr(skb);
skb->dev = NULL;
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;
if (!sock_owned_by_user(sk)) {
if (!tcp_prequeue(sk, skb))
ret = tcp_v4_do_rcv(sk, skb);
} else if (tcp_add_backlog(sk, skb)) {
goto discard_and_relse;
}
bh_unlock_sock(sk);
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;
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;
}
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));
if (sk2) {
inet_twsk_deschedule_put(inet_twsk(sk));
sk = sk2;
refcounted = false;
goto process;
}
/* Fall through to ACK */
}
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;
}
4、发送TCP数据包
从用户空间中创建的 TCP 套接字发送数据包,可使用多个系统调用,包括 send()
,sendto(
),sendmsg()
和 write()
,系统调用最终由方法tcp_sendmsg
处理,它将来自用户空间的有效负载复制到内核,将其作为TCP数据段进行发送。
int tcp_sendmsg(struct sock *sk, struct msghdr *msg, size_t size)