最近被手机游戏中时不时误点或者主动弹出来的广告烦到了,于是做了个广告拦截器。
框架用的是 cocos creator 3.8.2。
基本原理就是建立一个vpn服务,拦截所有的域名解析请求,如果需要解析的域名在屏蔽列表中,直接返回解析失败,从而达到屏蔽广告请求的目的。
vpn服务类:
package com.cocos.game.adi.vpn;
import static com.cocos.lib.GlobalObject.getActivity;
import static com.cocos.lib.GlobalObject.getContext;
import android.annotation.SuppressLint;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.ConnectivityManager;
import android.net.VpnService;
import android.os.Build;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.app.NotificationCompat;
import androidx.core.content.ContextCompat;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import com.cocos.game.AppActivity;
import com.cocos.game.XUtils;
import com.cocos.game.adi.Configuration;
import com.cocos.game.adi.FileHelper;
import com.cocos.game.adi.NotificationChannels;
import com.cocos.game.adi.StartStopVpn;
import java.lang.ref.WeakReference;
import adi.panda.com.R;
public class AdVpnService extends VpnService implements Handler.Callback {
public static final int NOTIFICATION_ID_STATE = 10;
public static final int REQUEST_CODE_START = 43;
public static final int REQUEST_CODE_PAUSE = 42;
/* The handler may only keep a weak reference around, otherwise it leaks */
private static class MyHandler extends Handler {
private final WeakReference<Handler.Callback> callback;
public MyHandler(Handler.Callback callback) {
this.callback = new WeakReference<Callback>(callback);
}
@Override
public void handleMessage(Message msg) {
Handler.Callback callback = this.callback.get();
if (callback != null) {
callback.handleMessage(msg);
}
super.handleMessage(msg);
}
}
public static final int VPN_STATUS_STARTING = 0;
public static final int VPN_STATUS_RUNNING = 1;
public static final int VPN_STATUS_STOPPING = 2;
public static final int VPN_STATUS_WAITING_FOR_NETWORK = 3;
public static final int VPN_STATUS_RECONNECTING = 4;
public static final int VPN_STATUS_RECONNECTING_NETWORK_ERROR = 5;
public static final int VPN_STATUS_STOPPED = 6;
public static final String VPN_UPDATE_STATUS_INTENT = "com.cocos.game.VPN_UPDATE_STATUS";
public static final String VPN_UPDATE_STATUS_EXTRA = "VPN_STATUS";
private static final int VPN_MSG_STATUS_UPDATE = 0;
private static final int VPN_MSG_NETWORK_CHANGED = 1;
private static final String TAG = "VpnService";
// TODO: Temporary Hack til refactor is done
public static int vpnStatus = VPN_STATUS_STOPPED;
private final Handler handler = new MyHandler(this);
private AdVpnThread vpnThread = new AdVpnThread(this, new AdVpnThread.Notify() {
@Override
public void run(int value) {
handler.sendMessage(handler.obtainMessage(VPN_MSG_STATUS_UPDATE, value, 0));
}
});
private final BroadcastReceiver connectivityChangedReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
handler.sendMessage(handler.obtainMessage(VPN_MSG_NETWORK_CHANGED, intent));
}
};
private final NotificationCompat.Builder notificationBuilder =
new NotificationCompat.Builder(this,
NotificationChannels.SERVICE_RUNNING)
.setSmallIcon(R.drawable.ic_state_deny)
.setPriority(Notification.PRIORITY_MIN);
public static int vpnStatusToTextId(int status) {
switch (status) {
case VPN_STATUS_STARTING:
return R.string.notification_starting;
case VPN_STATUS_RUNNING:
return R.string.notification_running;
case VPN_STATUS_STOPPING:
return R.string.notification_stopping;
case VPN_STATUS_WAITING_FOR_NETWORK:
return R.string.notification_waiting_for_net;
case VPN_STATUS_RECONNECTING:
return R.string.notification_reconnecting;
case VPN_STATUS_RECONNECTING_NETWORK_ERROR:
return R.string.notification_reconnecting_error;
case VPN_STATUS_STOPPED:
return R.string.notification_stopped;
default:
throw new IllegalArgumentException("Invalid vpnStatus value (" + status + ")");
}
}
@Override
public void onCreate() {
super.onCreate();
NotificationChannels.onCreate(this);
notificationBuilder.addAction(0, getString(R.string.notification_action_pause),
PendingIntent.getService(this,
REQUEST_CODE_PAUSE,
new Intent(this, AdVpnService.class).putExtra("COMMAND", Command.PAUSE.ordinal()),
PendingIntent.FLAG_IMMUTABLE)
) .setColor(ContextCompat.getColor(this, R.color.colorPrimaryDark));
}
public static void checkStartVpnOnBoot(Context context) {
Log.i("BOOT", "Checking whether to start ad buster on boot");
Configuration config = Configuration.getInstance();
if (config == null) {
return;
}
if (!context.getSharedPreferences("state", MODE_PRIVATE).getBoolean("isActive", false)) {
return;
}
if (VpnService.prepare(context) != null) {
} else {
Log.i("BOOT", "VPN preparation not confirmed by user, changing enabled to false");
}
Log.i("BOOT", "Starting ad buster from boot");
NotificationChannels.onCreate(context);
Intent intent = getStartIntent(context);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
context.startForegroundService(intent);
context.startService(intent);
}
}
@NonNull
private static Intent getStartIntent(Context context) {
Intent intent = new Intent(context, AdVpnService.class);
intent.putExtra("COMMAND", Command.START.ordinal());
intent.putExtra("NOTIFICATION_INTENT",
PendingIntent.getActivity(context, 0,
new Intent(context, AppActivity.class), 0));
return intent;
}
@NonNull
private static Intent getResumeIntent(Context context) {
Intent intent = new Intent(context, AdVpnService.class);
intent.putExtra("COMMAND", Command.RESUME.ordinal());
intent.putExtra("NOTIFICATION_INTENT",
PendingIntent.getActivity(context, 0,
new Intent(context, AppActivity.class), 0));
return intent;
}
@Override
public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
Log.i(TAG, "onStartCommand" + intent);
switch (intent == null ? Command.START : Command.values()[intent.getIntExtra("COMMAND", Command.START.ordinal())]) {
case RESUME:
NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.cancelAll();
// fallthrough
case START:
getSharedPreferences("state", MODE_PRIVATE).edit().putBoolean("isActive", true).apply();
startVpn(intent == null ? null : (PendingIntent) intent.getParcelableExtra("NOTIFICATION_INTENT"));
break;
case STOP:
getSharedPreferences("state", MODE_PRIVATE).edit().putBoolean("isActive", false).apply();
stopVpn();
break;
case PAUSE:
pauseVpn();
break;
case RECONNECT:
reconnect();
break;
}
return Service.START_STICKY;
}
@SuppressLint("LaunchActivityFromNotification")
private void pauseVpn() {
stopVpn();
NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.notify(NOTIFICATION_ID_STATE, new NotificationCompat.Builder(this, NotificationChannels.SERVICE_PAUSED)
.setSmallIcon(R.drawable.ic_state_deny)
.setPriority(Notification.PRIORITY_LOW)
.setColor(ContextCompat.getColor(this, R.color.colorPrimaryDark))
.setContentTitle(getString(R.string.notification_paused_title))
.setContentText(getString(R.string.notification_paused_text))
.setContentIntent(PendingIntent.getService(this, REQUEST_CODE_START, getResumeIntent(this),
PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_IMMUTABLE))
.build());
}
private void updateVpnStatus(int status) {
vpnStatus = status;
int notificationTextId = vpnStatusToTextId(status);
notificationBuilder.setContentTitle(getString(notificationTextId));
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
startForeground(NOTIFICATION_ID_STATE, notificationBuilder.build());
Intent intent = new Intent(VPN_UPDATE_STATUS_INTENT);
intent.putExtra(VPN_UPDATE_STATUS_EXTRA, status);
LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
//LocalBroadcastManager: 用于在同一个应用内的不同组件间发送Broadcast
}
private void startVpn(PendingIntent notificationIntent) {
//NotificationCompat.Builder: 显示系统的桌面通知栏上,经常用于消息通知等
//ConnectivityManager: 主要负责查询网络连接状态以及在连接状态有变化的时候发出通知
notificationBuilder.setContentTitle(getString(R.string.notification_title));
if (notificationIntent != null)
notificationBuilder.setContentIntent(notificationIntent);
registerReceiver(connectivityChangedReceiver, new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION));
updateVpnStatus(VPN_STATUS_STARTING);
restartVpnThread();
}
private void restartVpnThread() {
if (vpnThread == null) {
Log.i(TAG, "restartVpnThread: Not restarting thread, could not find thread.");
return;
}
vpnThread.stopThread();
vpnThread.startThread();
}
private void stopVpnThread() {
vpnThread.stopThread();
}
private void waitForNetVpn() {
stopVpnThread();
updateVpnStatus(VPN_STATUS_WAITING_FOR_NETWORK);
}
private void reconnect() {
updateVpnStatus(VPN_STATUS_RECONNECTING);
restartVpnThread();
}
private void stopVpn() {
Log.i(TAG, "Stopping Service");
if (vpnThread != null)
stopVpnThread();
vpnThread = null;
try {
unregisterReceiver(connectivityChangedReceiver);
} catch (IllegalArgumentException e) {
Log.i(TAG, "Ignoring exception on unregistering receiver");
}
updateVpnStatus(VPN_STATUS_STOPPED);
stopSelf();
}
@Override
public void onDestroy() {
Log.i(TAG, "Destroyed, shutting down");
stopVpn();
}
@Override
public boolean handleMessage(Message message) {
if (message == null) {
return true;
}
switch (message.what) {
case VPN_MSG_STATUS_UPDATE:
updateVpnStatus(message.arg1);
break;
case VPN_MSG_NETWORK_CHANGED:
connectivityChanged((Intent) message.obj);
break;
default:
throw new IllegalArgumentException("Invalid message with what = " + message.what);
}
return true;
}
private void connectivityChanged(Intent intent) {
if (intent.getIntExtra(ConnectivityManager.EXTRA_NETWORK_TYPE, 0) == ConnectivityManager.TYPE_VPN) {
Log.i(TAG, "Ignoring connectivity changed for our own network");
return;
}
if (!ConnectivityManager.CONNECTIVITY_ACTION.equals(intent.getAction())) {
Log.e(TAG, "Got bad intent on connectivity changed " + intent.getAction());
}
if (intent.getBooleanExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY, false)) {
Log.i(TAG, "Connectivity changed to no connectivity, wait for a network");
waitForNetVpn();
} else {
Log.i(TAG, "Network changed, try to reconnect");
reconnect();
}
}
}
dns域名解析类:
package com.cocos.game.adi.vpn;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.net.ConnectivityManager;
import android.net.Network;
import android.net.NetworkInfo;
import android.net.VpnService;
import android.os.Build;
import android.os.ParcelFileDescriptor;
import android.system.ErrnoException;
import android.system.Os;
import android.system.OsConstants;
import android.system.StructPollfd;
import android.util.Log;
import com.cocos.game.AppActivity;
import com.cocos.game.adi.Configuration;
import com.cocos.game.adi.FileHelper;
import com.cocos.game.adi.StartStopVpn;
import org.pcap4j.packet.IpPacket;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java.util.Set;
class AdVpnThread implements Runnable, DnsPacketProxy.EventLoop {
private static final String TAG = "AdVpnThread";
private static final int MIN_RETRY_TIME = 5;
private static final int MAX_RETRY_TIME = 2 * 60;
/* If we had a successful connection for that long, reset retry timeout */
private static final long RETRY_RESET_SEC = 60;
/* Maximum number of responses we want to wait for */
private static final int DNS_MAXIMUM_WAITING = 1024;
private static final long DNS_TIMEOUT_SEC = 10;
/* Upstream DNS servers, indexed by our IP */
final ArrayList<InetAddress> upstreamDnsServers = new ArrayList<>();
private final VpnService vpnService;
private final Notify notify;
/* Data to be written to the device */
private final Queue<byte[]> deviceWrites = new LinkedList<>();
// HashMap that keeps an upper limit of packets
private final WospList dnsIn = new WospList();
// The object where we actually handle packets.
private final DnsPacketProxy dnsPacketProxy = new DnsPacketProxy(this);
// Watch dog that checks our connection is alive.
private final VpnWatchdog vpnWatchDog = new VpnWatchdog();
private Thread thread = null;
private FileDescriptor mBlockFd = null;
private FileDescriptor mInterruptFd = null;
/**
* Number of iterations since we last cleared the pcap4j cache
*/
private int pcap4jFactoryClearCacheCounter = 0;
private static boolean vpnStoped = false;
public AdVpnThread(VpnService vpnService, Notify notify) {
this.vpnService = vpnService;
this.notify = notify;
}
private static List<InetAddress> getDnsServers(Context context) throws VpnNetworkException {
Set<InetAddress> known = new HashSet<>();
List<InetAddress> out = new ArrayList<>();
ConnectivityManager cm = (ConnectivityManager) context.getSystemService(VpnService.CONNECTIVITY_SERVICE);
// Seriously, Android? Seriously?
NetworkInfo activeInfo = cm.getActiveNetworkInfo();
if (activeInfo == null)
throw new VpnNetworkException("No DNS Server");
for (Network nw : cm.getAllNetworks()) {
NetworkInfo ni = cm.getNetworkInfo(nw);
if (ni == null || !ni.isConnected() || ni.getType() != activeInfo.getType()
|| ni.getSubtype() != activeInfo.getSubtype())
continue;
for (InetAddress address : cm.getLinkProperties(nw).getDnsServers()) {
if (known.add(address))
out.add(address);
}
}
return out;
}
public void startThread() {
Log.i(TAG, "Starting Vpn Thread");
thread = new Thread(this, "AdVpnThread");
thread.start();
Log.i(TAG, "Vpn Thread started");
}
public void stopThread() {
Log.i(TAG, "Stopping Vpn Thread");
vpnStoped = true;
if (thread != null) thread.interrupt();
mInterruptFd = FileHelper.closeOrWarn(mInterruptFd, TAG, "stopThread: Could not close interruptFd");
try {
Log.w(TAG, "stopThread: waiting joining thread");
if (thread != null) thread.join(0);
} catch (InterruptedException e) {
Log.w(TAG, "stopThread: Interrupted while joining thread", e);
}
if (thread != null && thread.isAlive()) {
Log.w(TAG, "stopThread: Could not kill VPN thread, it is still alive");
} else {
thread = null;
Log.i(TAG, "Vpn Thread stopped");
}
}
@Override
public synchronized void run() {
Log.i(TAG, "Starting");
vpnStoped = false;
// Load the block list
try {
dnsPacketProxy.initialize(vpnService, upstreamDnsServers);
vpnWatchDog.initialize(false);
} catch (InterruptedException e) {
return;
}
if (notify != null) {
notify.run(AdVpnService.VPN_STATUS_STARTING);
}
int retryTimeout = MIN_RETRY_TIME;
// Try connecting the vpn continuously
while (!vpnStoped) {
long connectTimeMillis = 0;
try {
connectTimeMillis = System.currentTimeMillis();
// If the function returns, that means it was interrupted
runVpn();
Log.i(TAG, "Told to stop");
if (notify != null) {
notify.run(AdVpnService.VPN_STATUS_STOPPING);
}
break;
} catch (InterruptedException e) {
Log.i(TAG, "InterruptedException to stop");
break;
} catch (VpnNetworkException e) {
// We want to filter out VpnNetworkException from out crash analytics as these
// are exceptions that we expect to happen from network errors
Log.w(TAG, "Network exception in vpn thread, ignoring and reconnecting", e);
// If an exception was thrown, show to the user and try again
if (notify != null)
notify.run(AdVpnService.VPN_STATUS_RECONNECTING_NETWORK_ERROR);
} catch (Exception e) {
Log.e(TAG, "Network exception in vpn thread, reconnecting", e);
//ExceptionHandler.saveException(e, Thread.currentThread(), null);
if (notify != null)
notify.run(AdVpnService.VPN_STATUS_RECONNECTING_NETWORK_ERROR);
}
if (System.currentTimeMillis() - connectTimeMillis >= RETRY_RESET_SEC * 1000) {
Log.i(TAG, "Resetting timeout");
retryTimeout = MIN_RETRY_TIME;
}
// ...wait and try again
Log.i(TAG, "Retrying to connect in " + retryTimeout + "seconds...");
try {
Thread.sleep((long) retryTimeout * 1000);
} catch (InterruptedException e) {
Log.i(TAG, "InterruptedException sleep");
break;
}
if (retryTimeout < MAX_RETRY_TIME)
retryTimeout *= 2;
}
if (notify != null)
notify.run(AdVpnService.VPN_STATUS_STOPPED);
Log.i(TAG, "Exiting");
}
private void runVpn() throws InterruptedException, ErrnoException, IOException, VpnNetworkException {
// Allocate the buffer for a single packet.
byte[] packet = new byte[32767];
// A pipe we can interrupt the poll() call with by closing the interruptFd end
FileDescriptor[] pipes = Os.pipe();
mInterruptFd = pipes[0];
mBlockFd = pipes[1];
// Authenticate and configure the virtual network interface.
try (ParcelFileDescriptor pfd = configure()) {
// Read and write views of the tun device
FileInputStream inputStream = new FileInputStream(pfd.getFileDescriptor());
FileOutputStream outFd = new FileOutputStream(pfd.getFileDescriptor());
// Now we are connected. Set the flag and show the message.
if (notify != null)
notify.run(AdVpnService.VPN_STATUS_RUNNING);
// We keep forwarding packets till something goes wrong.
while (doOne(inputStream, outFd, packet));
} finally {
mBlockFd = FileHelper.closeOrWarn(mBlockFd, TAG, "runVpn: Could not close blockFd");
}
}
private boolean doOne(FileInputStream inputStream, FileOutputStream outFd, byte[] packet) throws IOException, ErrnoException, InterruptedException, VpnNetworkException {
StructPollfd deviceFd = new StructPollfd();
deviceFd.fd = inputStream.getFD();
deviceFd.events = (short) OsConstants.POLLIN;
StructPollfd blockFd = new StructPollfd();
blockFd.fd = mBlockFd;
blockFd.events = (short) (OsConstants.POLLHUP | OsConstants.POLLERR);
if (!deviceWrites.isEmpty())
deviceFd.events |= (short) OsConstants.POLLOUT;
StructPollfd[] polls = new StructPollfd[2 + dnsIn.size()];
polls[0] = deviceFd;
polls[1] = blockFd;
{
int i = -1;
for (WaitingOnSocketPacket wosp : dnsIn) {
i++;
StructPollfd pollFd = polls[2 + i] = new StructPollfd();
pollFd.fd = ParcelFileDescriptor.fromDatagramSocket(wosp.socket).getFileDescriptor();
pollFd.events = (short) OsConstants.POLLIN;
}
}
int pollTimeout = vpnWatchDog.getPollTimeout();
Log.d(TAG, "doOne: Polling begin " + polls.length + " file descriptors timeout:"+pollTimeout);
int result = FileHelper.poll(polls, pollTimeout );
Log.d(TAG, "doOne: Polling end " + polls.length + " file descriptors result:"+result);
if (result == 0) {
vpnWatchDog.handleTimeout();
return true;
}
if (blockFd.revents != 0) {
Log.i(TAG, "Told to stop VPN");
return false;
}
//在从设备读取之前需要这样做,否则,由于大小或超时限制,新插入的内容可能会使我们想要读取的某个套接字无效
{
int i = -1;
Iterator<WaitingOnSocketPacket> iter = dnsIn.iterator();
while (iter.hasNext()) {
i++;
WaitingOnSocketPacket wosp = iter.next();
if ((polls[i + 2].revents & OsConstants.POLLIN) != 0) {
Log.d(TAG, "Read from DNS socket begin " + wosp.socket);
iter.remove();
handleRawDnsResponse(wosp.packet, wosp.socket);
wosp.socket.close();
Log.d(TAG, "Read from DNS socket end " + wosp.socket);
}
}
}
Log.d(TAG, "doOne: middle");
if ((deviceFd.revents & OsConstants.POLLOUT) != 0) {
Log.d(TAG, "Write to device");
writeToDevice(outFd);
}
if ((deviceFd.revents & OsConstants.POLLIN) != 0) {
Log.d(TAG, "Read from device");
readPacketFromDevice(inputStream, packet);
}
Log.d(TAG, "doOne: finish");
return true;
}
private void writeToDevice(FileOutputStream outFd) throws VpnNetworkException {
try {
outFd.write(deviceWrites.poll());
} catch (IOException e) {
// TODO: Make this more specific, only for: "File descriptor closed"
throw new VpnNetworkException("Outgoing VPN output stream closed");
}
}
private void readPacketFromDevice(FileInputStream inputStream, byte[] packet) throws VpnNetworkException, SocketException {
// Read the outgoing packet from the input stream.
int length;
try {
length = inputStream.read(packet);
} catch (IOException e) {
throw new VpnNetworkException("Cannot read from device", e);
}
if (length == 0) {
// TODO: Possibly change to exception
Log.w(TAG, "Got empty packet!");
return;
}
final byte[] readPacket = Arrays.copyOfRange(packet, 0, length);
vpnWatchDog.handlePacket(readPacket);
dnsPacketProxy.handleDnsRequest(readPacket);
}
public void forwardPacket(DatagramPacket outPacket, IpPacket parsedPacket) throws VpnNetworkException {
DatagramSocket dnsSocket = null;
try {
// Packets to be sent to the real DNS server will need to be protected from the VPN
dnsSocket = new DatagramSocket();
vpnService.protect(dnsSocket);
dnsSocket.send(outPacket);
if (parsedPacket != null)
dnsIn.add(new WaitingOnSocketPacket(dnsSocket, parsedPacket));
else
FileHelper.closeOrWarn(dnsSocket, TAG, "handleDnsRequest: Cannot close socket in error");
} catch (IOException e) {
FileHelper.closeOrWarn(dnsSocket, TAG, "handleDnsRequest: Cannot close socket in error");
if (e.getCause() instanceof ErrnoException) {
ErrnoException errnoExc = (ErrnoException) e.getCause();
if ((errnoExc.errno == OsConstants.ENETUNREACH) || (errnoExc.errno == OsConstants.EPERM)) {
throw new VpnNetworkException("Cannot send message:", e);
}
}
Log.w(TAG, "handleDnsRequest: Could not send packet to upstream", e);
return;
}
}
private void handleRawDnsResponse(IpPacket parsedPacket, DatagramSocket dnsSocket) throws IOException {
byte[] datagramData = new byte[1024];
DatagramPacket replyPacket = new DatagramPacket(datagramData, datagramData.length);
dnsSocket.setSoTimeout(3000); //3秒超时
dnsSocket.receive(replyPacket);
dnsPacketProxy.handleDnsResponse(parsedPacket, datagramData);
}
public void queueDeviceWrite(IpPacket ipOutPacket) {
deviceWrites.add(ipOutPacket.getRawData());
}
void newDNSServer(VpnService.Builder builder, String format, byte[] ipv6Template, InetAddress addr) throws UnknownHostException {
// Optimally we'd allow either one, but the forwarder checks if upstream size is empty, so
// we really need to acquire both an ipv6 and an ipv4 subnet.
if (addr instanceof Inet6Address && ipv6Template == null) {
Log.i(TAG, "newDNSServer: Ignoring DNS server " + addr);
} else if (addr instanceof Inet4Address && format == null) {
Log.i(TAG, "newDNSServer: Ignoring DNS server " + addr);
} else if (addr instanceof Inet4Address) {
upstreamDnsServers.add(addr);
String alias = String.format(format, upstreamDnsServers.size() + 1);
Log.i(TAG, "configure: Adding DNS Server " + addr + " as " + alias);
builder.addDnsServer(alias);
builder.addRoute(alias, 32);
vpnWatchDog.setTarget(InetAddress.getByName(alias));
} else if (addr instanceof Inet6Address) {
upstreamDnsServers.add(addr);
ipv6Template[ipv6Template.length - 1] = (byte) (upstreamDnsServers.size() + 1);
InetAddress i6addr = Inet6Address.getByAddress(ipv6Template);
Log.i(TAG, "configure: Adding DNS Server " + addr + " as " + i6addr);
builder.addDnsServer(i6addr);
vpnWatchDog.setTarget(i6addr);
}
}
void configurePackages(VpnService.Builder builder, Configuration config) {
for (String app : config.allowlist.itemsOnVpn) {
try {
Log.d(TAG, "configure: Allowing " + app + " to use the DNS VPN");
builder.addAllowedApplication(app);
} catch (Exception e) {
Log.w(TAG, "configure: Cannot disallow", e);
}
}
for (String app : config.allowlist.itemsOffVpn) {
try {
Log.d(TAG, "configure: Disallowing " + app + " from using the DNS VPN");
builder.addDisallowedApplication(app);
} catch (Exception e) {
Log.w(TAG, "configure: Cannot disallow", e);
}
}
}
private ParcelFileDescriptor configure() throws VpnNetworkException {
Log.i(TAG, "Configuring" + this);
Configuration config = Configuration.getInstance();
// Get the current DNS servers before starting the VPN
List<InetAddress> dnsServers = getDnsServers(vpnService);
try {
String proxyHost = StartStopVpn.getProxyHost();
if (!proxyHost.isEmpty()) {
dnsServers.add(InetAddress.getByName(proxyHost));
}
} catch (Exception e) {
Log.e(TAG, "Got proxy host error" + e.getMessage());
}
Log.i(TAG, "Got DNS servers = " + dnsServers);
// Configure a builder while parsing the parameters.
VpnService.Builder builder = vpnService.new Builder();
String format = null;
// Determine a prefix we can use. These are all reserved prefixes for example
// use, so it's possible they might be blocked.
for (String prefix : new String[]{"192.0.2", "198.51.100", "203.0.113"}) {
try {
builder.addAddress(prefix + ".1", 24);
} catch (IllegalArgumentException e) {
continue;
}
format = prefix + ".%d";
break;
}
// For fancy reasons, this is the 2001:db8::/120 subnet of the /32 subnet reserved for
// documentation purposes. We should do this differently. Anyone have a free /120 subnet
// for us to use?
byte[] ipv6Template = new byte[]{32, 1, 13, (byte) (184 & 0xFF), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
if (hasIpV6Servers(config, dnsServers)) {
try {
InetAddress addr = Inet6Address.getByAddress(ipv6Template);
Log.d(TAG, "configure: Adding IPv6 address" + addr);
builder.addAddress(addr, 120);
} catch (Exception e) {
Log.e(TAG, e.getMessage()==null?"":e.getMessage());
ipv6Template = null;
}
} else {
ipv6Template = null;
}
if (format == null) {
Log.w(TAG, "configure: Could not find a prefix to use, directly using DNS servers");
builder.addAddress("192.168.50.1", 24);
}
// Add configured DNS servers
upstreamDnsServers.clear();
if (config.dnsServers.enabled) {
for (Configuration.Item item : config.dnsServers.items) {
if (item.state == Configuration.Item.STATE_ALLOW) {
try {
newDNSServer(builder, format, ipv6Template, InetAddress.getByName(item.location));
} catch (Exception e) {
Log.e(TAG, "configure: Cannot add custom DNS server", e);
}
}
}
}
// Add all knows DNS servers
for (InetAddress addr : dnsServers) {
try {
newDNSServer(builder, format, ipv6Template, addr);
} catch (Exception e) {
Log.e(TAG, "configure: Cannot add server:", e);
}
}
builder.setBlocking(true);
// Allow applications to bypass the VPN
builder.allowBypass();
// Explictly allow both families, so we do not block
// traffic for ones without DNS servers (issue 129).
builder.allowFamily(OsConstants.AF_INET);
builder.allowFamily(OsConstants.AF_INET6);
// // Set the VPN to unmetered
// if (Build.VERSION.SDK_INT > Build.VERSION_CODES.P)
// builder.setMetered(false);
configurePackages(builder, config);
// Create a new interface using the builder and save the parameters.
ParcelFileDescriptor pfd = builder
.setSession("AdiDNS")
.setConfigureIntent(
PendingIntent.getActivity(vpnService, 1, new Intent(vpnService, AppActivity.class),
PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE)).establish();
Log.i(TAG, "Configured");
return pfd;
}
boolean hasIpV6Servers(Configuration config, List<InetAddress> dnsServers) {
if (!config.ipV6Support)
return false;
if (config.dnsServers.enabled) {
for (Configuration.Item item : config.dnsServers.items) {
if (item.state == Configuration.Item.STATE_ALLOW && item.location.contains(":"))
return true;
}
}
for (InetAddress inetAddress : dnsServers) {
if (inetAddress instanceof Inet6Address)
return true;
}
return false;
}
public interface Notify {
void run(int value);
}
static class VpnNetworkException extends Exception {
VpnNetworkException(String s) {
super(s);
}
VpnNetworkException(String s, Throwable t) {
super(s, t);
}
}
/**
* Helper class holding a socket, the packet we are waiting the answer for, and a time
*/
private static class WaitingOnSocketPacket {
final DatagramSocket socket;
final IpPacket packet;
private final long time;
WaitingOnSocketPacket(DatagramSocket socket, IpPacket packet) {
this.socket = socket;
this.packet = packet;
this.time = System.currentTimeMillis();
}
long ageSeconds() {
return (System.currentTimeMillis() - time) / 1000;
}
}
/**
* Queue of WaitingOnSocketPacket, bound on time and space.
*/
private static class WospList implements Iterable<WaitingOnSocketPacket> {
private final LinkedList<WaitingOnSocketPacket> list = new LinkedList<WaitingOnSocketPacket>();
void add(WaitingOnSocketPacket wosp) {
if (list.size() > DNS_MAXIMUM_WAITING) {
Log.d(TAG, "Dropping socket due to space constraints: " + list.element().socket);
list.element().socket.close();
list.remove();
}
while (!list.isEmpty() && list.element().ageSeconds() > DNS_TIMEOUT_SEC) {
Log.d(TAG, "Timeout on socket " + list.element().socket);
list.element().socket.close();
list.remove();
}
list.add(wosp);
}
public Iterator<WaitingOnSocketPacket> iterator() {
return list.iterator();
}
int size() {
return list.size();
}
}
}
效果图:
支持 android5.1+
百度网盘:百度网盘 请输入提取码
提取码:pand