Changeset View
Changeset View
Standalone View
Standalone View
sys/netinet/tcp_syncache.c
Show First 20 Lines • Show All 74 Lines • ▼ Show 20 Lines | |||||
#ifdef INET6 | #ifdef INET6 | ||||
#include <netinet/ip6.h> | #include <netinet/ip6.h> | ||||
#include <netinet/icmp6.h> | #include <netinet/icmp6.h> | ||||
#include <netinet6/nd6.h> | #include <netinet6/nd6.h> | ||||
#include <netinet6/ip6_var.h> | #include <netinet6/ip6_var.h> | ||||
#include <netinet6/in6_pcb.h> | #include <netinet6/in6_pcb.h> | ||||
#endif | #endif | ||||
#include <netinet/tcp.h> | #include <netinet/tcp.h> | ||||
#ifdef TCP_RFC7413 | |||||
#include <netinet/tcp_fastopen.h> | |||||
#endif | |||||
#include <netinet/tcp_fsm.h> | #include <netinet/tcp_fsm.h> | ||||
#include <netinet/tcp_seq.h> | #include <netinet/tcp_seq.h> | ||||
#include <netinet/tcp_timer.h> | #include <netinet/tcp_timer.h> | ||||
#include <netinet/tcp_var.h> | #include <netinet/tcp_var.h> | ||||
#include <netinet/tcp_syncache.h> | #include <netinet/tcp_syncache.h> | ||||
#ifdef INET6 | #ifdef INET6 | ||||
#include <netinet6/tcp6_var.h> | #include <netinet6/tcp6_var.h> | ||||
#endif | #endif | ||||
▲ Show 20 Lines • Show All 965 Lines • ▼ Show 20 Lines | failed: | ||||
if (sc != NULL && sc != &scs) | if (sc != NULL && sc != &scs) | ||||
syncache_free(sc); | syncache_free(sc); | ||||
if (s != NULL) | if (s != NULL) | ||||
free(s, M_TCPLOG); | free(s, M_TCPLOG); | ||||
*lsop = NULL; | *lsop = NULL; | ||||
return (0); | return (0); | ||||
} | } | ||||
#ifdef TCP_RFC7413 | |||||
static void | |||||
syncache_tfo_expand(struct syncache *sc, struct socket **lsop, struct mbuf *m, | |||||
uint64_t response_cookie) | |||||
{ | |||||
struct inpcb *inp; | |||||
struct tcpcb *tp; | |||||
unsigned int *pending_counter; | |||||
/* | /* | ||||
* Global TCP locks are held because we manipulate the PCB lists | |||||
* and create a new socket. | |||||
*/ | |||||
INP_INFO_RLOCK_ASSERT(&V_tcbinfo); | |||||
pending_counter = intotcpcb(sotoinpcb(*lsop))->t_tfo_pending; | |||||
*lsop = syncache_socket(sc, *lsop, m); | |||||
if (*lsop == NULL) { | |||||
TCPSTAT_INC(tcps_sc_aborted); | |||||
atomic_subtract_int(pending_counter, 1); | |||||
} else { | |||||
inp = sotoinpcb(*lsop); | |||||
tp = intotcpcb(inp); | |||||
tp->t_flags |= TF_FASTOPEN; | |||||
tp->t_tfo_cookie = response_cookie; | |||||
tp->snd_max = tp->iss; | |||||
tp->snd_nxt = tp->iss; | |||||
tp->t_tfo_pending = pending_counter; | |||||
TCPSTAT_INC(tcps_sc_completed); | |||||
} | |||||
} | |||||
#endif /* TCP_RFC7413 */ | |||||
/* | |||||
* Given a LISTEN socket and an inbound SYN request, add | * Given a LISTEN socket and an inbound SYN request, add | ||||
* this to the syn cache, and send back a segment: | * this to the syn cache, and send back a segment: | ||||
* <SEQ=ISS><ACK=RCV_NXT><CTL=SYN,ACK> | * <SEQ=ISS><ACK=RCV_NXT><CTL=SYN,ACK> | ||||
* to the source. | * to the source. | ||||
* | * | ||||
* IMPORTANT NOTE: We do _NOT_ ACK data that might accompany the SYN. | * IMPORTANT NOTE: We do _NOT_ ACK data that might accompany the SYN. | ||||
* Doing so would require that we hold onto the data and deliver it | * Doing so would require that we hold onto the data and deliver it | ||||
* to the application. However, if we are the target of a SYN-flood | * to the application. However, if we are the target of a SYN-flood | ||||
* DoS attack, an attacker could send data which would eventually | * DoS attack, an attacker could send data which would eventually | ||||
* consume all available buffer space if it were ACKed. By not ACKing | * consume all available buffer space if it were ACKed. By not ACKing | ||||
* the data, we avoid this DoS scenario. | * the data, we avoid this DoS scenario. | ||||
* | |||||
* The exception to the above is when a SYN with a valid TCP Fast Open (TFO) | |||||
* cookie is processed, V_tcp_fastopen_enabled set to true, and the | |||||
* TCP_FASTOPEN socket option is set. In this case, a new socket is created | |||||
* and returned via lsop, the mbuf is not freed so that tcp_input() can | |||||
* queue its data to the socket, and 1 is returned to indicate the | |||||
* TFO-socket-creation path was taken. | |||||
*/ | */ | ||||
void | int | ||||
syncache_add(struct in_conninfo *inc, struct tcpopt *to, struct tcphdr *th, | syncache_add(struct in_conninfo *inc, struct tcpopt *to, struct tcphdr *th, | ||||
struct inpcb *inp, struct socket **lsop, struct mbuf *m, void *tod, | struct inpcb *inp, struct socket **lsop, struct mbuf *m, void *tod, | ||||
void *todctx) | void *todctx) | ||||
{ | { | ||||
struct tcpcb *tp; | struct tcpcb *tp; | ||||
struct socket *so; | struct socket *so; | ||||
struct syncache *sc = NULL; | struct syncache *sc = NULL; | ||||
struct syncache_head *sch; | struct syncache_head *sch; | ||||
struct mbuf *ipopts = NULL; | struct mbuf *ipopts = NULL; | ||||
u_int ltflags; | u_int ltflags; | ||||
int win, sb_hiwat, ip_ttl, ip_tos; | int win, sb_hiwat, ip_ttl, ip_tos; | ||||
char *s; | char *s; | ||||
int rv = 0; | |||||
#ifdef INET6 | #ifdef INET6 | ||||
int autoflowlabel = 0; | int autoflowlabel = 0; | ||||
#endif | #endif | ||||
#ifdef MAC | #ifdef MAC | ||||
struct label *maclabel; | struct label *maclabel; | ||||
#endif | #endif | ||||
struct syncache scs; | struct syncache scs; | ||||
struct ucred *cred; | struct ucred *cred; | ||||
#ifdef TCP_RFC7413 | |||||
uint64_t tfo_response_cookie; | |||||
int tfo_cookie_valid = 0; | |||||
int tfo_response_cookie_valid = 0; | |||||
#endif | |||||
INP_WLOCK_ASSERT(inp); /* listen socket */ | INP_WLOCK_ASSERT(inp); /* listen socket */ | ||||
KASSERT((th->th_flags & (TH_RST|TH_ACK|TH_SYN)) == TH_SYN, | KASSERT((th->th_flags & (TH_RST|TH_ACK|TH_SYN)) == TH_SYN, | ||||
("%s: unexpected tcp flags", __func__)); | ("%s: unexpected tcp flags", __func__)); | ||||
/* | /* | ||||
* Combine all so/tp operations very early to drop the INP lock as | * Combine all so/tp operations very early to drop the INP lock as | ||||
* soon as possible. | * soon as possible. | ||||
*/ | */ | ||||
so = *lsop; | so = *lsop; | ||||
tp = sototcpcb(so); | tp = sototcpcb(so); | ||||
cred = crhold(so->so_cred); | cred = crhold(so->so_cred); | ||||
#ifdef INET6 | #ifdef INET6 | ||||
if ((inc->inc_flags & INC_ISIPV6) && | if ((inc->inc_flags & INC_ISIPV6) && | ||||
(inp->inp_flags & IN6P_AUTOFLOWLABEL)) | (inp->inp_flags & IN6P_AUTOFLOWLABEL)) | ||||
autoflowlabel = 1; | autoflowlabel = 1; | ||||
#endif | #endif | ||||
ip_ttl = inp->inp_ip_ttl; | ip_ttl = inp->inp_ip_ttl; | ||||
ip_tos = inp->inp_ip_tos; | ip_tos = inp->inp_ip_tos; | ||||
win = sbspace(&so->so_rcv); | win = sbspace(&so->so_rcv); | ||||
sb_hiwat = so->so_rcv.sb_hiwat; | sb_hiwat = so->so_rcv.sb_hiwat; | ||||
ltflags = (tp->t_flags & (TF_NOOPT | TF_SIGNATURE)); | ltflags = (tp->t_flags & (TF_NOOPT | TF_SIGNATURE)); | ||||
#ifdef TCP_RFC7413 | |||||
if (V_tcp_fastopen_enabled && (tp->t_flags & TF_FASTOPEN) && | |||||
(tp->t_tfo_pending != NULL) && (to->to_flags & TOF_FASTOPEN)) { | |||||
/* | |||||
* Limit the number of pending TFO connections to | |||||
* approximately half of the queue limit. This prevents TFO | |||||
* SYN floods from starving the service by filling the | |||||
* listen queue with bogus TFO connections. | |||||
*/ | |||||
if (atomic_fetchadd_int(tp->t_tfo_pending, 1) <= | |||||
(so->so_qlimit / 2)) { | |||||
int result; | |||||
result = tcp_fastopen_check_cookie(inc, | |||||
to->to_tfo_cookie, to->to_tfo_len, | |||||
&tfo_response_cookie); | |||||
tfo_cookie_valid = (result > 0); | |||||
tfo_response_cookie_valid = (result >= 0); | |||||
} else | |||||
atomic_subtract_int(tp->t_tfo_pending, 1); | |||||
} | |||||
#endif | |||||
/* By the time we drop the lock these should no longer be used. */ | /* By the time we drop the lock these should no longer be used. */ | ||||
so = NULL; | so = NULL; | ||||
tp = NULL; | tp = NULL; | ||||
#ifdef MAC | #ifdef MAC | ||||
if (mac_syncache_init(&maclabel) != 0) { | if (mac_syncache_init(&maclabel) != 0) { | ||||
INP_WUNLOCK(inp); | INP_WUNLOCK(inp); | ||||
goto done; | goto done; | ||||
} else | } else | ||||
mac_syncache_create(maclabel, inp); | mac_syncache_create(maclabel, inp); | ||||
#endif | #endif | ||||
#ifdef TCP_RFC7413 | |||||
if (!tfo_cookie_valid) | |||||
#endif | |||||
INP_WUNLOCK(inp); | INP_WUNLOCK(inp); | ||||
/* | /* | ||||
* Remember the IP options, if any. | * Remember the IP options, if any. | ||||
*/ | */ | ||||
#ifdef INET6 | #ifdef INET6 | ||||
if (!(inc->inc_flags & INC_ISIPV6)) | if (!(inc->inc_flags & INC_ISIPV6)) | ||||
#endif | #endif | ||||
#ifdef INET | #ifdef INET | ||||
Show All 12 Lines | #endif | ||||
* XXX: We do not check the sequence number to see if this is a | * XXX: We do not check the sequence number to see if this is a | ||||
* real retransmit or a new connection attempt. The question is | * real retransmit or a new connection attempt. The question is | ||||
* how to handle such a case; either ignore it as spoofed, or | * how to handle such a case; either ignore it as spoofed, or | ||||
* drop the current entry and create a new one? | * drop the current entry and create a new one? | ||||
*/ | */ | ||||
sc = syncache_lookup(inc, &sch); /* returns locked entry */ | sc = syncache_lookup(inc, &sch); /* returns locked entry */ | ||||
SCH_LOCK_ASSERT(sch); | SCH_LOCK_ASSERT(sch); | ||||
if (sc != NULL) { | if (sc != NULL) { | ||||
#ifdef TCP_RFC7413 | |||||
if (tfo_cookie_valid) | |||||
INP_WUNLOCK(inp); | |||||
#endif | |||||
TCPSTAT_INC(tcps_sc_dupsyn); | TCPSTAT_INC(tcps_sc_dupsyn); | ||||
if (ipopts) { | if (ipopts) { | ||||
/* | /* | ||||
* If we were remembering a previous source route, | * If we were remembering a previous source route, | ||||
* forget it and use the new one we've been given. | * forget it and use the new one we've been given. | ||||
*/ | */ | ||||
if (sc->sc_ipopts) | if (sc->sc_ipopts) | ||||
(void) m_free(sc->sc_ipopts); | (void) m_free(sc->sc_ipopts); | ||||
Show All 26 Lines | if (syncache_respond(sc, sch, 1) == 0) { | ||||
syncache_timeout(sc, sch, 1); | syncache_timeout(sc, sch, 1); | ||||
TCPSTAT_INC(tcps_sndacks); | TCPSTAT_INC(tcps_sndacks); | ||||
TCPSTAT_INC(tcps_sndtotal); | TCPSTAT_INC(tcps_sndtotal); | ||||
} | } | ||||
SCH_UNLOCK(sch); | SCH_UNLOCK(sch); | ||||
goto done; | goto done; | ||||
} | } | ||||
#ifdef TCP_RFC7413 | |||||
if (tfo_cookie_valid) { | |||||
bzero(&scs, sizeof(scs)); | |||||
sc = &scs; | |||||
goto skip_alloc; | |||||
} | |||||
#endif | |||||
sc = uma_zalloc(V_tcp_syncache.zone, M_NOWAIT | M_ZERO); | sc = uma_zalloc(V_tcp_syncache.zone, M_NOWAIT | M_ZERO); | ||||
if (sc == NULL) { | if (sc == NULL) { | ||||
/* | /* | ||||
* The zone allocator couldn't provide more entries. | * The zone allocator couldn't provide more entries. | ||||
* Treat this as if the cache was full; drop the oldest | * Treat this as if the cache was full; drop the oldest | ||||
* entry and insert the new one. | * entry and insert the new one. | ||||
*/ | */ | ||||
TCPSTAT_INC(tcps_sc_zonefail); | TCPSTAT_INC(tcps_sc_zonefail); | ||||
if ((sc = TAILQ_LAST(&sch->sch_bucket, sch_head)) != NULL) | if ((sc = TAILQ_LAST(&sch->sch_bucket, sch_head)) != NULL) | ||||
syncache_drop(sc, sch); | syncache_drop(sc, sch); | ||||
sc = uma_zalloc(V_tcp_syncache.zone, M_NOWAIT | M_ZERO); | sc = uma_zalloc(V_tcp_syncache.zone, M_NOWAIT | M_ZERO); | ||||
if (sc == NULL) { | if (sc == NULL) { | ||||
if (V_tcp_syncookies) { | if (V_tcp_syncookies) { | ||||
bzero(&scs, sizeof(scs)); | bzero(&scs, sizeof(scs)); | ||||
sc = &scs; | sc = &scs; | ||||
} else { | } else { | ||||
SCH_UNLOCK(sch); | SCH_UNLOCK(sch); | ||||
if (ipopts) | if (ipopts) | ||||
(void) m_free(ipopts); | (void) m_free(ipopts); | ||||
goto done; | goto done; | ||||
} | } | ||||
} | } | ||||
} | } | ||||
#ifdef TCP_RFC7413 | |||||
skip_alloc: | |||||
if (!tfo_cookie_valid && tfo_response_cookie_valid) | |||||
sc->sc_tfo_cookie = &tfo_response_cookie; | |||||
#endif | |||||
/* | /* | ||||
* Fill in the syncache values. | * Fill in the syncache values. | ||||
*/ | */ | ||||
#ifdef MAC | #ifdef MAC | ||||
sc->sc_label = maclabel; | sc->sc_label = maclabel; | ||||
#endif | #endif | ||||
sc->sc_cred = cred; | sc->sc_cred = cred; | ||||
cred = NULL; | cred = NULL; | ||||
▲ Show 20 Lines • Show All 91 Lines • ▼ Show 20 Lines | if (V_tcp_syncookies) | ||||
sc->sc_flowlabel = sc->sc_iss; | sc->sc_flowlabel = sc->sc_iss; | ||||
else | else | ||||
sc->sc_flowlabel = ip6_randomflowlabel(); | sc->sc_flowlabel = ip6_randomflowlabel(); | ||||
sc->sc_flowlabel = htonl(sc->sc_flowlabel) & IPV6_FLOWLABEL_MASK; | sc->sc_flowlabel = htonl(sc->sc_flowlabel) & IPV6_FLOWLABEL_MASK; | ||||
} | } | ||||
#endif | #endif | ||||
SCH_UNLOCK(sch); | SCH_UNLOCK(sch); | ||||
#ifdef TCP_RFC7413 | |||||
if (tfo_cookie_valid) { | |||||
syncache_tfo_expand(sc, lsop, m, tfo_response_cookie); | |||||
/* INP_WUNLOCK(inp) will be performed by the called */ | |||||
rv = 1; | |||||
goto tfo_done; | |||||
} | |||||
#endif | |||||
/* | /* | ||||
* Do a standard 3-way handshake. | * Do a standard 3-way handshake. | ||||
*/ | */ | ||||
if (syncache_respond(sc, sch, 0) == 0) { | if (syncache_respond(sc, sch, 0) == 0) { | ||||
if (V_tcp_syncookies && V_tcp_syncookiesonly && sc != &scs) | if (V_tcp_syncookies && V_tcp_syncookiesonly && sc != &scs) | ||||
syncache_free(sc); | syncache_free(sc); | ||||
else if (sc != &scs) | else if (sc != &scs) | ||||
syncache_insert(sc, sch); /* locks and unlocks sch */ | syncache_insert(sc, sch); /* locks and unlocks sch */ | ||||
TCPSTAT_INC(tcps_sndacks); | TCPSTAT_INC(tcps_sndacks); | ||||
TCPSTAT_INC(tcps_sndtotal); | TCPSTAT_INC(tcps_sndtotal); | ||||
} else { | } else { | ||||
if (sc != &scs) | if (sc != &scs) | ||||
syncache_free(sc); | syncache_free(sc); | ||||
TCPSTAT_INC(tcps_sc_dropped); | TCPSTAT_INC(tcps_sc_dropped); | ||||
} | } | ||||
done: | done: | ||||
if (m) { | |||||
*lsop = NULL; | |||||
m_freem(m); | |||||
} | |||||
#ifdef TCP_RFC7413 | |||||
tfo_done: | |||||
#endif | |||||
if (cred != NULL) | if (cred != NULL) | ||||
crfree(cred); | crfree(cred); | ||||
#ifdef MAC | #ifdef MAC | ||||
if (sc == &scs) | if (sc == &scs) | ||||
mac_syncache_destroy(&maclabel); | mac_syncache_destroy(&maclabel); | ||||
#endif | #endif | ||||
if (m) { | return (rv); | ||||
*lsop = NULL; | |||||
m_freem(m); | |||||
} | } | ||||
} | |||||
static int | static int | ||||
syncache_respond(struct syncache *sc, struct syncache_head *sch, int locked) | syncache_respond(struct syncache *sc, struct syncache_head *sch, int locked) | ||||
{ | { | ||||
struct ip *ip = NULL; | struct ip *ip = NULL; | ||||
struct mbuf *m; | struct mbuf *m; | ||||
struct tcphdr *th = NULL; | struct tcphdr *th = NULL; | ||||
int optlen, error = 0; /* Make compiler happy */ | int optlen, error = 0; /* Make compiler happy */ | ||||
▲ Show 20 Lines • Show All 132 Lines • ▼ Show 20 Lines | if (sc->sc_flags & SCF_SIGNATURE) { | ||||
* connection. | * connection. | ||||
*/ | */ | ||||
if (locked == 0) | if (locked == 0) | ||||
SCH_LOCK(sch); | SCH_LOCK(sch); | ||||
sc->sc_flags &= ~SCF_SIGNATURE; | sc->sc_flags &= ~SCF_SIGNATURE; | ||||
if (locked == 0) | if (locked == 0) | ||||
SCH_UNLOCK(sch); | SCH_UNLOCK(sch); | ||||
} | } | ||||
} | |||||
#endif | |||||
#ifdef TCP_RFC7413 | |||||
if (sc->sc_tfo_cookie) { | |||||
to.to_flags |= TOF_FASTOPEN; | |||||
to.to_tfo_len = TCP_FASTOPEN_COOKIE_LEN; | |||||
to.to_tfo_cookie = sc->sc_tfo_cookie; | |||||
/* don't send cookie again when retransmitting response */ | |||||
sc->sc_tfo_cookie = NULL; | |||||
} | } | ||||
#endif | #endif | ||||
optlen = tcp_addoptions(&to, (u_char *)(th + 1)); | optlen = tcp_addoptions(&to, (u_char *)(th + 1)); | ||||
/* Adjust headers by option size. */ | /* Adjust headers by option size. */ | ||||
th->th_off = (sizeof(struct tcphdr) + optlen) >> 2; | th->th_off = (sizeof(struct tcphdr) + optlen) >> 2; | ||||
m->m_len += optlen; | m->m_len += optlen; | ||||
m->m_pkthdr.len += optlen; | m->m_pkthdr.len += optlen; | ||||
▲ Show 20 Lines • Show All 506 Lines • Show Last 20 Lines |