From 404754bc90b48636a04c70d952b5f3172570aac6 Mon Sep 17 00:00:00 2001 From: zzz Date: Fri, 6 Nov 2009 12:47:21 +0000 Subject: [PATCH 1/4] streaming lib packet capture first cut --- .../net/i2p/client/streaming/Connection.java | 4 + .../client/streaming/ConnectionHandler.java | 3 + .../client/streaming/ConnectionManager.java | 3 + .../i2p/client/streaming/MessageHandler.java | 4 +- .../src/net/i2p/client/streaming/Packet.java | 2 +- .../i2p/client/streaming/PacketHandler.java | 12 + .../net/i2p/client/streaming/PacketLocal.java | 36 +++ .../net/i2p/client/streaming/PacketQueue.java | 1 + .../net/i2p/client/streaming/PcapWriter.java | 289 ++++++++++++++++++ .../net/i2p/client/I2PSessionMuxedImpl.java | 2 +- .../src/net/i2p/router/RouterVersion.java | 2 +- 11 files changed, 354 insertions(+), 4 deletions(-) create mode 100644 apps/streaming/java/src/net/i2p/client/streaming/PcapWriter.java diff --git a/apps/streaming/java/src/net/i2p/client/streaming/Connection.java b/apps/streaming/java/src/net/i2p/client/streaming/Connection.java index a47d361a66..1c79e97d1c 100644 --- a/apps/streaming/java/src/net/i2p/client/streaming/Connection.java +++ b/apps/streaming/java/src/net/i2p/client/streaming/Connection.java @@ -73,6 +73,8 @@ public class Connection { private int _randomWait; private long _lifetimeBytesSent; + /** TBD for tcpdump-compatible ack output */ + private long _lowestBytesAckedThrough; private long _lifetimeBytesReceived; private long _lifetimeDupMessageSent; private long _lifetimeDupMessageReceived; @@ -742,7 +744,9 @@ public class Connection { public long getCongestionWindowEnd() { return _congestionWindowEnd; } public void setCongestionWindowEnd(long endMsg) { _congestionWindowEnd = endMsg; } + /** @return the highest outbound packet we have recieved an ack for */ public long getHighestAckedThrough() { return _highestAckedThrough; } + /** @deprecated unused */ public void setHighestAckedThrough(long msgNum) { _highestAckedThrough = msgNum; } public long getLastActivityOn() { diff --git a/apps/streaming/java/src/net/i2p/client/streaming/ConnectionHandler.java b/apps/streaming/java/src/net/i2p/client/streaming/ConnectionHandler.java index 382c984d9a..9e5e5c9635 100644 --- a/apps/streaming/java/src/net/i2p/client/streaming/ConnectionHandler.java +++ b/apps/streaming/java/src/net/i2p/client/streaming/ConnectionHandler.java @@ -175,6 +175,9 @@ public class ConnectionHandler { // between here and PacketHandler, causing the packet to loop forever.... _manager.getPacketHandler().receivePacketDirect(packet, false); } else { + // log it here, just before we kill it - dest will be unknown + ((PacketLocal)packet).logTCPDump(true); + // goodbye if (_log.shouldLog(Log.WARN)) _log.warn("Did not find con for queued non-syn packet, dropping: " + packet); diff --git a/apps/streaming/java/src/net/i2p/client/streaming/ConnectionManager.java b/apps/streaming/java/src/net/i2p/client/streaming/ConnectionManager.java index af44c41f24..1930881480 100644 --- a/apps/streaming/java/src/net/i2p/client/streaming/ConnectionManager.java +++ b/apps/streaming/java/src/net/i2p/client/streaming/ConnectionManager.java @@ -176,6 +176,9 @@ public class ConnectionManager { } con.setReceiveStreamId(receiveId); + // finally, we know enough that we can log the packet with the conn filled in + ((PacketLocal)synPacket).setConnection(con); + ((PacketLocal)synPacket).logTCPDump(true); try { con.getPacketHandler().receivePacket(synPacket, con); } catch (I2PException ie) { diff --git a/apps/streaming/java/src/net/i2p/client/streaming/MessageHandler.java b/apps/streaming/java/src/net/i2p/client/streaming/MessageHandler.java index 98165cf7de..3a74a49848 100644 --- a/apps/streaming/java/src/net/i2p/client/streaming/MessageHandler.java +++ b/apps/streaming/java/src/net/i2p/client/streaming/MessageHandler.java @@ -45,7 +45,9 @@ public class MessageHandler implements I2PSessionListener { return; } if (data == null) return; - Packet packet = new Packet(); + //Packet packet = new Packet(); + // for tcpdump + Packet packet = new PacketLocal(_context, null); try { packet.readPacket(data, 0, data.length); _manager.getPacketHandler().receivePacket(packet); diff --git a/apps/streaming/java/src/net/i2p/client/streaming/Packet.java b/apps/streaming/java/src/net/i2p/client/streaming/Packet.java index 2a6ae59ad1..e077d2f194 100644 --- a/apps/streaming/java/src/net/i2p/client/streaming/Packet.java +++ b/apps/streaming/java/src/net/i2p/client/streaming/Packet.java @@ -42,7 +42,7 @@ import net.i2p.util.Log; *
  • {@link #FLAG_MAX_PACKET_SIZE_INCLUDED}: 2 byte integer
  • *
  • {@link #FLAG_PROFILE_INTERACTIVE}: no option data
  • *
  • {@link #FLAG_ECHO}: no option data
  • - *
  • {@link #FLAG_NO_ACK}: no option data
  • + *
  • {@link #FLAG_NO_ACK}: no option data - this appears to be unused, we always ack, even for the first packet
  • * * *

    If the signature is included, it uses the Destination's DSA key diff --git a/apps/streaming/java/src/net/i2p/client/streaming/PacketHandler.java b/apps/streaming/java/src/net/i2p/client/streaming/PacketHandler.java index 19e62db0c9..a8233a26b1 100644 --- a/apps/streaming/java/src/net/i2p/client/streaming/PacketHandler.java +++ b/apps/streaming/java/src/net/i2p/client/streaming/PacketHandler.java @@ -107,6 +107,8 @@ public class PacketHandler { receiveUnknownCon(packet, sendId, queueIfNoConn); displayPacket(packet, "UNKN", null); } + // Don't log here, wait until we have the conn to make the dumps easier to follow + //((PacketLocal)packet).logTCPDump(true); } private static final SimpleDateFormat _fmt = new SimpleDateFormat("HH:mm:ss.SSS"); @@ -127,6 +129,9 @@ public class PacketHandler { } private void receiveKnownCon(Connection con, Packet packet) { + // is this ok here or does it need to be below each packetHandler().receivePacket() ? + ((PacketLocal)packet).setConnection(con); + ((PacketLocal)packet).logTCPDump(true); if (packet.isFlagSet(Packet.FLAG_ECHO)) { if (packet.getSendStreamId() > 0) { if (con.getOptions().getAnswerPings()) @@ -266,8 +271,13 @@ public class PacketHandler { } if (packet.isFlagSet(Packet.FLAG_SYNCHRONIZE)) { + // logTCPDump() will be called in ConnectionManager.receiveConnection(), + // which is called by ConnectionHandler.receiveNewSyn(), + // after we have a new conn, which makes the logging better. _manager.getConnectionHandler().receiveNewSyn(packet); } else if (queueIfNoConn) { + // don't call logTCPDump() here, wait for it to find a conn + // We can get here on the 2nd+ packet if the 1st (SYN) packet // is still on the _synQueue in the ConnectionHandler, and // ConnectionManager.receiveConnection() hasn't run yet to put @@ -293,6 +303,8 @@ public class PacketHandler { //packet.releasePayload(); _manager.getConnectionHandler().receiveNewSyn(packet); } else { + // log it here, just before we kill it - dest will be unknown + ((PacketLocal)packet).logTCPDump(true); // don't queue again (infinite loop!) sendReset(packet); packet.releasePayload(); diff --git a/apps/streaming/java/src/net/i2p/client/streaming/PacketLocal.java b/apps/streaming/java/src/net/i2p/client/streaming/PacketLocal.java index b1438a033a..127f53131d 100644 --- a/apps/streaming/java/src/net/i2p/client/streaming/PacketLocal.java +++ b/apps/streaming/java/src/net/i2p/client/streaming/PacketLocal.java @@ -1,5 +1,6 @@ package net.i2p.client.streaming; +import java.io.IOException; import java.util.Set; import net.i2p.I2PAppContext; @@ -28,6 +29,9 @@ public class PacketLocal extends Packet implements MessageOutputStream.WriteStat private volatile int _nackCount; private volatile boolean _retransmitted; private SimpleTimer2.TimedEvent _resendEvent; + private static final Object initLock = new Object(); + private static boolean _initialized; + private static PcapWriter _pcapWriter; public PacketLocal(I2PAppContext ctx, Destination to) { this(ctx, to, null); @@ -42,6 +46,12 @@ public class PacketLocal extends Packet implements MessageOutputStream.WriteStat _cancelledOn = -1; _nackCount = 0; _retransmitted = false; + synchronized(initLock) { + if (!_initialized) { + initPcap(); + _initialized = true; + } + } } public Destination getTo() { return _to; } @@ -139,6 +149,8 @@ public class PacketLocal extends Packet implements MessageOutputStream.WriteStat public int getNumSends() { return _numSends; } public long getLastSend() { return _lastSend; } public Connection getConnection() { return _connection; } + /** used to set the rcvd conn after the fact for incoming syn replies */ + public void setConnection(Connection con) { _connection = con; } public void incrementNACKs() { int cnt = ++_nackCount; @@ -242,4 +254,28 @@ public class PacketLocal extends Packet implements MessageOutputStream.WriteStat public boolean writeAccepted() { return _acceptedOn > 0 && _cancelledOn <= 0; } public boolean writeFailed() { return _cancelledOn > 0; } public boolean writeSuccessful() { return _ackOn > 0 && _cancelledOn <= 0; } + + static final String PCAP = "foo.pcap"; + private void initPcap() { + try { + _pcapWriter = new PcapWriter(_context, PCAP); + } catch (IOException ioe) { + System.err.println("pcap init ioe: " + ioe); + } + } + + /** Generate a pcap/tcpdump-compatible format, + * so we can use standard debugging tools. + */ + public void logTCPDump(boolean isInbound) { + if (!_log.shouldLog(Log.INFO)) return; + _log.info(toString()); + if (_pcapWriter != null) { + try { + _pcapWriter.write(this, isInbound); + } catch (IOException ioe) { + _log.warn("pcap write ioe: " + ioe); + } + } + } } diff --git a/apps/streaming/java/src/net/i2p/client/streaming/PacketQueue.java b/apps/streaming/java/src/net/i2p/client/streaming/PacketQueue.java index 8a4692ada7..4b6c69230e 100644 --- a/apps/streaming/java/src/net/i2p/client/streaming/PacketQueue.java +++ b/apps/streaming/java/src/net/i2p/client/streaming/PacketQueue.java @@ -150,6 +150,7 @@ public class PacketQueue { Connection c = packet.getConnection(); String suffix = (c != null ? "wsize " + c.getOptions().getWindowSize() + " rto " + c.getOptions().getRTO() : null); _connectionManager.getPacketHandler().displayPacket(packet, "SEND", suffix); + ((PacketLocal)packet).logTCPDump(false); } if ( (packet.getSequenceNum() == 0) && (!packet.isFlagSet(Packet.FLAG_SYNCHRONIZE)) ) { diff --git a/apps/streaming/java/src/net/i2p/client/streaming/PcapWriter.java b/apps/streaming/java/src/net/i2p/client/streaming/PcapWriter.java new file mode 100644 index 0000000000..724fb1ee30 --- /dev/null +++ b/apps/streaming/java/src/net/i2p/client/streaming/PcapWriter.java @@ -0,0 +1,289 @@ +package net.i2p.client.streaming; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; + +import net.i2p.I2PAppContext; +import net.i2p.data.DataFormatException; +import net.i2p.data.DataHelper; +import net.i2p.data.Hash; + +/** + * Write a standard pcap file with a "TCP" packet that can be analyzed with + * standard tools such as wireshark. + * + * The source and dest "IP" and "port" are fake but are generated from the + * hashes of the Destinations and stream ID's, so they will be consistent. + * The local "IP" will always be of the form 127.0.x.y + * Initial IP for a conn will be 127.0.0.0 for the local and 0.0.0.0 for the remote. + * + * Reference: http://wiki.wireshark.org/Development/LibpcapFileFormat + * + * The Jpcap library http://netresearch.ics.uci.edu/kfujii/jpcap/doc/ + * was close to what I want, but it requires you to instantiate a "captor" + * before you can write a file, and it requires a native lib to do so, + * and even then, it only wants to read the file, not write it. + * + * We even calculate a correct TCP header checksum to keep the tools happy. + * We don't, however, convert I2P-style sequence numbers, which count packets, + * to TCP-style byte counts. We don't track a lowest-acked-thru byte count atm, really. + * + * We do represent the window size in bytes though, so that's real confusing. + * + * This is designed to debug the streaming lib, but there are not log calls for every + * single packet - pings and pongs, and various odd cases where received packets + * are dropped, are not logged. + * + * Yes we could dump it natively and write a wireshark dissector. That sounds hard. + * And we wouldn't get the TCP stream analysis built into the tools. + * + * @author zzz + */ +public class PcapWriter { + + /** big-endian, see file format ref - 24 bytes */ + private static final byte[] FILE_HEADER = { (byte) 0xa1, (byte) 0xb2, (byte) 0xc3, (byte) 0xd4, + 0, 2, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, (byte) 0xff, (byte) 0xff, 0, 0, 0, 1 }; + + /** dummy macs and ethertype */ + private static final byte[] MAC_HEADER = { 1, 2, 3, 4, 5, 6, + 1, 2, 3, 4, 5, 6, + (byte) 0x80, 0 }; + private static final byte[] IP_HEADER_1 = { 0x45, 0 }; // the length goes after this + private static final byte[] IP_HEADER_2 = { 0x12, 0x34, 0x40, 0, 64, 6 }; // ID, flags, TTL and TCP + private static final byte[] UNK_IP = { (byte) 0xff, 0, 0, 0}; + private static final byte[] MY_UNK_IP = {127, 0, 0, 0}; + /** max # of streaming lib payload bytes to dump */ + private static final int MAX_PAYLOAD_BYTES = 10; + + private FileOutputStream _fos; + private I2PAppContext _context; + + public PcapWriter(I2PAppContext ctx, String file) throws IOException { + _context = ctx; + File f = new File(ctx.getLogDir(), file); + //if (f.exists()) { + // _fos = new FileOutputStream(f, true); + //} else { + _fos = new FileOutputStream(f); + _fos.write(FILE_HEADER); + //} + } + + public void close() { + FileOutputStream fos = _fos; + if (fos != null) { + try { + fos.close(); + } catch (IOException ioe) {} + _fos = null; + } + } + + public void write(PacketLocal pkt, boolean isInbound) throws IOException { + try { + wrt(pkt, isInbound); + } catch (DataFormatException dfe) { + dfe.printStackTrace(); + throw new IOException(dfe.toString()); + } + // remove me + _fos.flush(); + } + + private synchronized void wrt(PacketLocal pkt, boolean isInbound) throws IOException, DataFormatException { + FileOutputStream fos = _fos; + if (fos == null) + throw new IOException("Not open or already closed"); + Connection con = pkt.getConnection(); + int includeLen = Math.min(MAX_PAYLOAD_BYTES, pkt.getPayloadSize()); + + // PCAP Header + long now; + if (isInbound) + now = pkt.getCreatedOn(); + else + now = pkt.getLastSend(); + DataHelper.writeLong(fos, 4, now / 1000); + DataHelper.writeLong(fos, 4, 1000 * (now % 1000)); + DataHelper.writeLong(fos, 4, 54 + includeLen); // 14 MAC + 20 IP + 20 TCP + DataHelper.writeLong(fos, 4, 58 + pkt.getPayloadSize()); // 54 + MAC checksum + + // MAC Header 14 bytes + fos.write(MAC_HEADER); + + // IP 20 bytes total + // IP Header 12 bytes + int length = 20 + 20 + pkt.getPayloadSize(); + fos.write(IP_HEADER_1); + DataHelper.writeLong(fos, 2, length); // total IP length + fos.write(IP_HEADER_2); + + // src and dst IP 8 bytes + // make our side always start with 127.0.x.x + byte[] srcAddr, dstAddr; + if (isInbound) { + if (con != null) { + dstAddr = new byte[4]; + dstAddr[0] = 127; + dstAddr[1] = 0; + System.arraycopy(con.getSession().getMyDestination().calculateHash().getData(), 0, dstAddr, 2, 2); + } else + dstAddr = MY_UNK_IP; + + if (con != null && con.getRemotePeer() != null) + srcAddr = con.getRemotePeer().calculateHash().getData(); + else if (pkt.getOptionalFrom() != null) + srcAddr = pkt.getOptionalFrom().calculateHash().getData(); + else + srcAddr = UNK_IP; + } else { + if (con != null) { + srcAddr = new byte[4]; + srcAddr[0] = 127; + srcAddr[1] = 0; + System.arraycopy(con.getSession().getMyDestination().calculateHash().getData(), 0, srcAddr, 2, 2); + } else + srcAddr = MY_UNK_IP; + + if (con != null && con.getRemotePeer() != null) + dstAddr = con.getRemotePeer().calculateHash().getData(); + else + dstAddr = UNK_IP; + } + + // calculate and output the correct IP header checksum to keep the analyzers happy + int checksum = length; + checksum = update(checksum, IP_HEADER_1); + checksum = update(checksum, IP_HEADER_2); + checksum = update(checksum, srcAddr, 4); + checksum = update(checksum, dstAddr, 4); + DataHelper.writeLong(fos, 2, checksum ^ 0xffff); + + // IPs + fos.write(srcAddr, 0, 4); + fos.write(dstAddr, 0, 4); + + // TCP header 20 bytes total + // src and dst port 4 bytes + // the rcv ID is the source, and the send ID is the dest. + DataHelper.writeLong(fos, 2, pkt.getReceiveStreamId() & 0xffff); + DataHelper.writeLong(fos, 2, pkt.getSendStreamId() & 0xffff); + + // seq and acks 8 bytes + long seq; + // wireshark wants the seq # in a SYN packet to be one less than the first data packet, + // so let's set it to 0. ??????????? + if (pkt.isFlagSet(Packet.FLAG_SYNCHRONIZE)) + seq = 0xffffffff; + else + seq = pkt.getSequenceNum(); + long acked = 0; + if (con != null) { + if (isInbound) + acked = getLowestAckedThrough(pkt, con); + else + acked = getLowestAckedThrough(pkt, con); + } + DataHelper.writeLong(fos, 4, pkt.getSequenceNum()); + DataHelper.writeLong(fos, 4, acked); + + // offset and flags 2 bytes + int flags = 0; + if (pkt.isFlagSet(Packet.FLAG_CLOSE)) + flags |= 0x01; + if (pkt.isFlagSet(Packet.FLAG_SYNCHRONIZE)) + flags |= 0x02; + if (pkt.isFlagSet(Packet.FLAG_RESET)) + flags |= 0x04; + if (!pkt.isFlagSet(Packet.FLAG_NO_ACK)) + flags |= 0x10; + // delay request -> ECE, not a perfect match, but ok for now + if (pkt.isFlagSet(Packet.FLAG_DELAY_REQUESTED)) + flags |= 0x40; + //if (pkt.isFlagSet(FLAG_DELAY_REQUESTED)) + // foo; + DataHelper.writeLong(fos, 1, 0x50); // 5 32-byte words + DataHelper.writeLong(fos, 1, flags); + + // window size 2 bytes + long window = ConnectionOptions.INITIAL_WINDOW_SIZE; + long msgSize = ConnectionOptions.DEFAULT_MAX_MESSAGE_SIZE; + if (con != null) { + if (isInbound) { + // try to represent what he thinks the window is, we don't really know + // this isn't really right, the lastsendid can get way ahead + window = acked + con.getOptions().getWindowSize() - con.getLastSendId(); + } else { + // following is from ConnectionPacketHandler + long ready = con.getInputStream().getHighestReadyBockId(); + int available = con.getOptions().getInboundBufferSize() - con.getInputStream().getTotalReadySize(); + int allowedBlocks = available/con.getOptions().getMaxMessageSize(); + window = (ready + allowedBlocks) - pkt.getSequenceNum(); + } + if (window < 0) + window = 0; + msgSize = con.getOptions().getMaxMessageSize(); + } + // messages -> bytes + window *= msgSize; + // for now we don't spoof window scaling + if (window > 65535) + window = 65535; + DataHelper.writeLong(fos, 2, window); + + // checksum and urgent pointer 4 bytes + DataHelper.writeLong(fos, 4, 0); + + // some data + if (includeLen > 0) + fos.write(pkt.getPayload().getData(), 0, includeLen); + } + + /** + * copied from Connection.ackPackets() + * + * This is really nasty, but if the packet has an ACK, then we + * find the lowest NACK, and we are acked thru the lowest - 1. + * + * If there is no ACK, then we could use the conn's highest acked through, + * for an inbound packet (containing acks for outbound packets) + * But it appears that all packets have ACKs, as FLAG_NO_ACK is never set. + * + * To do: Add the SACK option to the TCP header. + */ + private static long getLowestAckedThrough(PacketLocal pkt, Connection con) { + long nacks[] = pkt.getNacks(); + long lowest = pkt.getAckThrough(); // can return -1 but we increment below + if (nacks != null) { + for (int i = 0; i < nacks.length; i++) { + if (nacks[i] - 1 < lowest) + lowest = nacks[i] - 1; + } + } + // I2P ack is of current seq number; TCP is next expected seq number + // should be >= 0 now + lowest++; + // just in case + return Math.max(0, lowest); + } + + /** one's complement 2-byte checksum update */ + private static int update(int checksum, byte[] b) { + return update(checksum, b, b.length); + } + + private static int update(int checksum, byte[] b, int len) { + int rv = checksum; + for (int i = 0; i < len; i += 2) { + rv += ((b[i] << 8) & 0xff00) | (b[i+1] & 0xff); + if (rv > 0xffff) { + rv &= 0xffff; + rv++; + } + } + return rv; + } +} diff --git a/core/java/src/net/i2p/client/I2PSessionMuxedImpl.java b/core/java/src/net/i2p/client/I2PSessionMuxedImpl.java index c0533b1fff..54efdc1a89 100644 --- a/core/java/src/net/i2p/client/I2PSessionMuxedImpl.java +++ b/core/java/src/net/i2p/client/I2PSessionMuxedImpl.java @@ -271,7 +271,7 @@ class I2PSessionMuxedImpl extends I2PSessionImpl2 implements I2PSession { _demultiplexer.messageAvailable(I2PSessionMuxedImpl.this, msg.id, msg.size, msg.proto, msg.fromPort, msg.toPort); } catch (Exception e) { - _log.error("Error notifying app of message availability"); + _log.error("Error notifying app of message availability", e); } } } diff --git a/router/java/src/net/i2p/router/RouterVersion.java b/router/java/src/net/i2p/router/RouterVersion.java index 3e5e8985dd..ffb1da55c8 100644 --- a/router/java/src/net/i2p/router/RouterVersion.java +++ b/router/java/src/net/i2p/router/RouterVersion.java @@ -20,7 +20,7 @@ public class RouterVersion { public final static String VERSION = CoreVersion.VERSION; public final static long BUILD = 12; /** for example "-test" */ - public final static String EXTRA = ""; + public final static String EXTRA = "-pcap"; public final static String FULL_VERSION = VERSION + "-" + BUILD + EXTRA; public static void main(String args[]) { System.out.println("I2P Router version: " + FULL_VERSION); From 7c36c0c8e7394be41d54d697bdfeac28d7f6b353 Mon Sep 17 00:00:00 2001 From: zzz Date: Fri, 6 Nov 2009 16:39:05 +0000 Subject: [PATCH 2/4] add TCP options block --- .../net/i2p/client/streaming/PcapWriter.java | 90 +++++++++++++++++-- 1 file changed, 81 insertions(+), 9 deletions(-) diff --git a/apps/streaming/java/src/net/i2p/client/streaming/PcapWriter.java b/apps/streaming/java/src/net/i2p/client/streaming/PcapWriter.java index 724fb1ee30..e838ad0310 100644 --- a/apps/streaming/java/src/net/i2p/client/streaming/PcapWriter.java +++ b/apps/streaming/java/src/net/i2p/client/streaming/PcapWriter.java @@ -58,6 +58,20 @@ public class PcapWriter { /** max # of streaming lib payload bytes to dump */ private static final int MAX_PAYLOAD_BYTES = 10; + /** options - give our custom ones some mnemonics */ + private static final int MAX_OPTION_LEN = 40; + private static final byte OPTION_END = 0; + private static final byte OPTION_MSS = 2; + private static final byte OPTION_PING = 6; + private static final byte OPTION_PONG = 7; + private static final byte OPTION_SIGREQ = 0x55; + private static final byte OPTION_SIG = 0x56; + private static final byte OPTION_RDELAY = (byte) 0xde; + private static final byte OPTION_ODELAY = (byte) 0xd0; + private static final byte OPTION_FROM = (byte) 0xf0; + private static final byte OPTION_NACK = (byte) 0xac; + + private FileOutputStream _fos; private I2PAppContext _context; @@ -100,6 +114,31 @@ public class PcapWriter { Connection con = pkt.getConnection(); int includeLen = Math.min(MAX_PAYLOAD_BYTES, pkt.getPayloadSize()); + // option block + Options opts = new Options(); + if (pkt.isFlagSet(Packet.FLAG_MAX_PACKET_SIZE_INCLUDED)) + opts.add(OPTION_MSS, 2, pkt.getOptionalMaxSize()); + if (pkt.isFlagSet(Packet.FLAG_DELAY_REQUESTED)) + opts.add(OPTION_ODELAY, 2, pkt.getOptionalDelay()); + if (pkt.getResendDelay() > 0) + opts.add(OPTION_RDELAY, 1, pkt.getResendDelay()); + if (pkt.isFlagSet(Packet.FLAG_SIGNATURE_REQUESTED)) + opts.add(OPTION_SIGREQ); + if (pkt.isFlagSet(Packet.FLAG_SIGNATURE_INCLUDED)) + opts.add(OPTION_SIG); + if (pkt.isFlagSet(Packet.FLAG_FROM_INCLUDED)) + opts.add(OPTION_FROM); + if (pkt.isFlagSet(Packet.FLAG_ECHO)) { + if (pkt.getSendStreamId() > 0) + opts.add(OPTION_PING); + else + opts.add(OPTION_PONG); + } + if (pkt.getNacks() != null) + opts.add(OPTION_NACK, 1, pkt.getNacks().length); + int optLen = opts.size(); + byte options[] = opts.getData(); + // PCAP Header long now; if (isInbound) @@ -108,15 +147,15 @@ public class PcapWriter { now = pkt.getLastSend(); DataHelper.writeLong(fos, 4, now / 1000); DataHelper.writeLong(fos, 4, 1000 * (now % 1000)); - DataHelper.writeLong(fos, 4, 54 + includeLen); // 14 MAC + 20 IP + 20 TCP - DataHelper.writeLong(fos, 4, 58 + pkt.getPayloadSize()); // 54 + MAC checksum + DataHelper.writeLong(fos, 4, 54 + optLen + includeLen); // 14 MAC + 20 IP + 20 TCP + DataHelper.writeLong(fos, 4, 58 + optLen + pkt.getPayloadSize()); // 54 + MAC checksum // MAC Header 14 bytes fos.write(MAC_HEADER); // IP 20 bytes total // IP Header 12 bytes - int length = 20 + 20 + pkt.getPayloadSize(); + int length = 20 + 20 + optLen + pkt.getPayloadSize(); fos.write(IP_HEADER_1); DataHelper.writeLong(fos, 2, length); // total IP length fos.write(IP_HEADER_2); @@ -200,12 +239,9 @@ public class PcapWriter { flags |= 0x04; if (!pkt.isFlagSet(Packet.FLAG_NO_ACK)) flags |= 0x10; - // delay request -> ECE, not a perfect match, but ok for now - if (pkt.isFlagSet(Packet.FLAG_DELAY_REQUESTED)) - flags |= 0x40; - //if (pkt.isFlagSet(FLAG_DELAY_REQUESTED)) - // foo; - DataHelper.writeLong(fos, 1, 0x50); // 5 32-byte words + // offset byte + int osb = (5 + (optLen / 4)) << 4; + DataHelper.writeLong(fos, 1, osb); // 5 + optLen/4 32-byte words DataHelper.writeLong(fos, 1, flags); // window size 2 bytes @@ -237,6 +273,10 @@ public class PcapWriter { // checksum and urgent pointer 4 bytes DataHelper.writeLong(fos, 4, 0); + // TCP option block + if (optLen > 0) + fos.write(options, 0, optLen); + // some data if (includeLen > 0) fos.write(pkt.getPayload().getData(), 0, includeLen); @@ -270,6 +310,38 @@ public class PcapWriter { return Math.max(0, lowest); } + private static class Options { + byte[] _b; + int _len; + public Options() { + _b = new byte[MAX_OPTION_LEN]; + } + + /** 40 bytes long, caller must use size() to get actual size */ + public byte[] getData() { return _b; } + /** rounded to next 4 bytes */ + public int size() { return ((_len + 3) / 4) * 4; } + + public void add(byte type) { + add(type, 0, 0); + } + + public void add(byte type, int datalen, int data) { + // no room? drop silently + if (_len + datalen + 2 > MAX_OPTION_LEN) + return; + _b[_len++] = type; + _b[_len++] = (byte) (datalen + 2); + if (datalen > 0) { + for (int i = datalen - 1; i >= 0; i--) + _b[_len++] = (byte) ((data >> (i * 8)) & 0xff); + } + // end-of-options mark + if (_len < MAX_OPTION_LEN) + _b[_len] = OPTION_END; + } + } + /** one's complement 2-byte checksum update */ private static int update(int checksum, byte[] b) { return update(checksum, b, b.length); From d078ed396f63c2fd3e82c32f97a71774296108ad Mon Sep 17 00:00:00 2001 From: zzz Date: Fri, 6 Nov 2009 19:16:23 +0000 Subject: [PATCH 3/4] move init, add config --- .../streaming/I2PSocketManagerFull.java | 22 +++++++++++++++ .../net/i2p/client/streaming/PacketLocal.java | 27 ++++--------------- 2 files changed, 27 insertions(+), 22 deletions(-) diff --git a/apps/streaming/java/src/net/i2p/client/streaming/I2PSocketManagerFull.java b/apps/streaming/java/src/net/i2p/client/streaming/I2PSocketManagerFull.java index b904243b10..fc177ba238 100644 --- a/apps/streaming/java/src/net/i2p/client/streaming/I2PSocketManagerFull.java +++ b/apps/streaming/java/src/net/i2p/client/streaming/I2PSocketManagerFull.java @@ -93,6 +93,7 @@ public class I2PSocketManagerFull implements I2PSocketManager { _log.info("Socket manager created. \ndefault options: " + _defaultOptions + "\noriginal properties: " + opts); } + debugInit(context); } public I2PSocketOptions buildOptions() { return buildOptions(null); } @@ -269,4 +270,25 @@ public class I2PSocketManagerFull implements I2PSocketManager { public void removeDisconnectListener(I2PSocketManager.DisconnectListener lsnr) { _connectionManager.getMessageHandler().removeDisconnectListener(lsnr); } + + private static final Object _pcapInitLock = new Object(); + private static boolean _pcapInitialized; + static PcapWriter pcapWriter; + static final String PROP_PCAP = "i2p.streaming.pcap"; + private static final String PCAP_FILE = "streaming.pcap"; + + private static void debugInit(I2PAppContext ctx) { + if (!Boolean.valueOf(ctx.getProperty(PROP_PCAP)).booleanValue()) + return; + synchronized(_pcapInitLock) { + if (!_pcapInitialized) { + try { + pcapWriter = new PcapWriter(ctx, PCAP_FILE); + } catch (java.io.IOException ioe) { + System.err.println("pcap init ioe: " + ioe); + } + _pcapInitialized = true; + } + } + } } diff --git a/apps/streaming/java/src/net/i2p/client/streaming/PacketLocal.java b/apps/streaming/java/src/net/i2p/client/streaming/PacketLocal.java index 127f53131d..db4eb7293d 100644 --- a/apps/streaming/java/src/net/i2p/client/streaming/PacketLocal.java +++ b/apps/streaming/java/src/net/i2p/client/streaming/PacketLocal.java @@ -29,9 +29,6 @@ public class PacketLocal extends Packet implements MessageOutputStream.WriteStat private volatile int _nackCount; private volatile boolean _retransmitted; private SimpleTimer2.TimedEvent _resendEvent; - private static final Object initLock = new Object(); - private static boolean _initialized; - private static PcapWriter _pcapWriter; public PacketLocal(I2PAppContext ctx, Destination to) { this(ctx, to, null); @@ -46,12 +43,6 @@ public class PacketLocal extends Packet implements MessageOutputStream.WriteStat _cancelledOn = -1; _nackCount = 0; _retransmitted = false; - synchronized(initLock) { - if (!_initialized) { - initPcap(); - _initialized = true; - } - } } public Destination getTo() { return _to; } @@ -255,24 +246,16 @@ public class PacketLocal extends Packet implements MessageOutputStream.WriteStat public boolean writeFailed() { return _cancelledOn > 0; } public boolean writeSuccessful() { return _ackOn > 0 && _cancelledOn <= 0; } - static final String PCAP = "foo.pcap"; - private void initPcap() { - try { - _pcapWriter = new PcapWriter(_context, PCAP); - } catch (IOException ioe) { - System.err.println("pcap init ioe: " + ioe); - } - } - /** Generate a pcap/tcpdump-compatible format, * so we can use standard debugging tools. */ public void logTCPDump(boolean isInbound) { - if (!_log.shouldLog(Log.INFO)) return; - _log.info(toString()); - if (_pcapWriter != null) { + if (_log.shouldLog(Log.INFO)) + _log.info(toString()); + if (I2PSocketManagerFull.pcapWriter != null && + Boolean.valueOf(_context.getProperty(I2PSocketManagerFull.PROP_PCAP)).booleanValue()) { try { - _pcapWriter.write(this, isInbound); + I2PSocketManagerFull.pcapWriter.write(this, isInbound); } catch (IOException ioe) { _log.warn("pcap write ioe: " + ioe); } From 61ee957add71095b13a8f8f486a04b6a2b3a62fd Mon Sep 17 00:00:00 2001 From: zzz Date: Sat, 27 Oct 2012 18:03:54 +0000 Subject: [PATCH 4/4] pcap: - Buffer output - Separate methods for inbound and outbound, so we don't need to use PacketLocal for inbound - Cleanups after prop - Finals etc. --- .../client/streaming/ConnectionHandler.java | 4 +- .../client/streaming/ConnectionManager.java | 5 +- .../streaming/I2PSocketManagerFull.java | 4 +- .../i2p/client/streaming/MessageHandler.java | 4 +- .../src/net/i2p/client/streaming/Packet.java | 14 ++- .../i2p/client/streaming/PacketHandler.java | 9 +- .../net/i2p/client/streaming/PacketLocal.java | 13 +-- .../net/i2p/client/streaming/PacketQueue.java | 4 +- .../net/i2p/client/streaming/PcapWriter.java | 107 +++++++++++------- .../src/net/i2p/router/RouterVersion.java | 2 +- 10 files changed, 101 insertions(+), 65 deletions(-) diff --git a/apps/streaming/java/src/net/i2p/client/streaming/ConnectionHandler.java b/apps/streaming/java/src/net/i2p/client/streaming/ConnectionHandler.java index a0f7bac552..275da2725c 100644 --- a/apps/streaming/java/src/net/i2p/client/streaming/ConnectionHandler.java +++ b/apps/streaming/java/src/net/i2p/client/streaming/ConnectionHandler.java @@ -195,7 +195,9 @@ class ConnectionHandler { _manager.getPacketHandler().receivePacketDirect(packet, false); } else { // log it here, just before we kill it - dest will be unknown - ((PacketLocal)packet).logTCPDump(true); + if (I2PSocketManagerFull.pcapWriter != null && + _context.getBooleanProperty(I2PSocketManagerFull.PROP_PCAP)) + packet.logTCPDump(null); // goodbye if (_log.shouldLog(Log.WARN)) diff --git a/apps/streaming/java/src/net/i2p/client/streaming/ConnectionManager.java b/apps/streaming/java/src/net/i2p/client/streaming/ConnectionManager.java index adce3dc99c..c058c74b4b 100644 --- a/apps/streaming/java/src/net/i2p/client/streaming/ConnectionManager.java +++ b/apps/streaming/java/src/net/i2p/client/streaming/ConnectionManager.java @@ -267,8 +267,9 @@ class ConnectionManager { con.setReceiveStreamId(receiveId); // finally, we know enough that we can log the packet with the conn filled in - ((PacketLocal)synPacket).setConnection(con); - ((PacketLocal)synPacket).logTCPDump(true); + if (I2PSocketManagerFull.pcapWriter != null && + _context.getBooleanProperty(I2PSocketManagerFull.PROP_PCAP)) + synPacket.logTCPDump(con); try { // This validates the packet, and sets the con's SendStreamID and RemotePeer con.getPacketHandler().receivePacket(synPacket, con); diff --git a/apps/streaming/java/src/net/i2p/client/streaming/I2PSocketManagerFull.java b/apps/streaming/java/src/net/i2p/client/streaming/I2PSocketManagerFull.java index ce6a65bc1e..ec0cd37814 100644 --- a/apps/streaming/java/src/net/i2p/client/streaming/I2PSocketManagerFull.java +++ b/apps/streaming/java/src/net/i2p/client/streaming/I2PSocketManagerFull.java @@ -315,6 +315,8 @@ public class I2PSocketManagerFull implements I2PSocketManager { } catch (I2PSessionException ise) { _log.warn("Unable to destroy the session", ise); } + if (pcapWriter != null) + pcapWriter.flush(); } } @@ -351,7 +353,7 @@ public class I2PSocketManagerFull implements I2PSocketManager { private static final String PCAP_FILE = "streaming.pcap"; private static void debugInit(I2PAppContext ctx) { - if (!Boolean.valueOf(ctx.getProperty(PROP_PCAP)).booleanValue()) + if (!ctx.getBooleanProperty(PROP_PCAP)) return; synchronized(_pcapInitLock) { if (!_pcapInitialized) { diff --git a/apps/streaming/java/src/net/i2p/client/streaming/MessageHandler.java b/apps/streaming/java/src/net/i2p/client/streaming/MessageHandler.java index 1f3dd66c42..4aba07d314 100644 --- a/apps/streaming/java/src/net/i2p/client/streaming/MessageHandler.java +++ b/apps/streaming/java/src/net/i2p/client/streaming/MessageHandler.java @@ -59,9 +59,7 @@ class MessageHandler implements I2PSessionMuxedListener { return; } if (data == null) return; - //Packet packet = new Packet(); - // for tcpdump - Packet packet = new PacketLocal(_context, null); + Packet packet = new Packet(); try { packet.readPacket(data, 0, data.length); packet.setRemotePort(fromPort); diff --git a/apps/streaming/java/src/net/i2p/client/streaming/Packet.java b/apps/streaming/java/src/net/i2p/client/streaming/Packet.java index a79f67cb75..e589b6273a 100644 --- a/apps/streaming/java/src/net/i2p/client/streaming/Packet.java +++ b/apps/streaming/java/src/net/i2p/client/streaming/Packet.java @@ -1,5 +1,6 @@ package net.i2p.client.streaming; +import java.io.IOException; import java.util.Arrays; import net.i2p.I2PAppContext; @@ -16,7 +17,8 @@ import net.i2p.util.Log; * This contains solely the data that goes out on the wire, * including the local and remote port which is embedded in * the I2CP overhead, not in the packet itself. - * For local state saved for outbound packets, see PacketLocal. + * This is the class used for inbound packets. + * For local state saved for outbound packets, see the PacketLocal extension. * *

    * @@ -709,4 +711,14 @@ class Packet { if (isFlagSet(FLAG_SYNCHRONIZE)) buf.append(" SYN"); return buf.toString(); } + + /** Generate a pcap/tcpdump-compatible format, + * so we can use standard debugging tools. + */ + public void logTCPDump(Connection con) { + try { + I2PSocketManagerFull.pcapWriter.write(this, con); + } catch (IOException ioe) { + } + } } diff --git a/apps/streaming/java/src/net/i2p/client/streaming/PacketHandler.java b/apps/streaming/java/src/net/i2p/client/streaming/PacketHandler.java index d54e253bb6..6cdf3dc81a 100644 --- a/apps/streaming/java/src/net/i2p/client/streaming/PacketHandler.java +++ b/apps/streaming/java/src/net/i2p/client/streaming/PacketHandler.java @@ -131,8 +131,9 @@ class PacketHandler { private void receiveKnownCon(Connection con, Packet packet) { // is this ok here or does it need to be below each packetHandler().receivePacket() ? - ((PacketLocal)packet).setConnection(con); - ((PacketLocal)packet).logTCPDump(true); + if (I2PSocketManagerFull.pcapWriter != null && + _context.getBooleanProperty(I2PSocketManagerFull.PROP_PCAP)) + packet.logTCPDump(con); if (packet.isFlagSet(Packet.FLAG_ECHO)) { if (packet.getSendStreamId() > 0) { if (con.getOptions().getAnswerPings()) @@ -318,7 +319,9 @@ class PacketHandler { _manager.getConnectionHandler().receiveNewSyn(packet); } else { // log it here, just before we kill it - dest will be unknown - ((PacketLocal)packet).logTCPDump(true); + if (I2PSocketManagerFull.pcapWriter != null && + _context.getBooleanProperty(I2PSocketManagerFull.PROP_PCAP)) + packet.logTCPDump(null); // don't queue again (infinite loop!) sendReset(packet); packet.releasePayload(); diff --git a/apps/streaming/java/src/net/i2p/client/streaming/PacketLocal.java b/apps/streaming/java/src/net/i2p/client/streaming/PacketLocal.java index b5df7236e6..97666db6f4 100644 --- a/apps/streaming/java/src/net/i2p/client/streaming/PacketLocal.java +++ b/apps/streaming/java/src/net/i2p/client/streaming/PacketLocal.java @@ -10,6 +10,8 @@ import net.i2p.util.Log; import net.i2p.util.SimpleTimer2; /** + * This is the class used for outbound packets. + * * coordinate local attributes about a packet - send time, ack time, number of * retries, etc. */ @@ -143,8 +145,6 @@ class PacketLocal extends Packet implements MessageOutputStream.WriteStatus { /** @return null if not bound */ public Connection getConnection() { return _connection; } - /** used to set the rcvd conn after the fact for incoming syn replies */ - public void setConnection(Connection con) { _connection = con; } /** * Will force a fast restransmit on the 3rd call (FAST_RETRANSMIT_THRESHOLD) @@ -263,16 +263,11 @@ class PacketLocal extends Packet implements MessageOutputStream.WriteStatus { /** Generate a pcap/tcpdump-compatible format, * so we can use standard debugging tools. */ - public void logTCPDump(boolean isInbound) { - if (_log.shouldLog(Log.INFO)) - _log.info(toString()); - if (I2PSocketManagerFull.pcapWriter != null && - Boolean.valueOf(_context.getProperty(I2PSocketManagerFull.PROP_PCAP)).booleanValue()) { + public void logTCPDump() { try { - I2PSocketManagerFull.pcapWriter.write(this, isInbound); + I2PSocketManagerFull.pcapWriter.write(this); } catch (IOException ioe) { _log.warn("pcap write ioe: " + ioe); } - } } } diff --git a/apps/streaming/java/src/net/i2p/client/streaming/PacketQueue.java b/apps/streaming/java/src/net/i2p/client/streaming/PacketQueue.java index c415e84827..4764c9abfa 100644 --- a/apps/streaming/java/src/net/i2p/client/streaming/PacketQueue.java +++ b/apps/streaming/java/src/net/i2p/client/streaming/PacketQueue.java @@ -169,7 +169,9 @@ class PacketQueue { Connection c = packet.getConnection(); String suffix = (c != null ? "wsize " + c.getOptions().getWindowSize() + " rto " + c.getOptions().getRTO() : null); _connectionManager.getPacketHandler().displayPacket(packet, "SEND", suffix); - ((PacketLocal)packet).logTCPDump(false); + if (I2PSocketManagerFull.pcapWriter != null && + _context.getBooleanProperty(I2PSocketManagerFull.PROP_PCAP)) + ((PacketLocal)packet).logTCPDump(); } if ( (packet.getSequenceNum() == 0) && (!packet.isFlagSet(Packet.FLAG_SYNCHRONIZE)) ) { diff --git a/apps/streaming/java/src/net/i2p/client/streaming/PcapWriter.java b/apps/streaming/java/src/net/i2p/client/streaming/PcapWriter.java index e838ad0310..3bc119d817 100644 --- a/apps/streaming/java/src/net/i2p/client/streaming/PcapWriter.java +++ b/apps/streaming/java/src/net/i2p/client/streaming/PcapWriter.java @@ -1,8 +1,10 @@ package net.i2p.client.streaming; +import java.io.BufferedOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; +import java.io.OutputStream; import net.i2p.I2PAppContext; import net.i2p.data.DataFormatException; @@ -72,8 +74,8 @@ public class PcapWriter { private static final byte OPTION_NACK = (byte) 0xac; - private FileOutputStream _fos; - private I2PAppContext _context; + private final OutputStream _fos; + private final I2PAppContext _context; public PcapWriter(I2PAppContext ctx, String file) throws IOException { _context = ctx; @@ -81,37 +83,52 @@ public class PcapWriter { //if (f.exists()) { // _fos = new FileOutputStream(f, true); //} else { - _fos = new FileOutputStream(f); + _fos = new BufferedOutputStream(new FileOutputStream(f), 64*1024); _fos.write(FILE_HEADER); //} } public void close() { - FileOutputStream fos = _fos; - if (fos != null) { try { - fos.close(); + _fos.close(); } catch (IOException ioe) {} - _fos = null; - } } - public void write(PacketLocal pkt, boolean isInbound) throws IOException { + public void flush() { + try { + _fos.flush(); + } catch (IOException ioe) {} + } + + /** + * For outbound packets + */ + public void write(PacketLocal pkt) throws IOException { try { - wrt(pkt, isInbound); + wrt(pkt, pkt.getConnection(), false); } catch (DataFormatException dfe) { dfe.printStackTrace(); throw new IOException(dfe.toString()); } - // remove me - _fos.flush(); } - private synchronized void wrt(PacketLocal pkt, boolean isInbound) throws IOException, DataFormatException { - FileOutputStream fos = _fos; - if (fos == null) - throw new IOException("Not open or already closed"); - Connection con = pkt.getConnection(); + /** + * For inbound packets + * @param con may be null + */ + public void write(Packet pkt, Connection con) throws IOException { + try { + wrt(pkt, con, true); + } catch (DataFormatException dfe) { + dfe.printStackTrace(); + throw new IOException(dfe.toString()); + } + } + + /** + * @param con may be null + */ + private synchronized void wrt(Packet pkt, Connection con, boolean isInbound) throws IOException, DataFormatException { int includeLen = Math.min(MAX_PAYLOAD_BYTES, pkt.getPayloadSize()); // option block @@ -142,23 +159,23 @@ public class PcapWriter { // PCAP Header long now; if (isInbound) - now = pkt.getCreatedOn(); + now = _context.clock().now(); else - now = pkt.getLastSend(); - DataHelper.writeLong(fos, 4, now / 1000); - DataHelper.writeLong(fos, 4, 1000 * (now % 1000)); - DataHelper.writeLong(fos, 4, 54 + optLen + includeLen); // 14 MAC + 20 IP + 20 TCP - DataHelper.writeLong(fos, 4, 58 + optLen + pkt.getPayloadSize()); // 54 + MAC checksum + now = ((PacketLocal)pkt).getLastSend(); + DataHelper.writeLong(_fos, 4, now / 1000); + DataHelper.writeLong(_fos, 4, 1000 * (now % 1000)); + DataHelper.writeLong(_fos, 4, 54 + optLen + includeLen); // 14 MAC + 20 IP + 20 TCP + DataHelper.writeLong(_fos, 4, 58 + optLen + pkt.getPayloadSize()); // 54 + MAC checksum // MAC Header 14 bytes - fos.write(MAC_HEADER); + _fos.write(MAC_HEADER); // IP 20 bytes total // IP Header 12 bytes int length = 20 + 20 + optLen + pkt.getPayloadSize(); - fos.write(IP_HEADER_1); - DataHelper.writeLong(fos, 2, length); // total IP length - fos.write(IP_HEADER_2); + _fos.write(IP_HEADER_1); + DataHelper.writeLong(_fos, 2, length); // total IP length + _fos.write(IP_HEADER_2); // src and dst IP 8 bytes // make our side always start with 127.0.x.x @@ -199,17 +216,17 @@ public class PcapWriter { checksum = update(checksum, IP_HEADER_2); checksum = update(checksum, srcAddr, 4); checksum = update(checksum, dstAddr, 4); - DataHelper.writeLong(fos, 2, checksum ^ 0xffff); + DataHelper.writeLong(_fos, 2, checksum ^ 0xffff); // IPs - fos.write(srcAddr, 0, 4); - fos.write(dstAddr, 0, 4); + _fos.write(srcAddr, 0, 4); + _fos.write(dstAddr, 0, 4); // TCP header 20 bytes total // src and dst port 4 bytes // the rcv ID is the source, and the send ID is the dest. - DataHelper.writeLong(fos, 2, pkt.getReceiveStreamId() & 0xffff); - DataHelper.writeLong(fos, 2, pkt.getSendStreamId() & 0xffff); + DataHelper.writeLong(_fos, 2, pkt.getReceiveStreamId() & 0xffff); + DataHelper.writeLong(_fos, 2, pkt.getSendStreamId() & 0xffff); // seq and acks 8 bytes long seq; @@ -226,8 +243,8 @@ public class PcapWriter { else acked = getLowestAckedThrough(pkt, con); } - DataHelper.writeLong(fos, 4, pkt.getSequenceNum()); - DataHelper.writeLong(fos, 4, acked); + DataHelper.writeLong(_fos, 4, pkt.getSequenceNum()); + DataHelper.writeLong(_fos, 4, acked); // offset and flags 2 bytes int flags = 0; @@ -241,8 +258,8 @@ public class PcapWriter { flags |= 0x10; // offset byte int osb = (5 + (optLen / 4)) << 4; - DataHelper.writeLong(fos, 1, osb); // 5 + optLen/4 32-byte words - DataHelper.writeLong(fos, 1, flags); + DataHelper.writeLong(_fos, 1, osb); // 5 + optLen/4 32-byte words + DataHelper.writeLong(_fos, 1, flags); // window size 2 bytes long window = ConnectionOptions.INITIAL_WINDOW_SIZE; @@ -268,18 +285,20 @@ public class PcapWriter { // for now we don't spoof window scaling if (window > 65535) window = 65535; - DataHelper.writeLong(fos, 2, window); + DataHelper.writeLong(_fos, 2, window); // checksum and urgent pointer 4 bytes - DataHelper.writeLong(fos, 4, 0); + DataHelper.writeLong(_fos, 4, 0); // TCP option block if (optLen > 0) - fos.write(options, 0, optLen); + _fos.write(options, 0, optLen); // some data if (includeLen > 0) - fos.write(pkt.getPayload().getData(), 0, includeLen); + _fos.write(pkt.getPayload().getData(), 0, includeLen); + if (pkt.isFlagSet(Packet.FLAG_CLOSE)) + _fos.flush(); } /** @@ -294,7 +313,7 @@ public class PcapWriter { * * To do: Add the SACK option to the TCP header. */ - private static long getLowestAckedThrough(PacketLocal pkt, Connection con) { + private static long getLowestAckedThrough(Packet pkt, Connection con) { long nacks[] = pkt.getNacks(); long lowest = pkt.getAckThrough(); // can return -1 but we increment below if (nacks != null) { @@ -311,14 +330,16 @@ public class PcapWriter { } private static class Options { - byte[] _b; - int _len; + private final byte[] _b; + private int _len; + public Options() { _b = new byte[MAX_OPTION_LEN]; } /** 40 bytes long, caller must use size() to get actual size */ public byte[] getData() { return _b; } + /** rounded to next 4 bytes */ public int size() { return ((_len + 3) / 4) * 4; } diff --git a/router/java/src/net/i2p/router/RouterVersion.java b/router/java/src/net/i2p/router/RouterVersion.java index ca8312125f..a411b00998 100644 --- a/router/java/src/net/i2p/router/RouterVersion.java +++ b/router/java/src/net/i2p/router/RouterVersion.java @@ -21,7 +21,7 @@ public class RouterVersion { public final static long BUILD = 0; /** for example "-test" */ - public final static String EXTRA = "-pcap"; + public final static String EXTRA = ""; public final static String FULL_VERSION = VERSION + "-" + BUILD + EXTRA; public static void main(String args[]) { System.out.println("I2P Router version: " + FULL_VERSION);