内核发送reset报文的函数为tcp_v4_send_reset,如下其赋值给了tcp_request_sock_ops结构的成员send_reset。
struct request_sock_ops tcp_request_sock_ops __read_mostly = {
.family = PF_INET,
...
.send_reset = tcp_v4_send_reset,
监听套接口不存在
如下tcp_v4_rcv函数中,__inet_lookup_skb没有找到监听套接口,跳转到no_tcp_socket标签处,如果此触发报文的checksum没有问题,将回复Reset报文。另外在函数tcp_v4_send_reset中会检查当前报文是否设置了reset标志位,不对接收到的reset报文回复reset报文。
int tcp_v4_rcv(struct sk_buff *skb)
{
th = (const struct tcphdr *)skb->data;
iph = ip_hdr(skb);
lookup:
sk = __inet_lookup_skb(&tcp_hashinfo, skb, __tcp_hdrlen(th), th->source,
th->dest, sdif, &refcounted);
if (!sk)
goto no_tcp_socket;
process:
if (sk->sk_state == TCP_TIME_WAIT)
goto do_time_wait;
这里为找不到套接口,发送reset报文的位置。
no_tcp_socket:
...
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);
}
握手阶段不合法ACK
接着上一节,如果找到了监听套接口,并且其状态为TCP_NEW_SYN_RECV,而且成功创建了子套接口,但是在子套接口处理函数tcp_child_process中返回错误,也将发送reset报文。错误情况稍后在函数tcp_rcv_state_process中介绍。
int tcp_v4_rcv(struct sk_buff *skb)
{
if (sk->sk_state == TCP_NEW_SYN_RECV) {
struct request_sock *req = inet_reqsk(sk);
...
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 == 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;
如下tcp_rcv_state_process函数,在处理ACK报文的函数tcp_ack返回值小于0时,认为是一个不可接受的非法ACK报文,返回1,由函数tcp_v4_rcv发送Reset报文。注意在此之前已经由函数tcp_validate_incoming进行了PAWS和序号等的合法性检查,对于非法报文不回复RESET报文。
这里仅限于对三次握手中的ACK处理。
int tcp_rcv_state_process(struct sock *sk, struct sk_buff *skb)
{
...
if (!th->ack && !th->rst && !th->syn)
goto discard;
if (!tcp_validate_incoming(sk, skb, th, 0))
return 0;
/* step 5: check the ACK field */
acceptable = tcp_ack(sk, skb, FLAG_SLOWPATH |
FLAG_UPDATE_TS_RECENT |
FLAG_NO_CHALLENGE_ACK) > 0;
if (!acceptable) {
if (sk->sk_state == TCP_SYN_RECV)
return 1; /* send one RST */
以下看一看函数tcp_ack对ACK报文的检查,如果接收到的ACK报文确认的是已经确认过的数据,判定为old_ack;再进一步其确认序号与当前待确认序号相差大于max_window,发送challenge-ACK,以便对端调整序号。
另外如果ACK确认的数据,本地还没有发送,判定为无效的ACK(invalid_ack)。这里的三种情况,都将导致tcp_ack函数返回小于或者等于0的值,参考以上的tcp_rcv_state_process函数,如果此时套接口的状态为TCP_SYN_RECV,回复reset报文,断开连接。
static int tcp_ack(struct sock *sk, const struct sk_buff *skb, int flag)
{
u32 prior_snd_una = tp->snd_una;
u32 ack_seq = TCP_SKB_CB(skb)->seq;
u32 ack = TCP_SKB_CB(skb)->ack_seq;
/* If the ack is older than previous acks then we can probably ignore it.
*/
if (before(ack, prior_snd_una)) {
/* RFC 5961 5.2 [Blind Data Injection Attack].[Mitigation] */
if (before(ack, prior_snd_una - tp->max_window)) {
if (!(flag & FLAG_NO_CHALLENGE_ACK))
tcp_send_challenge_ack(sk, skb);
return -1;
}
goto old_ack;
}
/* If the ack includes data we haven't sent yet, discard
* this segment (RFC793 Section 3.9).
*/
if (after(ack, tp->snd_nxt))
goto invalid_ack;
错误的SYN报文
如下函数tcp_v4_rcv中,对于套接口处于TCP_NEW_SYN_RECV状态接收的报文,由函数tcp_check_req检查。
int tcp_v4_rcv(struct sk_buff *skb)
{
if (sk->sk_state == TCP_NEW_SYN_RECV) {
struct request_sock *req = inet_reqsk(sk);
sk = req->rsk_listener;
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);
}
此时,应当接收到客户端三次握手的ACK报文,但是实际接收的报文设置了TCP_FLAG_RST或者TCP_FLAG_SYN标志,并且此报文也不属于重传的SYN报文。
struct sock *tcp_check_req(struct sock *sk, struct sk_buff *skb,
struct request_sock *req, bool fastopen, bool *req_stolen)
{
__be32 flg = tcp_flag_word(th) & (TCP_FLAG_RST|TCP_FLAG_SYN|TCP_FLAG_ACK);
/* Check for pure retransmitted SYN. */
if (TCP_SKB_CB(skb)->seq == tcp_rsk(req)->rcv_isn &&
flg == TCP_FLAG_SYN &&
!paws_reject) {
...
return NULL;
}
/* RFC793: "first check sequence number". */
if (paws_reject || !tcp_in_window(TCP_SKB_CB(skb)->seq, TCP_SKB_CB(skb)->end_seq,
tcp_rsk(req)->rcv_nxt, tcp_rsk(req)->rcv_nxt + req->rsk_rcv_wnd)) {
/* Out of window: send ACK and drop. */
...
}
/* RFC793: "second check the RST bit" and "fourth, check the SYN bit"
*/
if (flg & (TCP_FLAG_RST|TCP_FLAG_SYN)) {
__TCP_INC_STATS(sock_net(sk), TCP_MIB_ATTEMPTFAILS);
goto embryonic_reset;
}
如果当前报文没有设置RST标志位,发送RESET报文。否则,如果设置了RST标志位,释放请求套接口结构(inet_csk_reqsk_queue_drop);
embryonic_reset:
if (!(flg & TCP_FLAG_RST)) {
/* Received a bad SYN pkt - for TFO We try not to reset
* the local connection unless it's really necessary to
* avoid becoming vulnerable to outside attack aiming at
* resetting legit local connections.
*/
req->rsk_ops->send_reset(sk, skb);
} else if (fastopen) { /* received a valid RST pkt */
reqsk_fastopen_remove(sk, req, true);
tcp_reset(sk);
}
if (!fastopen) {
inet_csk_reqsk_queue_drop(sk, req);
__NET_INC_STATS(sock_net(sk), LINUX_MIB_EMBRYONICRSTS);
}
挥手阶段的RESET报文
对于TCP_TIME_WAIT状态的套接口,当函数tcp_timewait_state_process返回值为TCP_TW_RST时,发送reset报文,有以下两者情况。
int tcp_v4_rcv(struct sk_buff *skb)
{
do_time_wait:
...
switch (tcp_timewait_state_process(inet_twsk(sk), skb, th)) {
...
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_timewait_state_process中,如果套接口状态为TCP_FIN_WAIT2,即本端已经关闭连接,正在等待对端的FIN报文,此时接收到设置SYN标志的报文,并且其序号位于待接收序号tw_rcv_nxt之后,即对端要错误的发起新的三次握手,回复RESET报文。
enum tcp_tw_status
tcp_timewait_state_process(struct inet_timewait_sock *tw, struct sk_buff *skb, const struct tcphdr *th)
{
struct tcp_options_received tmp_opt;
struct tcp_timewait_sock *tcptw = tcp_twsk((struct sock *)tw);
...
if (tw->tw_substate == TCP_FIN_WAIT2) {
/* Just repeat all the checks of tcp_rcv_state_process() */
/* Out of window, send ACK */
if (paws_reject ||
!tcp_in_window(TCP_SKB_CB(skb)->seq, TCP_SKB_CB(skb)->end_seq,
tcptw->tw_rcv_nxt,
tcptw->tw_rcv_nxt + tcptw->tw_rcv_wnd))
return tcp_timewait_check_oow_rate_limit(
tw, skb, LINUX_MIB_TCPACKSKIPPEDFINWAIT2);
if (th->rst) goto kill;
if (th->syn && !before(TCP_SKB_CB(skb)->seq, tcptw->tw_rcv_nxt))
return TCP_TW_RST;
如果报文没有设置ACK标志,或者ACK报文的序号在待接收序号tw_rcv_nxt之前,即接收到重复的ACK报文,返回TCP_TW_SUCCESS,释放此报文。再者,如果当前报文没有设置FIN标志,或者设置了FIN,但是报文的结束序号end_seq不等于待接收序号加一(FIN占用一个序号),表明FIN报文中携带有数据,回复RESET报文。
/* Dup ACK? */
if (!th->ack ||
!after(TCP_SKB_CB(skb)->end_seq, tcptw->tw_rcv_nxt) ||
TCP_SKB_CB(skb)->end_seq == TCP_SKB_CB(skb)->seq) {
inet_twsk_put(tw);
return TCP_TW_SUCCESS;
}
/* New data or FIN. If new data arrive after half-duplex close, reset.
*/
if (!th->fin ||
TCP_SKB_CB(skb)->end_seq != tcptw->tw_rcv_nxt + 1)
return TCP_TW_RST;
TCP状态机处理
以上主要是在TCP的连接建立和断开阶段,触发的RESET报文。在以下函数tcp_v4_do_rcv中,TCP状态机处理函数tcp_rcv_state_process返回不等于0的值,也将导致reset报文的发送。
int tcp_v4_do_rcv(struct sock *sk, struct sk_buff *skb)
{
...
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);
如下TCP_LISTEN状态的监听套接口接收到带有ACK标志的报文,回复RESET报文。如果接收到SYN报文,交由IPv4/IPv6协议的conn_request指针函数处理,当其返回值小于零时,表明SYN报文不可接受返回1,由以上函数tcp_v4_do_rcv发送reset报文,但是目前conn_request不会返回小于零的值。
int tcp_rcv_state_process(struct sock *sk, struct sk_buff *skb)
{
struct tcp_sock *tp = tcp_sk(sk);
struct inet_connection_sock *icsk = inet_csk(sk);
const struct tcphdr *th = tcp_hdr(skb);
switch (sk->sk_state) {
case TCP_LISTEN:
if (th->ack)
return 1;
if (th->rst)
goto discard;
if (th->syn) {
if (th->fin)
goto discard;
/* It is possible that we process SYN packets from backlog,
* so we need to make sure to disable BH and RCU right there.
*/
rcu_read_lock();
local_bh_disable();
acceptable = icsk->icsk_af_ops->conn_request(sk, skb) >= 0;
local_bh_enable();
rcu_read_unlock();
if (!acceptable)
return 1;
如果套接口状态为TCP_SYN_SENT(TCP客户端),当前接收到的回复报文设置了ACK标志,但是确认序号不合法,回复RESET报文,参见函数tcp_rcv_synsent_state_process中的处理。
case TCP_SYN_SENT:
tp->rx_opt.saw_tstamp = 0;
tcp_mstamp_refresh(tp);
queued = tcp_rcv_synsent_state_process(sk, skb, th);
if (queued >= 0)
return queued;
对于进入TCP_FIN_WAIT1状态的套接口,接收到ACK报文,状态转变为TCP_FIN_WAIT2,如果选项TCP_LINGER2小于零,立即关闭并释放此套接口,未处理的数据将被释放,返回1,作为错误指示回复对端reset报文。
另外,如果此时接收到报文起始序号seq与结束序号end_seq不相同,不是纯ACK报文,并且结束序号位于待接收序号之后,表明为乱序的数据包,或者乱序的FIN报文,同样回复reset报文。
switch (sk->sk_state) {
case TCP_FIN_WAIT1: {
...
tcp_set_state(sk, TCP_FIN_WAIT2);
sk->sk_shutdown |= SEND_SHUTDOWN;
...
if (tp->linger2 < 0) {
tcp_done(sk);
NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPABORTONDATA);
return 1;
}
if (TCP_SKB_CB(skb)->end_seq != TCP_SKB_CB(skb)->seq &&
after(TCP_SKB_CB(skb)->end_seq - th->fin, tp->rcv_nxt)) {
/* Receive out of order FIN after close() */
if (tp->syn_fastopen && th->fin)
tcp_fastopen_active_disable(sk);
tcp_done(sk);
NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPABORTONDATA);
return 1;
}
套接口处于以下几种状态时,本地已经关闭接收(RCV_SHUTDOWN),但是又接收到数据时(此报文设置有RST标志),回复对端reset报文。
/* step 6: check the URG bit */
tcp_urg(sk, skb, th);
/* step 7: process the segment text */
switch (sk->sk_state) {
case TCP_CLOSE_WAIT:
case TCP_CLOSING:
case TCP_LAST_ACK:
if (!before(TCP_SKB_CB(skb)->seq, tp->rcv_nxt))
break;
/* fall through */
case TCP_FIN_WAIT1:
case TCP_FIN_WAIT2:
/* RFC 793 says to queue data in these states,
* RFC 1122 says we MUST send a reset.
* BSD 4.4 also does reset.
*/
if (sk->sk_shutdown & RCV_SHUTDOWN) {
if (TCP_SKB_CB(skb)->end_seq != TCP_SKB_CB(skb)->seq &&
after(TCP_SKB_CB(skb)->end_seq - th->fin, tp->rcv_nxt)) {
NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPABORTONDATA);
tcp_reset(sk);
return 1;
主动发送reset发送
以上介绍的情况都是使用tcp_v4_send_reset发送复位报文,内核中还有另一个函数tcp_send_active_reset可发送复位报文。注释中说明在显式调用close关闭套接口,或者exit调用触发关闭套接口是,如果套接口接收队列中还有未读的数据时,使用此函数向对端发送reset报文。
/* We get here when a process closes a file descriptor (either due to
* an explicit close() or as a byproduct of exit()'ing) and there
* was unread data in the receive queue. This behavior is recommended
* by RFC 2525, section 2.17. -DaveM
*/
void tcp_send_active_reset(struct sock *sk, gfp_t priority)
{
struct sk_buff *skb;
TCP_INC_STATS(sock_net(sk), TCP_MIB_OUTRSTS);
/* NOTE: No TCP options attached and we never retransmit this. */
skb = alloc_skb(MAX_TCP_HEADER, priority);
if (!skb) {
NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPABORTFAILED);
return;
}
/* Reserve space for headers and prepare control bits. */
skb_reserve(skb, MAX_TCP_HEADER);
tcp_init_nondata_skb(skb, tcp_acceptable_seq(sk),
TCPHDR_ACK | TCPHDR_RST);
tcp_mstamp_refresh(tcp_sk(sk));
/* Send it off. */
if (tcp_transmit_skb(sk, skb, 0, priority))
NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPABORTFAILED);
函数tcp_check_oom检查当前系统中的orphan套接口数量是否超过/proc/sys/net/ipv4/tcp_max_orphans中规定的值; 或者,系统中套接口使用的内存空间超出/proc/sys/net/ipv4/tcp_mem中定义的最大内存空间,认定为发送OOM。如果此时,1)上一个报文刚刚发送;或者2)发送窗口为0,没有in-flight报文,向对端发送复位报文。
static int tcp_out_of_resources(struct sock *sk, bool do_reset)
{
struct tcp_sock *tp = tcp_sk(sk);
int shift = 0;
/* If peer does not open window for long time, or did not transmit
* anything for long time, penalize it. */
if ((s32)(tcp_jiffies32 - tp->lsndtime) > 2*TCP_RTO_MAX || !do_reset)
shift++;
/* If some dubious ICMP arrived, penalize even more. */
if (sk->sk_err_soft)
shift++;
if (tcp_check_oom(sk, shift)) {
/* Catch exceptional cases, when connection requires reset.
* 1. Last segment was sent recently. */
if ((s32)(tcp_jiffies32 - tp->lsndtime) <= TCP_TIMEWAIT_LEN ||
/* 2. Window is closed. */
(!tp->snd_wnd && !tp->packets_out))
do_reset = true;
if (do_reset)
tcp_send_active_reset(sk, GFP_ATOMIC);
tcp_done(sk);
__NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPABORTONMEMORY);
在如下的tcp_keepalive_timer保活处理函数中,如果套接口状态为TCP_FIN_WAIT2,并且其已经设置了SOCK_DEAD标志(例如变为orphan套接口),此时如果linger2时长小于0时,向对端发送reset报文。或者,linger2时长大于等于0,但是计算而得的剩余超时时长小于零,同样发送reset报文。
static void tcp_keepalive_timer (struct timer_list *t)
{
struct sock *sk = from_timer(sk, t, sk_timer);
struct inet_connection_sock *icsk = inet_csk(sk);
struct tcp_sock *tp = tcp_sk(sk);
...
tcp_mstamp_refresh(tp);
if (sk->sk_state == TCP_FIN_WAIT2 && sock_flag(sk, SOCK_DEAD)) {
if (tp->linger2 >= 0) {
const int tmo = tcp_fin_time(sk) - TCP_TIMEWAIT_LEN;
if (tmo > 0) {
tcp_time_wait(sk, TCP_FIN_WAIT2, tmo);
goto out;
}
}
tcp_send_active_reset(sk, GFP_ATOMIC);
goto death;
}
如果已经发送过探测报文,并且上次接收到对端报文到当下经过的时长超过了用户指定的时长icsk_user_timeout; 或者,用户未指定超时时长,但是发送探测报文的次数已经超限,向对端发送reset报文。
elapsed = keepalive_time_elapsed(tp);
if (elapsed >= keepalive_time_when(tp)) {
/* If the TCP_USER_TIMEOUT option is enabled, use that
* to determine when to timeout instead.
*/
if ((icsk->icsk_user_timeout != 0 &&
elapsed >= msecs_to_jiffies(icsk->icsk_user_timeout) &&
icsk->icsk_probes_out > 0) ||
(icsk->icsk_user_timeout == 0 &&
icsk->icsk_probes_out >= keepalive_probes(tp))) {
tcp_send_active_reset(sk, GFP_ATOMIC);
tcp_write_err(sk);
套接口关闭错误
在关闭套接口时,接收队列中还有未读取的数据(data_was_unread),向对端发送reset报文。
void tcp_close(struct sock *sk, long timeout)
{
...
while ((skb = __skb_dequeue(&sk->sk_receive_queue)) != NULL) {
u32 len = TCP_SKB_CB(skb)->end_seq - TCP_SKB_CB(skb)->seq;
if (TCP_SKB_CB(skb)->tcp_flags & TCPHDR_FIN)
len--;
data_was_unread += len;
__kfree_skb(skb);
}
/* If socket has been already reset (e.g. in tcp_reset()) - kill it. */
if (sk->sk_state == TCP_CLOSE)
goto adjudge_to_death;
if (unlikely(tcp_sk(sk)->repair)) {
sk->sk_prot->disconnect(sk, 0);
} else if (data_was_unread) {
/* Unread data was tossed, zap the connection. */
NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPABORTONCLOSE);
tcp_set_state(sk, TCP_CLOSE);
tcp_send_active_reset(sk, sk->sk_allocation);
} else if (sock_flag(sk, SOCK_LINGER) && !sk->sk_lingertime) {
/* Check zero linger _after_ checking for unread data. */
sk->sk_prot->disconnect(sk, 0);
NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPABORTONDATA);
当套接口处于TCP_FIN_WAIT2状态,linger2选项小于零时,向对端发送reset报文。另外,如果套接口没有置成TCP_CLOSE状态,作为一个检查OOM的时机,调用tcp_check_oom函数,如果发生OOM,向对端发送reset报文。
if (sk->sk_state == TCP_FIN_WAIT2) {
struct tcp_sock *tp = tcp_sk(sk);
if (tp->linger2 < 0) {
tcp_set_state(sk, TCP_CLOSE);
tcp_send_active_reset(sk, GFP_ATOMIC);
__NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPABORTONLINGER);
} else {
...
}
}
if (sk->sk_state != TCP_CLOSE) {
sk_mem_reclaim(sk);
if (tcp_check_oom(sk, 0)) {
tcp_set_state(sk, TCP_CLOSE);
tcp_send_active_reset(sk, GFP_ATOMIC);
__NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPABORTONMEMORY);
以上tcp_close函数中调用的sk->sk_prot->disconnect指针指向函数tcp_disconnect,如果套接口开启了SOCK_LINGER选项(与TCP层的linger2不同),并且sk_lingertime时长不为零,调用以下的tcp_disconnect函数。
如果套接口状态不等于TCP_LISTEN,并且符合tcp_need_reset函数中定义的状态; 或者还有数据没有发送,并且套接口状态为TCPF_CLOSING或者TCPF_LAST_ACK,向对端发送reset报文。
int tcp_disconnect(struct sock *sk, int flags)
{
struct inet_sock *inet = inet_sk(sk);
struct inet_connection_sock *icsk = inet_csk(sk);
struct tcp_sock *tp = tcp_sk(sk);
int old_state = sk->sk_state;
if (old_state != TCP_CLOSE)
tcp_set_state(sk, TCP_CLOSE);
/* ABORT function of RFC793 */
if (old_state == TCP_LISTEN) {
inet_csk_listen_stop(sk);
} else if (unlikely(tp->repair)) {
sk->sk_err = ECONNABORTED;
} else if (tcp_need_reset(old_state) ||
(tp->snd_nxt != tp->write_seq &&
(1 << old_state) & (TCPF_CLOSING | TCPF_LAST_ACK))) {
/* The last check adjusts for discrepancy of Linux wrt. RFC states
*/
tcp_send_active_reset(sk, gfp_any());
sk->sk_err = ECONNRESET;
static inline bool tcp_need_reset(int state)
{
return (1 << state) &
(TCPF_ESTABLISHED | TCPF_CLOSE_WAIT | TCPF_FIN_WAIT1 |
TCPF_FIN_WAIT2 | TCPF_SYN_RECV);
函数tcp_abort在销毁诊断套接口时被调用,如果此时套接口还没有正常关闭(SOCK_DEAD),根据tcp_need_reset的返回值,就决定是否发送reset报文。
int tcp_abort(struct sock *sk, int err)
{
...
if (!sock_flag(sk, SOCK_DEAD)) {
sk->sk_err = err;
/* This barrier is coupled with smp_rmb() in tcp_poll() */
smp_wmb();
sk->sk_error_report(sk);
if (tcp_need_reset(sk->sk_state))
tcp_send_active_reset(sk, GFP_ATOMIC);
tcp_done(sk);
内核版本 5.0