我正在使用VpnService为Android实施简单的防火墙。我的应用程序类似于ToyVpnService,但它不会将原始IP数据包发送到远程VPN服务器,这会将它们转发到目的地。
我可以在本地执行所有这些转发例程吗?这就是我试图实施的目标。
我初始化一个TUN设备及其文件描述符:
mInterface = new Builder().setSession(getString(R.string.app_name))
.addAddress("10.0.2.1", 24)
.addRoute("0.0.0.0", 1)
.addRoute("128.0.0.0", 1)
.establish();
in = new FileInputStream(mInterface.getFileDescriptor());
out = new FileOutputStream(mInterface.getFileDescriptor());
我将0.0.0.0/1和128.0.0.0/1分配给TUN设备,使其比0.0.0.0/0的默认路由更优先。我使用0.0.0.0/0并遇到了下面的相同异常。
这是一个示例UDP请求。
1)。我从TUN设备读取了一个IP数据包。
05-06 00:46:52.749: D/UDPChecksum(31077): Sent == [69, 0, 0, 36, 0, 0, 64, 0, 64, 17, 108, 91, 10, 0, 2, 1, -64, -88, 1, -59, -53, 1, -50, -87, 0, 16, 89, -114, 85, 68, 80, 95, 68, 65, 84, 65]
请考虑IPv4数据包结构here. 例如,第一个数字69(二进制的0100 0101)表示IP协议的版本是4(4个高位)。并且4个低位代表32位字中的Internet Header Length (IHL)。
2)。然后创建一个protected DatagramSocket
并将数据(不带其IP和UDP标头)发送到目标地址我从捕获的IP数据包中读取。
3)。我收到来自远程计算机的回复,并希望将其发回给初始化请求的应用。
4)。我交换了IP数据包中的源和目标IP地址和端口号,计算了IPv4报头校验和和UDP校验和(构建了一个IPv4伪报头)。
05-06 00:46:52.889: D/UDPChecksum(31077): mIpv4PseudoHeader == [-64, -88, 1, -59, 10, 0, 2, 1, 0, 17, 0, 14]
5)。然后我将计算出的校验和设置为IP数据包的相应索引,并将IP数据包写入out
,即TUN设备的输出流。
05-06 00:46:52.889: D/UDPChecksum(31077): To TUN == [69, 0, 0, 34, 0, 0, 64, 0, 64, 17, 108, 93, -64, -88, 1, -59, 10, 0, 2, 1, -50, -87, -53, 1, 0, 14, -105, -72, 85, 68, 80, 95, 79, 75]
响应到达我的应用。调用其receive()方法后被阻塞的DatagramSocket
填充我提供的缓冲区。
byte[] responseBuffer = new byte[RESPONSE_SIZE];
try {
mDatagramSocket.send(mDatagramPacket);
final DatagramPacket response = new DatagramPacket(responseBuffer, responseBuffer.length);
mDatagramSocket.receive(response);
} catch (IOException e) {
Log.e("NoRootFwService", "error: " + Arrays.toString(responseBuffer)); // I can see the correct response here.
logException(e);
}
但是当超过超时时,它的套接字会抛出异常。
05-05 23:46:58.389: E/CLIENT(20553): java.net.SocketTimeoutException
05-05 23:46:58.389: E/CLIENT(20553): at libcore.io.IoBridge.maybeThrowAfterRecvfrom(IoBridge.java:551)
05-05 23:46:58.389: E/CLIENT(20553): at libcore.io.IoBridge.recvfrom(IoBridge.java:509)
05-05 23:46:58.389: E/CLIENT(20553): at java.net.PlainDatagramSocketImpl.doRecv(PlainDatagramSocketImpl.java:161)
05-05 23:46:58.389: E/CLIENT(20553): at java.net.PlainDatagramSocketImpl.receive(PlainDatagramSocketImpl.java:169)
05-05 23:46:58.389: E/CLIENT(20553): at java.net.DatagramSocket.receive(DatagramSocket.java:250)
05-05 23:46:58.389: E/CLIENT(20553): at socket.client.MainActivity$UdpThread.run(MainActivity.java:195)
05-05 23:46:58.389: E/CLIENT(20553): Caused by: libcore.io.ErrnoException: recvfrom failed: EAGAIN (Try again)
05-05 23:46:58.389: E/CLIENT(20553): at libcore.io.Posix.recvfromBytes(Native Method)
05-05 23:46:58.389: E/CLIENT(20553): at libcore.io.Posix.recvfrom(Posix.java:141)
05-05 23:46:58.389: E/CLIENT(20553): at libcore.io.BlockGuardOs.recvfrom(BlockGuardOs.java:164)
05-05 23:46:58.389: E/CLIENT(20553): at libcore.io.IoBridge.recvfrom(IoBridge.java:506)
05-05 23:46:58.389: E/CLIENT(20553): ... 4 more
没有防火墙,一切正常。 我阅读了这些讨论,但它们并没有帮助:
答案 0 :(得分:4)
尝试添加缺少的 SocketTimeoutException 异常处理程序
byte[] responseBuffer = new byte[RESPONSE_SIZE];
try {
mDatagramSocket.send(mDatagramPacket);
final DatagramPacket response = new DatagramPacket(responseBuffer, responseBuffer.length);
mDatagramSocket.receive(response);
} catch (SocketTimeoutException e) {
// ignore
; // continue;
} catch (IOException e) {
Log.e("NoRootFwService", "error: " + Arrays.toString(responseBuffer)); // I can see the correct response here.
logException(e);
}
答案 1 :(得分:3)
从 recvfrom()调用docs:
[EAGAIN]或[EWOULDBLOCK] 套接字的文件描述符标记为O_NONBLOCK,没有数据等待接收;或MSG_OOB已设置且没有带外数据 可用,并且套接字的文件描述符标记为O_NONBLOCK 或者套接字不支持阻塞以等待带外数据。
简化说明
如果套接字上没有可用消息,则接收呼叫等待 要到达的消息,除非套接字是非阻塞的(参见fcntl(2)), 在这种情况下,返回值-1和外部变量errno 设置为EAGAIN
DatagramSocket没有处于阻塞模式,看起来你正试图从套接字读取并且没有数据要读取,你确定你真的在接收数据吗?尝试清除每个收到的数据包的缓冲区。
答案 2 :(得分:0)
关键是我使用了错误的IPv4伪标头来计算校验和。它既不包含UDP报头也不包含传输数据。由于我包含了UDP标头和数据,我还没有看到原始问题的例外情况。