summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorattilio <attilio@FreeBSD.org>2011-04-25 17:13:40 +0000
committerattilio <attilio@FreeBSD.org>2011-04-25 17:13:40 +0000
commit2e19c21f223f834300d9e8b7fc46636902205453 (patch)
treea9b2378f15a6b24078acade80e7136f7779e5dbb
parent99f9647714747def6d98acca6bfe0f865c0d2f1c (diff)
downloadFreeBSD-src-2e19c21f223f834300d9e8b7fc46636902205453.zip
FreeBSD-src-2e19c21f223f834300d9e8b7fc46636902205453.tar.gz
Add the possibility to verify MD5 hash of incoming TCP packets.
As long as this is a costy function, even when compiled in (along with the option TCP_SIGNATURE), it can be disabled via the net.inet.tcp.signature_verify_input sysctl. Sponsored by: Sandvine Incorporated Reviewed by: emaste, bz MFC after: 2 weeks
-rw-r--r--sys/netinet/tcp_input.c103
-rw-r--r--sys/netinet/tcp_subr.c66
-rw-r--r--sys/netinet/tcp_syncache.c9
-rw-r--r--sys/netinet/tcp_var.h9
4 files changed, 179 insertions, 8 deletions
diff --git a/sys/netinet/tcp_input.c b/sys/netinet/tcp_input.c
index cb9453f..cf1daa8 100644
--- a/sys/netinet/tcp_input.c
+++ b/sys/netinet/tcp_input.c
@@ -215,6 +215,12 @@ static void tcp_pulloutofband(struct socket *,
struct tcphdr *, struct mbuf *, int);
static void tcp_xmit_timer(struct tcpcb *, int);
static void tcp_newreno_partial_ack(struct tcpcb *, struct tcphdr *);
+static void inline tcp_fields_to_host(struct tcphdr *);
+#ifdef TCP_SIGNATURE
+static void inline tcp_fields_to_net(struct tcphdr *);
+static int inline tcp_signature_verify_input(struct mbuf *, int, int,
+ int, struct tcpopt *, struct tcphdr *, u_int);
+#endif
static void inline cc_ack_received(struct tcpcb *tp, struct tcphdr *th,
uint16_t type);
static void inline cc_conn_init(struct tcpcb *tp);
@@ -440,6 +446,40 @@ cc_post_recovery(struct tcpcb *tp, struct tcphdr *th)
tp->t_bytes_acked = 0;
}
+static inline void
+tcp_fields_to_host(struct tcphdr *th)
+{
+
+ th->th_seq = ntohl(th->th_seq);
+ th->th_ack = ntohl(th->th_ack);
+ th->th_win = ntohs(th->th_win);
+ th->th_urp = ntohs(th->th_urp);
+}
+
+#ifdef TCP_SIGNATURE
+static inline void
+tcp_fields_to_net(struct tcphdr *th)
+{
+
+ th->th_seq = htonl(th->th_seq);
+ th->th_ack = htonl(th->th_ack);
+ th->th_win = htons(th->th_win);
+ th->th_urp = htons(th->th_urp);
+}
+
+static inline int
+tcp_signature_verify_input(struct mbuf *m, int off0, int tlen, int optlen,
+ struct tcpopt *to, struct tcphdr *th, u_int tcpbflag)
+{
+ int ret;
+
+ tcp_fields_to_net(th);
+ ret = tcp_signature_verify(m, off0, tlen, optlen, to, th, tcpbflag);
+ tcp_fields_to_host(th);
+ return (ret);
+}
+#endif
+
/* Neighbor Discovery, Neighbor Unreachability Detection Upper layer hint. */
#ifdef INET6
#define ND6_HINT(tp) \
@@ -519,6 +559,9 @@ tcp_input(struct mbuf *m, int off0)
int thflags;
int rstreason = 0; /* For badport_bandlim accounting purposes */
uint8_t iptos;
+#ifdef TCP_SIGNATURE
+ uint8_t sig_checked = 0;
+#endif
#ifdef IPFIREWALL_FORWARD
struct m_tag *fwd_tag;
#endif
@@ -676,10 +719,7 @@ tcp_input(struct mbuf *m, int off0)
/*
* Convert TCP protocol specific fields to host format.
*/
- th->th_seq = ntohl(th->th_seq);
- th->th_ack = ntohl(th->th_ack);
- th->th_win = ntohs(th->th_win);
- th->th_urp = ntohs(th->th_urp);
+ tcp_fields_to_host(th);
/*
* Delay dropping TCP, IP headers, IPv6 ext headers, and TCP options.
@@ -861,8 +901,24 @@ relocked:
}
INP_INFO_WLOCK_ASSERT(&V_tcbinfo);
+#ifdef TCP_SIGNATURE
+ tcp_dooptions(&to, optp, optlen,
+ (thflags & TH_SYN) ? TO_SYN : 0);
+ if (sig_checked == 0) {
+ tp = intotcpcb(inp);
+ if (tp == NULL || tp->t_state == TCPS_CLOSED) {
+ rstreason = BANDLIM_RST_CLOSEDPORT;
+ goto dropwithreset;
+ }
+ if (!tcp_signature_verify_input(m, off0, tlen, optlen,
+ &to, th, tp->t_flags))
+ goto dropunlock;
+ sig_checked = 1;
+ }
+#else
if (thflags & TH_SYN)
tcp_dooptions(&to, optp, optlen, TO_SYN);
+#endif
/*
* NB: tcp_twcheck unlocks the INP and frees the mbuf.
*/
@@ -1021,6 +1077,26 @@ relocked:
tp = intotcpcb(inp);
KASSERT(tp->t_state == TCPS_SYN_RECEIVED,
("%s: ", __func__));
+#ifdef TCP_SIGNATURE
+ if (sig_checked == 0) {
+ tcp_dooptions(&to, optp, optlen,
+ (thflags & TH_SYN) ? TO_SYN : 0);
+ if (!tcp_signature_verify_input(m, off0, tlen,
+ optlen, &to, th, tp->t_flags)) {
+
+ /*
+ * In SYN_SENT state if it receives an
+ * RST, it is allowed for further
+ * processing.
+ */
+ if ((thflags & TH_RST) == 0 ||
+ (tp->t_state == TCPS_SYN_SENT) == 0)
+ goto dropunlock;
+ }
+ sig_checked = 1;
+ }
+#endif
+
/*
* Process the segment and the data it
* contains. tcp_do_segment() consumes
@@ -1225,6 +1301,25 @@ relocked:
return;
}
+#ifdef TCP_SIGNATURE
+ if (sig_checked == 0) {
+ tcp_dooptions(&to, optp, optlen,
+ (thflags & TH_SYN) ? TO_SYN : 0);
+ if (!tcp_signature_verify_input(m, off0, tlen, optlen, &to,
+ th, tp->t_flags)) {
+
+ /*
+ * In SYN_SENT state if it receives an RST, it is
+ * allowed for further processing.
+ */
+ if ((thflags & TH_RST) == 0 ||
+ (tp->t_state == TCPS_SYN_SENT) == 0)
+ goto dropunlock;
+ }
+ sig_checked = 1;
+ }
+#endif
+
/*
* Segment belongs to a connection in SYN_SENT, ESTABLISHED or later
* state. tcp_do_segment() always consumes the mbuf chain, unlocks
diff --git a/sys/netinet/tcp_subr.c b/sys/netinet/tcp_subr.c
index 909381a..f106b2d 100644
--- a/sys/netinet/tcp_subr.c
+++ b/sys/netinet/tcp_subr.c
@@ -213,6 +213,12 @@ SYSCTL_INT(_net_inet_tcp, OID_AUTO, soreceive_stream, CTLFLAG_RDTUN,
&tcp_soreceive_stream, 0, "Using soreceive_stream for TCP sockets");
#endif
+#ifdef TCP_SIGNATURE
+static int tcp_sig_checksigs = 1;
+SYSCTL_INT(_net_inet_tcp, OID_AUTO, signature_verify_input, CTLFLAG_RW,
+ &tcp_sig_checksigs, 0, "Verify RFC2385 digests on inbound traffic");
+#endif
+
VNET_DEFINE(uma_zone_t, sack_hole_zone);
#define V_sack_hole_zone VNET(sack_hole_zone)
@@ -1998,6 +2004,66 @@ tcp_signature_compute(struct mbuf *m, int _unused, int len, int optlen,
KEY_FREESAV(&sav);
return (0);
}
+
+/*
+ * Verify the TCP-MD5 hash of a TCP segment. (RFC2385)
+ *
+ * Parameters:
+ * m pointer to head of mbuf chain
+ * len length of TCP segment data, excluding options
+ * optlen length of TCP segment options
+ * buf pointer to storage for computed MD5 digest
+ * direction direction of flow (IPSEC_DIR_INBOUND or OUTBOUND)
+ *
+ * Return 1 if successful, otherwise return 0.
+ */
+int
+tcp_signature_verify(struct mbuf *m, int off0, int tlen, int optlen,
+ struct tcpopt *to, struct tcphdr *th, u_int tcpbflag)
+{
+ char tmpdigest[TCP_SIGLEN];
+
+ if (tcp_sig_checksigs == 0)
+ return (1);
+ if ((tcpbflag & TF_SIGNATURE) == 0) {
+ if ((to->to_flags & TOF_SIGNATURE) != 0) {
+
+ /*
+ * If this socket is not expecting signature but
+ * the segment contains signature just fail.
+ */
+ TCPSTAT_INC(tcps_sig_err_sigopt);
+ TCPSTAT_INC(tcps_sig_rcvbadsig);
+ return (0);
+ }
+
+ /* Signature is not expected, and not present in segment. */
+ return (1);
+ }
+
+ /*
+ * If this socket is expecting signature but the segment does not
+ * contain any just fail.
+ */
+ if ((to->to_flags & TOF_SIGNATURE) == 0) {
+ TCPSTAT_INC(tcps_sig_err_nosigopt);
+ TCPSTAT_INC(tcps_sig_rcvbadsig);
+ return (0);
+ }
+ if (tcp_signature_compute(m, off0, tlen, optlen, &tmpdigest[0],
+ IPSEC_DIR_INBOUND) == -1) {
+ TCPSTAT_INC(tcps_sig_err_buildsig);
+ TCPSTAT_INC(tcps_sig_rcvbadsig);
+ return (0);
+ }
+
+ if (bcmp(to->to_signature, &tmpdigest[0], TCP_SIGLEN) != 0) {
+ TCPSTAT_INC(tcps_sig_rcvbadsig);
+ return (0);
+ }
+ TCPSTAT_INC(tcps_sig_rcvgoodsig);
+ return (1);
+}
#endif /* TCP_SIGNATURE */
static int
diff --git a/sys/netinet/tcp_syncache.c b/sys/netinet/tcp_syncache.c
index ede3702..be828ef 100644
--- a/sys/netinet/tcp_syncache.c
+++ b/sys/netinet/tcp_syncache.c
@@ -1010,7 +1010,8 @@ _syncache_add(struct in_conninfo *inc, struct tcpopt *to, struct tcphdr *th,
struct syncache_head *sch;
struct mbuf *ipopts = NULL;
u_int32_t flowtmp;
- int win, sb_hiwat, ip_ttl, ip_tos, noopt;
+ u_int ltflags;
+ int win, sb_hiwat, ip_ttl, ip_tos;
char *s;
#ifdef INET6
int autoflowlabel = 0;
@@ -1043,7 +1044,7 @@ _syncache_add(struct in_conninfo *inc, struct tcpopt *to, struct tcphdr *th,
ip_tos = inp->inp_ip_tos;
win = sbspace(&so->so_rcv);
sb_hiwat = so->so_rcv.sb_hiwat;
- noopt = (tp->t_flags & TF_NOOPT);
+ ltflags = (tp->t_flags & (TF_NOOPT | TF_SIGNATURE));
/* By the time we drop the lock these should no longer be used. */
so = NULL;
@@ -1238,14 +1239,14 @@ _syncache_add(struct in_conninfo *inc, struct tcpopt *to, struct tcphdr *th,
* XXX: Currently we always record the option by default and will
* attempt to use it in syncache_respond().
*/
- if (to->to_flags & TOF_SIGNATURE)
+ if (to->to_flags & TOF_SIGNATURE || ltflags & TF_SIGNATURE)
sc->sc_flags |= SCF_SIGNATURE;
#endif
if (to->to_flags & TOF_SACKPERM)
sc->sc_flags |= SCF_SACK;
if (to->to_flags & TOF_MSS)
sc->sc_peer_mss = to->to_mss; /* peer mss may be zero */
- if (noopt)
+ if (ltflags & TF_NOOPT)
sc->sc_flags |= SCF_NOOPT;
if ((th->th_flags & (TH_ECE|TH_CWR)) && V_tcp_do_ecn)
sc->sc_flags |= SCF_ECN;
diff --git a/sys/netinet/tcp_var.h b/sys/netinet/tcp_var.h
index e265057..5d92d87 100644
--- a/sys/netinet/tcp_var.h
+++ b/sys/netinet/tcp_var.h
@@ -485,6 +485,13 @@ struct tcpstat {
u_long tcps_ecn_shs; /* ECN successful handshakes */
u_long tcps_ecn_rcwnd; /* # times ECN reduced the cwnd */
+ /* TCP_SIGNATURE related stats */
+ u_long tcps_sig_rcvgoodsig; /* Total matching signature received */
+ u_long tcps_sig_rcvbadsig; /* Total bad signature received */
+ u_long tcps_sig_err_buildsig; /* Mismatching signature received */
+ u_long tcps_sig_err_sigopt; /* No signature expected by socket */
+ u_long tcps_sig_err_nosigopt; /* No signature provided by segment */
+
u_long _pad[12]; /* 6 UTO, 6 TBD */
};
@@ -684,6 +691,8 @@ int tcp_twrespond(struct tcptw *, int);
void tcp_setpersist(struct tcpcb *);
#ifdef TCP_SIGNATURE
int tcp_signature_compute(struct mbuf *, int, int, int, u_char *, u_int);
+int tcp_signature_verify(struct mbuf *, int, int, int, struct tcpopt *,
+ struct tcphdr *, u_int);
#endif
void tcp_slowtimo(void);
struct tcptemp *
OpenPOWER on IntegriCloud