static int efab_tcp_drop_from_acceptq(ci_private_t *priv, void *arg) { struct oo_op_tcp_drop_from_acceptq *carg = arg; tcp_helper_resource_t *thr; tcp_helper_endpoint_t *ep; citp_waitable *w; ci_tcp_state *ts; int rc = -EINVAL; /* find stack */ rc = efab_thr_table_lookup(NULL, carg->stack_id, EFAB_THR_TABLE_LOOKUP_CHECK_USER | EFAB_THR_TABLE_LOOKUP_NO_UL, &thr); if( rc < 0 ) return rc; ci_assert( thr->k_ref_count & TCP_HELPER_K_RC_NO_USERLAND ); /* find endpoint and drop OS socket */ ep = ci_trs_get_valid_ep(thr, carg->sock_id); if( ep == NULL ) goto fail1; w = SP_TO_WAITABLE(&thr->netif, carg->sock_id); if( !(w->state & CI_TCP_STATE_TCP) || w->state == CI_TCP_LISTEN ) goto fail2; ts = SP_TO_TCP(&thr->netif, carg->sock_id); ci_assert(ep->os_port_keeper); ci_assert_equal(ep->os_socket, NULL); LOG_TV(ci_log("%s: send reset to non-accepted connection", __FUNCTION__)); /* copy from ci_tcp_listen_shutdown_queues() */ ci_assert(ts->s.b.sb_aflags & CI_SB_AFLAG_TCP_IN_ACCEPTQ); rc = ci_netif_lock(&thr->netif); if( rc != 0 ) { ci_assert_equal(rc, -EINTR); rc = -ERESTARTSYS; goto fail2; } ci_bit_clear(&ts->s.b.sb_aflags, CI_SB_AFLAG_TCP_IN_ACCEPTQ_BIT); /* We have no way to close this connection from the other side: * there was no RST from peer. */ ci_assert_nequal(ts->s.b.state, CI_TCP_CLOSED); ci_assert_nequal(ts->s.b.state, CI_TCP_TIME_WAIT); ci_tcp_send_rst(&thr->netif, ts); ci_tcp_drop(&thr->netif, ts, ECONNRESET); ci_assert_equal(ep->os_port_keeper, NULL); ci_netif_unlock(&thr->netif); efab_tcp_helper_k_ref_count_dec(thr, 1); return 0; fail1: efab_thr_release(thr); fail2: ci_log("%s: inconsistent ep %d:%d", __func__, carg->stack_id, carg->sock_id); return rc; }
void ci_tcp_linger(ci_netif* ni, ci_tcp_state* ts) { /* This is called at user-level when a socket is closed if linger is ** enabled and has a timeout, and there is TX data outstanding. ** ** Our job is to block until all data is successfully sent and acked, or ** until timeout. */ ci_uint64 sleep_seq; int rc = 0; ci_uint32 timeout = ts->s.so.linger * 1000; LOG_TC(log("%s: "NTS_FMT, __FUNCTION__, NTS_PRI_ARGS(ni, ts))); ci_assert(ts->s.b.sb_aflags & CI_SB_AFLAG_ORPHAN); ci_assert(ts->s.b.sb_aflags & CI_SB_AFLAG_IN_SO_LINGER); ci_assert(ts->s.s_flags & CI_SOCK_FLAG_LINGER); ci_assert(ts->s.b.state != CI_TCP_LISTEN); while( 1 ) { sleep_seq = ts->s.b.sleep_seq.all; ci_rmb(); if( SEQ_EQ(tcp_enq_nxt(ts), tcp_snd_una(ts)) ) return; rc = ci_sock_sleep(ni, &ts->s.b, CI_SB_FLAG_WAKE_TX, 0, sleep_seq, &timeout); if( rc ) break; } if( ! SEQ_EQ(tcp_enq_nxt(ts), tcp_snd_una(ts)) ) { ci_netif_lock(ni); /* check we are working with the same socket, and it was not closed and * dropped under our feet. */ if( ! SEQ_EQ(tcp_enq_nxt(ts), tcp_snd_una(ts)) && (ts->s.b.sb_aflags & CI_SB_AFLAG_IN_SO_LINGER) ) ci_tcp_drop(ni, ts, 0); ci_netif_unlock(ni); } }
void ci_ip_send_tcp_slow(ci_netif* ni, ci_tcp_state* ts, ci_ip_pkt_fmt* pkt) { /* We're here because the ipcache is not valid. */ int rc, prev_mtu = ts->s.pkt.mtu; cicp_user_retrieve(ni, &ts->s.pkt, &ts->s.cp); if( ts->s.pkt.status == retrrc_success ) { if( ts->s.pkt.mtu != prev_mtu ) CI_PMTU_TIMER_NOW(ni, &ts->pmtus); ci_ip_set_mac_and_port(ni, &ts->s.pkt, pkt); ci_netif_send(ni, pkt); return; } else if( ts->s.pkt.status == retrrc_localroute && (ts->s.pkt.flags & CI_IP_CACHE_IS_LOCALROUTE) ) ci_ip_local_send(ni, pkt, &ts->s, OO_SP_NULL); /* For TCP, we want the ipcache to only be valid when onloadable. */ ci_ip_cache_invalidate(&ts->s.pkt); switch( ts->s.pkt.status ) { case retrrc_nomac: rc = 0; /* If we resend SYN, and there is no MAC - it means ARP failed. * Connect() should return with EHOSTUNREACH. * We verify twice - on the first and the second retransmit. * Very hackish. */ if( ts->s.b.state == CI_TCP_SYN_SENT ) { if( ts->retransmits == 1 ) ts->tcpflags |= CI_TCPT_FLAG_NO_ARP; else if( (ts->tcpflags & CI_TCPT_FLAG_NO_ARP) && ts->retransmits == 2 ) { ci_tcp_drop(ni, ts, EHOSTUNREACH); return; } } cicp_user_defer_send(ni, retrrc_nomac, &rc, OO_PKT_P(pkt), ts->s.pkt.ifindex); ++ts->stats.tx_nomac_defer; return; case retrrc_noroute: rc = -EHOSTUNREACH; break; case retrrc_alienroute: case retrrc_localroute: /* ?? TODO: inc some stat */ return; default: ci_assert_lt(ts->s.pkt.status, 0); if( ts->s.pkt.status < 0 ) rc = ts->s.pkt.status; else /* belt and braces... */ rc = 0; } ci_assert_le(rc, 0); /* In most cases, we should ignore return code; the packet will be resend * later, because of RTO. However, in SYN-SENT we should pass errors to * user. At the same time, we should not pass ENOBUFS to user - it is * pretty internal problem of cplane, so we should try again. Possibly, * there may be other internal problems, such as ENOMEM. * * Also, do not break connection when the first SYN fails: * - Linux does not do it; * - cplane has some latency, so we have false positives here; * - ci_tcp_connect() does not expect it. */ if( ts->s.b.state == CI_TCP_SYN_SENT && rc < 0 && ts->retransmits > 0 && (rc == -EHOSTUNREACH || rc == -ENETUNREACH || rc == -ENETDOWN) ) ci_tcp_drop(ni, ts, -rc); }
static int ci_tcp_connect_ul_syn_sent(ci_netif *ni, ci_tcp_state *ts) { int rc = 0; if( ts->s.b.state == CI_TCP_SYN_SENT ) { ci_netif_poll(ni); if( OO_SP_NOT_NULL(ts->local_peer) ) { /* No reason to sleep. Obviously, listener have dropped our syn * because of some reason. Go away! */ ci_tcp_drop(ni, ts, EBUSY); RET_WITH_ERRNO(EBUSY); } CI_TCP_SLEEP_WHILE(ni, ts, CI_SB_FLAG_WAKE_RX, ts->s.so.sndtimeo_msec, ts->s.b.state == CI_TCP_SYN_SENT, &rc); } if( rc == -EAGAIN ) { LOG_TC(log( LNT_FMT "timeout on sleep: %d", LNT_PRI_ARGS(ni, ts), -rc)); if( ! (ts->tcpflags & CI_TCPT_FLAG_NONBLOCK_CONNECT) ) { ts->tcpflags |= CI_TCPT_FLAG_NONBLOCK_CONNECT; CI_SET_ERROR(rc, EINPROGRESS); } else CI_SET_ERROR(rc, EALREADY); return rc; } else if( rc == -EINTR ) { LOG_TC(log(LNT_FMT "connect() was interrupted by a signal", LNT_PRI_ARGS(ni, ts))); ts->tcpflags |= CI_TCPT_FLAG_NONBLOCK_CONNECT; CI_SET_ERROR(rc, EINTR); return rc; } /*! \TODO propagate the correct error code: CONNREFUSED, NOROUTE, etc. */ if( ts->s.b.state == CI_TCP_CLOSED ) { /* Bug 3558: * Set OS socket state to allow/disallow next bind(). * It is Linux hack. */ #ifdef __ci_driver__ CI_TRY(efab_tcp_helper_set_tcp_close_os_sock(netif2tcp_helper_resource(ni), S_SP(ts))); #else CI_TRY(ci_tcp_helper_set_tcp_close_os_sock(ni, S_SP(ts))); #endif /* We should re-bind socket on the next use if the port was determined by * OS. */ if( ! (ts->s.s_flags & CI_SOCK_FLAG_PORT_BOUND) ) ts->s.s_flags |= CI_SOCK_FLAG_CONNECT_MUST_BIND; /* - if SO_ERROR is set, handle it and return this value; * - else if rx_errno is set, return it; * - else (TCP_RX_ERRNO==0, socket is CI_SHUT_RD) return ECONNABORTED */ if( (rc = ci_tcp_connect_handle_so_error(&ts->s)) == 0) rc = TCP_RX_ERRNO(ts) ? TCP_RX_ERRNO(ts) : ECONNABORTED; CI_SET_ERROR(rc, rc); if( ! (ts->s.s_flags & CI_SOCK_FLAG_ADDR_BOUND) ) { ts->s.pkt.ip.ip_saddr_be32 = 0; ts->s.cp.ip_laddr_be32 = 0; } return rc; } return 0; }