From 42ad6c2c8137cb82f73fa2efd5e5f47959f92f17 Mon Sep 17 00:00:00 2001 From: M66B Date: Thu, 29 Oct 2015 22:44:18 +0100 Subject: [PATCH] Added class for packet decoding/encoding --- .../java/eu/faircode/netguard/Packet.java | 557 ++++++++++++++++++ 1 file changed, 557 insertions(+) create mode 100644 app/src/main/java/eu/faircode/netguard/Packet.java diff --git a/app/src/main/java/eu/faircode/netguard/Packet.java b/app/src/main/java/eu/faircode/netguard/Packet.java new file mode 100644 index 00000000..a21e9d60 --- /dev/null +++ b/app/src/main/java/eu/faircode/netguard/Packet.java @@ -0,0 +1,557 @@ +package eu.faircode.netguard; + +import android.util.Log; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.net.InetAddress; +import java.nio.ByteBuffer; +import java.util.Scanner; + +public class Packet { + private static final String TAG = "NetGuard.Packet"; + + private ByteBuffer packet; + public IPv4Header IPv4; + public UDPHeader UDP = null; + public TCP TCP = null; + + private Packet() { + } + + public Packet(ByteBuffer buffer) throws IOException { + packet = buffer; + + try { + IPv4 = new IPv4Header(buffer); + IPv4.validate(); + + if (IPv4.protocol == IPv4.UDP) { + UDP = new UDPHeader(buffer); + UDP.validate(); + Log.w(TAG, "Unsupported protocol"); + } else if (IPv4.protocol == IPv4.TCP) { + TCP = new TCP(IPv4.sourceAddress, IPv4.destinationAddress, buffer); + TCP.validate(); + } else + Log.w(TAG, "Unsupported protocol"); + } catch (IOException ex) { + throw new IOException(ex.toString() + " " + this); + } + } + + public void swapAddresses() { + InetAddress sourceAddress = this.IPv4.sourceAddress; + this.IPv4.sourceAddress = this.IPv4.destinationAddress; + this.IPv4.destinationAddress = sourceAddress; + if (this.TCP != null) + this.TCP.swapPorts(); + } + + private void encode(ByteBuffer buffer) { + this.IPv4.encode(buffer); + if (this.TCP != null) + this.TCP.encode(this.IPv4.sourceAddress, this.IPv4.destinationAddress, buffer); + buffer.position(0); + } + + public void send(FileOutputStream out) throws IOException { + this.packet = ByteBuffer.allocate(32767); + encode(this.packet); + byte[] r = new byte[this.packet.limit()]; + this.packet.get(r); + out.write(r); + } + + public String toShortString() { + StringBuilder sb = new StringBuilder(); + sb.append(this.IPv4.sourceAddress); + if (this.TCP != null) + sb.append(':').append(this.TCP.sourcePort); + sb.append(" > "); + sb.append(this.IPv4.destinationAddress); + if (this.TCP != null) + sb.append(':').append(this.TCP.destinationPort); + if (this.TCP != null) { + sb.append(" seq=").append(this.TCP.sequenceNumber); + sb.append(" ack=").append(this.TCP.acknowledgementNumber); + sb.append(this.TCP.getFlags()); + sb.append(this.TCP.getOptions()); + sb.append(" len=").append((this.TCP.data.length)); + } + return sb.toString(); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder("Packet("); + sb.append(this.IPv4); + + if (this.UDP != null) + sb.append(", ").append(this.UDP); + + if (this.TCP != null) + sb.append(", ").append(this.TCP); + + packet.position(0); + byte[] buffer = new byte[packet.limit()]; + packet.get(buffer); + sb.append(", bytes=" + Util.getHex(buffer)); + + sb.append(')'); + return sb.toString(); + } + + // https://en.wikipedia.org/wiki/IPv4#Packet_structure + public static class IPv4Header { + public byte version; + public byte IHL; // 32-bit words + public byte DSCP; // Type of service + public byte EN; + public int totalLength; + public int identification; + public byte reserved; + public boolean DF; // Don't Fragment + public boolean MF; // More Fragments + public int fragmentOffset; + public int TTL; + public int protocol; + public int headerChecksum; + public InetAddress sourceAddress; + public InetAddress destinationAddress; + public byte[] options; + + public int calculatedHeaderChecksum; + + public static final int ICMP = 1; // Internet Control Message Protocol + public static final int IGMP = 2; // Internet Group Management Protocol + public static final int TCP = 6; // Transmission Control Protocol + public static final int UDP = 17; // User Packet Protocol + public static final int ENCAP = 41; // IPv6 encapsulation + public static final int OSPF = 89; // Open Shortest Path First + public static final int SCTP = 132; // Stream Control Transmission Protocol + + private IPv4Header(int protocol, InetAddress sourceAddress, InetAddress destinationAddress) { + this.version = 4; + this.IHL = 5; + this.DSCP = 0; + this.EN = 0; + this.totalLength = 20; + this.identification = 0; + this.reserved = 0; + this.DF = false; + this.MF = false; + this.fragmentOffset = 0; + this.TTL = 64; + this.protocol = protocol; + this.sourceAddress = sourceAddress; + this.destinationAddress = destinationAddress; + this.options = new byte[0]; + } + + public IPv4Header(ByteBuffer buffer) throws IOException { + int pos = buffer.position(); + + int b = buffer.get(); + this.version = (byte) (b >> 4); + this.IHL = (byte) (b & 0xF); + + if (this.version != 4) + throw new IOException("IP: Invalid version=" + this.version); + + b = buffer.get(); + this.DSCP = (byte) (b >> 4); + this.EN = (byte) (b & 0xF); + + this.totalLength = buffer.getShort() & 0xFFFF; + this.identification = buffer.getShort() & 0xFFFF; + + b = buffer.getShort(); + byte flags = (byte) (b >> 13); + this.reserved = (byte) (flags & 1); + this.DF = (flags & 2) != 0; + this.MF = (flags & 4) != 0; + this.fragmentOffset = b & 0x1FFF; // eight-byte blocks (64 bits) + + this.TTL = buffer.get() & 0xFF; + this.protocol = buffer.get() & 0xFF; + this.headerChecksum = buffer.getShort() & 0xFFFF; + + byte[] addressBytes = new byte[4]; + buffer.get(addressBytes, 0, 4); + this.sourceAddress = InetAddress.getByAddress(addressBytes); + + buffer.get(addressBytes, 0, 4); + this.destinationAddress = InetAddress.getByAddress(addressBytes); + + int optionsLength = this.IHL * 4 - 20; + this.options = new byte[optionsLength]; + if (optionsLength > 0) + buffer.get(this.options); + + buffer.putShort(pos + 10, (short) 0); + this.calculatedHeaderChecksum = Util.getChecksum(buffer, pos, buffer.position() - pos); + } + + private int getFlags() { + return this.reserved | (this.DF ? 2 : 0) | (this.MF ? 4 : 0); + } + + public void encode(ByteBuffer buffer) { + int pos = buffer.position(); + + buffer.put((byte) (this.version << 4 | this.IHL)); + buffer.put((byte) (this.DSCP << 4 | this.EN)); + buffer.putShort((short) this.totalLength); + buffer.putShort((short) this.identification); + buffer.putShort((short) (this.getFlags() << 13 | this.fragmentOffset)); + buffer.put((byte) this.TTL); + buffer.put((byte) this.protocol); + buffer.putShort((short) 0); // checksum + buffer.put(this.sourceAddress.getAddress()); + buffer.put(this.destinationAddress.getAddress()); + buffer.put(this.options); + + int checksum = Util.getChecksum(buffer, pos, buffer.position() - pos); + buffer.putShort(pos + 10, (short) checksum); + } + + public void validate() throws IOException { + if (this.IHL < 5) + throw new IOException("IP: Invalid IHL"); + if (this.totalLength < 20) + throw new IOException("IP: Invalid total length"); + if (this.reserved != 0) + throw new IOException("IP: Reserved not zero"); + if (this.headerChecksum != this.calculatedHeaderChecksum) + throw new IOException(("IP: Invalid header checksum")); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder("IPv4Header("); + sb.append("version=").append(this.version); + sb.append(", IHL=").append(this.IHL); + sb.append(", DSCP=").append(this.DSCP); + sb.append(", EN=").append(this.EN); + sb.append(", totalLength=").append(this.totalLength); + sb.append(", reserved=").append(this.reserved); + sb.append(", DF=").append(this.DF); + sb.append(", MF=").append(this.MF); + sb.append(", fragmentOffset=").append(this.fragmentOffset); + sb.append(", TTL=").append(this.TTL); + sb.append(", protocol=").append(this.protocol); + sb.append(", headerChecksum=").append(this.headerChecksum); + sb.append(", sourceAddress=").append(this.sourceAddress.getHostAddress()); + sb.append(", destinationAddress=").append(this.destinationAddress.getHostAddress()); + sb.append(", options=" + Util.getHex(this.options)); + sb.append(", calculatedHeaderChecksum=").append(this.calculatedHeaderChecksum); + sb.append(')'); + return sb.toString(); + } + } + + // https://en.wikipedia.org/wiki/User_Datagram_Protocol + public class UDPHeader { + public UDPHeader(ByteBuffer buffer) { + } + + public void validate() throws IOException { + } + } + + // https://en.wikipedia.org/wiki/Transmission_Control_Protocol + public static class TCP { + public int sourcePort; + public int destinationPort; + public long sequenceNumber; + public long acknowledgementNumber; + public byte dataOffset; // 32-bit words + public byte reserved; + public boolean NS; + public boolean CWR; + public boolean ECE; + public boolean URG; + public boolean ACK; + public boolean PSH; + public boolean RST; + public boolean SYN; + public boolean FIN; + public int windowSize; + public int checksum; + public int urgentPointer; + public byte[] options; + public byte[] data; + + public int calculatedChecksum; + + private TCP(int sourcePort, int destinationPort) { + this.sourcePort = sourcePort; + this.destinationPort = destinationPort; + this.dataOffset = 5; + this.reserved = 0; + this.windowSize = 65535; + this.urgentPointer = 0; + this.options = new byte[0]; + } + + public TCP(InetAddress source, InetAddress destination, ByteBuffer buffer) { + int pos = buffer.position(); + + this.sourcePort = buffer.getShort() & 0xFFFF; + this.destinationPort = buffer.getShort() & 0xFFFF; + this.sequenceNumber = buffer.getInt() & 0xFFFFFFFFL; + this.acknowledgementNumber = buffer.getInt() & 0xFFFFFFFFL; + + int flags = buffer.getShort() & 0xFFFF; + this.dataOffset = (byte) (flags >> 12); + this.reserved = (byte) ((flags >> 9) & 0x7); + this.NS = (flags & 0x100) != 0; + this.CWR = (flags & 0x80) != 0; + this.ECE = (flags & 0x40) != 0; + this.URG = (flags & 0x20) != 0; + this.ACK = (flags & 0x10) != 0; + this.PSH = (flags & 0x8) != 0; + this.RST = (flags & 0x4) != 0; + this.SYN = (flags & 0x2) != 0; + this.FIN = (flags & 0x1) != 0; + + this.windowSize = buffer.getShort() & 0xFFFF; + this.checksum = buffer.getShort() & 0xFFFF; + this.urgentPointer = buffer.getShort() & 0xFFFF; + + int optionsLength = this.dataOffset * 4 - 20; + this.options = new byte[optionsLength]; + if (optionsLength > 0) + buffer.get(this.options); + + this.data = new byte[buffer.limit() - buffer.position()]; + buffer.get(this.data); + + buffer.putShort(pos + 16, (short) 0); + + // pseudo header + ByteBuffer cc = ByteBuffer.allocate(12 + buffer.position() - pos); + cc.put(source.getAddress()); + cc.put(destination.getAddress()); + cc.put((byte) 0); + cc.put((byte) 6); + cc.putShort((short) (buffer.position() - pos)); + // TODO: combine two checksums + for (int i = pos; i < buffer.position(); i++) + cc.put(buffer.get(i)); + + this.calculatedChecksum = Util.getChecksum(cc, 0, cc.limit()); + } + + private int getFlagValue() { + return (this.NS ? 0x100 : 0) | + (this.CWR ? 0x80 : 0) | + (this.ECE ? 0x40 : 0) | + (this.URG ? 0x20 : 0) | + (this.ACK ? 0x10 : 0) | + (this.PSH ? 0x8 : 0) | + (this.RST ? 0x4 : 0) | + (this.SYN ? 0x2 : 0) | + (this.FIN ? 0x1 : 0); + } + + public void clearFlags() { + this.NS = false; + this.CWR = false; + this.ECE = false; + this.URG = false; + this.ACK = false; + this.PSH = false; + this.RST = false; + this.SYN = false; + this.FIN = false; + } + + public void encode(InetAddress source, InetAddress destination, ByteBuffer buffer) { + int pos = buffer.position(); + + buffer.putShort((short) this.sourcePort); + buffer.putShort((short) this.destinationPort); + buffer.putInt((int) this.sequenceNumber); + buffer.putInt((int) this.acknowledgementNumber); + buffer.putShort((short) (this.dataOffset << 12 | this.reserved << 9 | this.getFlagValue())); + buffer.putShort((short) this.windowSize); + buffer.putShort((short) 0); // checksum + buffer.putShort((short) this.urgentPointer); + buffer.put(this.options); + buffer.put(this.data); + + ByteBuffer cc = ByteBuffer.allocate(12 + buffer.position() - pos); + cc.put(source.getAddress()); + cc.put(destination.getAddress()); + cc.put((byte) 0); // reserved + cc.put((byte) 6); // protocol=TCP + cc.putShort((short) (buffer.position() - pos)); + // TODO: combine two checksums + for (int i = pos; i < buffer.position(); i++) + cc.put(buffer.get(i)); + int checksum = Util.getChecksum(cc, 0, cc.limit()); + + buffer.putShort(pos + 16, (short) checksum); + } + + public void validate() throws IOException { + if (this.dataOffset < 5 || this.dataOffset > 15) + throw new IOException("TCP: Invalid data offset"); + if (this.reserved != 0) + throw new IOException("TCP: Reserved not zero"); + if (this.checksum != this.calculatedChecksum) + throw new IOException(("TCP: Invalid checksum")); + } + + public String getFlags() { + StringBuilder sb = new StringBuilder(); + if (this.FIN) + sb.append(" FIN"); + if (this.SYN) + sb.append(" SYN"); + if (this.RST) + sb.append(" RST"); + if (this.PSH) + sb.append(" PSH"); + if (this.ACK) + sb.append(" ACK"); + if (this.URG) + sb.append(" URG"); + if (this.ECE) + sb.append(" ECE"); + if (this.CWR) + sb.append(" CWR"); + if (this.NS) + sb.append(" NS"); + return sb.toString(); + } + + public String getOptions() { + StringBuilder sb = new StringBuilder(); + int i = 0; + while (i < this.options.length) { + byte okind = this.options[i++]; + sb.append(' ').append(okind).append('='); + if (okind > 1) { + int olen = this.options[i++] - 2; + while (olen > 0) { + olen--; + sb.append(String.format("%02X", this.options[i++])); + } + } + } + return sb.toString(); + } + + public void swapPorts() { + int sourcePort = this.sourcePort; + this.sourcePort = this.destinationPort; + this.destinationPort = sourcePort; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder("TCP("); + sb.append("sourcePort=").append(this.sourcePort); + sb.append(", destinationPort=").append(this.destinationPort); + sb.append(", sequenceNumber=").append(this.sequenceNumber); + sb.append(", acknowledgementNumber=").append(this.acknowledgementNumber); + sb.append(", dataOffset=").append(this.dataOffset); + sb.append(", reserved=").append(this.reserved); + sb.append(", flags=").append(getFlags()); + sb.append(", windowSize=").append(this.windowSize); + sb.append(", checksum=").append(this.checksum); + sb.append(", urgentPointer=").append(this.urgentPointer); + sb.append(", option=").append(getOptions()); + sb.append(", data=").append(Util.getHex(this.data)); + sb.append(", calculatedHeaderChecksum=").append(this.calculatedChecksum); + sb.append(')'); + return sb.toString(); + } + } + + public int getUid4() { + String addr = ""; + byte[] b = this.IPv4.sourceAddress.getAddress(); + for (int i = b.length - 1; i >= 0; i--) + addr += String.format("%02X", b[i]); + addr += ":" + String.format("%04X", this.TCP.sourcePort); + + int uid = scanUid("0000000000000000FFFF0000" + addr, "/proc/net/tcp6"); + if (uid < 0) + uid = scanUid(addr, "/proc/net/tcp"); + return uid; + } + + private static int scanUid(String addr, String name) { + File file = new File(name); + Scanner scanner = null; + try { + scanner = new Scanner(file); + while (scanner.hasNextLine()) { + String line = scanner.nextLine().trim(); + if (line.startsWith("sl")) + continue; + + String[] field = line.split("\\s+"); + if (addr.equals(field[1])) + return Integer.parseInt(field[7]); + } + } catch (FileNotFoundException ex) { + Log.e(TAG, ex.toString() + "\n" + Log.getStackTraceString(ex)); + } finally { + if (scanner != null) + scanner.close(); + } + + return -1; + } + + public static class Util { + private static final char[] hex = "0123456789ABCDEF".toCharArray(); + + public static String getHex(byte[] buffer) { + StringBuffer sb = new StringBuffer(buffer.length * 3); + for (int i = 0; i < buffer.length; i++) { + int v = buffer[i] & 0xFF; + if (i != 0) + sb.append(' '); + sb.append(hex[v >>> 4]); + sb.append(hex[v & 0x0F]); + } + return sb.toString(); + } + + public static int getChecksum(ByteBuffer buffer, int position, int length) { + int i = 0; + long sum = 0; + long data; + while (length > 1) { + data = (((buffer.get(position + i) << 8) & 0xFF00) | (buffer.get(position + i + 1) & 0xFF)); + sum += data; + if ((sum & 0xFFFF0000) > 0) { + sum = sum & 0xFFFF; + sum += 1; + } + i += 2; + length -= 2; + } + + if (length > 0) { + sum += (buffer.get(position + i) << 8 & 0xFF00); + if ((sum & 0xFFFF0000) > 0) { + sum = sum & 0xFFFF; + sum += 1; + } + } + + sum = ~sum; + sum = sum & 0xFFFF; + return (int) sum; + } + } +}