Брандмауэр Android с VpnService
Я пытаюсь реализовать простой брандмауэр для Android с VpnService для проекта BS. Я выбираю VpnService, потому что он будет работать на некорневых устройствах. Он будет регистрировать соединения и позволит вам фильтровать соединения. (На основе IP)
Есть приложение, делающее это, так что это возможно.
Магазин приложений Google Play
Я провел некоторое исследование и обнаружил, что VpnService создает интерфейс Tun. Ничего более. (Нет реализации VPN, только туннель). Он позволяет указать адрес этого интерфейса и добавить маршруты. Возвращает дескриптор файла. Вы можете читать исходящие пакеты и писать входящие пакеты.
Я создал производный класс VpnService и начал обслуживание. Я могу настроить tun0
с классом VpnService.Builder. Когда я смотрю на mobiwol's
связь с adb shell netcfg
это создает tun0
интерфейс с адресом 10.2.3.4/32. Он направляет все пакеты в эту частную сеть и отправляет в интернет. Я пытаюсь так же. Создан интерфейс с адресом 10.0.0.2/32. Добавлен маршрут с функцией addRoute. 0.0.0.0/0, поэтому я могу захватить все пакеты из всей сети, насколько я понимаю. (Я довольно новичок в этом предмете и все еще учусь. Я нашел фрагменты по интернету, так что я не совсем уверен. Поправь меня, если я ошибаюсь.)
Я создал 2 темы в обслуживании. Каждый читает из файлового дескриптора и записывает его в 127.0.0.1 с защищенным сокетом. (Я не совсем уверен, должен ли я читать / писать на 127.0.0.1. Может быть, это проблема.)
Я проанализировал пакеты, которые я прочитал из файлового дескриптора. Например:
01000101 byte:69 //ipv4 20byte header
00000000 byte:0 //TOS
00000000 byte:0 //Total Length
00111100 byte:60 //Total Length
11111100 byte:-4 //ID
11011011 byte:-37 //ID
01000000 byte:64 //fragment
00000000 byte:0 //"
01000000 byte:64 //TTL
00000110 byte:6 //Protocol 6 -> TCP
01011110 byte:94 //Header checksum
11001111 byte:-49 //Header checksum
00001010 byte:10 //10.0.0.2
00000000 byte:0
00000000 byte:0
00000010 byte:2
10101101 byte:-83 //173.194.39.78 //google
00111110 byte:-62
00100111 byte:39
******** byte:78
10110100 byte:-76 // IP option
01100101 byte:101
00000001 byte:1
10111011 byte:-69
//20byte IP haeder
01101101 byte:109
. . //40byte data (i couldnt parse TCP header,
I think its not needed when I route this in IP layer)
. .
. .
00000110 byte:6
Я не нашел никаких других заголовков IP в остальных данных. Я думаю, что должна быть инкапсуляция между сетью 10.0.0.2 в локальную сеть (192.168.2.1) и Интернет. Я не уверен.
Моя настоящая проблема в том, что я застрял в ветке входящих пакетов. Я не могу ничего читать. Нет ответа Как видно на скриншоте, нет входящих данных:
Я пытаюсь прочитать из того же соединения, которое я использую для записи в 127.0.0.1 с защищенным сокетом.
Android <-> Tun Interface (tun0) <-> Интернет-соединение
Все пакеты <-> 10.0.0.2 <-> 127.0.0.1? <-> 192.168.2.1 <-> Интернет?
Я не мог найти ничего полезного о VpnService. (Пример ToyVPN просто бесполезен) Я читаю документы о Linux Tun/Tap, но о туннелировании между хостом и удаленным. Я хочу хост и удаленный на одном устройстве. Не похоже на туннелирование.
Как я могу это сделать?
Изменить: Код запрошен. Это на очень ранней стадии. Как я упоминал ранее, это производный класс VpnService. 2 темы (чтение и запись) созданы в служебной теме.
package com.git.firewall;
public class GITVpnService extends VpnService implements Handler.Callback, Runnable {
private static final String TAG = "GITVpnService";
private String mServerAddress = "127.0.0.1";
private int mServerPort = 55555;
private PendingIntent mConfigureIntent;
private Handler mHandler;
private Thread mThread;
private ParcelFileDescriptor mInterface;
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
// The handler is only used to show messages.
if (mHandler == null) {
mHandler = new Handler(this);
}
// Stop the previous session by interrupting the thread.
if (mThread != null) {
mThread.interrupt();
}
// Start a new session by creating a new thread.
mThread = new Thread(this, "VpnThread");
mThread.start();
return START_STICKY;
}
@Override
public void onDestroy() {
if (mThread != null) {
mThread.interrupt();
}
}
@Override
public boolean handleMessage(Message message) {
if (message != null) {
Toast.makeText(this, (String)message.obj, Toast.LENGTH_SHORT).show();
}
return true;
}
@Override
public synchronized void run() {
try {
Log.i(TAG, "Starting");
InetSocketAddress server = new InetSocketAddress(
mServerAddress, mServerPort);
run(server);
} catch (Exception e) {
Log.e(TAG, "Got " + e.toString());
try {
mInterface.close();
} catch (Exception e2) {
// ignore
}
Message msgObj = mHandler.obtainMessage();
msgObj.obj = "Disconnected";
mHandler.sendMessage(msgObj);
} finally {
}
}
DatagramChannel mTunnel = null;
private boolean run(InetSocketAddress server) throws Exception {
boolean connected = false;
android.os.Debug.waitForDebugger();
// Create a DatagramChannel as the VPN tunnel.
mTunnel = DatagramChannel.open();
// Protect the tunnel before connecting to avoid loopback.
if (!protect(mTunnel.socket())) {
throw new IllegalStateException("Cannot protect the tunnel");
}
// Connect to the server.
mTunnel.connect(server);
// For simplicity, we use the same thread for both reading and
// writing. Here we put the tunnel into non-blocking mode.
mTunnel.configureBlocking(false);
// Authenticate and configure the virtual network interface.
handshake();
// Now we are connected. Set the flag and show the message.
connected = true;
Message msgObj = mHandler.obtainMessage();
msgObj.obj = "Connected";
mHandler.sendMessage(msgObj);
new Thread ()
{
public void run ()
{
// Packets to be sent are queued in this input stream.
FileInputStream in = new FileInputStream(mInterface.getFileDescriptor());
// Allocate the buffer for a single packet.
ByteBuffer packet = ByteBuffer.allocate(32767);
int length;
try
{
while (true)
{
while ((length = in.read(packet.array())) > 0) {
// Write the outgoing packet to the tunnel.
packet.limit(length);
debugPacket(packet); // Packet size, Protocol, source, destination
mTunnel.write(packet);
packet.clear();
}
}
}
catch (IOException e)
{
e.printStackTrace();
}
}
}.start();
new Thread ()
{
public void run ()
{
DatagramChannel tunnel = mTunnel;
// Allocate the buffer for a single packet.
ByteBuffer packet = ByteBuffer.allocate(8096);
// Packets received need to be written to this output stream.
FileOutputStream out = new FileOutputStream(mInterface.getFileDescriptor());
while (true)
{
try
{
// Read the incoming packet from the tunnel.
int length;
while ((length = tunnel.read(packet)) > 0)
{
// Write the incoming packet to the output stream.
out.write(packet.array(), 0, length);
packet.clear();
}
}
catch (IOException ioe)
{
ioe.printStackTrace();
}
}
}
}.start();
return connected;
}
private void handshake() throws Exception {
if (mInterface == null)
{
Builder builder = new Builder();
builder.setMtu(1500);
builder.addAddress("10.0.0.2",32);
builder.addRoute("0.0.0.0", 0);
//builder.addRoute("192.168.2.0",24);
//builder.addDnsServer("8.8.8.8");
// Close the old interface since the parameters have been changed.
try {
mInterface.close();
} catch (Exception e) {
// ignore
}
// Create a new interface using the builder and save the parameters.
mInterface = builder.setSession("GIT VPN")
.setConfigureIntent(mConfigureIntent)
.establish();
}
}
private void debugPacket(ByteBuffer packet)
{
/*
for(int i = 0; i < length; ++i)
{
byte buffer = packet.get();
Log.d(TAG, "byte:"+buffer);
}*/
int buffer = packet.get();
int version;
int headerlength;
version = buffer >> 4;
headerlength = buffer & 0x0F;
headerlength *= 4;
Log.d(TAG, "IP Version:"+version);
Log.d(TAG, "Header Length:"+headerlength);
String status = "";
status += "Header Length:"+headerlength;
buffer = packet.get(); //DSCP + EN
buffer = packet.getChar(); //Total Length
Log.d(TAG, "Total Length:"+buffer);
buffer = packet.getChar(); //Identification
buffer = packet.getChar(); //Flags + Fragment Offset
buffer = packet.get(); //Time to Live
buffer = packet.get(); //Protocol
Log.d(TAG, "Protocol:"+buffer);
status += " Protocol:"+buffer;
buffer = packet.getChar(); //Header checksum
String sourceIP = "";
buffer = packet.get(); //Source IP 1st Octet
sourceIP += buffer;
sourceIP += ".";
buffer = packet.get(); //Source IP 2nd Octet
sourceIP += buffer;
sourceIP += ".";
buffer = packet.get(); //Source IP 3rd Octet
sourceIP += buffer;
sourceIP += ".";
buffer = packet.get(); //Source IP 4th Octet
sourceIP += buffer;
Log.d(TAG, "Source IP:"+sourceIP);
status += " Source IP:"+sourceIP;
String destIP = "";
buffer = packet.get(); //Destination IP 1st Octet
destIP += buffer;
destIP += ".";
buffer = packet.get(); //Destination IP 2nd Octet
destIP += buffer;
destIP += ".";
buffer = packet.get(); //Destination IP 3rd Octet
destIP += buffer;
destIP += ".";
buffer = packet.get(); //Destination IP 4th Octet
destIP += buffer;
Log.d(TAG, "Destination IP:"+destIP);
status += " Destination IP:"+destIP;
/*
msgObj = mHandler.obtainMessage();
msgObj.obj = status;
mHandler.sendMessage(msgObj);
*/
//Log.d(TAG, "version:"+packet.getInt());
//Log.d(TAG, "version:"+packet.getInt());
//Log.d(TAG, "version:"+packet.getInt());
}
}
2 ответа
Подобный вопрос был задан несколько месяцев назад, и хотя ответы там не очень проницательны, комментарии в принятом ответе дают некоторое представление о том, что может пойти не так.
Вы должны иметь в виду, какой уровень в модели OSI находится в вашей логике:
Входящие и исходящие потоки VpnService находятся на сетевом уровне; вы получаете (и должны в свою очередь передавать) необработанные IP-пакеты, как вы описали в своем вопросе.
В вашем примере потока байтов вы можете видеть, что входящий поток байтов является дейтаграммой IPv4, поскольку первые четыре бита
0100
(4). Обратитесь к этой спецификации структуры пакета для деталей по IPv4.При пересылке запросов вы находитесь на уровне приложения; Вы должны передавать содержимое полезной нагрузки UDP или TCP (т.е. только их данные, а не сами заголовки), используя соответственно DatagramSocket или Socket.
Имейте в виду, что это пропускает транспортный уровень, так как эти реализации заботятся о создании заголовка UDP (в случае DatagramSocket), а также заголовка и параметров TCP (в случае Socket).
В сущности, ваше приложение должно иметь возможность интерпретировать и создавать заголовки и параметры IPv4 и IPv6, а также полезную нагрузку IP, заголовки UDP и заголовки и параметры TCP.
Может быть, лучше искать проекты с открытым исходным кодом, такие как OpenVpn. Работает на уровне API 14+ (Ice Cream Sandwhich) без Root Access .