void ssh_virtual_adapter_send(SshInterceptor interceptor, SshInterceptorPacket pp) { unsigned char *packet, *internal; size_t packet_len, internal_len; /* Linearize the packet. */ packet_len = ssh_interceptor_packet_len(pp); packet = ssh_xmalloc(packet_len); ssh_interceptor_packet_copyout(pp, 0, packet, packet_len); if (!ssh_interceptor_packet_export_internal_data(pp, &internal, &internal_len)) return; SSH_DEBUG(SSH_D_NICETOKNOW, ("sending send request for virtual adapter %d to forwarder.", (int) pp->ifnum_out)); /* Send the packet to the kernel forwarder module. */ ssh_usermode_interceptor_send_encode( interceptor, SSH_ENGINE_IPM_FORWARDER_VIRTUAL_ADAPTER_SEND, SSH_FORMAT_UINT32, pp->ifnum_in, SSH_FORMAT_UINT32, pp->ifnum_out, SSH_FORMAT_UINT32, pp->protocol, SSH_FORMAT_UINT32_STR, packet, packet_len, SSH_FORMAT_UINT32_STR, internal, internal_len, SSH_FORMAT_END); /* Free the linearized packet. */ ssh_xfree(packet); /* Free the original packet object. */ ssh_interceptor_packet_free(pp); }
void ssh_virtual_adapter_send(SshInterceptor interceptor, SshInterceptorPacket pp) { SshVirtualAdapter adapter; SshInterceptorInternalPacket ipp = (SshInterceptorInternalPacket) pp; struct net_device_stats *stats; struct sk_buff *skb; local_bh_disable(); ssh_kernel_mutex_lock(interceptor->interceptor_lock); adapter = ssh_virtual_adapter_ifnum_to_adapter(interceptor, pp->ifnum_out); if (adapter == NULL) { ssh_kernel_mutex_unlock(interceptor->interceptor_lock); local_bh_enable(); SSH_DEBUG(SSH_D_ERROR, ("Virtual adapter %d does not exist", (int)pp->ifnum_out)); goto error; } /* Check the type of the source packet. */ if (pp->protocol == SSH_PROTOCOL_ETHERNET) { /* We can send this directly. */ } else if (pp->protocol == SSH_PROTOCOL_IP4 #ifdef SSH_LINUX_INTERCEPTOR_IPV6 || pp->protocol == SSH_PROTOCOL_IP6 #endif /* SSH_LINUX_INTERCEPTOR_IPV6 */ ) { unsigned char ether_hdr[SSH_ETHERH_HDRLEN]; SshIpAddrStruct src; SshUInt16 ethertype = SSH_ETHERTYPE_IP; unsigned char *cp = NULL; size_t packet_len; /* Add ethernet framing. */ /* Destination is virtual adapter's ethernet address. */ memcpy(ether_hdr + SSH_ETHERH_OFS_DST, adapter->dev->dev_addr, SSH_ETHERH_ADDRLEN); /* Resolve packet's source and the ethernet type to use. */ packet_len = ssh_interceptor_packet_len(pp); /* IPv4 */ if (pp->protocol == SSH_PROTOCOL_IP4) { if (packet_len < SSH_IPH4_HDRLEN) { ssh_kernel_mutex_unlock(interceptor->interceptor_lock); local_bh_enable(); SSH_DEBUG(SSH_D_ERROR, ("Packet is too short to contain IPv4 header")); goto error; } /* Pullup requests data from the header of a writable skb. */ if (likely(skb_headlen(ipp->skb) >= SSH_IPH4_HDRLEN && !skb_shared(ipp->skb) && SSH_SKB_WRITABLE(ipp->skb, SSH_IPH4_HDRLEN))) cp = ipp->skb->data; if (cp == NULL) { ssh_kernel_mutex_unlock(interceptor->interceptor_lock); local_bh_enable(); goto error_already_freed; } SSH_IPH4_SRC(&src, cp); } #ifdef SSH_LINUX_INTERCEPTOR_IPV6 /* IPv6 */ else { if (packet_len < SSH_IPH6_HDRLEN) { ssh_kernel_mutex_unlock(interceptor->interceptor_lock); local_bh_enable(); SSH_DEBUG(SSH_D_ERROR, ("Packet too short to contain IPv6 header")); goto error; } if (likely(skb_headlen(ipp->skb) >= SSH_IPH6_HDRLEN && !skb_shared(ipp->skb) && SSH_SKB_WRITABLE(ipp->skb, SSH_IPH6_HDRLEN))) cp = ipp->skb->data; if (cp == NULL) { ssh_kernel_mutex_unlock(interceptor->interceptor_lock); local_bh_enable(); goto error_already_freed; } SSH_IPH6_SRC(&src, cp); ethertype = SSH_ETHERTYPE_IPv6; } #endif /* SSH_LINUX_INTERCEPTOR_IPV6 */ /* Finalize ethernet header. */ ssh_virtual_adapter_ip_ether_address(&src, ether_hdr + SSH_ETHERH_OFS_SRC); SSH_PUT_16BIT(ether_hdr + SSH_ETHERH_OFS_TYPE, ethertype); /* Insert header to the packet. */ cp = NULL; if (likely((skb_headroom(ipp->skb) >= (SSH_ETHERH_HDRLEN + SSH_INTERCEPTOR_PACKET_HARD_HEAD_ROOM)) && !skb_shared(ipp->skb) && SSH_SKB_WRITABLE(ipp->skb, 0))) cp = skb_push(ipp->skb, SSH_ETHERH_HDRLEN); if (cp == NULL) { ssh_kernel_mutex_unlock(interceptor->interceptor_lock); goto error_already_freed; } memcpy(cp, ether_hdr, SSH_ETHERH_HDRLEN); /* Just to be pedantic. */ pp->protocol = SSH_PROTOCOL_ETHERNET; } else { ssh_kernel_mutex_unlock(interceptor->interceptor_lock); local_bh_enable(); SSH_DEBUG(SSH_D_ERROR, ("Can not handle protocol %d", pp->protocol)); goto error; } /* Tear off the internal packet from the generic SshInterceptorPacket. */ skb = ipp->skb; ipp->skb = NULL; /* (re-)receive the packet via the interface; this should make the packet go back up the stack */ skb->protocol = eth_type_trans(skb, adapter->dev); skb->dev = adapter->dev; /* Update per virtual adapter statistics. */ stats = &adapter->low_level_stats; stats->rx_packets++; stats->rx_bytes += skb->len; ssh_kernel_mutex_unlock(interceptor->interceptor_lock); local_bh_enable(); /* Send the skb up towards stack. If it is IP (or ARP), it will be intercepted by ssh_interceptor_packet_in. */ netif_rx(skb); /* Put the packet header on freelist. */ ssh_interceptor_packet_free((SshInterceptorPacket) ipp); return; error: ssh_interceptor_packet_free(pp); error_already_freed: return; }
void ssh_engine_test_basic(SshEngine engine, SshUInt32 flags) { SshUInt32 pass, packetflags, proto, ifnum_in, ifnum_out, sum, offset; SshUInt32 i; size_t seglen, prevseglen, len, iterlen; unsigned char *seg, *prevseg; SshInterceptorPacket pp; engine->test = "basic"; SSH_DEBUG(0, ("testing basic packet processing functions")); for (pass = 0; pass < 1000; pass++) { if (pass % 100 == 0) SSH_DEBUG(0, ("pass=%d", (int)pass)); /* Compute packet length. We want to test all small values, and others at random. */ if (ssh_rand() % 2 == 0) packetflags = SSH_PACKET_FROMADAPTER; else packetflags = SSH_PACKET_FROMPROTOCOL; if (pass < 300) len = pass; else len = ssh_rand() % 100000; ifnum_in = ssh_rand() % ((SshInterceptorIfnum) 0xffffffff); ifnum_out = ssh_rand() % ((SshInterceptorIfnum) 0xffffffff); proto = SSH_PROTOCOL_IP4; SSH_DEBUG(1, ("packetflags 0x%lx, len %ld, proto %d", (long)packetflags, (long)len, (int)proto)); pp = ssh_interceptor_packet_alloc(engine->interceptor, packetflags, proto, ifnum_in, ifnum_out, len); if (pp == NULL) { ssh_engine_test_fail(engine, "packet_alloc returned NULL"); return; } if ((pp->flags & (0xffffff00|SSH_PACKET_FROMPROTOCOL|SSH_PACKET_FROMADAPTER)) != packetflags) { ssh_engine_test_fail(engine, "packet_alloc flags not properly set"); ssh_interceptor_packet_free(pp); return; } /* Add all flags reserved to the engine so that the interceptor cannot use them for anything. */ pp->flags |= 0xffffff00; /* Check protocol and ifnum. */ if (pp->protocol != proto) { ssh_engine_test_fail(engine, "packet_alloc proto not properly set"); ssh_interceptor_packet_free(pp); return; } if (pp->ifnum_in != ifnum_in) { ssh_engine_test_fail(engine, "packet_alloc ifnum_in not properly set"); ssh_interceptor_packet_free(pp); return; } if (pp->ifnum_out != ifnum_out) { ssh_engine_test_fail(engine, "packet_alloc ifnum_out not properly set"); ssh_interceptor_packet_free(pp); return; } /* Check that packet length is correctly returned. */ if (ssh_interceptor_packet_len(pp) != len) { ssh_engine_test_fail(engine, "packet_alloc returned wrong len %ld " "should have been %ld", (long)ssh_interceptor_packet_len(pp), (long)len); ssh_interceptor_packet_free(pp); return; } if (flags & SSH_INTERCEPTOR_TEST_BASIC_ITERATE) { SSH_DEBUG(1, ("iterating")); for (i = 0; i < 10; i++) { offset = ssh_rand() % (len + 1); iterlen = ssh_rand() % (len - offset + 1); SSH_ASSERT(offset + iterlen <= len); sum = 0; prevseg = NULL; prevseglen = 0; ssh_interceptor_packet_reset_iteration(pp, offset, iterlen); while (ssh_interceptor_packet_next_iteration(pp, &seg, &seglen)) { if (prevseg && prevseglen != 0) if (prevseg[0] != (prevseglen & 0xff)) { ssh_engine_test_fail(engine, "iter pointer not preserved"); ssh_interceptor_packet_free(pp); return; } sum += seglen; memset(seg, seglen & 0xff, seglen); prevseg = seg, prevseglen = seglen; } if (seg != NULL) { ssh_engine_test_fail(engine, "next iteration fails"); return; } if (sum != iterlen) { ssh_engine_test_fail(engine, "iteration test fails"); ssh_interceptor_packet_free(pp); return; } } } if (flags & SSH_INTERCEPTOR_TEST_BASIC_PREPEND) { SSH_DEBUG(1, ("inserting (prepend)")); seg = ssh_interceptor_packet_insert(pp, 0, 80); if (seg == NULL) { ssh_engine_test_fail(engine, "insert (prepend) 80 failed"); return; } memset(seg, 'I', 80); len += 80; for (i = 0; i < 10; i++) { seglen = ssh_rand() % (80 + 1); SSH_ASSERT(seglen <= 80); seg = ssh_interceptor_packet_insert(pp, 0, seglen); if (seg == NULL) { ssh_engine_test_fail(engine, "insert (prepend) failed"); return; } memset(seg, 'I', seglen); len += seglen; } if (len != ssh_interceptor_packet_len(pp)) { ssh_engine_test_fail(engine, "len mismatch after insert"); ssh_interceptor_packet_free(pp); return; } } if (flags & SSH_INTERCEPTOR_TEST_BASIC_PULLUP) { SSH_DEBUG(1, ("pullup")); for (i = 0; i < 10; i++) { seglen = ssh_rand() % (80 + 1); SSH_ASSERT(seglen <= 80); if (seglen > len) seglen = len; seg = ssh_interceptor_packet_pullup(pp, seglen); if (seg == NULL) { ssh_engine_test_fail(engine, "pullup failed"); return; } if (flags & SSH_INTERCEPTOR_TEST_BASIC_PREPEND) for (offset = 0; offset < seglen; offset++) if (seg[offset] != 'I') { ssh_engine_test_fail(engine, "pullup compare failed"); ssh_interceptor_packet_free(pp); return; } } if (len != ssh_interceptor_packet_len(pp)) { ssh_engine_test_fail(engine, "len mismatch after pullup"); ssh_interceptor_packet_free(pp); return; } } if (flags & SSH_INTERCEPTOR_TEST_BASIC_INSERT) { SSH_DEBUG(1, ("random inserts")); for (i = 0; i < 10; i++) { offset = ssh_rand() % (len + 1); seglen = ssh_rand() % (80 + 1); seg = ssh_interceptor_packet_insert(pp, offset, seglen); if (seg == NULL) { ssh_engine_test_fail(engine, "insert failed"); return; } memset(seg, 'i', seglen); len += seglen; } if (len != ssh_interceptor_packet_len(pp)) { ssh_engine_test_fail(engine, "len mismatch after pullup"); ssh_interceptor_packet_free(pp); return; } } if (flags & SSH_INTERCEPTOR_TEST_BASIC_DELETE) { SSH_DEBUG(1, ("random deletes")); for (i = 0; i < 10; i++) { offset = ssh_rand() % (len + 1); seglen = ssh_rand() % (len - offset + 1); if (!ssh_interceptor_packet_delete(pp, offset, seglen)) { ssh_engine_test_fail(engine, "packet_delete failed"); return; } len -= seglen; } if (len != ssh_interceptor_packet_len(pp)) { ssh_engine_test_fail(engine, "len mismatch after delete"); ssh_interceptor_packet_free(pp); return; } } if (flags & SSH_INTERCEPTOR_TEST_BASIC_ITERATE) { SSH_DEBUG(1, ("iterating again")); /* Again check that all iterations return correct total length. */ for (i = 0; i < 10; i++) { offset = ssh_rand() % (len + 1); iterlen = ssh_rand() % (len - offset + 1); SSH_ASSERT(offset + iterlen <= len); sum = 0; prevseg = NULL; prevseglen = 0; ssh_interceptor_packet_reset_iteration(pp, offset, iterlen); while (ssh_interceptor_packet_next_iteration(pp, &seg, &seglen)) { if (prevseg && prevseglen != 0) if (prevseg[0] != (prevseglen & 0xff)) { ssh_engine_test_fail(engine, "iter pointer not preserved"); ssh_interceptor_packet_free(pp); return; } sum += seglen; memset(seg, seglen & 0xff, seglen); prevseg = seg, prevseglen = seglen; } if (seg != NULL) { ssh_engine_test_fail(engine, "next iteration fails"); return; } if (sum != iterlen) { ssh_engine_test_fail(engine, "iteration (again) test fails"); ssh_interceptor_packet_free(pp); return; } } } SSH_DEBUG(1, ("freeing")); ssh_interceptor_packet_free(pp); } ssh_engine_test_ok(engine); }
/* Processes a TCP/IP packet for a flow. */ SshEngineProtocolMonitorRet ssh_engine_tcp_packet(SshEngineFlowData flow, SshEnginePacketContext pc) { SshEngineTcpData tcpdata = NULL; SshUInt32 tcp_data_offset; unsigned char tcph[SSH_TCP_HEADER_LEN]; SshUInt32 seq = 0, ack_seq, tcp_len = 0, pp_flags; SshUInt16 flags = 0; Boolean forward = FALSE; #ifdef SSH_IPSEC_TCP_SEQUENCE_MONITOR SshUInt32 *seq_ptr = NULL, *ack_seq_ptr; SshUInt32 *data_ptr, *ack_data_ptr; SshUInt8 *wnd_scale_ptr = NULL; #endif /* SSH_IPSEC_TCP_SEQUENCE_MONITOR */ #ifdef SSH_IPSEC_TCP_SEQUENCE_RANDOMIZER SshUInt32 new_seq, old_seq, new_ack; SshUInt16 sum; #endif /* SSH_IPEC_TCP_SEQUENCE_RANDOMIZER */ SSH_DEBUG(SSH_D_LOWOK, ("tcp/ip state processing")); SSH_ASSERT(pc->packet_len == ssh_interceptor_packet_len(pc->pp)); pc->audit.corruption = SSH_PACKET_CORRUPTION_NONE; /* Store flags in case pc->pp gets invalidated. */ pp_flags = pc->pp->flags; /* Check IP protocol. The flow mechanism also associates ICMP Destination Unreachable packets to this flow. */ if (pc->ipproto != SSH_IPPROTO_TCP) return SSH_ENGINE_MRET_PASS; /* Let all but first fragments through */ if ((pc->pp->flags & SSH_ENGINE_P_ISFRAG) && (pc->pp->flags & SSH_ENGINE_P_FIRSTFRAG) == 0) { tcp_data_offset = 0; goto pass; } /* Sanity check packet length. */ if (pc->packet_len < pc->hdrlen + SSH_TCP_HEADER_LEN) { SSH_DEBUG(SSH_D_NETGARB, ("DROP; packet too short to contain TCP header, len=%d", pc->packet_len)); return SSH_ENGINE_MRET_DROP; } /* Get TCP header. */ ssh_interceptor_packet_copyout(pc->pp, pc->hdrlen, tcph, SSH_TCP_HEADER_LEN); flags = SSH_TCPH_FLAGS(tcph); flags &= 0x3f; if (flow == NULL) { if (flags == SSH_TCPH_FLAG_SYN) return SSH_ENGINE_MRET_PASS; else return SSH_ENGINE_MRET_DROP; } seq = SSH_TCPH_SEQ(tcph); ack_seq = SSH_TCPH_ACK(tcph); /* Compute an estimate for the packet length */ if (pc->pp->flags & SSH_ENGINE_P_ISFRAG) { SSH_ASSERT(pc->pp->flags & SSH_ENGINE_P_FIRSTFRAG); tcp_data_offset = (SSH_TCPH_DATAOFFSET(tcph) << 2); tcp_len = 0xFFFF - pc->hdrlen - tcp_data_offset; } else { tcp_data_offset = (SSH_TCPH_DATAOFFSET(tcph) << 2); tcp_len = pc->packet_len - pc->hdrlen - tcp_data_offset; } /* If the data is beyond this packet boundary, (even if it is a fragment, we drop it) */ if (tcp_data_offset + pc->hdrlen > pc->packet_len) { SSH_DEBUG(SSH_D_NICETOKNOW, ("dropping packet because TCP header not in first fragment!")); return SSH_ENGINE_MRET_DROP; } if (flags & SSH_TCPH_FLAG_SYN) tcp_len++; else if (flags & SSH_TCPH_FLAG_FIN) tcp_len++; /* Dispatch based on the state of the session. */ forward = (pc->flags & SSH_ENGINE_PC_FORWARD) != 0; tcpdata = &flow->u.tcp; #ifdef SSH_IPSEC_TCP_SEQUENCE_RANDOMIZER /* Cancel previous ACK/seq values from checksum */ sum = SSH_TCPH_CHECKSUM(tcph); /* Compute new ack/seq values */ new_ack = 0; if (forward) { new_seq = (SshUInt32)(seq + tcpdata->delta_i_to_r); if (ack_seq != 0 || (flags & SSH_TCPH_FLAG_ACK) != 0) new_ack = (SshUInt32)(ack_seq - tcpdata->delta_r_to_i); SSH_DEBUG(SSH_D_MY, ("TCP sequence deltas [%u:%u]", tcpdata->delta_i_to_r, tcpdata->delta_r_to_i)); } else { new_seq = (SshUInt32)(seq + tcpdata->delta_r_to_i); if (ack_seq != 0 || (flags & SSH_TCPH_FLAG_ACK) != 0) new_ack = (SshUInt32)(ack_seq - tcpdata->delta_i_to_r); SSH_DEBUG(SSH_D_MY, ("TCP sequence deltas [%u:%u]", tcpdata->delta_r_to_i, tcpdata->delta_i_to_r)); } /* Cache old sequence value for IP cksum update */ old_seq = seq; seq = new_seq; #endif /* SSH_IPSEC_TCP_SEQUENCE_RANDOMIZER */ #ifdef SSH_IPSEC_TCP_SEQUENCE_MONITOR if (forward) { wnd_scale_ptr = &tcpdata->win_scale_r_to_i; seq_ptr = &tcpdata->seq_i_to_r; data_ptr = &tcpdata->data_i_to_r; ack_seq_ptr = &tcpdata->seq_r_to_i; ack_data_ptr = &tcpdata->data_r_to_i; } else { wnd_scale_ptr = &tcpdata->win_scale_i_to_r; seq_ptr = &tcpdata->seq_r_to_i; data_ptr = &tcpdata->data_r_to_i; ack_seq_ptr = &tcpdata->seq_i_to_r; ack_data_ptr = &tcpdata->data_i_to_r; } #endif /* SSH_IPSEC_TCP_SEQUENCE_MONITOR */ SSH_DEBUG(SSH_D_LOWOK, ("tcp: flow_flags=0x%04x forward=%d, state=%d, flags=0x%x, " "seq=%u, len=%u, ack=%u", (int)flow->data_flags, (int)forward, (int)tcpdata->state, (int)flags, seq, tcp_len, ack_seq)); switch (tcpdata->state) { case SSH_ENGINE_TCP_INITIAL: if (!forward) { SSH_DEBUG(SSH_D_NETGARB, ("Invalid TCP state")); goto reject; } /* SYN must open the session. */ if (flags == SSH_TCPH_FLAG_SYN) { #ifdef SSH_IPSEC_TCP_SEQUENCE_MONITOR /* Support for large TCP windows */ tcpdata->win_scale_i_to_r = 0; /* default max. size: 64k */ /* Does the packet contain TCP options? */ if (tcp_data_offset > SSH_TCP_HEADER_LEN) { SshTcpOptionStruct opt; const unsigned char *opt_buff; size_t opt_len; #ifndef SSH_IPSEC_IP_ONLY_INTERCEPTOR SSH_ASSERT(pc->protocol_offset == 0); #endif /* SSH_IPSEC_IP_ONLY_INTERCEPTOR */ opt_buff = ssh_interceptor_packet_pullup_read(pc->pp, pc->hdrlen + tcp_data_offset); if (opt_buff == NULL) { SSH_DEBUG(SSH_D_FAIL, ("ssh_interceptor_packet_pullup_read failed")); return SSH_ENGINE_MRET_ERROR; } opt_buff += pc->hdrlen + SSH_TCP_HEADER_LEN; opt_len = tcp_data_offset - SSH_TCP_HEADER_LEN; /* Read the shift count from window scale factor TCP option. Use default value if the option does not exist in this SYN packet. */ if (ssh_engine_tcp_option_parse(SSH_TCPOPT_WS, opt_buff, opt_len, &opt)) tcpdata->win_scale_i_to_r = opt.u.ws; } #endif /* SSH_IPSEC_TCP_SEQUENCE_MONITOR */ SSH_TCP_GOTO(flow, tcpdata, SSH_ENGINE_TCP_SYN); #ifdef SSH_IPSEC_TCP_SEQUENCE_MONITOR tcpdata->seq_i_to_r = seq; tcpdata->data_i_to_r = 1; #endif /* SSH_IPSEC_TCP_SEQUENCE_MONITOR */ break; } /* NULL-SCAN */ if (flags == 0) { pc->audit.corruption = SSH_PACKET_CORRUPTION_TCP_NULL; goto drop; } /* FIN-SCAN */ if (flags == SSH_TCPH_FLAG_FIN) { pc->audit.corruption = SSH_PACKET_CORRUPTION_TCP_FIN; goto drop; } SSH_DEBUG(SSH_D_NETGARB, ("Invalid packet in INITIAL state")); goto reject; case SSH_ENGINE_TCP_SYN: /* SYN is followed by SYN-ACK in the reverse direction */ if (flags & SSH_TCPH_FLAG_RST) { SSH_TCP_GOTO(flow, tcpdata, SSH_ENGINE_TCP_CLOSED); break; } if (forward) { /* Resend of SYN */ if (flags == SSH_TCPH_FLAG_SYN) { #ifdef SSH_IPSEC_TCP_SEQUENCE_MONITOR if (tcpdata->seq_i_to_r != seq) { SSH_DEBUG(SSH_D_NETGARB, ("bad sequence number for resent SYN")); pc->audit.corruption = SSH_PACKET_CORRUPTION_TCP_BAD_SEQUENCE; goto drop; } #endif /* SSH_IPSEC_TCP_SEQUENCE_MONITOR */ break; } } else { /* Must be SYN-ACK or RST */ if (flags == (SSH_TCPH_FLAG_SYN | SSH_TCPH_FLAG_ACK)) { #ifdef SSH_IPSEC_TCP_SEQUENCE_MONITOR if (tcpdata->seq_i_to_r + tcpdata->data_i_to_r != ack_seq) { SSH_DEBUG(SSH_D_NETGARB, ("bad ack sequence number for SYN-ACK")); pc->audit.corruption = SSH_PACKET_CORRUPTION_TCP_BAD_SEQUENCE; goto drop; } /* Support for large TCP windows */ tcpdata->win_scale_r_to_i = 0; /* default max. size: 64k */ /* Does the packet contain TCP options? */ if (tcp_data_offset > SSH_TCP_HEADER_LEN) { SshTcpOptionStruct opt; const unsigned char *opt_buff; size_t opt_len; #ifndef SSH_IPSEC_IP_ONLY_INTERCEPTOR SSH_ASSERT(pc->protocol_offset == 0); #endif /* SSH_IPSEC_IP_ONLY_INTERCEPTOR */ opt_buff = ssh_interceptor_packet_pullup_read(pc->pp, pc->hdrlen + tcp_data_offset); if (opt_buff == NULL) { SSH_DEBUG(SSH_D_FAIL, ("ssh_interceptor_packet_pullup_read failed")); return SSH_ENGINE_MRET_ERROR; } opt_buff += pc->hdrlen + SSH_TCP_HEADER_LEN; opt_len = tcp_data_offset - SSH_TCP_HEADER_LEN; /* Read the shift count from window scale factor TCP option. Use default value if the option does not exist in this SYN packet. */ if (ssh_engine_tcp_option_parse(SSH_TCPOPT_WS, opt_buff, opt_len, &opt)) tcpdata->win_scale_r_to_i = opt.u.ws; } tcpdata->seq_i_to_r = ack_seq; tcpdata->data_i_to_r = 0; tcpdata->seq_r_to_i = seq; tcpdata->data_r_to_i = 1; #endif /* SSH_IPSEC_TCP_SEQUENCE_MONITOR */ SSH_TCP_GOTO(flow, tcpdata, SSH_ENGINE_TCP_SYN_ACK); break; } } SSH_DEBUG(SSH_D_NETGARB, ("Invalid packet in SYN state")); goto reject; case SSH_ENGINE_TCP_SYN_ACK: /* SYN-ACK is followed by ACK in the forward direction */ if (flags & SSH_TCPH_FLAG_RST) { SSH_TCP_GOTO(flow, tcpdata, SSH_ENGINE_TCP_CLOSED); break; } if (forward) { /* Drop syn packet. If a correct SYN-ACK has been received, then this is obviously unnecessary. */ if (flags == SSH_TCPH_FLAG_SYN) { SSH_DEBUG(SSH_D_NICETOKNOW, ("Dropping extra SYN packet")); goto drop; } /* ACK of SYN-ACK, possibly with data as well. */ if ((flags & SSH_TCPH_FLAG_ACK) && !(flags & SSH_TCPH_FLAG_SYN)) { #ifdef SSH_IPSEC_TCP_SEQUENCE_MONITOR if ((SshUInt32)(tcpdata->seq_i_to_r + tcpdata->data_i_to_r) != seq) { SSH_DEBUG(SSH_D_NETGARB, ("bad sequence number in TCP packet")); pc->audit.corruption = SSH_PACKET_CORRUPTION_TCP_BAD_SEQUENCE; goto drop; } tcpdata->seq_i_to_r = (SshUInt32)seq; tcpdata->data_i_to_r = tcp_len; #endif /* SSH_IPSEC_TCP_SEQUENCE_MONITOR */ SSH_TCP_GOTO(flow, tcpdata, SSH_ENGINE_TCP_SYN_ACK_ACK); break; } } else { /* Must be resend of SYN_ACK */ if (flags == (SSH_TCPH_FLAG_SYN | SSH_TCPH_FLAG_ACK)) { #ifdef SSH_IPSEC_TCP_SEQUENCE_MONITOR if (tcpdata->seq_r_to_i != seq) { SSH_DEBUG(SSH_D_NETGARB, ("bad sequence number in TCP packet")); pc->audit.corruption = SSH_PACKET_CORRUPTION_TCP_BAD_SEQUENCE; goto drop; } #endif /* SSH_IPSEC_TCP_SEQUENCE_MONITOR */ break; } } SSH_DEBUG(SSH_D_NETGARB, ("Invalid packet in SYN_ACK state")); goto reject; case SSH_ENGINE_TCP_SYN_ACK_ACK: /* forward ACK means established, or resend of SYN-ACK */ #ifdef SSH_IPSEC_TCP_SEQUENCE_MONITOR if (ssh_engine_tcp_seq_magic(flags, seq, ack_seq, (SshUInt16)tcp_len, seq_ptr, data_ptr, ack_seq_ptr, ack_data_ptr, SSH_TCP_WINDOW_MAX(flags, 0)) == FALSE) { /* If this is out-of-order packet, just let it go. We don't wan't to cause unnecessary events to the policymanager. */ if (*seq_ptr > seq) { SSH_DEBUG(SSH_D_NETGARB, ("bad sequence number in TCP packet, window " "[%u,%u] ack %u", *seq_ptr, *data_ptr, seq)); pc->audit.corruption = SSH_PACKET_CORRUPTION_TCP_BAD_SEQUENCE; goto drop; } } if (flags & SSH_TCPH_FLAG_RST) { SSH_TCP_GOTO(flow, tcpdata, SSH_ENGINE_TCP_CLOSED); break; } #endif /* SSH_IPSEC_TCP_SEQUENCE_MONITOR */ if (forward) { /* A SYN in the forward direction might be a resend of an old SYN. Drop it. */ if (flags & SSH_TCPH_FLAG_SYN) { SSH_DEBUG(SSH_D_NETGARB, ("Resend of old SYN")); goto drop; } /* After forward ACK received, allow any established */ if (flags & SSH_TCPH_FLAG_FIN) { SSH_TCP_GOTO(flow, tcpdata, SSH_ENGINE_TCP_FIN_FWD); break; } } else { /* After forward ACK received, allow established or resend SYN-ACK */ if (flags == (SSH_TCPH_FLAG_SYN | SSH_TCPH_FLAG_ACK)) { break; } if (flags & SSH_TCPH_FLAG_SYN) { SSH_DEBUG(SSH_D_NETGARB, ("Invalid SYN packet in SYN_ACK_ACK state")); goto reject; } if (flags & SSH_TCPH_FLAG_FIN) { SSH_TCP_GOTO(flow, tcpdata, SSH_ENGINE_TCP_FIN_REV); break; } /* Server sent some data, it must have seen our forward ACK. */ SSH_TCP_GOTO(flow, tcpdata, SSH_ENGINE_TCP_ESTABLISHED); } break; /* In this state we accept by default. */ case SSH_ENGINE_TCP_ESTABLISHED: #ifndef SSH_IPSEC_TCP_SEQUENCE_MONITOR case SSH_ENGINE_TCP_FIN_FWD: case SSH_ENGINE_TCP_FIN_REV: #endif /* SSH_IPEC_TCP_SEQUENCE_MONITOR */ /* Established and reverse ack seen. */ #ifdef SSH_IPSEC_TCP_SEQUENCE_MONITOR if (ssh_engine_tcp_seq_magic(flags, seq, ack_seq, (SshUInt16)tcp_len, seq_ptr, data_ptr, ack_seq_ptr, ack_data_ptr, SSH_TCP_WINDOW_MAX(flags, *wnd_scale_ptr)) == FALSE) { pc->audit.corruption = SSH_PACKET_CORRUPTION_TCP_BAD_SEQUENCE; goto drop; } #endif /* SSH_IPSEC_TCP_SEQUENCE_MONITOR */ if (flags & SSH_TCPH_FLAG_SYN) { SSH_DEBUG(SSH_D_NETGARB, ("Unacceptable SYN packet!")); if (forward) goto drop; else goto reject; } if (flags & SSH_TCPH_FLAG_FIN) { if (tcpdata->state == SSH_ENGINE_TCP_ESTABLISHED) SSH_TCP_GOTO(flow, tcpdata, (forward ? SSH_ENGINE_TCP_FIN_FWD : SSH_ENGINE_TCP_FIN_REV)); #ifndef SSH_IPSEC_TCP_SEQUENCE_MONITOR else if ((tcpdata->state == SSH_ENGINE_TCP_FIN_FWD && forward == 0) || (tcpdata->state == SSH_ENGINE_TCP_FIN_REV && forward)) SSH_TCP_GOTO(flow, tcpdata, SSH_ENGINE_TCP_FIN_FIN); #endif /* SSH_IPSEC_TCP_SEQUENCE_MONITOR */ } /* RST's are heeded only if sequence numbers are monitored */ else if ((flags & SSH_TCPH_FLAG_RST) #ifndef SSH_IPSEC_TCP_SEQUENCE_MONITOR /* Handle so-called "half-duplex close" correctly */ && (tcpdata->state == SSH_ENGINE_TCP_FIN_FWD || tcpdata->state == SSH_ENGINE_TCP_FIN_REV) #endif /* SSH_IPSEC_TCP_SEQUENCE_MONITOR */ ) { SSH_TCP_GOTO(flow, tcpdata, SSH_ENGINE_TCP_CLOSED); break; } break; #ifdef SSH_IPSEC_TCP_SEQUENCE_MONITOR case SSH_ENGINE_TCP_FIN_FWD: /* FIN seen in forward direction. */ if (!forward && (flags & SSH_TCPH_FLAG_FIN)) SSH_TCP_GOTO(flow, tcpdata, SSH_ENGINE_TCP_FIN_FIN); else if (flags & SSH_TCPH_FLAG_RST) { SSH_TCP_GOTO(flow, tcpdata, SSH_ENGINE_TCP_CLOSED); break; } if (flags & SSH_TCPH_FLAG_SYN) { SSH_DEBUG(SSH_D_NETGARB, ("Invalid SYN packet in FIN_FWD state")); goto reject; } break; case SSH_ENGINE_TCP_FIN_REV: /* FIN seen in reverse direction. */ if (forward && (flags & SSH_TCPH_FLAG_FIN)) SSH_TCP_GOTO(flow, tcpdata, SSH_ENGINE_TCP_FIN_FIN); else if (flags & SSH_TCPH_FLAG_RST) { SSH_TCP_GOTO(flow, tcpdata, SSH_ENGINE_TCP_CLOSED); break; } if (flags & SSH_TCPH_FLAG_SYN) { SSH_DEBUG(SSH_D_NETGARB, ("Invalid SYN packet in FIN_REV state")); goto reject; } break; #endif /* SSH_IPSEC_TCP_SEQUENCE_MONITOR */ case SSH_ENGINE_TCP_FIN_FIN: case SSH_ENGINE_TCP_CLOSE_WAIT: /* FIN seen in both directions. Could get ACK or resend. */ if (flags & SSH_TCPH_FLAG_RST) { SSH_TCP_GOTO(flow, tcpdata, SSH_ENGINE_TCP_CLOSED); break; } if (flags & SSH_TCPH_FLAG_SYN) { SSH_DEBUG(SSH_D_NETGARB, ("Invalid SYN packet in TCP state %u", tcpdata->state)); goto reject; } if (flags == SSH_TCPH_FLAG_ACK) SSH_TCP_GOTO(flow, tcpdata, SSH_ENGINE_TCP_CLOSE_WAIT); break; case SSH_ENGINE_TCP_CLOSED: /* Only RST packets are allowed through */ if (flags == SSH_TCPH_FLAG_RST || flags == (SSH_TCPH_FLAG_RST|SSH_TCPH_FLAG_ACK)) break; /* No traffic allowed in any direction. */ SSH_DEBUG(SSH_D_NETGARB, ("No traffic allowed in CLOSED state")); goto reject; default: ssh_fatal("ssh_engine_tcp_packet: invalid state %d", tcpdata->state); } /* Insert modified sequence numbers into packet if it is going to be passed. */ #ifdef SSH_IPSEC_TCP_SEQUENCE_RANDOMIZER /* Update header */ SSH_TCPH_SET_SEQ(tcph, new_seq); SSH_TCPH_SET_ACK(tcph, new_ack); /* Offset is only used for checking alignment */ sum = ssh_ip_cksum_update_long(sum, 0, old_seq, new_seq); sum = ssh_ip_cksum_update_long(sum, 0, ack_seq, new_ack); SSH_TCPH_SET_CHECKSUM(tcph, sum); /* Copy TCP header back to the packet. */ if (!ssh_interceptor_packet_copyin(pc->pp, pc->hdrlen, tcph, SSH_TCP_HEADER_LEN)) return SSH_ENGINE_MRET_ERROR; /* pc->pp is already freed. */ #endif /* SSH_IPSEC_TCP_SEQUENCE_RANDOMIZER */ pass: return SSH_ENGINE_MRET_PASS; drop: return SSH_ENGINE_MRET_DROP; reject: return SSH_ENGINE_MRET_REJECT; }