Files
Gleb Smirnoff 776604651e hpts: remove call into TCP HPTS from userret()
This hack introduced in d7955cc0ff and e3cbc572f1 proved to have more
ill side effects than benefits.  Sorry for that.

Now the HPTS soft clock is called only after the LRO completion.  Refactor
HPTS module linkage to address that and share the pointer only between
HPTS and LRO.

Reviewed by:		Nick Banks
Differential Revision:	https://reviews.freebsd.org/D55640
2026-03-04 19:16:00 -08:00

678 lines
18 KiB
C

/*-
* Copyright (c) 2016-2018 Netflix, Inc.
* Copyright (c) 2016-2021 Mellanox Technologies.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
*/
#include "opt_inet.h"
#include "opt_inet6.h"
#include <sys/param.h>
#include <sys/bus.h>
#include <sys/interrupt.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/malloc.h>
#include <sys/mbuf.h>
#include <sys/socket.h>
#include <sys/socketvar.h>
#include <sys/sysctl.h>
#include <net/if.h>
#include <net/if_var.h>
#include <net/if_private.h>
#include <net/ethernet.h>
#include <net/bpf.h>
#include <net/vnet.h>
#include <net/if_dl.h>
#include <net/if_media.h>
#include <net/if_types.h>
#include <net/infiniband.h>
#include <net/if_lagg.h>
#include <net/pfil.h>
#include <netinet/in.h>
#include <netinet/in_kdtrace.h>
#include <netinet/ip6.h>
#include <netinet/ip.h>
#include <netinet/ip_var.h>
#include <netinet/in_pcb.h>
#include <netinet6/in6_pcb.h>
#include <netinet6/ip6_var.h>
#include <netinet/tcp.h>
#include <netinet/tcp_lro.h>
#include <netinet/tcp_var.h>
#include <netinet/tcp_hpts.h>
#include <netinet/tcp_hpts_internal.h>
#ifdef TCP_BLACKBOX
#include <netinet/tcp_log_buf.h>
#endif
static void
build_ack_entry(struct tcp_ackent *ae, struct tcphdr *th, struct mbuf *m,
uint32_t *ts_ptr, uint16_t iptos)
{
/*
* Given a TCP ACK, summarize it down into the small TCP ACK
* entry.
*/
ae->timestamp = m->m_pkthdr.rcv_tstmp;
ae->flags = 0;
if (m->m_flags & M_TSTMP_LRO)
ae->flags |= TSTMP_LRO;
else if (m->m_flags & M_TSTMP)
ae->flags |= TSTMP_HDWR;
ae->seq = th->th_seq;
ae->ack = th->th_ack;
ae->flags |= tcp_get_flags(th);
if (ts_ptr != NULL) {
ae->ts_value = ntohl(ts_ptr[1]);
ae->ts_echo = ntohl(ts_ptr[2]);
ae->flags |= HAS_TSTMP;
}
ae->win = th->th_win;
ae->codepoint = iptos;
}
static inline bool
tcp_lro_ack_valid(struct mbuf *m, struct tcphdr *th, uint32_t **ppts, bool *other_opts)
{
/*
* This function returns two bits of valuable information.
* a) Is what is present capable of being ack-compressed,
* we can ack-compress if there is no options or just
* a timestamp option, and of course the th_flags must
* be correct as well.
* b) Our other options present such as SACK. This is
* used to determine if we want to wakeup or not.
*/
bool ret = true;
switch (th->th_off << 2) {
case (sizeof(*th) + TCPOLEN_TSTAMP_APPA):
*ppts = (uint32_t *)(th + 1);
/* Check if we have only one timestamp option. */
if (**ppts == TCP_LRO_TS_OPTION)
*other_opts = false;
else {
*other_opts = true;
ret = false;
}
break;
case (sizeof(*th)):
/* No options. */
*ppts = NULL;
*other_opts = false;
break;
default:
*ppts = NULL;
*other_opts = true;
ret = false;
break;
}
/* For ACKCMP we only accept ACK, PUSH, ECE and CWR. */
if ((tcp_get_flags(th) & ~(TH_ACK | TH_PUSH | TH_ECE | TH_CWR)) != 0)
ret = false;
/* If it has data on it we cannot compress it */
if (m->m_pkthdr.lro_tcp_d_len)
ret = false;
/* ACK flag must be set. */
if (!(tcp_get_flags(th) & TH_ACK))
ret = false;
return (ret);
}
static bool
tcp_lro_check_wake_status(struct tcpcb *tp)
{
if (tp->t_fb->tfb_early_wake_check != NULL)
return ((tp->t_fb->tfb_early_wake_check)(tp));
return (false);
}
#ifdef TCP_BLACKBOX
static void
tcp_lro_log(struct tcpcb *tp, const struct lro_ctrl *lc,
const struct lro_entry *le, const struct mbuf *m,
int frm, int32_t tcp_data_len, uint32_t th_seq,
uint32_t th_ack, uint16_t th_win)
{
if (tcp_bblogging_on(tp)) {
union tcp_log_stackspecific log;
struct timeval tv, btv;
uint32_t cts;
cts = tcp_get_usecs(&tv);
memset(&log, 0, sizeof(union tcp_log_stackspecific));
log.u_bbr.flex8 = frm;
log.u_bbr.flex1 = tcp_data_len;
if (m)
log.u_bbr.flex2 = m->m_pkthdr.len;
else
log.u_bbr.flex2 = 0;
if (le->m_head) {
log.u_bbr.flex3 = le->m_head->m_pkthdr.lro_nsegs;
log.u_bbr.flex4 = le->m_head->m_pkthdr.lro_tcp_d_len;
log.u_bbr.flex5 = le->m_head->m_pkthdr.len;
log.u_bbr.delRate = le->m_head->m_flags;
log.u_bbr.rttProp = le->m_head->m_pkthdr.rcv_tstmp;
}
log.u_bbr.inflight = th_seq;
log.u_bbr.delivered = th_ack;
log.u_bbr.timeStamp = cts;
log.u_bbr.epoch = le->next_seq;
log.u_bbr.lt_epoch = le->ack_seq;
log.u_bbr.pacing_gain = th_win;
log.u_bbr.cwnd_gain = le->window;
log.u_bbr.lost = curcpu;
log.u_bbr.cur_del_rate = (uintptr_t)m;
log.u_bbr.bw_inuse = (uintptr_t)le->m_head;
bintime2timeval(&lc->lro_last_queue_time, &btv);
log.u_bbr.flex6 = tcp_tv_to_usec(&btv);
log.u_bbr.flex7 = le->compressed;
log.u_bbr.pacing_gain = le->uncompressed;
if (in_epoch(net_epoch_preempt))
log.u_bbr.inhpts = 1;
else
log.u_bbr.inhpts = 0;
TCP_LOG_EVENTP(tp, NULL, &tptosocket(tp)->so_rcv,
&tptosocket(tp)->so_snd,
TCP_LOG_LRO, 0, 0, &log, false, &tv);
}
}
#endif
static struct mbuf *
tcp_lro_get_last_if_ackcmp(struct lro_ctrl *lc, struct lro_entry *le,
struct tcpcb *tp, int32_t *new_m, bool can_append_old_cmp)
{
struct mbuf *m;
/* Look at the last mbuf if any in queue */
if (can_append_old_cmp) {
m = STAILQ_LAST(&tp->t_inqueue, mbuf, m_stailqpkt);
if (m != NULL && (m->m_flags & M_ACKCMP) != 0) {
if (M_TRAILINGSPACE(m) >= sizeof(struct tcp_ackent)) {
#ifdef TCP_BLACKBOX
tcp_lro_log(tp, lc, le, NULL, 23, 0, 0, 0, 0);
#endif
*new_m = 0;
counter_u64_add(tcp_extra_mbuf, 1);
return (m);
} else {
/* Mark we ran out of space */
tp->t_flags2 |= TF2_MBUF_L_ACKS;
}
}
}
/* Decide mbuf size. */
#ifdef TCP_BLACKBOX
tcp_lro_log(tp, lc, le, NULL, 21, 0, 0, 0, 0);
#endif
if (tp->t_flags2 & TF2_MBUF_L_ACKS)
m = m_getcl(M_NOWAIT, MT_DATA, M_ACKCMP | M_PKTHDR);
else
m = m_gethdr(M_NOWAIT, MT_DATA);
if (__predict_false(m == NULL)) {
counter_u64_add(tcp_would_have_but, 1);
return (NULL);
}
counter_u64_add(tcp_comp_total, 1);
m->m_pkthdr.rcvif = lc->ifp;
m->m_flags |= M_ACKCMP;
*new_m = 1;
return (m);
}
/*
* Do BPF tap for either ACK_CMP packets or MBUF QUEUE type packets
* and strip all, but the IPv4/IPv6 header.
*/
static bool
do_bpf_strip_and_compress(struct tcpcb *tp, struct lro_ctrl *lc,
struct lro_entry *le, struct mbuf **pp, struct mbuf **cmp,
struct mbuf **mv_to, bool *should_wake, bool bpf_req, bool lagg_bpf_req,
struct ifnet *lagg_ifp, bool can_append_old_cmp)
{
union {
void *ptr;
struct ip *ip4;
struct ip6_hdr *ip6;
} l3;
struct mbuf *m;
struct mbuf *nm;
struct tcphdr *th;
struct tcp_ackent *ack_ent;
uint32_t *ts_ptr;
int32_t n_mbuf;
bool other_opts, can_compress;
uint8_t lro_type;
uint16_t iptos;
int tcp_hdr_offset;
int idx;
/* Get current mbuf. */
m = *pp;
/* Let the BPF see the packet */
if (__predict_false(bpf_req))
ETHER_BPF_MTAP(lc->ifp, m);
if (__predict_false(lagg_bpf_req))
ETHER_BPF_MTAP(lagg_ifp, m);
tcp_hdr_offset = m->m_pkthdr.lro_tcp_h_off;
lro_type = le->inner.data.lro_type;
switch (lro_type) {
case LRO_TYPE_NONE:
lro_type = le->outer.data.lro_type;
switch (lro_type) {
case LRO_TYPE_IPV4_TCP:
tcp_hdr_offset -= sizeof(*le->outer.ip4);
m->m_pkthdr.lro_etype = ETHERTYPE_IP;
IP_PROBE(receive, NULL, NULL, le->outer.ip4, lc->ifp,
le->outer.ip4, NULL);
break;
case LRO_TYPE_IPV6_TCP:
tcp_hdr_offset -= sizeof(*le->outer.ip6);
m->m_pkthdr.lro_etype = ETHERTYPE_IPV6;
IP_PROBE(receive, NULL, NULL, le->outer.ip6, lc->ifp,
NULL, le->outer.ip6);
break;
default:
goto compressed;
}
break;
case LRO_TYPE_IPV4_TCP:
switch (le->outer.data.lro_type) {
case LRO_TYPE_IPV4_UDP:
IP_PROBE(receive, NULL, NULL, le->outer.ip4, lc->ifp,
le->outer.ip4, NULL);
UDP_PROBE(receive, NULL, NULL, le->outer.ip4, NULL,
le->outer.udp);
break;
case LRO_TYPE_IPV6_UDP:
IP_PROBE(receive, NULL, NULL, le->outer.ip6, lc->ifp,
NULL, le->outer.ip6);
UDP_PROBE(receive, NULL, NULL, le->outer.ip6, NULL,
le->outer.udp);
break;
default:
__assert_unreachable();
break;
}
tcp_hdr_offset -= sizeof(*le->outer.ip4);
m->m_pkthdr.lro_etype = ETHERTYPE_IP;
IP_PROBE(receive, NULL, NULL, le->inner.ip4, NULL,
le->inner.ip4, NULL);
break;
case LRO_TYPE_IPV6_TCP:
switch (le->outer.data.lro_type) {
case LRO_TYPE_IPV4_UDP:
IP_PROBE(receive, NULL, NULL, le->outer.ip4, lc->ifp,
le->outer.ip4, NULL);
UDP_PROBE(receive, NULL, NULL, le->outer.ip4, NULL,
le->outer.udp);
break;
case LRO_TYPE_IPV6_UDP:
IP_PROBE(receive, NULL, NULL, le->outer.ip6, lc->ifp,
NULL, le->outer.ip6);
UDP_PROBE(receive, NULL, NULL, le->outer.ip6, NULL,
le->outer.udp);
break;
default:
__assert_unreachable();
break;
}
tcp_hdr_offset -= sizeof(*le->outer.ip6);
m->m_pkthdr.lro_etype = ETHERTYPE_IPV6;
IP_PROBE(receive, NULL, NULL, le->inner.ip6, NULL, NULL,
le->inner.ip6);
break;
default:
goto compressed;
}
MPASS(tcp_hdr_offset >= 0);
m_adj(m, tcp_hdr_offset);
m->m_flags |= M_LRO_EHDRSTRP;
m->m_flags &= ~M_ACKCMP;
m->m_pkthdr.lro_tcp_h_off -= tcp_hdr_offset;
th = tcp_lro_get_th(m);
th->th_sum = 0; /* TCP checksum is valid. */
tcp_fields_to_host(th);
TCP_PROBE5(receive, NULL, tp, m, tp, th);
/* Check if ACK can be compressed */
can_compress = tcp_lro_ack_valid(m, th, &ts_ptr, &other_opts);
/* Now lets look at the should wake states */
if ((other_opts == true) &&
((tp->t_flags2 & TF2_DONT_SACK_QUEUE) == 0)) {
/*
* If there are other options (SACK?) and the
* tcp endpoint has not expressly told us it does
* not care about SACKS, then we should wake up.
*/
*should_wake = true;
} else if (*should_wake == false) {
/* Wakeup override check if we are false here */
*should_wake = tcp_lro_check_wake_status(tp);
}
/* Is the ack compressable? */
if (can_compress == false)
goto done;
/* Does the TCP endpoint support ACK compression? */
if ((tp->t_flags2 & TF2_MBUF_ACKCMP) == 0)
goto done;
/* Lets get the TOS/traffic class field */
l3.ptr = mtod(m, void *);
switch (lro_type) {
case LRO_TYPE_IPV4_TCP:
iptos = l3.ip4->ip_tos;
break;
case LRO_TYPE_IPV6_TCP:
iptos = IPV6_TRAFFIC_CLASS(l3.ip6);
break;
default:
iptos = 0; /* Keep compiler happy. */
break;
}
/* Now lets get space if we don't have some already */
if (*cmp == NULL) {
new_one:
nm = tcp_lro_get_last_if_ackcmp(lc, le, tp, &n_mbuf,
can_append_old_cmp);
if (__predict_false(nm == NULL))
goto done;
*cmp = nm;
if (n_mbuf) {
/*
* Link in the new cmp ack to our in-order place,
* first set our cmp ack's next to where we are.
*/
nm->m_nextpkt = m;
(*pp) = nm;
/*
* Set it up so mv_to is advanced to our
* compressed ack. This way the caller can
* advance pp to the right place.
*/
*mv_to = nm;
/*
* Advance it here locally as well.
*/
pp = &nm->m_nextpkt;
}
} else {
/* We have one already we are working on */
nm = *cmp;
if (M_TRAILINGSPACE(nm) < sizeof(struct tcp_ackent)) {
/* We ran out of space */
tp->t_flags2 |= TF2_MBUF_L_ACKS;
goto new_one;
}
}
MPASS(M_TRAILINGSPACE(nm) >= sizeof(struct tcp_ackent));
counter_u64_add(tcp_inp_lro_compressed, 1);
le->compressed++;
/* We can add in to the one on the tail */
ack_ent = mtod(nm, struct tcp_ackent *);
idx = (nm->m_len / sizeof(struct tcp_ackent));
build_ack_entry(&ack_ent[idx], th, m, ts_ptr, iptos);
/* Bump the size of both pkt-hdr and len */
nm->m_len += sizeof(struct tcp_ackent);
nm->m_pkthdr.len += sizeof(struct tcp_ackent);
compressed:
/* Advance to next mbuf before freeing. */
*pp = m->m_nextpkt;
m->m_nextpkt = NULL;
m_freem(m);
return (true);
done:
counter_u64_add(tcp_uncomp_total, 1);
le->uncompressed++;
return (false);
}
static void
tcp_queue_pkts(struct tcpcb *tp, struct lro_entry *le)
{
INP_WLOCK_ASSERT(tptoinpcb(tp));
STAILQ_HEAD(, mbuf) q = { le->m_head,
&STAILQ_NEXT(le->m_last_mbuf, m_stailqpkt) };
STAILQ_CONCAT(&tp->t_inqueue, &q);
le->m_head = NULL;
le->m_last_mbuf = NULL;
}
static struct tcpcb *
tcp_lro_lookup(struct ifnet *ifp, struct lro_parser *pa)
{
struct inpcb *inp;
CURVNET_ASSERT_SET();
switch (pa->data.lro_type) {
#ifdef INET6
case LRO_TYPE_IPV6_TCP:
inp = in6_pcblookup(&V_tcbinfo,
&pa->data.s_addr.v6,
pa->data.s_port,
&pa->data.d_addr.v6,
pa->data.d_port,
INPLOOKUP_WLOCKPCB,
ifp);
break;
#endif
#ifdef INET
case LRO_TYPE_IPV4_TCP:
inp = in_pcblookup(&V_tcbinfo,
pa->data.s_addr.v4,
pa->data.s_port,
pa->data.d_addr.v4,
pa->data.d_port,
INPLOOKUP_WLOCKPCB,
ifp);
break;
#endif
default:
return (NULL);
}
return (intotcpcb(inp));
}
static int
_tcp_lro_flush_tcphpts(struct lro_ctrl *lc, struct lro_entry *le)
{
struct tcpcb *tp;
struct mbuf **pp, *cmp, *mv_to;
struct ifnet *lagg_ifp;
bool bpf_req, lagg_bpf_req, should_wake, can_append_old_cmp;
/* Check if packet doesn't belongs to our network interface. */
if ((tcplro_stacks_wanting_mbufq == 0) ||
(le->outer.data.vlan_id != 0) ||
(le->inner.data.lro_type != LRO_TYPE_NONE))
return (TCP_LRO_CANNOT);
#ifdef INET6
/*
* Be proactive about unspecified IPv6 address in source. As
* we use all-zero to indicate unbounded/unconnected pcb,
* unspecified IPv6 address can be used to confuse us.
*
* Note that packets with unspecified IPv6 destination is
* already dropped in ip6_input.
*/
if (__predict_false(le->outer.data.lro_type == LRO_TYPE_IPV6_TCP &&
IN6_IS_ADDR_UNSPECIFIED(&le->outer.data.s_addr.v6)))
return (TCP_LRO_CANNOT);
if (__predict_false(le->inner.data.lro_type == LRO_TYPE_IPV6_TCP &&
IN6_IS_ADDR_UNSPECIFIED(&le->inner.data.s_addr.v6)))
return (TCP_LRO_CANNOT);
#endif
CURVNET_SET(lc->ifp->if_vnet);
/*
* Ensure that there are no packet filter hooks which would normally
* being triggered in ether_demux(), ip_input(), or ip6_input().
*/
if (
#ifdef INET
PFIL_HOOKED_IN(V_inet_pfil_head) ||
#endif
#ifdef INET6
PFIL_HOOKED_IN(V_inet6_pfil_head) ||
#endif
PFIL_HOOKED_IN(V_link_pfil_head)) {
CURVNET_RESTORE();
return (TCP_LRO_CANNOT);
}
/* Lookup inp, if any. Returns locked TCP inpcb. */
tp = tcp_lro_lookup(lc->ifp,
(le->inner.data.lro_type == LRO_TYPE_NONE) ? &le->outer : &le->inner);
CURVNET_RESTORE();
if (tp == NULL)
return (TCP_LRO_CANNOT);
counter_u64_add(tcp_inp_lro_locks_taken, 1);
/* Check if the inp is dead, Jim. */
if (tp->t_state == TCPS_TIME_WAIT) {
INP_WUNLOCK(tptoinpcb(tp));
return (TCP_LRO_CANNOT);
}
if (tp->t_lro_cpu == HPTS_CPU_NONE && lc->lro_cpu_is_set == 1)
tp->t_lro_cpu = lc->lro_last_cpu;
/* Check if the transport doesn't support the needed optimizations. */
if ((tp->t_flags2 & (TF2_SUPPORTS_MBUFQ | TF2_MBUF_ACKCMP)) == 0) {
INP_WUNLOCK(tptoinpcb(tp));
return (TCP_LRO_CANNOT);
}
if (tp->t_flags2 & TF2_MBUF_QUEUE_READY)
should_wake = false;
else
should_wake = true;
/* Check if packets should be tapped to BPF. */
bpf_req = bpf_peers_present(lc->ifp->if_bpf);
lagg_bpf_req = false;
lagg_ifp = NULL;
if (lc->ifp->if_type == IFT_IEEE8023ADLAG ||
lc->ifp->if_type == IFT_INFINIBANDLAG) {
struct lagg_port *lp = lc->ifp->if_lagg;
struct lagg_softc *sc = lp->lp_softc;
lagg_ifp = sc->sc_ifp;
if (lagg_ifp != NULL)
lagg_bpf_req = bpf_peers_present(lagg_ifp->if_bpf);
}
/* Strip and compress all the incoming packets. */
can_append_old_cmp = true;
cmp = NULL;
for (pp = &le->m_head; *pp != NULL; ) {
mv_to = NULL;
if (do_bpf_strip_and_compress(tp, lc, le, pp, &cmp, &mv_to,
&should_wake, bpf_req, lagg_bpf_req, lagg_ifp,
can_append_old_cmp) == false) {
/* Advance to next mbuf. */
pp = &(*pp)->m_nextpkt;
/*
* Once we have appended we can't look in the pending
* inbound packets for a compressed ack to append to.
*/
can_append_old_cmp = false;
/*
* Once we append we also need to stop adding to any
* compressed ack we were remembering. A new cmp
* ack will be required.
*/
cmp = NULL;
#ifdef TCP_BLACKBOX
tcp_lro_log(tp, lc, le, NULL, 25, 0, 0, 0, 0);
#endif
} else if (mv_to != NULL) {
/* We are asked to move pp up */
pp = &mv_to->m_nextpkt;
#ifdef TCP_BLACKBOX
tcp_lro_log(tp, lc, le, NULL, 24, 0, 0, 0, 0);
} else
tcp_lro_log(tp, lc, le, NULL, 26, 0, 0, 0, 0);
#else
}
#endif
}
/* Update "m_last_mbuf", if any. */
if (pp == &le->m_head)
le->m_last_mbuf = *pp;
else
le->m_last_mbuf = __containerof(pp, struct mbuf, m_nextpkt);
/* Check if any data mbufs left. */
if (le->m_head != NULL) {
counter_u64_add(tcp_inp_lro_direct_queue, 1);
#ifdef TCP_BLACKBOX
tcp_lro_log(tp, lc, le, NULL, 22, 1, tp->t_flags2, 0, 1);
#endif
tcp_queue_pkts(tp, le);
}
if (should_wake) {
/* Wakeup */
counter_u64_add(tcp_inp_lro_wokeup_queue, 1);
if ((*tp->t_fb->tfb_do_queued_segments)(tp, 0))
/* TCP cb gone and unlocked. */
return (0);
}
INP_WUNLOCK(tptoinpcb(tp));
return (0); /* Success. */
}
void
tcp_lro_hpts_init(void)
{
tcp_hpts_softclock = _tcp_hpts_softclock;
tcp_lro_flush_tcphpts = _tcp_lro_flush_tcphpts;
}
void
tcp_lro_hpts_uninit(void)
{
tcp_hpts_softclock = NULL;
tcp_lro_flush_tcphpts = NULL;
}