/* * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. * * http://www.gnu.org/copyleft/gpl.html */ package com.aionemu.packetsamurai.session; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.util.List; import java.util.Map; import javolution.util.FastList; import javolution.util.FastMap; import jpcap.packet.TCPPacket; /** * Buffer for sequencing packets, since they are captured out of sequence * @author Ulysses R. Ribeiro */ public class TCPPacketBuffer { public static final boolean DEBUG_RAW_TRAFFIC = false; private static FileOutputStream _bao; private static final long MODULO = 4294967296L; private Map<Long, SeqHolder> _waitingPrevious = new FastMap<Long, SeqHolder>(); private List<TCPPacket> _sequenced = new FastList<TCPPacket>();; private long _lastAck; public TCPPacketBuffer() { if (DEBUG_RAW_TRAFFIC) { try { _bao = new FileOutputStream("lol.bin"); } catch (FileNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } public void add(TCPPacket p) { for (SeqHolder sh : _waitingPrevious.values()) { TCPPacket old = sh.getPacket(); if (sh.getPacket().sequence == p.sequence) { if (old.data.length < p.data.length) { int diff = p.data.length - old.data.length; //System.err.printf("DIFF %d = %d - %d \n",diff, p.data.length, old.data.length); long seq = (old.sequence + old.data.length)%MODULO; //System.err.println("ADJUSTED TO SEQ: "+seq+"\nPACKET: "+p); byte[] data = new byte[diff]; System.arraycopy(p.data, p.data.length - diff, data, 0, data.length); p.data = data; p.sequence = seq; //System.err.println(Util.hexDump(data)); } else if (old.data.length == p.data.length) { // packet retransmitted // dont add, else the data will be duped (acked again) return; } } } long nextSeq = (p.sequence + p.data.length)%MODULO; _waitingPrevious.put(nextSeq, new SeqHolder(nextSeq, p)); this.processAck(_lastAck); } public void ack(TCPPacket p) { _lastAck = p.ack_num; processAck(p.ack_num); byte[] options = p.option; if (options != null) { long start = -1; long end = -1; for (int i = 0; i < options.length;) { // 0 end of opts // 1 nop if (options[i] == 0 || options[i] == 1) { i++; continue; } int type = options[i++] & 0xff; int size = options[i++] & 0xff; switch (type) { case 5: //SACK if (size-2 == 8) { //System.err.println("SACK no pacote: "+p); //System.exit(0); start = getUInt(options, i); i += 4; end = getUInt(options, i); } else { throw new RuntimeException("No support for multiple S-ACK"); } break; default: //unsupported operation break; } i += size - 2; } while (end != -1 && start != -1) { SeqHolder sh = null; for (SeqHolder seqHolder : _waitingPrevious.values()) { if (seqHolder.getPacket().sequence == start) { long nextSeq = (seqHolder.getPacket().sequence + seqHolder.getPacket().data.length)%MODULO; // ignore if SACK covers whole packet if (end != nextSeq) { sh = seqHolder; break; } } } // if not already ack'ed (duplicate ack recvd) if (sh != null) { //System.err.println("NULL ACK: "+_lastAck+" -> "+p.toString()); //System.err.println("END: "+end+" START: "+start); int diff = (int) ((end - start)%MODULO); TCPPacket packet = sh.getPacket(); // check if sack doesnt overlap to next packet if (diff > packet.data.length) { // this one covers whole packet so just ignore start = (start + packet.data.length)%MODULO; continue; } start = -1; end = -1; byte[] data = new byte[diff]; //System.err.println("sh SEQ: "+sh.getPacket().sequence+" LEN: "+packet.data.length+" DIFF: "+diff); byte[] data2 = new byte[packet.data.length - diff]; System.arraycopy(sh.getPacket().data, 0, data, 0, data.length); System.arraycopy(sh.getPacket().data, diff, data2, 0, data2.length); //System.err.println("### SACK ("+data2.length+")\nCAUSED BY: "+p+"\nON: "+sh.getPacket()); TCPPacket sackPacket = new TCPPacket(packet.src_port, packet.dst_port, packet.sequence, packet.ack_num, packet.urg, packet.ack, packet.psh, packet.rst, packet.syn, packet.fin, packet.rsv1, packet.rsv2, packet.window, packet.urgent_pointer); sackPacket.data = data; sh.getPacket().data = data2; this.addSequenced(sackPacket); //System.err.println(Util.printData(sackPacket.data)); //long prevSeq = (p.ack_num - sh.getPacket().data.length)%MODULO; //long prevSeq = sh.getPacket().sequence; //processAck(p.ack_num); //this.addSequenced(_lastAck); } else { end = -1; start = -1; } } } else { //processAck(p.ack_num); } } private static long getUInt(byte[] array, int offset) { if (array.length < offset + 4) { throw new IllegalArgumentException("Invalid offset for size"); } long ret = ((array[offset] & 0xffl) << 24); ret |= ((array[offset+1] & 0xffl) << 16); ret |= ((array[offset+2] & 0xffl) << 8); ret |= (array[offset+3] & 0xffl); return ret; } public void processAck(long ack) { SeqHolder sh = _waitingPrevious.get(ack); if (sh != null && !sh.isAcked()) { //long previousSeq = (ack - sh.getPacket().data.length)%MODULO; long previousSeq = sh.getPacket().sequence; processAck(previousSeq); this.addSequenced(ack); } } private void addSequenced(long ack) { SeqHolder seqHolder = _waitingPrevious.get(ack); seqHolder.ack(); TCPPacket packet = seqHolder.getPacket(); this.addSequenced(packet); /*if (packet.src_port == 7777) { System.err.println("ACKED: "+ack); }*/ } private void addSequenced(TCPPacket packet) { if (DEBUG_RAW_TRAFFIC) { try { if (packet.src_port == 7777) { System.err.println("WROTE"); _bao.write(packet.data); _bao.flush(); } } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } _sequenced.add(packet); } public List<TCPPacket> getSequencedPackets() { return _sequenced; } public int getPendingSequencePackets() { return _waitingPrevious.size(); } public void flush() { _sequenced.clear(); } public boolean hasSequencedPacket() { return (!_sequenced.isEmpty()); } static class SeqHolder { private final long _nextSeq; private final TCPPacket _packet; private boolean _acked; public SeqHolder(long nextSeq, TCPPacket packet) { _nextSeq = nextSeq; _packet = packet; } /** * @return the nextSeq */ public long getNextSeq() { return _nextSeq; } /** * @return the packet */ public TCPPacket getPacket() { return _packet; } public void ack() { _acked = true; } public boolean isAcked() { return _acked; } } }