#include "opt_inet.h"
#include "opt_inet6.h"
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/sysctl.h>
#include <sys/malloc.h>
#include <sys/mbuf.h>
#include <sys/md5.h>
#include <sys/proc.h>
#include <sys/random.h>
#include <sys/socket.h>
#include <sys/socketvar.h>
#include <sys/in_cksum.h>
#include <sys/msgport2.h>
#include <net/netmsg2.h>
#include <net/netisr2.h>
#include <net/if.h>
#include <net/route.h>
#include <netinet/in.h>
#include <netinet/in_systm.h>
#include <netinet/ip.h>
#include <netinet/in_var.h>
#include <netinet/in_pcb.h>
#include <netinet/ip_var.h>
#include <netinet/ip6.h>
#ifdef INET6
#include <netinet/icmp6.h>
#include <netinet6/nd6.h>
#endif
#include <netinet6/ip6_var.h>
#include <netinet6/in6_pcb.h>
#include <netinet/tcp.h>
#include <netinet/tcp_fsm.h>
#include <netinet/tcp_seq.h>
#include <netinet/tcp_timer.h>
#include <netinet/tcp_timer2.h>
#include <netinet/tcp_var.h>
#include <netinet6/tcp6_var.h>
static int tcp_syncookies = 1;
SYSCTL_INT(_net_inet_tcp, OID_AUTO, syncookies, CTLFLAG_RW,
&tcp_syncookies, 0,
"Use TCP SYN cookies if the syncache overflows");
static void syncache_drop(struct syncache *, struct syncache_head *);
static void syncache_free(struct syncache *);
static void syncache_insert(struct syncache *, struct syncache_head *);
static struct syncache *syncache_lookup(struct in_conninfo *,
struct syncache_head **);
static int syncache_respond(struct syncache *, struct mbuf *);
static struct socket *syncache_socket(struct syncache *, struct socket *,
struct mbuf *);
static void syncache_timer(void *);
static u_int32_t syncookie_generate(struct syncache *);
static struct syncache *syncookie_lookup(struct in_conninfo *,
struct tcphdr *, struct socket *);
#define SYNCACHE_MAXREXMTS 4
#define TCP_SYNCACHE_HASHSIZE 512
#define TCP_SYNCACHE_BUCKETLIMIT 30
static void syncache_timer_handler(netmsg_t);
static int syncache_sysctl_count(SYSCTL_HANDLER_ARGS);
struct tcp_syncache {
u_int hashsize;
u_int hashmask;
u_int bucket_limit;
u_int cache_limit;
u_int rexmt_limit;
u_int hash_secret;
};
static struct tcp_syncache tcp_syncache;
struct syncache_timerq {
TAILQ_HEAD(, syncache) list;
struct callout timeo;
struct netmsg_base nm;
};
struct tcp_syncache_percpu {
struct syncache_head *hashbase;
u_int cache_count;
struct syncache_timerq timerq[SYNCACHE_MAXREXMTS + 1];
};
static struct tcp_syncache_percpu *tcp_syncache_percpu[MAXCPU];
SYSCTL_NODE(_net_inet_tcp, OID_AUTO, syncache, CTLFLAG_RW, 0, "TCP SYN cache");
SYSCTL_INT(_net_inet_tcp_syncache, OID_AUTO, bucketlimit, CTLFLAG_RD,
&tcp_syncache.bucket_limit, 0, "Per-bucket hash limit for syncache");
SYSCTL_INT(_net_inet_tcp_syncache, OID_AUTO, cachelimit, CTLFLAG_RD,
&tcp_syncache.cache_limit, 0, "Overall entry limit for syncache");
SYSCTL_PROC(_net_inet_tcp_syncache, OID_AUTO, count, (CTLTYPE_INT | CTLFLAG_RD),
0, 0, syncache_sysctl_count, "I", "Current number of entries in syncache");
SYSCTL_INT(_net_inet_tcp_syncache, OID_AUTO, hashsize, CTLFLAG_RD,
&tcp_syncache.hashsize, 0, "Size of TCP syncache hashtable");
SYSCTL_INT(_net_inet_tcp_syncache, OID_AUTO, rexmtlimit, CTLFLAG_RW,
&tcp_syncache.rexmt_limit, 0, "Limit on SYN/ACK retransmissions");
static MALLOC_DEFINE(M_SYNCACHE, "syncache", "TCP syncache");
#define SYNCACHE_HASH(inc, mask) \
((tcp_syncache.hash_secret ^ \
(inc)->inc_faddr.s_addr ^ \
((inc)->inc_faddr.s_addr >> 16) ^ \
(inc)->inc_fport ^ (inc)->inc_lport) & mask)
#define SYNCACHE_HASH6(inc, mask) \
((tcp_syncache.hash_secret ^ \
(inc)->inc6_faddr.s6_addr32[0] ^ \
(inc)->inc6_faddr.s6_addr32[3] ^ \
(inc)->inc_fport ^ (inc)->inc_lport) & mask)
#define ENDPTS_EQ(a, b) ( \
(a)->ie_fport == (b)->ie_fport && \
(a)->ie_lport == (b)->ie_lport && \
(a)->ie_faddr.s_addr == (b)->ie_faddr.s_addr && \
(a)->ie_laddr.s_addr == (b)->ie_laddr.s_addr \
)
#define ENDPTS6_EQ(a, b) (memcmp(a, b, sizeof(*a)) == 0)
static __inline int
syncache_rto(int slot)
{
if (tcp_low_rtobase)
return (TCPTV_RTOBASE * tcp_syn_backoff_low[slot]);
else
return (TCPTV_RTOBASE * tcp_syn_backoff[slot]);
}
static __inline void
syncache_timeout(struct tcp_syncache_percpu *syncache_percpu,
struct syncache *sc, int slot)
{
struct syncache_timerq *tq;
int rto;
KASSERT(slot <= SYNCACHE_MAXREXMTS,
("syncache: invalid slot %d", slot));
if (slot > 0) {
sc->sc_rxtused += syncache_rto(slot - 1);
}
sc->sc_rxtslot = slot;
rto = syncache_rto(slot);
sc->sc_rxttime = ticks + rto;
tq = &syncache_percpu->timerq[slot];
TAILQ_INSERT_TAIL(&tq->list, sc, sc_timerq);
if (!callout_active(&tq->timeo))
callout_reset(&tq->timeo, rto, syncache_timer, &tq->nm);
}
static void
syncache_free(struct syncache *sc)
{
struct rtentry *rt;
#ifdef INET6
const boolean_t isipv6 = sc->sc_inc.inc_isipv6;
#else
const boolean_t isipv6 = FALSE;
#endif
if (sc->sc_ipopts)
m_free(sc->sc_ipopts);
rt = isipv6 ? sc->sc_route6.ro_rt : sc->sc_route.ro_rt;
if (rt != NULL) {
if ((rt->rt_flags & (RTF_WASCLONED | RTF_LLINFO)) ==
RTF_WASCLONED && rt->rt_refcnt == 1) {
rtrequest(RTM_DELETE, rt_key(rt), rt->rt_gateway,
rt_mask(rt), rt->rt_flags, NULL);
}
RTFREE(rt);
}
kfree(sc, M_SYNCACHE);
}
static void
syncache_init_dispatch(netmsg_t nm)
{
struct tcp_syncache_percpu *syncache_percpu;
int i;
ASSERT_NETISR_NCPUS(mycpuid);
syncache_percpu = kmalloc(sizeof(*syncache_percpu), M_SYNCACHE,
M_WAITOK | M_ZERO);
syncache_percpu->hashbase = kmalloc(tcp_syncache.hashsize *
sizeof(struct syncache_head),
M_SYNCACHE, M_WAITOK | M_ZERO);
for (i = 0; i < tcp_syncache.hashsize; i++) {
struct syncache_head *bucket;
bucket = &syncache_percpu->hashbase[i];
TAILQ_INIT(&bucket->sch_bucket);
bucket->sch_length = 0;
}
for (i = 0; i <= SYNCACHE_MAXREXMTS; i++) {
struct syncache_timerq *tq =
&syncache_percpu->timerq[i];
TAILQ_INIT(&tq->list);
callout_init_mp(&tq->timeo);
netmsg_init(&tq->nm, NULL, &netisr_adone_rport,
MSGF_PRIORITY, syncache_timer_handler);
tq->nm.lmsg.u.ms_result = i;
}
tcp_syncache_percpu[mycpuid] = syncache_percpu;
netisr_forwardmsg(&nm->base, mycpuid + 1);
}
void
syncache_init(void)
{
struct netmsg_base nm;
tcp_syncache.hashsize = TCP_SYNCACHE_HASHSIZE;
tcp_syncache.bucket_limit = TCP_SYNCACHE_BUCKETLIMIT;
tcp_syncache.cache_limit =
tcp_syncache.hashsize * tcp_syncache.bucket_limit;
tcp_syncache.rexmt_limit = SYNCACHE_MAXREXMTS;
tcp_syncache.hash_secret = karc4random();
TUNABLE_INT_FETCH("net.inet.tcp.syncache.hashsize",
&tcp_syncache.hashsize);
TUNABLE_INT_FETCH("net.inet.tcp.syncache.cachelimit",
&tcp_syncache.cache_limit);
TUNABLE_INT_FETCH("net.inet.tcp.syncache.bucketlimit",
&tcp_syncache.bucket_limit);
if (!powerof2(tcp_syncache.hashsize)) {
kprintf("WARNING: syncache hash size is not a power of 2.\n");
tcp_syncache.hashsize = 512;
}
tcp_syncache.hashmask = tcp_syncache.hashsize - 1;
netmsg_init(&nm, NULL, &curthread->td_msgport, 0,
syncache_init_dispatch);
netisr_domsg_global(&nm);
}
static void
syncache_insert(struct syncache *sc, struct syncache_head *sch)
{
struct tcp_syncache_percpu *syncache_percpu;
struct syncache *sc2;
int i;
syncache_percpu = tcp_syncache_percpu[mycpu->gd_cpuid];
if (sch->sch_length >= tcp_syncache.bucket_limit) {
sc2 = TAILQ_FIRST(&sch->sch_bucket);
if (sc2->sc_tp != NULL)
sc2->sc_tp->ts_recent = ticks;
syncache_drop(sc2, sch);
tcpstat.tcps_sc_bucketoverflow++;
} else if (syncache_percpu->cache_count >= tcp_syncache.cache_limit) {
for (i = SYNCACHE_MAXREXMTS; i >= 0; i--) {
sc2 = TAILQ_FIRST(&syncache_percpu->timerq[i].list);
if (sc2 != NULL)
break;
}
if (sc2->sc_tp != NULL)
sc2->sc_tp->ts_recent = ticks;
syncache_drop(sc2, NULL);
tcpstat.tcps_sc_cacheoverflow++;
}
syncache_timeout(syncache_percpu, sc, 0);
TAILQ_INSERT_TAIL(&sch->sch_bucket, sc, sc_hash);
sch->sch_length++;
syncache_percpu->cache_count++;
tcpstat.tcps_sc_added++;
}
void
syncache_destroy(struct tcpcb *tp, struct tcpcb *tp_inh)
{
struct tcp_syncache_percpu *syncache_percpu;
int i;
ASSERT_NETISR_NCPUS(mycpuid);
syncache_percpu = tcp_syncache_percpu[mycpu->gd_cpuid];
for (i = 0; i < tcp_syncache.hashsize; i++) {
struct syncache_head *bucket;
struct syncache *sc;
bucket = &syncache_percpu->hashbase[i];
TAILQ_FOREACH(sc, &bucket->sch_bucket, sc_hash) {
if (sc->sc_tp == tp)
sc->sc_tp = tp_inh;
}
}
}
static void
syncache_drop(struct syncache *sc, struct syncache_head *sch)
{
struct tcp_syncache_percpu *syncache_percpu;
#ifdef INET6
const boolean_t isipv6 = sc->sc_inc.inc_isipv6;
#else
const boolean_t isipv6 = FALSE;
#endif
syncache_percpu = tcp_syncache_percpu[mycpu->gd_cpuid];
if (sch == NULL) {
if (isipv6) {
sch = &syncache_percpu->hashbase[
SYNCACHE_HASH6(&sc->sc_inc, tcp_syncache.hashmask)];
} else {
sch = &syncache_percpu->hashbase[
SYNCACHE_HASH(&sc->sc_inc, tcp_syncache.hashmask)];
}
}
TAILQ_REMOVE(&sch->sch_bucket, sc, sc_hash);
sch->sch_length--;
syncache_percpu->cache_count--;
sc->sc_tp = NULL;
TAILQ_REMOVE(&syncache_percpu->timerq[sc->sc_rxtslot].list, sc,
sc_timerq);
syncache_free(sc);
}
static void
syncache_timer(void *p)
{
struct netmsg_base *msg = p;
KKASSERT(mycpuid < netisr_ncpus);
crit_enter();
if (msg->lmsg.ms_flags & MSGF_DONE)
netisr_sendmsg_oncpu(msg);
crit_exit();
}
static void
syncache_timer_handler(netmsg_t msg)
{
struct tcp_syncache_percpu *syncache_percpu;
struct syncache *nsc;
struct syncache_timerq *tq;
int slot;
ASSERT_NETISR_NCPUS(mycpuid);
crit_enter();
netisr_replymsg(&msg->base, 0);
crit_exit();
syncache_percpu = tcp_syncache_percpu[mycpu->gd_cpuid];
slot = msg->lmsg.u.ms_result;
KASSERT(slot <= SYNCACHE_MAXREXMTS,
("syncache: invalid slot %d", slot));
tq = &syncache_percpu->timerq[slot];
nsc = TAILQ_FIRST(&tq->list);
while (nsc != NULL) {
struct syncache *sc;
if (ticks < nsc->sc_rxttime)
break;
sc = nsc;
if (sc->sc_tp == NULL) {
nsc = TAILQ_NEXT(sc, sc_timerq);
syncache_drop(sc, NULL);
tcpstat.tcps_sc_stale++;
continue;
}
if (slot == SYNCACHE_MAXREXMTS ||
slot >= tcp_syncache.rexmt_limit ||
sc->sc_tp->t_inpcb->inp_gencnt != sc->sc_inp_gencnt) {
nsc = TAILQ_NEXT(sc, sc_timerq);
syncache_drop(sc, NULL);
tcpstat.tcps_sc_stale++;
continue;
}
syncache_respond(sc, NULL);
tcpstat.tcps_sc_retransmitted++;
nsc = TAILQ_NEXT(sc, sc_timerq);
TAILQ_REMOVE(&tq->list, sc, sc_timerq);
syncache_timeout(syncache_percpu, sc, slot + 1);
}
if (nsc != NULL) {
callout_reset(&tq->timeo, nsc->sc_rxttime - ticks,
syncache_timer, &tq->nm);
} else {
callout_deactivate(&tq->timeo);
}
}
static struct syncache *
syncache_lookup(struct in_conninfo *inc, struct syncache_head **schp)
{
struct tcp_syncache_percpu *syncache_percpu;
struct syncache *sc;
struct syncache_head *sch;
syncache_percpu = tcp_syncache_percpu[mycpu->gd_cpuid];
#ifdef INET6
if (inc->inc_isipv6) {
sch = &syncache_percpu->hashbase[
SYNCACHE_HASH6(inc, tcp_syncache.hashmask)];
*schp = sch;
TAILQ_FOREACH(sc, &sch->sch_bucket, sc_hash)
if (ENDPTS6_EQ(&inc->inc_ie, &sc->sc_inc.inc_ie))
return (sc);
} else
#endif
{
sch = &syncache_percpu->hashbase[
SYNCACHE_HASH(inc, tcp_syncache.hashmask)];
*schp = sch;
TAILQ_FOREACH(sc, &sch->sch_bucket, sc_hash) {
#ifdef INET6
if (sc->sc_inc.inc_isipv6)
continue;
#endif
if (ENDPTS_EQ(&inc->inc_ie, &sc->sc_inc.inc_ie))
return (sc);
}
}
return (NULL);
}
void
syncache_chkrst(struct in_conninfo *inc, struct tcphdr *th)
{
struct syncache *sc;
struct syncache_head *sch;
ASSERT_NETISR_NCPUS(mycpuid);
sc = syncache_lookup(inc, &sch);
if (sc == NULL) {
return;
}
if (SEQ_GEQ(th->th_seq, sc->sc_irs) &&
SEQ_LEQ(th->th_seq, sc->sc_irs + sc->sc_wnd)) {
syncache_drop(sc, sch);
tcpstat.tcps_sc_reset++;
}
}
void
syncache_badack(struct in_conninfo *inc)
{
struct syncache *sc;
struct syncache_head *sch;
ASSERT_NETISR_NCPUS(mycpuid);
sc = syncache_lookup(inc, &sch);
if (sc != NULL) {
syncache_drop(sc, sch);
tcpstat.tcps_sc_badack++;
}
}
void
syncache_unreach(struct in_conninfo *inc, const struct tcphdr *th)
{
struct syncache *sc;
struct syncache_head *sch;
ASSERT_NETISR_NCPUS(mycpuid);
sc = syncache_lookup(inc, &sch);
if (sc == NULL)
return;
if (ntohl(th->th_seq) != sc->sc_iss)
return;
if ((sc->sc_flags & SCF_UNREACH) == 0 || sc->sc_rxtslot < 3) {
sc->sc_flags |= SCF_UNREACH;
return;
}
syncache_drop(sc, sch);
tcpstat.tcps_sc_unreach++;
}
static struct socket *
syncache_socket(struct syncache *sc, struct socket *lso, struct mbuf *m)
{
struct inpcb *inp = NULL, *linp;
struct socket *so;
struct tcpcb *tp, *ltp;
lwkt_port_t port;
#ifdef INET6
const boolean_t isipv6 = sc->sc_inc.inc_isipv6;
#else
const boolean_t isipv6 = FALSE;
#endif
struct sockaddr_in sin_faddr;
struct sockaddr_in6 sin6_faddr;
struct sockaddr *faddr;
KASSERT(m->m_flags & M_HASH, ("mbuf has no hash"));
if (isipv6) {
faddr = (struct sockaddr *)&sin6_faddr;
sin6_faddr.sin6_family = AF_INET6;
sin6_faddr.sin6_len = sizeof(sin6_faddr);
sin6_faddr.sin6_addr = sc->sc_inc.inc6_faddr;
sin6_faddr.sin6_port = sc->sc_inc.inc_fport;
sin6_faddr.sin6_flowinfo = sin6_faddr.sin6_scope_id = 0;
} else {
faddr = (struct sockaddr *)&sin_faddr;
sin_faddr.sin_family = AF_INET;
sin_faddr.sin_len = sizeof(sin_faddr);
sin_faddr.sin_addr = sc->sc_inc.inc_faddr;
sin_faddr.sin_port = sc->sc_inc.inc_fport;
bzero(sin_faddr.sin_zero, sizeof(sin_faddr.sin_zero));
}
so = sonewconn_faddr(lso, SS_ISCONNECTED, faddr,
FALSE );
if (so == NULL) {
tcpstat.tcps_listendrop++;
goto abort;
}
inp = so->so_pcb;
inp->inp_inc.inc_isipv6 = sc->sc_inc.inc_isipv6;
if (isipv6) {
inp->in6p_laddr = sc->sc_inc.inc6_laddr;
} else {
KASSERT(INP_ISIPV4(inp), ("not inet pcb"));
inp->inp_laddr = sc->sc_inc.inc_laddr;
}
inp->inp_lport = sc->sc_inc.inc_lport;
linp = lso->so_pcb;
ltp = intotcpcb(linp);
tcp_pcbport_insert(ltp, inp);
if (isipv6) {
struct in6_addr laddr6;
inp->inp_flags |= linp->inp_flags & INP_CONTROLOPTS;
if (linp->in6p_outputopts)
inp->in6p_outputopts =
ip6_copypktopts(linp->in6p_outputopts, M_INTWAIT);
inp->in6p_route = sc->sc_route6;
sc->sc_route6.ro_rt = NULL;
laddr6 = inp->in6p_laddr;
if (IN6_IS_ADDR_UNSPECIFIED(&inp->in6p_laddr))
inp->in6p_laddr = sc->sc_inc.inc6_laddr;
if (in6_pcbconnect(inp, faddr, &thread0)) {
inp->in6p_laddr = laddr6;
goto abort;
}
port = tcp6_addrport();
} else {
struct in_addr laddr;
inp->inp_options = ip_srcroute(m);
if (inp->inp_options == NULL) {
inp->inp_options = sc->sc_ipopts;
sc->sc_ipopts = NULL;
}
inp->inp_route = sc->sc_route;
sc->sc_route.ro_rt = NULL;
laddr = inp->inp_laddr;
if (inp->inp_laddr.s_addr == INADDR_ANY)
inp->inp_laddr = sc->sc_inc.inc_laddr;
if (in_pcbconnect(inp, faddr, &thread0)) {
inp->inp_laddr = laddr;
goto abort;
}
inp->inp_flags |= INP_HASH;
inp->inp_hashval = m->m_pkthdr.hash;
port = netisr_hashport(inp->inp_hashval);
}
KASSERT(port == &curthread->td_msgport,
("TCP PORT MISMATCH %p vs %p\n", port, &curthread->td_msgport));
tp = intotcpcb(inp);
TCP_STATE_CHANGE(tp, TCPS_SYN_RECEIVED);
tp->iss = sc->sc_iss;
tp->irs = sc->sc_irs;
tcp_rcvseqinit(tp);
tcp_sendseqinit(tp);
tp->snd_wnd = sc->sc_sndwnd;
tp->snd_wl1 = sc->sc_irs;
tp->rcv_up = sc->sc_irs + 1;
tp->rcv_wnd = sc->sc_wnd;
tp->rcv_adv += tp->rcv_wnd;
tp->t_flags = sototcpcb(lso)->t_flags & (TF_NOPUSH | TF_NODELAY);
if (sc->sc_flags & SCF_NOOPT)
tp->t_flags |= TF_NOOPT;
if (sc->sc_flags & SCF_WINSCALE) {
tp->t_flags |= TF_REQ_SCALE | TF_RCVD_SCALE;
tp->snd_scale = sc->sc_requested_s_scale;
tp->request_r_scale = sc->sc_request_r_scale;
}
if (sc->sc_flags & SCF_TIMESTAMP) {
tp->t_flags |= TF_REQ_TSTMP | TF_RCVD_TSTMP;
tp->ts_recent = sc->sc_tsrecent;
tp->ts_recent_age = ticks;
}
if (sc->sc_flags & SCF_SACK_PERMITTED)
tp->t_flags |= TF_SACK_PERMITTED;
#ifdef TCP_SIGNATURE
if (sc->sc_flags & SCF_SIGNATURE)
tp->t_flags |= TF_SIGNATURE;
#endif
tp->t_rxtsyn = sc->sc_rxtused;
tcp_rmx_init(tp, sc->sc_peer_mss);
tp->t_keepinit = ltp->t_keepinit;
tp->t_keepidle = ltp->t_keepidle;
tp->t_keepintvl = ltp->t_keepintvl;
tp->t_keepcnt = ltp->t_keepcnt;
tp->t_maxidle = ltp->t_maxidle;
tcp_create_timermsg(tp, port);
tcp_callout_reset(tp, tp->tt_keep, tp->t_keepinit, tcp_timer_keep);
tcpstat.tcps_accepts++;
return (so);
abort:
if (so != NULL)
soabort_direct(so);
return (NULL);
}
int
syncache_expand(struct in_conninfo *inc, struct tcphdr *th, struct socket **sop,
struct mbuf *m)
{
struct syncache *sc;
struct syncache_head *sch;
struct socket *so;
ASSERT_NETISR_NCPUS(mycpuid);
sc = syncache_lookup(inc, &sch);
if (sc == NULL) {
if (!tcp_syncookies)
return (0);
sc = syncookie_lookup(inc, th, *sop);
if (sc == NULL)
return (0);
sch = NULL;
tcpstat.tcps_sc_recvcookie++;
}
if (th->th_ack != sc->sc_iss + 1)
return (0);
so = syncache_socket(sc, *sop, m);
if (so == NULL) {
#if 0
resetandabort:
tcp_respond(NULL, m, m, th,
th->th_seq + tlen, (tcp_seq)0, TH_RST | TH_ACK);
#endif
m_freem(m);
tcpstat.tcps_sc_aborted++;
} else {
tcpstat.tcps_sc_completed++;
}
if (sch == NULL)
syncache_free(sc);
else
syncache_drop(sc, sch);
*sop = so;
return (1);
}
int
syncache_add(struct in_conninfo *inc, struct tcpopt *to, struct tcphdr *th,
struct socket *so, struct mbuf *m)
{
struct tcp_syncache_percpu *syncache_percpu;
struct tcpcb *tp;
struct syncache *sc = NULL;
struct syncache_head *sch;
struct mbuf *ipopts = NULL;
int win;
ASSERT_NETISR_NCPUS(mycpuid);
KASSERT(m->m_flags & M_HASH, ("mbuf has no hash"));
syncache_percpu = tcp_syncache_percpu[mycpu->gd_cpuid];
tp = sototcpcb(so);
#ifdef INET6
if (!inc->inc_isipv6)
#endif
ipopts = ip_srcroute(m);
sc = syncache_lookup(inc, &sch);
if (sc != NULL) {
KASSERT(sc->sc_flags & SCF_HASH, ("syncache has no hash"));
KASSERT(sc->sc_hashval == m->m_pkthdr.hash,
("syncache/mbuf hash mismatches"));
tcpstat.tcps_sc_dupsyn++;
if (ipopts) {
if (sc->sc_ipopts)
m_free(sc->sc_ipopts);
sc->sc_ipopts = ipopts;
}
if (sc->sc_flags & SCF_TIMESTAMP)
sc->sc_tsrecent = to->to_tsval;
if (tcp_do_sack && (to->to_flags & TOF_SACK_PERMITTED))
sc->sc_flags |= SCF_SACK_PERMITTED;
else
sc->sc_flags &= ~SCF_SACK_PERMITTED;
sc->sc_sndwnd = th->th_win;
sc->sc_tp = tp;
sc->sc_inp_gencnt = tp->t_inpcb->inp_gencnt;
if (syncache_respond(sc, m) == 0) {
TAILQ_REMOVE(
&syncache_percpu->timerq[sc->sc_rxtslot].list,
sc, sc_timerq);
syncache_timeout(syncache_percpu, sc, sc->sc_rxtslot);
tcpstat.tcps_sndacks++;
tcpstat.tcps_sndtotal++;
}
return (1);
}
sc = kmalloc(sizeof(struct syncache), M_SYNCACHE, M_WAITOK|M_ZERO);
sc->sc_inp_gencnt = tp->t_inpcb->inp_gencnt;
sc->sc_ipopts = ipopts;
sc->sc_inc.inc_fport = inc->inc_fport;
sc->sc_inc.inc_lport = inc->inc_lport;
sc->sc_tp = tp;
#ifdef INET6
sc->sc_inc.inc_isipv6 = inc->inc_isipv6;
if (inc->inc_isipv6) {
sc->sc_inc.inc6_faddr = inc->inc6_faddr;
sc->sc_inc.inc6_laddr = inc->inc6_laddr;
sc->sc_route6.ro_rt = NULL;
} else
#endif
{
sc->sc_inc.inc_faddr = inc->inc_faddr;
sc->sc_inc.inc_laddr = inc->inc_laddr;
sc->sc_route.ro_rt = NULL;
}
sc->sc_irs = th->th_seq;
sc->sc_flags = SCF_HASH;
sc->sc_hashval = m->m_pkthdr.hash;
sc->sc_peer_mss = to->to_flags & TOF_MSS ? to->to_mss : 0;
if (tcp_syncookies)
sc->sc_iss = syncookie_generate(sc);
else
sc->sc_iss = karc4random();
win = ssb_space(&so->so_rcv);
win = imax(win, 0);
win = imin(win, TCP_MAXWIN);
sc->sc_wnd = win;
if (tcp_do_rfc1323) {
if (to->to_flags & TOF_TS) {
sc->sc_tsrecent = to->to_tsval;
sc->sc_flags |= SCF_TIMESTAMP;
}
if (to->to_flags & TOF_SCALE) {
int wscale = TCP_MIN_WINSHIFT;
while (wscale < TCP_MAX_WINSHIFT &&
(TCP_MAXWIN << wscale) < so->so_rcv.ssb_hiwat) {
wscale++;
}
sc->sc_request_r_scale = wscale;
sc->sc_requested_s_scale = to->to_requested_s_scale;
sc->sc_flags |= SCF_WINSCALE;
}
}
if (tcp_do_sack && (to->to_flags & TOF_SACK_PERMITTED))
sc->sc_flags |= SCF_SACK_PERMITTED;
if (tp->t_flags & TF_NOOPT)
sc->sc_flags = SCF_NOOPT;
#ifdef TCP_SIGNATURE
if (to->to_flags & TOF_SIGNATURE)
sc->sc_flags = SCF_SIGNATURE;
#endif
sc->sc_sndwnd = th->th_win;
if (syncache_respond(sc, m) == 0) {
syncache_insert(sc, sch);
tcpstat.tcps_sndacks++;
tcpstat.tcps_sndtotal++;
} else {
syncache_free(sc);
tcpstat.tcps_sc_dropped++;
}
return (1);
}
static int
syncache_respond(struct syncache *sc, struct mbuf *m)
{
u_int8_t *optp;
int optlen, error;
u_int16_t tlen, hlen, mssopt;
struct ip *ip = NULL;
struct rtentry *rt;
struct tcphdr *th;
struct ip6_hdr *ip6 = NULL;
#ifdef INET6
const boolean_t isipv6 = sc->sc_inc.inc_isipv6;
#else
const boolean_t isipv6 = FALSE;
#endif
if (isipv6) {
rt = tcp_rtlookup6(&sc->sc_inc);
if (rt != NULL)
mssopt = rt->rt_ifp->if_mtu -
(sizeof(struct ip6_hdr) + sizeof(struct tcphdr));
else
mssopt = tcp_v6mssdflt;
hlen = sizeof(struct ip6_hdr);
} else {
rt = tcp_rtlookup(&sc->sc_inc);
if (rt != NULL)
mssopt = rt->rt_ifp->if_mtu -
(sizeof(struct ip) + sizeof(struct tcphdr));
else
mssopt = tcp_mssdflt;
hlen = sizeof(struct ip);
}
if (sc->sc_flags & SCF_NOOPT) {
optlen = 0;
} else {
optlen = TCPOLEN_MAXSEG +
((sc->sc_flags & SCF_WINSCALE) ? 4 : 0) +
((sc->sc_flags & SCF_TIMESTAMP) ? TCPOLEN_TSTAMP_APPA : 0) +
((sc->sc_flags & SCF_SACK_PERMITTED) ?
TCPOLEN_SACK_PERMITTED_ALIGNED : 0);
#ifdef TCP_SIGNATURE
optlen += ((sc->sc_flags & SCF_SIGNATURE) ?
(TCPOLEN_SIGNATURE + 2) : 0);
#endif
}
tlen = hlen + sizeof(struct tcphdr) + optlen;
KASSERT(max_linkhdr + tlen <= MHLEN, ("syncache: mbuf too small"));
if (m)
m_freem(m);
m = m_gethdr(M_NOWAIT, MT_HEADER);
if (m == NULL)
return (ENOBUFS);
m->m_data += max_linkhdr;
m->m_len = tlen;
m->m_pkthdr.len = tlen;
m->m_pkthdr.rcvif = NULL;
if (tcp_prio_synack)
m->m_flags |= M_PRIO;
if (isipv6) {
ip6 = mtod(m, struct ip6_hdr *);
ip6->ip6_vfc = IPV6_VERSION;
ip6->ip6_nxt = IPPROTO_TCP;
ip6->ip6_src = sc->sc_inc.inc6_laddr;
ip6->ip6_dst = sc->sc_inc.inc6_faddr;
ip6->ip6_plen = htons(tlen - hlen);
th = (struct tcphdr *)(ip6 + 1);
} else {
ip = mtod(m, struct ip *);
ip->ip_v = IPVERSION;
ip->ip_hl = sizeof(struct ip) >> 2;
ip->ip_len = htons(tlen);
ip->ip_id = 0;
ip->ip_off = 0;
ip->ip_sum = 0;
ip->ip_p = IPPROTO_TCP;
ip->ip_src = sc->sc_inc.inc_laddr;
ip->ip_dst = sc->sc_inc.inc_faddr;
ip->ip_ttl = sc->sc_tp->t_inpcb->inp_ip_ttl;
ip->ip_tos = sc->sc_tp->t_inpcb->inp_ip_tos;
if (path_mtu_discovery
&& ((sc->sc_flags & SCF_UNREACH) == 0))
{
ip->ip_off |= htons(IP_DF);
}
th = (struct tcphdr *)(ip + 1);
}
th->th_sport = sc->sc_inc.inc_lport;
th->th_dport = sc->sc_inc.inc_fport;
th->th_seq = htonl(sc->sc_iss);
th->th_ack = htonl(sc->sc_irs + 1);
th->th_off = (sizeof(struct tcphdr) + optlen) >> 2;
th->th_x2 = 0;
th->th_flags = TH_SYN | TH_ACK;
th->th_win = htons(sc->sc_wnd);
th->th_urp = 0;
if (optlen == 0)
goto no_options;
optp = (u_int8_t *)(th + 1);
*optp++ = TCPOPT_MAXSEG;
*optp++ = TCPOLEN_MAXSEG;
*optp++ = (mssopt >> 8) & 0xff;
*optp++ = mssopt & 0xff;
if (sc->sc_flags & SCF_WINSCALE) {
*((u_int32_t *)optp) = htonl(TCPOPT_NOP << 24 |
TCPOPT_WINDOW << 16 | TCPOLEN_WINDOW << 8 |
sc->sc_request_r_scale);
optp += 4;
}
if (sc->sc_flags & SCF_TIMESTAMP) {
u_int32_t *lp = (u_int32_t *)(optp);
*lp++ = htonl(TCPOPT_TSTAMP_HDR);
*lp++ = htonl(ticks);
*lp = htonl(sc->sc_tsrecent);
optp += TCPOLEN_TSTAMP_APPA;
}
#ifdef TCP_SIGNATURE
if (sc->sc_flags & SCF_SIGNATURE) {
u_int8_t *bp = optp;
int i;
*bp++ = TCPOPT_SIGNATURE;
*bp++ = TCPOLEN_SIGNATURE;
for (i = 0; i < TCP_SIGLEN; i++)
*bp++ = 0;
tcpsignature_compute(m, 0, optlen,
optp + 2, IPSEC_DIR_OUTBOUND);
*bp++ = TCPOPT_NOP;
*bp++ = TCPOPT_EOL;
optp += TCPOLEN_SIGNATURE + 2;
}
#endif
if (sc->sc_flags & SCF_SACK_PERMITTED) {
*((u_int32_t *)optp) = htonl(TCPOPT_SACK_PERMITTED_ALIGNED);
optp += TCPOLEN_SACK_PERMITTED_ALIGNED;
}
no_options:
if (isipv6) {
struct route_in6 *ro6 = &sc->sc_route6;
th->th_sum = 0;
th->th_sum = in6_cksum(m, IPPROTO_TCP, hlen, tlen - hlen);
ip6->ip6_hlim = in6_selecthlim(NULL,
ro6->ro_rt ? ro6->ro_rt->rt_ifp : NULL);
error = ip6_output(m, NULL, ro6, 0, NULL, NULL,
sc->sc_tp->t_inpcb);
} else {
th->th_sum = in_pseudo(ip->ip_src.s_addr, ip->ip_dst.s_addr,
htons(tlen - hlen + IPPROTO_TCP));
m->m_pkthdr.csum_flags = CSUM_TCP;
m->m_pkthdr.csum_data = offsetof(struct tcphdr, th_sum);
m->m_pkthdr.csum_thlen = sizeof(struct tcphdr) + optlen;
KASSERT(sc->sc_flags & SCF_HASH, ("syncache has no hash"));
m_sethash(m, sc->sc_hashval);
error = ip_output(m, sc->sc_ipopts, &sc->sc_route,
IP_DEBUGROUTE, NULL, sc->sc_tp->t_inpcb);
}
return (error);
}
#define SYNCOOKIE_WNDBITS 5
#define SYNCOOKIE_TIMESHIFT 1
#define SYNCOOKIE_WNDMASK ((1 << SYNCOOKIE_WNDBITS) - 1)
#define SYNCOOKIE_NSECRETS (1 << SYNCOOKIE_WNDBITS)
#define SYNCOOKIE_TIMEOUT \
(hz * (1 << SYNCOOKIE_WNDBITS) / (1 << SYNCOOKIE_TIMESHIFT))
#define SYNCOOKIE_DATAMASK ((3 << SYNCOOKIE_WNDBITS) | SYNCOOKIE_WNDMASK)
static struct {
u_int32_t ts_secbits[4];
u_int ts_expire;
} tcp_secret[SYNCOOKIE_NSECRETS];
static int tcp_msstab[] = { 0, 536, 1460, 8960 };
static MD5_CTX syn_ctx;
#define MD5Add(v) MD5Update(&syn_ctx, (u_char *)&v, sizeof(v))
struct md5_add {
u_int32_t laddr, faddr;
u_int32_t secbits[4];
u_int16_t lport, fport;
};
#ifdef CTASSERT
CTASSERT(sizeof(struct md5_add) == 28);
#endif
static u_int32_t
syncookie_generate(struct syncache *sc)
{
u_int32_t md5_buffer[4];
u_int32_t data;
int idx, i;
struct md5_add add;
#ifdef INET6
const boolean_t isipv6 = sc->sc_inc.inc_isipv6;
#else
const boolean_t isipv6 = FALSE;
#endif
idx = ((ticks << SYNCOOKIE_TIMESHIFT) / hz) & SYNCOOKIE_WNDMASK;
if (tcp_secret[idx].ts_expire < ticks) {
for (i = 0; i < 4; i++)
tcp_secret[idx].ts_secbits[i] = karc4random();
tcp_secret[idx].ts_expire = ticks + SYNCOOKIE_TIMEOUT;
}
for (data = NELEM(tcp_msstab) - 1; data > 0; data--)
if (tcp_msstab[data] <= sc->sc_peer_mss)
break;
data = (data << SYNCOOKIE_WNDBITS) | idx;
data ^= sc->sc_irs;
MD5Init(&syn_ctx);
if (isipv6) {
MD5Add(sc->sc_inc.inc6_laddr);
MD5Add(sc->sc_inc.inc6_faddr);
add.laddr = 0;
add.faddr = 0;
} else {
add.laddr = sc->sc_inc.inc_laddr.s_addr;
add.faddr = sc->sc_inc.inc_faddr.s_addr;
}
add.lport = sc->sc_inc.inc_lport;
add.fport = sc->sc_inc.inc_fport;
add.secbits[0] = tcp_secret[idx].ts_secbits[0];
add.secbits[1] = tcp_secret[idx].ts_secbits[1];
add.secbits[2] = tcp_secret[idx].ts_secbits[2];
add.secbits[3] = tcp_secret[idx].ts_secbits[3];
MD5Add(add);
MD5Final((u_char *)&md5_buffer, &syn_ctx);
data ^= (md5_buffer[0] & ~SYNCOOKIE_WNDMASK);
return (data);
}
static struct syncache *
syncookie_lookup(struct in_conninfo *inc, struct tcphdr *th, struct socket *so)
{
u_int32_t md5_buffer[4];
struct syncache *sc;
u_int32_t data;
int wnd, idx;
struct md5_add add;
data = (th->th_ack - 1) ^ (th->th_seq - 1);
idx = data & SYNCOOKIE_WNDMASK;
if (tcp_secret[idx].ts_expire < ticks ||
sototcpcb(so)->ts_recent + SYNCOOKIE_TIMEOUT < ticks)
return (NULL);
MD5Init(&syn_ctx);
#ifdef INET6
if (inc->inc_isipv6) {
MD5Add(inc->inc6_laddr);
MD5Add(inc->inc6_faddr);
add.laddr = 0;
add.faddr = 0;
} else
#endif
{
add.laddr = inc->inc_laddr.s_addr;
add.faddr = inc->inc_faddr.s_addr;
}
add.lport = inc->inc_lport;
add.fport = inc->inc_fport;
add.secbits[0] = tcp_secret[idx].ts_secbits[0];
add.secbits[1] = tcp_secret[idx].ts_secbits[1];
add.secbits[2] = tcp_secret[idx].ts_secbits[2];
add.secbits[3] = tcp_secret[idx].ts_secbits[3];
MD5Add(add);
MD5Final((u_char *)&md5_buffer, &syn_ctx);
data ^= md5_buffer[0];
if (data & ~SYNCOOKIE_DATAMASK)
return (NULL);
data = data >> SYNCOOKIE_WNDBITS;
sc = kmalloc(sizeof(struct syncache), M_SYNCACHE, M_WAITOK|M_ZERO);
sc->sc_ipopts = NULL;
sc->sc_inc.inc_fport = inc->inc_fport;
sc->sc_inc.inc_lport = inc->inc_lport;
#ifdef INET6
sc->sc_inc.inc_isipv6 = inc->inc_isipv6;
if (inc->inc_isipv6) {
sc->sc_inc.inc6_faddr = inc->inc6_faddr;
sc->sc_inc.inc6_laddr = inc->inc6_laddr;
sc->sc_route6.ro_rt = NULL;
} else
#endif
{
sc->sc_inc.inc_faddr = inc->inc_faddr;
sc->sc_inc.inc_laddr = inc->inc_laddr;
sc->sc_route.ro_rt = NULL;
}
sc->sc_irs = th->th_seq - 1;
sc->sc_iss = th->th_ack - 1;
wnd = ssb_space(&so->so_rcv);
wnd = imax(wnd, 0);
wnd = imin(wnd, TCP_MAXWIN);
sc->sc_wnd = wnd;
sc->sc_flags = 0;
sc->sc_rxtslot = 0;
sc->sc_peer_mss = tcp_msstab[data];
return (sc);
}
static int
syncache_sysctl_count(SYSCTL_HANDLER_ARGS)
{
u_int count = 0;
int cpu;
for (cpu = 0; cpu < netisr_ncpus; ++cpu)
count += tcp_syncache_percpu[cpu]->cache_count;
return sysctl_handle_int(oidp, &count, 0, req);
}