Android Vpn 服务 - 写入 vpn 接口时出错

Android Vpn service - error on write to vpn interface

我正在使用 VpnService API,如 ToyVpn Android 示例所示;

vpn设备正确打开(显示系统图标)。

写入vpn接口时出现异常。我尝试写入随机字节或完整捕获的数据包(在下面的示例中)结果是相同的,即 "Invalid argument".

报告 tun 设备描述符有效。

可能是什么问题?是否对写入 vpn 设备的数据进行控制?

到目前为止,我已经在两个不同的模拟器上测试了这个,Android 7.1 和 Android 4.2,我在这两种情况下都遇到了异常。

这是 Android 7.1 上的完整日志:

02-01 11:01:08.099 ... I/MainActivity: onActivityResult
02-01 11:01:08.105 ... I/TestVpnService: opening tun device
02-01 11:01:08.118 ... I/TestVpnService: mInterface {ParcelFileDescriptor: java.io.FileDescriptor@c8cad37}
02-01 11:01:08.118 ... I/TestVpnService: mInterface FD: 45
02-01 11:01:08.118 ... I/TestVpnService: vpn descriptor valid? true
02-01 11:01:08.118 ... I/TestVpnService: testing write to tun device...
02-01 11:01:08.119 ... E/TestVpnService: error: Invalid argument
02-01 11:01:08.119 ... W/System.err: java.io.IOException: Invalid argument
02-01 11:01:08.119 ... W/System.err:     at sun.nio.ch.FileDispatcherImpl.write0(Native Method)
02-01 11:01:08.119 ... W/System.err:     at sun.nio.ch.FileDispatcherImpl.write(FileDispatcherImpl.java:63)
02-01 11:01:08.119 ... W/System.err:     at sun.nio.ch.IOUtil.writeFromNativeBuffer(IOUtil.java:93)
02-01 11:01:08.119 ... W/System.err:     at sun.nio.ch.IOUtil.write(IOUtil.java:65)
02-01 11:01:08.119 ... W/System.err:     at sun.nio.ch.FileChannelImpl.write(FileChannelImpl.java:214)
02-01 11:01:08.119 ... W/System.err:     at testvpn.vpnwritetest.AndroidVpnService$override.onStartCommand(AndroidVpnService.java:71)
02-01 11:01:08.119 ... W/System.err:     at testvpn.vpnwritetest.AndroidVpnService$override.access$dispatch(AndroidVpnService.java)
02-01 11:01:08.120 ... W/System.err:     at testvpn.vpnwritetest.AndroidVpnService.onStartCommand(AndroidVpnService.java:0)
02-01 11:01:08.120 ... W/System.err:     at android.app.ActivityThread.handleServiceArgs(ActivityThread.java:3326)
02-01 11:01:08.120 ... W/System.err:     at android.app.ActivityThread.-wrap21(ActivityThread.java)
02-01 11:01:08.120 ... W/System.err:     at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1582)
02-01 11:01:08.120 ... W/System.err:     at android.os.Handler.dispatchMessage(Handler.java:102)
02-01 11:01:08.120 ... W/System.err:     at android.os.Looper.loop(Looper.java:154)
02-01 11:01:08.120 ... W/System.err:     at android.app.ActivityThread.main(ActivityThread.java:6119)
02-01 11:01:08.120 ... W/System.err:     at java.lang.reflect.Method.invoke(Native Method)
02-01 11:01:08.120 ... W/System.err:     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:886)
02-01 11:01:08.120 ... W/System.err:     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:776)
02-01 11:01:08.120 ... I/TestVpnService: closing tun device

清单:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="testvpn.vpnwritetest">

    <uses-permission android:name="android.permission.INTERNET" />

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <service
            android:name=".AndroidVpnService"
            android:permission="android.permission.BIND_VPN_SERVICE">
            <intent-filter>
                <action android:name="android.net.VpnService" />
            </intent-filter>
        </service>


        <activity
            android:name=".MainActivity"
            android:label="@string/app_name"
            android:theme="@style/AppTheme.NoActionBar">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

activity:

package testvpn.vpnwritetest;

import android.content.Intent;
import android.net.VpnService;
import android.os.Bundle;
import android.support.design.widget.FloatingActionButton;
import android.support.design.widget.Snackbar;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.util.Log;
import android.view.View;
import android.widget.Button;

public class MainActivity extends AppCompatActivity {
    Button button_vpn_test;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
        fab.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
                        .setAction("Action", null).show();
            }
        });

        button_vpn_test = (Button) findViewById(R.id.button_vpn_test);

        button_vpn_test.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

                Intent intent = VpnService.prepare(MainActivity.this);
                if (intent != null) {
                    startActivityForResult(intent, 0);
                } else {
                    onActivityResult(0, RESULT_OK, null);
                }

            }
        });

    }

    @Override
    protected void onActivityResult(int request, int result, Intent data) {

        Log.i("MainActivity", "onActivityResult");

        if (result == RESULT_OK) {


            Intent intent = new Intent(this, AndroidVpnService.class);

            startService(intent);
        }
    }


}

服务:

package testvpn.vpnwritetest;

import android.content.Intent;
import android.net.VpnService;
import android.os.ParcelFileDescriptor;
import android.util.Log;

import java.io.FileDescriptor;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

/**
 * Created by mrtexaz on 2/1/17.
 */

public class AndroidVpnService extends VpnService {

    private static final String TAG = "TestVpnService";


    private ParcelFileDescriptor mInterface;

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {



        Log.i(TAG, "opening tun device");

        Builder builder = new Builder();
        builder = new Builder();
        builder.setMtu(1500);
        builder.addAddress("10.0.0.2", 24);
//        builder.addRoute("0.0.0.0", 0);

        mInterface = builder.establish();

        Log.i(TAG,"mInterface " + mInterface);
        Log.i(TAG,"mInterface FD: " + mInterface.getFd());

        FileDescriptor df = mInterface.getFileDescriptor();

        Log.i(TAG,"vpn descriptor valid? " + df.valid());

        FileChannel vpnOutput = new FileOutputStream(mInterface.getFileDescriptor()).getChannel();


        int [] testpayload = { 0x98, 0xe7, 0xf5, 0xd7, 0xce, 0xb6, 0x10, 0x02, 0xb5, 0x56, 0x59, 0x87, 0x08, 0x00, 0x45, 0x00,
                0x00, 0x54, 0xd0, 0x93, 0x40, 0x00, 0x40, 0x01, 0xe6, 0x5f, 0xc0, 0xa8, 0x01, 0x64, 0xc0, 0xa8,
                0x01, 0x01, 0x08, 0x00, 0x63, 0xc6, 0x7e, 0x2e, 0x00, 0x02, 0x95, 0xaa, 0x91, 0x58, 0x00, 0x00,
                0x00, 0x00, 0x21, 0x33, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15,
                0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25,
                0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35,
                0x36, 0x37};

        ByteBuffer test = ByteBuffer.allocate(testpayload.length);

        for (int i = 0; i < testpayload.length; i++) {

            test.array()[i] = (byte) (testpayload[i] & 0xFF);

        }

        try {

            Log.i(TAG, "testing write to tun device...");


            vpnOutput.write(test);

            Log.i(TAG, "testing write to tun device OK");

        } catch (Exception ex) {
            Log.e(TAG,"error: " + ex.getMessage());
            ex.printStackTrace();
        }


        Log.i(TAG, "closing tun device");

        try {
            mInterface.close();
        } catch (IOException e) {
            e.printStackTrace();
        }




        return START_STICKY;
    }

    @Override
    public void onDestroy() {

    }


}

好的,解决了!

我需要向 tun 设备写入一个有效的 tcp/ip 数据包。

在示例中,删除以太网 II headers(示例数组的前 14 个字节)就足够了。

所以使用这个示例负载,写入 vpn 设备是可行的:

int [] testpayload = {
        //0x98, 0xe7, 0xf5, 0xd7, 0xce, 0xb6, 0x10, 0x02, 0xb5, 0x56, 0x59, 0x87, 0x08, 0x00,
        0x45, 0x00,
        0x00, 0x54, 0xd0, 0x93, 0x40, 0x00, 0x40, 0x01, 0xe6, 0x5f, 0xc0, 0xa8, 0x01, 0x64, 0xc0, 0xa8,
        0x01, 0x01, 0x08, 0x00, 0x63, 0xc6, 0x7e, 0x2e, 0x00, 0x02, 0x95, 0xaa, 0x91, 0x58, 0x00, 0x00,
        0x00, 0x00, 0x21, 0x33, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15,
        0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25,
        0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35,
        0x36, 0x37};