/* * Copyright 2010 NCHOVY * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.krakenapps.pcap.decoder.tcp; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.UnknownHostException; import java.nio.BufferUnderflowException; import java.nio.ByteBuffer; import org.krakenapps.pcap.Injectable; import org.krakenapps.pcap.PacketBuilder; import org.krakenapps.pcap.decoder.ip.InternetProtocol; import org.krakenapps.pcap.decoder.ip.IpPacket; import org.krakenapps.pcap.decoder.ip.Ipv4Packet; import org.krakenapps.pcap.decoder.ipv6.Ipv6Packet; import org.krakenapps.pcap.live.PcapDeviceManager; import org.krakenapps.pcap.live.PcapDeviceMetadata; import org.krakenapps.pcap.util.Buffer; import org.krakenapps.pcap.util.ChainBuffer; /** * @author mindori */ public class TcpPacket implements TcpSegment, Injectable { private IpPacket ipPacket; private TcpSessionKeyImpl sessionKey; private InetAddress sourceAddr; private InetAddress destinationAddr; /** * tcp header + data */ private int tcpLength; private TcpDirection direction; private int srcPort; private int dstPort; private int seq; private int ack; private int relativeSeq; private int relativeAck; /** * header length = data start offset */ private int dataOffset; private int flags; private int window; private int checksum; private int urgentPointer; private byte[] options; private byte[] padding; private Buffer data; private int dataLength; private boolean isJumbo = false; private boolean isGarbage = false; private int reassembledLength = 0; private TcpPacket() { } /* copy constructor */ public TcpPacket(TcpPacket other) { ipPacket = other.ipPacket; sessionKey = (TcpSessionKeyImpl) other.getSessionKey(); sourceAddr = other.getSourceAddress(); destinationAddr = other.getDestinationAddress(); tcpLength = other.getTotalLength(); direction = other.getDirection(); srcPort = other.getSourcePort(); dstPort = other.getDestinationPort(); seq = other.getSeq(); ack = other.getAck(); relativeSeq = other.getRelativeSeq(); relativeAck = other.getRelativeAck(); dataOffset = other.getDataOffset(); flags = other.getFlags(); window = other.getWindow(); checksum = other.getChecksum(); urgentPointer = other.getUrgentPointer(); options = other.getOptions(); padding = other.getPadding(); if (other.getData() != null) data = new ChainBuffer(other.getData()); dataLength = other.getDataLength(); } public static TcpPacket parse(Ipv4Packet p) throws BufferUnderflowException { InetAddress source = p.getSourceAddress(); InetAddress destination = p.getDestinationAddress(); Buffer data = p.getData(); int tcpLength = p.getTotalLength() - p.getIhl(); return parse(p, source, destination, tcpLength, data); } public static TcpPacket parse(Ipv6Packet p) throws BufferUnderflowException { InetAddress source = p.getSourceAddress(); InetAddress destination = p.getDestinationAddress(); int tcpLength = p.getPayloadLength() - 40; Buffer data = p.getData(); return parse(p, source, destination, tcpLength, data); } private static TcpPacket parse(IpPacket p, InetAddress source, InetAddress destination, int tcpLength, Buffer data) throws BufferUnderflowException { TcpPacket s = new TcpPacket(); s.ipPacket = p; s.tcpLength = tcpLength; s.sourceAddr = source; s.destinationAddr = destination; s.srcPort = data.getUnsignedShort(); s.dstPort = data.getUnsignedShort(); s.sessionKey = new TcpSessionKeyImpl(s.sourceAddr, s.destinationAddr, s.srcPort, s.dstPort); s.seq = data.getInt(); s.ack = data.getInt(); s.parseDataOffsetAndFlags(data); s.window = data.getUnsignedShort(); s.checksum = data.getUnsignedShort(); s.urgentPointer = data.getUnsignedShort(); s.parseOptions(data); s.parseData(data); return s; } private void parseDataOffsetAndFlags(Buffer dataBuffer) { byte dataOffsetAndReserved = dataBuffer.get(); byte reservedAndFlags = dataBuffer.get(); dataOffset = ((dataOffsetAndReserved >> 4) & 0x0f) & 0xff; flags = (reservedAndFlags & 0x3f) & 0xff; } private void parseOptions(Buffer dataBuffer) { int headerLength = dataOffset * 4; if (headerLength <= 20) return; int optionLength = headerLength - 20; options = new byte[optionLength]; for (int i = 0; i < optionLength; i++) options[i] = dataBuffer.get(); if ((optionLength % 4) == 0) return; for (int i = 0; i < (optionLength % 4); i++) padding[i] = dataBuffer.get(); } private void parseData(Buffer dataBuffer) throws BufferUnderflowException { /* set length of data */ dataLength = tcpLength - (dataOffset * 4); if (dataLength > 0) { try { dataBuffer.discardReadBytes(); data = dataBuffer; } catch (BufferUnderflowException e) { data = null; isJumbo = true; } } } @Override public IpPacket getIpPacket() { return ipPacket; } public void setIpPacket(IpPacket ipPacket) { this.ipPacket = ipPacket; } @Override public boolean isSyn() { return (flags & 0x02) != 0; } @Override public boolean isAck() { return (flags & 0x10) != 0; } @Override public boolean isPsh() { return (flags & 0x08) != 0; } @Override public boolean isFin() { return (flags & 0x01) != 0; } @Override public boolean isRst() { return (flags & 0x04) != 0; } @Override public boolean isUrg() { return (flags & 0x20) != 0; } @Override public TcpSessionKey getSessionKey() { return sessionKey; } @Override public InetSocketAddress getSource() { return new InetSocketAddress(getSourceAddress(), getSourcePort()); } @Override public InetSocketAddress getDestination() { return new InetSocketAddress(getDestinationAddress(), getDestinationPort()); } @Override public InetAddress getSourceAddress() { return sourceAddr; } @Override public InetAddress getDestinationAddress() { return destinationAddr; } @Override public int getTotalLength() { return tcpLength; } @Override public int getSourcePort() { return srcPort; } @Override public int getDestinationPort() { return dstPort; } @Override public int getSeq() { return seq; } @Override public int getAck() { return ack; } @Override public int getRelativeSeq() { return relativeSeq; } public void setRelativeSeq(int relativeSeq) { this.relativeSeq = relativeSeq; } @Override public int getRelativeAck() { return relativeAck; } public void setRelativeAck(int relativeAck) { this.relativeAck = relativeAck; } /** * Header Length = dataOffset * 4 * * @return */ public int getDataOffset() { return dataOffset; } public int getFlags() { return flags; } public int getWindow() { return window; } public int getChecksum() { return checksum; } public int getUrgentPointer() { return urgentPointer; } public byte[] getOptions() { return options; } public byte[] getPadding() { return padding; } @Override public Buffer getData() { return data; } @Override public TcpDirection getDirection() { return direction; } public void setDirection(TcpSession session) { if (session.getKey().getClientPort() == srcPort) { direction = TcpDirection.ToServer; } else { direction = TcpDirection.ToClient; this.sessionKey.flip(); } } public void setDirection(TcpSessionImpl session) { if (session.getKey().getClientPort() == srcPort) { direction = TcpDirection.ToServer; } else { direction = TcpDirection.ToClient; this.sessionKey.flip(); } } public int getDataLength() { return dataLength; } public boolean isJumbo() { return isJumbo; } public boolean isGarbage() { return isGarbage; } public void setGarbage(boolean isGarbage) { this.isGarbage = isGarbage; } public int getReassembledLength() { return reassembledLength; } public void setReassembledLength(int reassembledLength) { this.reassembledLength = reassembledLength; } @Override public String toString() { if (getRelativeSeq() == -1 && getRelativeAck() == -1) return String.format("tcp {%s:%d > %s:%d - %s window: %d, urgent: %d}", sourceAddr.getHostAddress(), getSourcePort(), destinationAddr.getHostAddress(), getDestinationPort(), getFlagSymbol(flags), window, urgentPointer); else if (getRelativeAck() == -1) return String.format("tcp {%s:%d > %s:%d - %s seq: %d, window: %d, urgent: %d}", sourceAddr.getHostAddress(), getSourcePort(), destinationAddr.getHostAddress(), getDestinationPort(), getFlagSymbol(flags), getRelativeSeq(), window, urgentPointer); else return String.format("tcp {%s:%d > %s:%d - %s seq: %d, ack: %d, window: %d, urgent: %d}", sourceAddr.getHostAddress(), getSourcePort(), destinationAddr.getHostAddress(), getDestinationPort(), getFlagSymbol(flags), getRelativeSeq(), getRelativeAck(), window, urgentPointer); } private String getFlagSymbol(int flags) { if (isUrg()) return "Urg"; else if (isPsh()) return "P"; else if (isRst()) return "R"; else if (isSyn()) return "S"; else if (isFin()) return "F"; else return "."; } @Override public Buffer getBuffer() { ByteBuffer hbuf = ByteBuffer.allocate(20); // hardcoded header size hbuf.putShort((short) srcPort); hbuf.putShort((short) dstPort); hbuf.putInt(seq); hbuf.putInt(ack); hbuf.put((byte) (dataOffset << 4)); hbuf.put((byte) flags); hbuf.putShort((short) window); hbuf.putShort((short) checksum); hbuf.putShort((short) urgentPointer); Buffer buf = new ChainBuffer(hbuf.array()); buf.addLast(data); return buf; } public static class Builder implements PacketBuilder { private InetAddress srcIp; private InetAddress dstIp; private Integer srcPort; private Integer dstPort; private Integer seq = 1; private Integer ack = 0; private Integer flags = 0; private Integer window = 8192; private Buffer data; private PacketBuilder nextBuilder; public Builder syn() { this.flags |= TcpFlag.SYN; return this; } public Builder ack() { this.flags |= TcpFlag.ACK; return this; } public Builder fin() { this.flags |= TcpFlag.FIN; return this; } public Builder rst() { this.flags |= TcpFlag.RST; return this; } public Builder urg() { this.flags |= TcpFlag.URG; return this; } public Builder psh() { this.flags |= TcpFlag.PSH; return this; } public Builder src(InetSocketAddress addr) { return src(addr.getAddress(), addr.getPort()); } public Builder src(String ip, int port) { try { return src(InetAddress.getByName(ip), port); } catch (UnknownHostException e) { throw new IllegalArgumentException("invalid ip format"); } } public Builder src(InetAddress ip, int port) { this.srcIp = ip; this.srcPort = port; return this; } public Builder dst(InetSocketAddress addr) { return dst(addr.getAddress(), addr.getPort()); } public Builder dst(String ip, int port) { try { return dst(InetAddress.getByName(ip), port); } catch (UnknownHostException e) { throw new IllegalArgumentException("invalid ip format"); } } public Builder dst(InetAddress ip, int port) { this.dstIp = ip; this.dstPort = port; return this; } public Builder seq(int seq) { this.seq = seq; return this; } public Builder ack(int ack) { this.ack = ack; return this; } public Builder window(int window) { this.window = window; return this; } public Builder data(Buffer data) { this.data = data; return this; } public Builder data(PacketBuilder builder) { this.nextBuilder = builder; return this; } @Override public Object getDefault(String name) { if (name.equals("src_ip")) return srcIp; if (name.equals("dst_ip")) return dstIp; if (name.equals("ip_proto")) return InternetProtocol.TCP; if (name.equals("src_port")) return srcPort; if (name.equals("dst_port")) return dstPort; if (nextBuilder != null) return nextBuilder.getDefault(name); return null; } @Override public TcpPacket build() { // resolve all values if (dstIp == null) throw new IllegalStateException("destination ip not found"); if (srcIp == null) { PcapDeviceMetadata metadata = PcapDeviceManager.getDeviceMetadata(dstIp); if (metadata == null) throw new IllegalArgumentException("route not found for destination " + dstIp.getHostAddress()); srcIp = metadata.getInet4Address(); } if (srcPort == null) srcPort = 40000; if (dstPort == null) throw new IllegalStateException("destination port not found"); // set TcpPacket p = new TcpPacket(); p.sourceAddr = srcIp; p.destinationAddr = dstIp; p.srcPort = srcPort; p.dstPort = dstPort; p.seq = seq; p.ack = ack; p.dataOffset = 5; // default header size 20 (5 * 4) p.flags = flags; p.window = window; p.data = data; if (data != null) p.dataLength = data.readableBytes(); p.tcpLength = (p.dataOffset * 4) + p.dataLength; p.checksum = TcpChecksum.sum(p); return p; } } }