diff options
Diffstat (limited to 'sys/contrib/ipfilter/netinet/ip_state.c')
-rw-r--r-- | sys/contrib/ipfilter/netinet/ip_state.c | 218 |
1 files changed, 158 insertions, 60 deletions
diff --git a/sys/contrib/ipfilter/netinet/ip_state.c b/sys/contrib/ipfilter/netinet/ip_state.c index 255bdad..2e8b8f3 100644 --- a/sys/contrib/ipfilter/netinet/ip_state.c +++ b/sys/contrib/ipfilter/netinet/ip_state.c @@ -93,7 +93,7 @@ #if !defined(lint) static const char sccsid[] = "@(#)ip_state.c 1.8 6/5/96 (C) 1993-2000 Darren Reed"; -static const char rcsid[] = "@(#)$Id: ip_state.c,v 2.30.2.61 2002/03/06 14:07:36 darrenr Exp $"; +static const char rcsid[] = "@(#)$Id: ip_state.c,v 2.30.2.66 2002/04/15 12:14:03 darrenr Exp $"; #endif #ifndef MIN @@ -123,6 +123,7 @@ static ips_stat_t *fr_statetstats __P((void)); static void fr_delstate __P((ipstate_t *)); static int fr_state_remove __P((caddr_t)); static void fr_ipsmove __P((ipstate_t **, ipstate_t *, u_int)); +static int fr_tcpoptions __P((tcphdr_t *)); int fr_stputent __P((caddr_t)); int fr_stgetent __P((caddr_t)); void fr_stinsert __P((ipstate_t *)); @@ -298,7 +299,7 @@ caddr_t data; if ((sp->is_p == st.is_p) && (sp->is_v == st.is_v) && !bcmp((char *)&sp->is_src, (char *)&st.is_src, sizeof(st.is_src)) && - !bcmp((char *)&sp->is_dst, (char *)&st.is_src, + !bcmp((char *)&sp->is_dst, (char *)&st.is_dst, sizeof(st.is_dst)) && !bcmp((char *)&sp->is_ps, (char *)&st.is_ps, sizeof(st.is_ps))) { @@ -578,7 +579,8 @@ u_int flags; void *ifp; int out; - if (fr_state_lock || (fin->fin_off != 0) || (fin->fin_fl & FI_SHORT)) + if (fr_state_lock || (fin->fin_off != 0) || (fin->fin_fl & FI_SHORT) || + (fin->fin_misc & FM_BADSTATE)) return NULL; if (ips_num == fr_statemax) { ips_stats.iss_max++; @@ -619,6 +621,8 @@ u_int flags; switch (is->is_p) { + int off; + #ifdef USE_INET6 case IPPROTO_ICMPV6 : ic = (struct icmp *)fin->fin_dp; @@ -680,15 +684,22 @@ u_int flags; hv += is->is_dport; } is->is_send = ntohl(tcp->th_seq) + fin->fin_dlen - - (tcp->th_off << 2) + + (off = (tcp->th_off << 2)) + ((tcp->th_flags & TH_SYN) ? 1 : 0) + ((tcp->th_flags & TH_FIN) ? 1 : 0); is->is_maxsend = is->is_send; - is->is_dend = 0; is->is_maxdwin = 1; is->is_maxswin = ntohs(tcp->th_win); if (is->is_maxswin == 0) is->is_maxswin = 1; + + if ((tcp->th_flags & TH_OPENING) == TH_SYN) + is->is_fsm = 1; + + if ((tcp->th_flags & TH_SYN) && + ((tcp->th_off << 2) >= (sizeof(*tcp) + 4))) + is->is_swscale = fr_tcpoptions(tcp); + /* * If we're creating state for a starting connection, start the * timer on it as we'll never see an error if it fails to @@ -785,7 +796,7 @@ u_int flags; is->is_me = stsave; if (is->is_p == IPPROTO_TCP) { fr_tcp_age(&is->is_age, is->is_state, fin, - 0); /* 0 = packet from the source */ + 0, is->is_fsm); /* 0 = packet from the source */ } #ifdef IPFILTER_LOG ipstate_log(is, ISL_NEW); @@ -798,6 +809,46 @@ u_int flags; } +static int fr_tcpoptions(tcp) +tcphdr_t *tcp; +{ + u_char *opt, *last; + int wscale; + + opt = (u_char *) (tcp + 1); + last = ((u_char *)tcp) + (tcp->th_off << 2); + + /* If we don't find wscale here, we need to clear it */ + wscale = -2; + + /* Termination condition picked such that opt[0 .. 2] exist */ + while ((opt < last - 2) && (*opt != TCPOPT_EOL)) { + switch (*opt) { + case TCPOPT_NOP: + opt++; + continue; + case TCPOPT_WSCALE: + /* Proper length ? */ + if (opt[1] == 3) { + if (opt[2] > 14) + wscale = 14; + else + wscale = opt[2]; + } + break; + default: + /* Unknown options must be two bytes+ */ + if (opt[1] < 2) + break; + opt += opt[1]; + continue; + } + break; + } + return wscale; +} + + /* * check to see if a packet with TCP headers fits within the TCP window. @@ -813,9 +864,10 @@ tcphdr_t *tcp; register tcp_seq seq, ack, end; register int ackskew; tcpdata_t *fdata, *tdata; - u_short win, maxwin; - int ret = 0; + u_32_t win, maxwin; + int ret = 0, off; int source; + int wscale; /* * Find difference between last checked packet and this packet. @@ -825,15 +877,29 @@ tcphdr_t *tcp; source = 0; fdata = &is->is_tcp.ts_data[!source]; tdata = &is->is_tcp.ts_data[source]; + off = tcp->th_off << 2; seq = ntohl(tcp->th_seq); ack = ntohl(tcp->th_ack); win = ntohs(tcp->th_win); - end = seq + fin->fin_dlen - (tcp->th_off << 2) + + end = seq + fin->fin_dlen - off + ((tcp->th_flags & TH_SYN) ? 1 : 0) + ((tcp->th_flags & TH_FIN) ? 1 : 0); + + if ((tcp->th_flags & TH_SYN) && (off >= sizeof(*tcp) + 4)) + wscale = fr_tcpoptions(tcp); + else + wscale = -1; + MUTEX_ENTER(&is->is_lock); - if (fdata->td_end == 0) { + + if (wscale >= 0) + fdata->td_wscale = wscale; + else if (wscale == -2) + fdata->td_wscale = tdata->td_wscale = 0; + + if ((fdata->td_end == 0) && + (!is->is_fsm || ((tcp->th_flags & TH_OPENING) == TH_OPENING))) { /* * Must be a (outgoing) SYN-ACK in reply to a SYN. */ @@ -853,6 +919,7 @@ tcphdr_t *tcp; if (seq == end) seq = end = fdata->td_end; + win <<= fdata->td_wscale; maxwin = tdata->td_maxwin; ackskew = tdata->td_end - ack; @@ -878,29 +945,33 @@ tcphdr_t *tcp; * Thus, when ackskew is negative but still seems to belong * to this session, we bump up the destinations end value. */ - if (ackskew < 0) - tdata->td_end = ack; - - /* update max window seen */ - if (fdata->td_maxwin < win) - fdata->td_maxwin = win; - if (SEQ_GT(end, fdata->td_end)) - fdata->td_end = end; - if (SEQ_GE(ack + win, tdata->td_maxend)) { - tdata->td_maxend = ack + win; - if (win == 0) - tdata->td_maxend++; - } - - ATOMIC_INCL(ips_stats.iss_hits); /* * Nearing end of connection, start timeout. */ /* source ? 0 : 1 -> !source */ - fr_tcp_age(&is->is_age, is->is_state, fin, !source); - ret = 1; + if (fr_tcp_age(&is->is_age, is->is_state, fin, !source, + (int)is->is_fsm) == 0) { + if (ackskew < 0) + tdata->td_end = ack; + + /* update max window seen */ + if (fdata->td_maxwin < win) + fdata->td_maxwin = win; + if (SEQ_GT(end, fdata->td_end)) + fdata->td_end = end; + if (SEQ_GE(ack + win, tdata->td_maxend)) { + tdata->td_maxend = ack + win; + if (win == 0) + tdata->td_maxend++; + } + + ATOMIC_INCL(ips_stats.iss_hits); + ret = 1; + } } MUTEX_EXIT(&is->is_lock); + if ((ret == 0) && (tcp->th_flags != TH_SYN)) + fin->fin_misc |= FM_BADSTATE; return ret; } @@ -1079,9 +1150,9 @@ fr_info_t *fin; register ipstate_t *is, **isp; register u_short sport, dport; register u_char pr; + u_short savelen, ohlen; union i6addr dst, src; struct icmp *ic; - u_short savelen; icmphdr_t *icmp; fr_info_t ofin; int type, len; @@ -1110,14 +1181,15 @@ fr_info_t *fin; return NULL; oip = (ip_t *)((char *)ic + ICMPERR_ICMPHLEN); - if (fin->fin_plen < ICMPERR_MAXPKTLEN + ((oip->ip_hl - 5) << 2)) + ohlen = oip->ip_hl << 2; + if (fin->fin_plen < ICMPERR_MAXPKTLEN + ohlen - sizeof(*oip)) return NULL; /* * Sanity checks. */ len = fin->fin_dlen - ICMPERR_ICMPHLEN; - if ((len <= 0) || ((oip->ip_hl << 2) > len)) + if ((len <= 0) || (ohlen > len)) return NULL; /* @@ -1157,7 +1229,7 @@ fr_info_t *fin; switch (oip->ip_p) { case IPPROTO_ICMP : - icmp = (icmphdr_t *)((char *)oip + (oip->ip_hl << 2)); + icmp = (icmphdr_t *)((char *)oip + ohlen); /* * a ICMP error can only be generated as a result of an @@ -1187,7 +1259,7 @@ fr_info_t *fin; savelen = oip->ip_len; oip->ip_len = len; ofin.fin_v = 4; - fr_makefrip(oip->ip_hl << 2, oip, &ofin); + fr_makefrip(ohlen, oip, &ofin); oip->ip_len = savelen; ofin.fin_ifp = fin->fin_ifp; ofin.fin_out = !fin->fin_out; @@ -1209,12 +1281,14 @@ fr_info_t *fin; case IPPROTO_TCP : case IPPROTO_UDP : + if (fin->fin_plen < ICMPERR_MAXPKTLEN) + return NULL; break; default : return NULL; } - tcp = (tcphdr_t *)((char *)oip + (oip->ip_hl << 2)); + tcp = (tcphdr_t *)((char *)oip + ohlen); dport = tcp->th_dport; sport = tcp->th_sport; @@ -1239,7 +1313,7 @@ fr_info_t *fin; savelen = oip->ip_len; oip->ip_len = len; ofin.fin_v = 4; - fr_makefrip(oip->ip_hl << 2, oip, &ofin); + fr_makefrip(ohlen, oip, &ofin); oip->ip_len = savelen; ofin.fin_ifp = fin->fin_ifp; ofin.fin_out = !fin->fin_out; @@ -1481,9 +1555,8 @@ retry_tcpudp: fr_matchsrcdst(is, src, dst, fin, tcp)) { rev = fin->fin_rev; if ((pr == IPPROTO_TCP)) { - if (!fr_tcpstate(is, fin, ip, tcp)) { - continue; - } + if (!fr_tcpstate(is, fin, ip, tcp)) + is = NULL; } else if ((pr == IPPROTO_UDP)) { if (is->is_frage[rev] != 0) is->is_age = is->is_frage[rev]; @@ -1504,6 +1577,7 @@ retry_tcpudp: } break; } + RWLOCK_EXIT(&ipf_state); if (!tryagain && ips_wild) { hv -= dport; @@ -1703,15 +1777,16 @@ void fr_timeoutstate() * dir == 1 : a packet from dest to source * */ -void fr_tcp_age(age, state, fin, dir) +int fr_tcp_age(age, state, fin, dir, fsm) u_long *age; u_char *state; fr_info_t *fin; -int dir; +int dir, fsm; { tcphdr_t *tcp = (tcphdr_t *)fin->fin_dp; u_char flags = tcp->th_flags; int dlen, ostate; + u_long newage; ostate = state[1 - dir]; @@ -1725,10 +1800,10 @@ int dir; *age = fr_tcpclosewait; state[dir] = TCPS_CLOSE_WAIT; } - return; + return 0; } - *age = fr_tcptimeout; /* default 4 mins */ + newage = 0; switch(state[dir]) { @@ -1739,11 +1814,11 @@ int dir; * CLOSED -> SYN_RECEIVED */ state[dir] = TCPS_SYN_RECEIVED; - *age = fr_tcptimeout; - } else if ((flags & (TH_SYN|TH_ACK)) == TH_SYN) { + newage = fr_tcptimeout; + } else if ((flags & TH_OPENING) == TH_SYN) { /* 'dir' sent S, CLOSED -> SYN_SENT */ state[dir] = TCPS_SYN_SENT; - *age = fr_tcptimeout; + newage = fr_tcptimeout; } /* * The next piece of code makes it possible to get @@ -1752,12 +1827,12 @@ int dir; * does not work when a strict 'flags S keep state' is * used for tcp connections of course */ - if ((flags & (TH_FIN|TH_SYN|TH_RST|TH_ACK)) == TH_ACK) { + if (!fsm && (flags & (TH_FIN|TH_SYN|TH_RST|TH_ACK)) == TH_ACK) { /* we saw an A, guess 'dir' is in ESTABLISHED mode */ if (state[1 - dir] == TCPS_CLOSED || state[1 - dir] == TCPS_ESTABLISHED) { state[dir] = TCPS_ESTABLISHED; - *age = fr_tcpidletimeout; + newage = fr_tcpidletimeout; } } /* @@ -1772,14 +1847,24 @@ int dir; break; case TCPS_SYN_SENT: /* 2 */ - if ((flags & (TH_SYN|TH_FIN|TH_ACK)) == TH_ACK) { + if (flags == TH_SYN) { + /* + * A retransmitted SYN packet. We do not reset the + * timeout here to fr_tcptimeout because a connection + * connect timeout does not renew after every packet + * that is sent. We need to set newage to something + * to indicate the packet has passed the check for its + * flags being valid in the TCP FSM. + */ + newage = *age; + } else if ((flags & (TH_SYN|TH_FIN|TH_ACK)) == TH_ACK) { /* * We see an A from 'dir' which is in SYN_SENT * state: 'dir' sent an A in response to an SA * which it received, SYN_SENT -> ESTABLISHED */ state[dir] = TCPS_ESTABLISHED; - *age = fr_tcpidletimeout; + newage = fr_tcpidletimeout; } else if (flags & TH_FIN) { /* * We see an F from 'dir' which is in SYN_SENT @@ -1787,7 +1872,7 @@ int dir; * connection; SYN_SENT -> FIN_WAIT_1 */ state[dir] = TCPS_FIN_WAIT_1; - *age = fr_tcpidletimeout; /* or fr_tcptimeout? */ + newage = fr_tcpidletimeout; /* or fr_tcptimeout? */ } else if ((flags & TH_OPENING) == TH_OPENING) { /* * We see an SA from 'dir' which is already in @@ -1795,7 +1880,7 @@ int dir; * simultaneous open; SYN_SENT -> SYN_RECEIVED */ state[dir] = TCPS_SYN_RECEIVED; - *age = fr_tcptimeout; + newage = fr_tcptimeout; } break; @@ -1807,7 +1892,7 @@ int dir; * SYN_RECEIVED -> ESTABLISHED */ state[dir] = TCPS_ESTABLISHED; - *age = fr_tcpidletimeout; + newage = fr_tcpidletimeout; } else if (flags & TH_FIN) { /* * We see an F from 'dir' which is in SYN_RECEIVED @@ -1815,7 +1900,7 @@ int dir; * SYN_RECEIVED -> FIN_WAIT_1 */ state[dir] = TCPS_FIN_WAIT_1; - *age = fr_tcpidletimeout; + newage = fr_tcpidletimeout; } break; @@ -1827,7 +1912,7 @@ int dir; * ESTABLISHED -> FIN_WAIT_1 */ state[dir] = TCPS_FIN_WAIT_1; - *age = fr_tcphalfclosed; + newage = fr_tcphalfclosed; } else if (flags & TH_ACK) { /* an ACK, should we exclude other flags here? */ if (ostate == TCPS_FIN_WAIT_1) { @@ -1839,13 +1924,13 @@ int dir; * a half-closed connection */ state[dir] = TCPS_CLOSE_WAIT; - *age = fr_tcphalfclosed; + newage = fr_tcphalfclosed; } else if (ostate < TCPS_CLOSE_WAIT) /* * Still a fully established connection, * reset timeout */ - *age = fr_tcpidletimeout; + newage = fr_tcpidletimeout; } break; @@ -1855,7 +1940,7 @@ int dir; * Application closed and 'dir' sent a FIN, we're now * going into LAST_ACK state */ - *age = fr_tcplastack; + newage = fr_tcplastack; state[dir] = TCPS_LAST_ACK; } else { /* @@ -1863,7 +1948,7 @@ int dir; * closed already and we did not close our side yet; * reset timeout */ - *age = fr_tcphalfclosed; + newage = fr_tcphalfclosed; } break; @@ -1880,14 +1965,14 @@ int dir; * packet here? does the window code guarantee that? */ state[dir] = TCPS_TIME_WAIT; - *age = fr_tcptimeout; + newage = fr_tcptimeout; } else /* * We closed our side of the connection already but the * other side is still active (ESTABLISHED/CLOSE_WAIT); * continue with this half-closed connection */ - *age = fr_tcphalfclosed; + newage = fr_tcphalfclosed; break; case TCPS_CLOSING: /* 7 */ @@ -1901,7 +1986,7 @@ int dir; * There is still data to be delivered, reset * timeout */ - *age = fr_tcplastack; + newage = fr_tcplastack; } /* * We cannot detect when we go out of LAST_ACK state to CLOSED @@ -1916,9 +2001,16 @@ int dir; break; case TCPS_TIME_WAIT: /* 10 */ + newage = fr_tcptimeout; /* default 4 mins */ /* we're in 2MSL timeout now */ break; } + + if (newage != 0) { + *age = newage; + return 0; + } + return -1; } @@ -2068,8 +2160,14 @@ fr_info_t *fin; hv = (pr = oip->ip6_nxt); src.in6 = oip->ip6_src; hv += src.in4.s_addr; + hv += src.i6[1]; + hv += src.i6[2]; + hv += src.i6[3]; dst.in6 = oip->ip6_dst; hv += dst.in4.s_addr; + hv += dst.i6[1]; + hv += dst.i6[2]; + hv += dst.i6[3]; hv += dport; hv += sport; hv %= fr_statesize; |