mirror of
https://github.com/freebsd/freebsd-src.git
synced 2026-10-28 11:25:21 +01:00
This hack introduced ind7955cc0ffande3cbc572f1proved 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
678 lines
18 KiB
C
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;
|
|
}
|