mirror of
https://github.com/M66B/NetGuard.git
synced 2024-12-23 08:17:51 +00:00
Added class for packet decoding/encoding
This commit is contained in:
parent
404e99d08d
commit
42ad6c2c81
1 changed files with 557 additions and 0 deletions
557
app/src/main/java/eu/faircode/netguard/Packet.java
Normal file
557
app/src/main/java/eu/faircode/netguard/Packet.java
Normal file
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue