#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: tcp_syncache.c,v 1.7 2024/06/29 12:59:08 riastradh Exp $");
#ifdef _KERNEL_OPT
#include "opt_inet.h"
#include "opt_ipsec.h"
#endif
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/mbuf.h>
#include <sys/protosw.h>
#include <sys/socket.h>
#include <sys/socketvar.h>
#include <sys/errno.h>
#include <sys/syslog.h>
#include <sys/pool.h>
#include <sys/domain.h>
#include <sys/kernel.h>
#include <sys/lwp.h>
#include <sys/cprng.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/in_pcb.h>
#include <netinet/in_var.h>
#include <netinet/ip_var.h>
#include <netinet/ip6.h>
#ifdef INET6
#include <netinet6/ip6_var.h>
#include <netinet6/in6_pcb.h>
#include <netinet6/ip6_var.h>
#include <netinet6/in6_var.h>
#endif
#include <netinet/tcp.h>
#include <netinet/tcp_fsm.h>
#include <netinet/tcp_seq.h>
#include <netinet/tcp_timer.h>
#include <netinet/tcp_var.h>
#include <netinet/tcp_private.h>
#include <netinet/tcp_syncache.h>
#ifdef TCP_SIGNATURE
#ifdef IPSEC
#include <netipsec/ipsec.h>
#include <netipsec/key.h>
#ifdef INET6
#include <netipsec/ipsec6.h>
#endif
#endif
#endif
static void syn_cache_timer(void *);
static struct syn_cache *
syn_cache_lookup(const struct sockaddr *, const struct sockaddr *,
struct syn_cache_head **);
static int syn_cache_respond(struct syn_cache *);
#define TCP_SYN_HASH_SIZE 293
#define TCP_SYN_BUCKET_SIZE 35
static int tcp_syn_cache_size = TCP_SYN_HASH_SIZE;
int tcp_syn_cache_limit = TCP_SYN_HASH_SIZE*TCP_SYN_BUCKET_SIZE;
int tcp_syn_bucket_limit = 3*TCP_SYN_BUCKET_SIZE;
static struct syn_cache_head tcp_syn_cache[TCP_SYN_HASH_SIZE];
u_long syn_cache_count;
static u_int32_t syn_hash1, syn_hash2;
#define SYN_HASH(sa, sp, dp) \
((((sa)->s_addr^syn_hash1)*(((((u_int32_t)(dp))<<16) + \
((u_int32_t)(sp)))^syn_hash2)))
#ifndef INET6
#define SYN_HASHALL(hash, src, dst) \
do { \
hash = SYN_HASH(&((const struct sockaddr_in *)(src))->sin_addr, \
((const struct sockaddr_in *)(src))->sin_port, \
((const struct sockaddr_in *)(dst))->sin_port); \
} while ( 0)
#else
#define SYN_HASH6(sa, sp, dp) \
((((sa)->s6_addr32[0] ^ (sa)->s6_addr32[3] ^ syn_hash1) * \
(((((u_int32_t)(dp))<<16) + ((u_int32_t)(sp)))^syn_hash2)) \
& 0x7fffffff)
#define SYN_HASHALL(hash, src, dst) \
do { \
switch ((src)->sa_family) { \
case AF_INET: \
hash = SYN_HASH(&((const struct sockaddr_in *)(src))->sin_addr, \
((const struct sockaddr_in *)(src))->sin_port, \
((const struct sockaddr_in *)(dst))->sin_port); \
break; \
case AF_INET6: \
hash = SYN_HASH6(&((const struct sockaddr_in6 *)(src))->sin6_addr, \
((const struct sockaddr_in6 *)(src))->sin6_port, \
((const struct sockaddr_in6 *)(dst))->sin6_port); \
break; \
default: \
hash = 0; \
} \
} while (0)
#endif
static struct pool syn_cache_pool;
static inline void
syn_cache_timer_arm(struct syn_cache *sc)
{
TCPT_RANGESET(sc->sc_rxtcur,
TCPTV_SRTTDFLT * tcp_backoff[sc->sc_rxtshift], TCPTV_MIN,
TCPTV_REXMTMAX);
callout_reset(&sc->sc_timer,
sc->sc_rxtcur * (hz / PR_SLOWHZ), syn_cache_timer, sc);
}
#define SYN_CACHE_TIMESTAMP(sc) (tcp_now - (sc)->sc_timebase)
static inline void
syn_cache_rm(struct syn_cache *sc)
{
TAILQ_REMOVE(&tcp_syn_cache[sc->sc_bucketidx].sch_bucket,
sc, sc_bucketq);
sc->sc_tp = NULL;
LIST_REMOVE(sc, sc_tpq);
tcp_syn_cache[sc->sc_bucketidx].sch_length--;
callout_stop(&sc->sc_timer);
syn_cache_count--;
}
static inline void
syn_cache_put(struct syn_cache *sc)
{
if (sc->sc_ipopts)
(void) m_free(sc->sc_ipopts);
rtcache_free(&sc->sc_route);
sc->sc_flags |= SCF_DEAD;
if (!callout_invoking(&sc->sc_timer))
callout_schedule(&(sc)->sc_timer, 1);
}
void
syn_cache_init(void)
{
int i;
pool_init(&syn_cache_pool, sizeof(struct syn_cache), 0, 0, 0,
"synpl", NULL, IPL_SOFTNET);
for (i = 0; i < tcp_syn_cache_size; i++)
TAILQ_INIT(&tcp_syn_cache[i].sch_bucket);
}
void
syn_cache_insert(struct syn_cache *sc, struct tcpcb *tp)
{
struct syn_cache_head *scp;
struct syn_cache *sc2;
int s;
if (syn_cache_count == 0) {
syn_hash1 = cprng_fast32();
syn_hash2 = cprng_fast32();
}
SYN_HASHALL(sc->sc_hash, &sc->sc_src.sa, &sc->sc_dst.sa);
sc->sc_bucketidx = sc->sc_hash % tcp_syn_cache_size;
scp = &tcp_syn_cache[sc->sc_bucketidx];
s = splsoftnet();
if (scp->sch_length >= tcp_syn_bucket_limit) {
TCP_STATINC(TCP_STAT_SC_BUCKETOVERFLOW);
sc2 = TAILQ_FIRST(&scp->sch_bucket);
#ifdef DIAGNOSTIC
if (sc2 == NULL)
panic("syn_cache_insert: bucketoverflow: impossible");
#endif
syn_cache_rm(sc2);
syn_cache_put(sc2);
} else if (syn_cache_count >= tcp_syn_cache_limit) {
struct syn_cache_head *scp2, *sce;
TCP_STATINC(TCP_STAT_SC_OVERFLOWED);
scp2 = scp;
if (TAILQ_EMPTY(&scp2->sch_bucket)) {
sce = &tcp_syn_cache[tcp_syn_cache_size];
for (++scp2; scp2 != scp; scp2++) {
if (scp2 >= sce)
scp2 = &tcp_syn_cache[0];
if (! TAILQ_EMPTY(&scp2->sch_bucket))
break;
}
#ifdef DIAGNOSTIC
if (scp2 == scp)
panic("syn_cache_insert: cacheoverflow: "
"impossible");
#endif
}
sc2 = TAILQ_FIRST(&scp2->sch_bucket);
syn_cache_rm(sc2);
syn_cache_put(sc2);
}
sc->sc_rxttot = 0;
sc->sc_rxtshift = 0;
syn_cache_timer_arm(sc);
LIST_INSERT_HEAD(&tp->t_sc, sc, sc_tpq);
TAILQ_INSERT_TAIL(&scp->sch_bucket, sc, sc_bucketq);
scp->sch_length++;
syn_cache_count++;
TCP_STATINC(TCP_STAT_SC_ADDED);
splx(s);
}
static void
syn_cache_timer(void *arg)
{
struct syn_cache *sc = arg;
mutex_enter(softnet_lock);
KERNEL_LOCK(1, NULL);
callout_ack(&sc->sc_timer);
if (__predict_false(sc->sc_flags & SCF_DEAD)) {
TCP_STATINC(TCP_STAT_SC_DELAYED_FREE);
goto free;
}
if (__predict_false(sc->sc_rxtshift == TCP_MAXRXTSHIFT)) {
goto dropit;
}
sc->sc_rxttot += sc->sc_rxtcur;
if (sc->sc_rxttot >= MIN(tcp_keepinit, TCP_TIMER_MAXTICKS))
goto dropit;
TCP_STATINC(TCP_STAT_SC_RETRANSMITTED);
(void)syn_cache_respond(sc);
sc->sc_rxtshift++;
syn_cache_timer_arm(sc);
goto out;
dropit:
TCP_STATINC(TCP_STAT_SC_TIMED_OUT);
syn_cache_rm(sc);
if (sc->sc_ipopts)
(void) m_free(sc->sc_ipopts);
rtcache_free(&sc->sc_route);
free:
callout_destroy(&sc->sc_timer);
pool_put(&syn_cache_pool, sc);
out:
KERNEL_UNLOCK_ONE(NULL);
mutex_exit(softnet_lock);
}
void
syn_cache_cleanup(struct tcpcb *tp)
{
struct syn_cache *sc, *nsc;
int s;
s = splsoftnet();
for (sc = LIST_FIRST(&tp->t_sc); sc != NULL; sc = nsc) {
nsc = LIST_NEXT(sc, sc_tpq);
#ifdef DIAGNOSTIC
if (sc->sc_tp != tp)
panic("invalid sc_tp in syn_cache_cleanup");
#endif
syn_cache_rm(sc);
syn_cache_put(sc);
}
LIST_INIT(&tp->t_sc);
splx(s);
}
static struct syn_cache *
syn_cache_lookup(const struct sockaddr *src, const struct sockaddr *dst,
struct syn_cache_head **headp)
{
struct syn_cache *sc;
struct syn_cache_head *scp;
u_int32_t hash;
int s;
SYN_HASHALL(hash, src, dst);
scp = &tcp_syn_cache[hash % tcp_syn_cache_size];
*headp = scp;
s = splsoftnet();
for (sc = TAILQ_FIRST(&scp->sch_bucket); sc != NULL;
sc = TAILQ_NEXT(sc, sc_bucketq)) {
if (sc->sc_hash != hash)
continue;
if (!memcmp(&sc->sc_src, src, src->sa_len) &&
!memcmp(&sc->sc_dst, dst, dst->sa_len)) {
splx(s);
return (sc);
}
}
splx(s);
return (NULL);
}
struct socket *
syn_cache_get(struct sockaddr *src, struct sockaddr *dst,
struct tcphdr *th, struct socket *so, struct mbuf *m)
{
struct syn_cache *sc;
struct syn_cache_head *scp;
struct inpcb *inp = NULL;
struct tcpcb *tp;
int s;
struct socket *oso;
s = splsoftnet();
if ((sc = syn_cache_lookup(src, dst, &scp)) == NULL) {
splx(s);
return NULL;
}
if ((th->th_ack != sc->sc_iss + 1) ||
SEQ_LEQ(th->th_seq, sc->sc_irs) ||
SEQ_GT(th->th_seq, sc->sc_irs + 1 + sc->sc_win)) {
m_freem(m);
(void)syn_cache_respond(sc);
splx(s);
return ((struct socket *)(-1));
}
syn_cache_rm(sc);
splx(s);
oso = so;
so = sonewconn(so, true);
if (so == NULL)
goto resetandabort;
inp = sotoinpcb(so);
switch (src->sa_family) {
case AF_INET:
if (inp->inp_af == AF_INET) {
in4p_laddr(inp) = ((struct sockaddr_in *)dst)->sin_addr;
inp->inp_lport = ((struct sockaddr_in *)dst)->sin_port;
inp->inp_options = ip_srcroute(m);
inpcb_set_state(inp, INP_BOUND);
if (inp->inp_options == NULL) {
inp->inp_options = sc->sc_ipopts;
sc->sc_ipopts = NULL;
}
}
#ifdef INET6
else if (inp->inp_af == AF_INET6) {
memset(&in6p_laddr(inp), 0, sizeof(in6p_laddr(inp)));
in6p_laddr(inp).s6_addr16[5] = htons(0xffff);
bcopy(&((struct sockaddr_in *)dst)->sin_addr,
&in6p_laddr(inp).s6_addr32[3],
sizeof(((struct sockaddr_in *)dst)->sin_addr));
inp->inp_lport = ((struct sockaddr_in *)dst)->sin_port;
intotcpcb(inp)->t_family = AF_INET;
if (sotoinpcb(oso)->inp_flags & IN6P_IPV6_V6ONLY)
inp->inp_flags |= IN6P_IPV6_V6ONLY;
else
inp->inp_flags &= ~IN6P_IPV6_V6ONLY;
inpcb_set_state(inp, INP_BOUND);
}
#endif
break;
#ifdef INET6
case AF_INET6:
if (inp->inp_af == AF_INET6) {
in6p_laddr(inp) = ((struct sockaddr_in6 *)dst)->sin6_addr;
inp->inp_lport = ((struct sockaddr_in6 *)dst)->sin6_port;
inpcb_set_state(inp, INP_BOUND);
}
break;
#endif
}
#ifdef INET6
if (inp && intotcpcb(inp)->t_family == AF_INET6 && sotoinpcb(oso)) {
struct inpcb *oinp = sotoinpcb(oso);
inp->inp_flags |= (oinp->inp_flags & IN6P_CONTROLOPTS);
if (inp->inp_flags & IN6P_CONTROLOPTS) {
m_freem(inp->inp_options);
inp->inp_options = NULL;
}
ip6_savecontrol(inp, &inp->inp_options,
mtod(m, struct ip6_hdr *), m);
}
#endif
rtcache_copy(&inp->inp_route, &sc->sc_route);
rtcache_free(&sc->sc_route);
if (inp->inp_af == AF_INET) {
struct sockaddr_in sin;
memcpy(&sin, src, src->sa_len);
if (inpcb_connect(inp, &sin, &lwp0)) {
goto resetandabort;
}
}
#ifdef INET6
else if (inp->inp_af == AF_INET6) {
struct sockaddr_in6 sin6;
memcpy(&sin6, src, src->sa_len);
if (src->sa_family == AF_INET) {
in6_sin_2_v4mapsin6((struct sockaddr_in *)src, &sin6);
}
if (in6pcb_connect(inp, &sin6, NULL)) {
goto resetandabort;
}
}
#endif
else {
goto resetandabort;
}
tp = intotcpcb(inp);
tp->t_flags = sototcpcb(oso)->t_flags & TF_NODELAY;
if (sc->sc_request_r_scale != 15) {
tp->requested_s_scale = sc->sc_requested_s_scale;
tp->request_r_scale = sc->sc_request_r_scale;
tp->snd_scale = sc->sc_requested_s_scale;
tp->rcv_scale = sc->sc_request_r_scale;
tp->t_flags |= TF_REQ_SCALE|TF_RCVD_SCALE;
}
if (sc->sc_flags & SCF_TIMESTAMP)
tp->t_flags |= TF_REQ_TSTMP|TF_RCVD_TSTMP;
tp->ts_timebase = sc->sc_timebase;
tp->t_template = tcp_template(tp);
if (tp->t_template == 0) {
tp = tcp_drop(tp, ENOBUFS);
so = NULL;
m_freem(m);
goto abort;
}
tp->iss = sc->sc_iss;
tp->irs = sc->sc_irs;
tcp_sendseqinit(tp);
tcp_rcvseqinit(tp);
tp->t_state = TCPS_SYN_RECEIVED;
TCP_TIMER_ARM(tp, TCPT_KEEP, tp->t_keepinit);
TCP_STATINC(TCP_STAT_ACCEPTS);
if ((sc->sc_flags & SCF_SACK_PERMIT) && tcp_do_sack)
tp->t_flags |= TF_WILL_SACK;
if ((sc->sc_flags & SCF_ECN_PERMIT) && tcp_do_ecn)
tp->t_flags |= TF_ECN_PERMIT;
#ifdef TCP_SIGNATURE
if (sc->sc_flags & SCF_SIGNATURE)
tp->t_flags |= TF_SIGNATURE;
#endif
tp->t_ourmss = sc->sc_ourmaxseg;
tcp_mss_from_peer(tp, sc->sc_peermaxseg);
if (sc->sc_rxtshift)
tp->snd_cwnd = tp->t_peermss;
else {
int ss = tcp_init_win;
if (inp->inp_af == AF_INET && in_localaddr(in4p_faddr(inp)))
ss = tcp_init_win_local;
#ifdef INET6
else if (inp->inp_af == AF_INET6 && in6_localaddr(&in6p_faddr(inp)))
ss = tcp_init_win_local;
#endif
tp->snd_cwnd = TCP_INITIAL_WINDOW(ss, tp->t_peermss);
}
tcp_rmx_rtt(tp);
tp->snd_wl1 = sc->sc_irs;
tp->rcv_up = sc->sc_irs + 1;
tp->snd_up = tp->snd_una;
tp->snd_max = tp->snd_nxt = tp->iss+1;
TCP_TIMER_ARM(tp, TCPT_REXMT, tp->t_rxtcur);
if (sc->sc_win > 0 && SEQ_GT(tp->rcv_nxt + sc->sc_win, tp->rcv_adv))
tp->rcv_adv = tp->rcv_nxt + sc->sc_win;
tp->last_ack_sent = tp->rcv_nxt;
tp->t_partialacks = -1;
tp->t_dupacks = 0;
TCP_STATINC(TCP_STAT_SC_COMPLETED);
s = splsoftnet();
syn_cache_put(sc);
splx(s);
return so;
resetandabort:
(void)tcp_respond(NULL, m, m, th, (tcp_seq)0, th->th_ack, TH_RST);
abort:
if (so != NULL) {
(void) soqremque(so, 1);
(void) soabort(so);
mutex_enter(softnet_lock);
}
s = splsoftnet();
syn_cache_put(sc);
splx(s);
TCP_STATINC(TCP_STAT_SC_ABORTED);
return ((struct socket *)(-1));
}
void
syn_cache_reset(struct sockaddr *src, struct sockaddr *dst, struct tcphdr *th)
{
struct syn_cache *sc;
struct syn_cache_head *scp;
int s = splsoftnet();
if ((sc = syn_cache_lookup(src, dst, &scp)) == NULL) {
splx(s);
return;
}
if (SEQ_LT(th->th_seq, sc->sc_irs) ||
SEQ_GT(th->th_seq, sc->sc_irs+1)) {
splx(s);
return;
}
syn_cache_rm(sc);
TCP_STATINC(TCP_STAT_SC_RESET);
syn_cache_put(sc);
splx(s);
}
void
syn_cache_unreach(const struct sockaddr *src, const struct sockaddr *dst,
struct tcphdr *th)
{
struct syn_cache *sc;
struct syn_cache_head *scp;
int s;
s = splsoftnet();
if ((sc = syn_cache_lookup(src, dst, &scp)) == NULL) {
splx(s);
return;
}
if (ntohl(th->th_seq) != sc->sc_iss) {
splx(s);
return;
}
if ((sc->sc_flags & SCF_UNREACH) == 0 || sc->sc_rxtshift < 3) {
sc->sc_flags |= SCF_UNREACH;
splx(s);
return;
}
syn_cache_rm(sc);
TCP_STATINC(TCP_STAT_SC_UNREACH);
syn_cache_put(sc);
splx(s);
}
int
syn_cache_add(struct sockaddr *src, struct sockaddr *dst, struct tcphdr *th,
unsigned int toff, struct socket *so, struct mbuf *m, u_char *optp,
int optlen, struct tcp_opt_info *oi)
{
struct tcpcb tb, *tp;
long win;
struct syn_cache *sc;
struct syn_cache_head *scp;
struct mbuf *ipopts;
int s;
tp = sototcpcb(so);
win = sbspace(&so->so_rcv);
if (win > TCP_MAXWIN)
win = TCP_MAXWIN;
#ifdef TCP_SIGNATURE
if (optp || (tp->t_flags & TF_SIGNATURE))
#else
if (optp)
#endif
{
tb.t_flags = tcp_do_rfc1323 ? (TF_REQ_SCALE|TF_REQ_TSTMP) : 0;
#ifdef TCP_SIGNATURE
tb.t_flags |= (tp->t_flags & TF_SIGNATURE);
#endif
tb.t_state = TCPS_LISTEN;
if (tcp_dooptions(&tb, optp, optlen, th, m, toff, oi) < 0)
return 0;
} else
tb.t_flags = 0;
switch (src->sa_family) {
case AF_INET:
ipopts = ip_srcroute(m);
break;
default:
ipopts = NULL;
}
if ((sc = syn_cache_lookup(src, dst, &scp)) != NULL) {
TCP_STATINC(TCP_STAT_SC_DUPESYN);
if (ipopts) {
if (sc->sc_ipopts)
(void)m_free(sc->sc_ipopts);
sc->sc_ipopts = ipopts;
}
sc->sc_timestamp = tb.ts_recent;
m_freem(m);
if (syn_cache_respond(sc) == 0) {
net_stat_ref_t tcps = TCP_STAT_GETREF();
_NET_STATINC_REF(tcps, TCP_STAT_SNDACKS);
_NET_STATINC_REF(tcps, TCP_STAT_SNDTOTAL);
TCP_STAT_PUTREF();
}
return 1;
}
s = splsoftnet();
sc = pool_get(&syn_cache_pool, PR_NOWAIT);
splx(s);
if (sc == NULL) {
if (ipopts)
(void)m_free(ipopts);
return 0;
}
memset(sc, 0, sizeof(struct syn_cache));
callout_init(&sc->sc_timer, CALLOUT_MPSAFE);
memcpy(&sc->sc_src, src, src->sa_len);
memcpy(&sc->sc_dst, dst, dst->sa_len);
sc->sc_flags = 0;
sc->sc_ipopts = ipopts;
sc->sc_irs = th->th_seq;
switch (src->sa_family) {
case AF_INET:
{
struct sockaddr_in *srcin = (void *)src;
struct sockaddr_in *dstin = (void *)dst;
sc->sc_iss = tcp_new_iss1(&dstin->sin_addr,
&srcin->sin_addr, dstin->sin_port,
srcin->sin_port, sizeof(dstin->sin_addr));
break;
}
#ifdef INET6
case AF_INET6:
{
struct sockaddr_in6 *srcin6 = (void *)src;
struct sockaddr_in6 *dstin6 = (void *)dst;
sc->sc_iss = tcp_new_iss1(&dstin6->sin6_addr,
&srcin6->sin6_addr, dstin6->sin6_port,
srcin6->sin6_port, sizeof(dstin6->sin6_addr));
break;
}
#endif
}
sc->sc_peermaxseg = oi->maxseg;
sc->sc_ourmaxseg = tcp_mss_to_advertise(m->m_flags & M_PKTHDR ?
m_get_rcvif_NOMPSAFE(m) : NULL, sc->sc_src.sa.sa_family);
sc->sc_win = win;
sc->sc_timebase = tcp_now - 1;
sc->sc_timestamp = tb.ts_recent;
if ((tb.t_flags & (TF_REQ_TSTMP|TF_RCVD_TSTMP)) ==
(TF_REQ_TSTMP|TF_RCVD_TSTMP))
sc->sc_flags |= SCF_TIMESTAMP;
if ((tb.t_flags & (TF_RCVD_SCALE|TF_REQ_SCALE)) ==
(TF_RCVD_SCALE|TF_REQ_SCALE)) {
sc->sc_requested_s_scale = tb.requested_s_scale;
sc->sc_request_r_scale = 0;
while (sc->sc_request_r_scale < TCP_MAX_WINSHIFT &&
(TCP_MAXWIN << sc->sc_request_r_scale) < sb_max)
sc->sc_request_r_scale++;
} else {
sc->sc_requested_s_scale = 15;
sc->sc_request_r_scale = 15;
}
if ((tb.t_flags & TF_SACK_PERMIT) && tcp_do_sack)
sc->sc_flags |= SCF_SACK_PERMIT;
if ((th->th_flags & (TH_ECE|TH_CWR)) && tcp_do_ecn)
sc->sc_flags |= SCF_ECN_PERMIT;
#ifdef TCP_SIGNATURE
if (tb.t_flags & TF_SIGNATURE)
sc->sc_flags |= SCF_SIGNATURE;
#endif
sc->sc_tp = tp;
m_freem(m);
if (syn_cache_respond(sc) == 0) {
net_stat_ref_t tcps = TCP_STAT_GETREF();
_NET_STATINC_REF(tcps, TCP_STAT_SNDACKS);
_NET_STATINC_REF(tcps, TCP_STAT_SNDTOTAL);
TCP_STAT_PUTREF();
syn_cache_insert(sc, tp);
} else {
s = splsoftnet();
syn_cache_timer_arm(sc);
syn_cache_put(sc);
splx(s);
TCP_STATINC(TCP_STAT_SC_DROPPED);
}
return 1;
}
static int
syn_cache_respond(struct syn_cache *sc)
{
#ifdef INET6
struct rtentry *rt = NULL;
#endif
struct route *ro;
u_int8_t *optp;
int optlen, error;
u_int16_t tlen;
struct ip *ip = NULL;
#ifdef INET6
struct ip6_hdr *ip6 = NULL;
#endif
struct tcpcb *tp;
struct tcphdr *th;
struct mbuf *m;
u_int hlen;
#ifdef TCP_SIGNATURE
struct secasvar *sav = NULL;
u_int8_t *sigp = NULL;
#endif
ro = &sc->sc_route;
switch (sc->sc_src.sa.sa_family) {
case AF_INET:
hlen = sizeof(struct ip);
break;
#ifdef INET6
case AF_INET6:
hlen = sizeof(struct ip6_hdr);
break;
#endif
default:
return EAFNOSUPPORT;
}
tlen = hlen + sizeof(struct tcphdr) + MAX_TCPOPTLEN;
KASSERT(max_linkhdr + tlen <= MCLBYTES);
MGETHDR(m, M_DONTWAIT, MT_DATA);
if (m && (max_linkhdr + tlen) > MHLEN) {
MCLGET(m, M_DONTWAIT);
if ((m->m_flags & M_EXT) == 0) {
m_freem(m);
m = NULL;
}
}
if (m == NULL)
return ENOBUFS;
MCLAIM(m, &tcp_tx_mowner);
tp = sc->sc_tp;
m->m_data += max_linkhdr;
m_reset_rcvif(m);
memset(mtod(m, void *), 0, tlen);
switch (sc->sc_src.sa.sa_family) {
case AF_INET:
ip = mtod(m, struct ip *);
ip->ip_v = 4;
ip->ip_dst = sc->sc_src.sin.sin_addr;
ip->ip_src = sc->sc_dst.sin.sin_addr;
ip->ip_p = IPPROTO_TCP;
th = (struct tcphdr *)(ip + 1);
th->th_dport = sc->sc_src.sin.sin_port;
th->th_sport = sc->sc_dst.sin.sin_port;
break;
#ifdef INET6
case AF_INET6:
ip6 = mtod(m, struct ip6_hdr *);
ip6->ip6_vfc = IPV6_VERSION;
ip6->ip6_dst = sc->sc_src.sin6.sin6_addr;
ip6->ip6_src = sc->sc_dst.sin6.sin6_addr;
ip6->ip6_nxt = IPPROTO_TCP;
th = (struct tcphdr *)(ip6 + 1);
th->th_dport = sc->sc_src.sin6.sin6_port;
th->th_sport = sc->sc_dst.sin6.sin6_port;
break;
#endif
default:
panic("%s: impossible (1)", __func__);
}
th->th_seq = htonl(sc->sc_iss);
th->th_ack = htonl(sc->sc_irs + 1);
th->th_flags = TH_SYN|TH_ACK;
th->th_win = htons(sc->sc_win);
optp = (u_int8_t *)(th + 1);
optlen = 0;
*optp++ = TCPOPT_MAXSEG;
*optp++ = TCPOLEN_MAXSEG;
*optp++ = (sc->sc_ourmaxseg >> 8) & 0xff;
*optp++ = sc->sc_ourmaxseg & 0xff;
optlen += TCPOLEN_MAXSEG;
if (sc->sc_request_r_scale != 15) {
*((u_int32_t *)optp) = htonl(TCPOPT_NOP << 24 |
TCPOPT_WINDOW << 16 | TCPOLEN_WINDOW << 8 |
sc->sc_request_r_scale);
optp += TCPOLEN_WINDOW + TCPOLEN_NOP;
optlen += TCPOLEN_WINDOW + TCPOLEN_NOP;
}
if (sc->sc_flags & SCF_SACK_PERMIT) {
*optp++ = TCPOPT_SACK_PERMITTED;
*optp++ = TCPOLEN_SACK_PERMITTED;
optlen += TCPOLEN_SACK_PERMITTED;
}
if (sc->sc_flags & SCF_TIMESTAMP) {
while (optlen % 4 != 2) {
optlen += TCPOLEN_NOP;
*optp++ = TCPOPT_NOP;
}
*optp++ = TCPOPT_TIMESTAMP;
*optp++ = TCPOLEN_TIMESTAMP;
u_int32_t *lp = (u_int32_t *)(optp);
*lp++ = htonl(SYN_CACHE_TIMESTAMP(sc));
*lp = htonl(sc->sc_timestamp);
optp += TCPOLEN_TIMESTAMP - 2;
optlen += TCPOLEN_TIMESTAMP;
}
#ifdef TCP_SIGNATURE
if (sc->sc_flags & SCF_SIGNATURE) {
sav = tcp_signature_getsav(m);
if (sav == NULL) {
m_freem(m);
return EPERM;
}
*optp++ = TCPOPT_SIGNATURE;
*optp++ = TCPOLEN_SIGNATURE;
sigp = optp;
memset(optp, 0, TCP_SIGLEN);
optp += TCP_SIGLEN;
optlen += TCPOLEN_SIGNATURE;
}
#endif
if (optlen % 4) {
optlen += TCPOLEN_EOL;
*optp++ = TCPOPT_EOL;
}
while (optlen % 4) {
optlen += TCPOLEN_PAD;
*optp++ = TCPOPT_PAD;
}
tlen = hlen + sizeof(struct tcphdr) + optlen;
m->m_len = m->m_pkthdr.len = tlen;
th->th_off = (sizeof(struct tcphdr) + optlen) >> 2;
#ifdef TCP_SIGNATURE
if (sav) {
(void)tcp_signature(m, th, hlen, sav, sigp);
key_sa_recordxfer(sav, m);
KEY_SA_UNREF(&sav);
}
#endif
if ((sc->sc_flags & SCF_ECN_PERMIT) && tp &&
SEQ_GEQ(tp->snd_nxt, tp->snd_max)) {
th->th_flags |= TH_ECE;
TCP_STATINC(TCP_STAT_ECN_SHS);
switch (sc->sc_src.sa.sa_family) {
case AF_INET:
ip->ip_tos |= IPTOS_ECN_ECT0;
break;
#ifdef INET6
case AF_INET6:
ip6->ip6_flow |= htonl(IPTOS_ECN_ECT0 << 20);
break;
#endif
}
TCP_STATINC(TCP_STAT_ECN_ECT);
}
switch (sc->sc_src.sa.sa_family) {
case AF_INET:
ip->ip_len = htons(tlen - hlen);
th->th_sum = 0;
th->th_sum = in4_cksum(m, IPPROTO_TCP, hlen, tlen - hlen);
ip->ip_len = htons(tlen);
ip->ip_ttl = ip_defttl;
break;
#ifdef INET6
case AF_INET6:
ip6->ip6_plen = htons(tlen - hlen);
th->th_sum = 0;
th->th_sum = in6_cksum(m, IPPROTO_TCP, hlen, tlen - hlen);
ip6->ip6_vfc &= ~IPV6_VERSION_MASK;
ip6->ip6_vfc |= IPV6_VERSION;
ip6->ip6_plen = htons(tlen - hlen);
break;
#endif
}
tp = sc->sc_tp;
switch (sc->sc_src.sa.sa_family) {
case AF_INET:
error = ip_output(m, sc->sc_ipopts, ro,
(ip_mtudisc ? IP_MTUDISC : 0),
NULL, tp ? tp->t_inpcb : NULL);
break;
#ifdef INET6
case AF_INET6:
ip6->ip6_hlim = in6pcb_selecthlim(NULL,
(rt = rtcache_validate(ro)) != NULL ? rt->rt_ifp : NULL);
rtcache_unref(rt, ro);
error = ip6_output(m, NULL , ro, 0, NULL,
tp ? tp->t_inpcb : NULL, NULL);
break;
#endif
default:
panic("%s: impossible (2)", __func__);
}
return error;
}