Exemplo n.º 1
0
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;
}
Exemplo n.º 3
0
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);
}
Exemplo n.º 4
0
/* 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;
}