android实现网络防火墙控制app访问wifi/移动数据网络

iptables是Linux的一个命令行工具,经过设置一些规则能够直接把指定uid或网址的数据包从ip层过滤掉,从而实现网络防火墙的功能,这部分已经比较成熟,android或厂商只是对iptables命令进行了封装,让android app能够经过iptables命令进行防火墙设置,iptables有不少复杂的功能,咱们主要看看怎么设置白名单只让指定的uid app能够联网和设置黑名单让指定的uid app不能联网,咱们经过代码流程来分析,代码是mtk android8.1。java

root后经过adb shell iptables -L能够查看当前的规则列表,Chain INPUT,OUTPUT就是控制数据包的输入输出,没作任何设置前应该张下面这个样子,Chain OUTPUT的数据包经过Chain fw_OUTPUT控制, Chain fw_OUTPUT的规则是空的,因此当前对网络不作限制。android

$ adb shell iptables -L
Chain INPUT (policy ACCEPT)
target     prot opt source               destination         
bw_INPUT   all  --  anywhere             anywhere            
fw_INPUT   all  --  anywhere             anywhere            shell

Chain FORWARD (policy ACCEPT)
target     prot opt source               destination         
oem_fwd    all  --  anywhere             anywhere            
fw_FORWARD  all  --  anywhere             anywhere            
bw_FORWARD  all  --  anywhere             anywhere            
natctrl_FORWARD  all  --  anywhere             anywhere            网络

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination         
oem_out    all  --  anywhere             anywhere            
firewall   all  --  anywhere             anywhere            
fw_OUTPUT  all  --  anywhere             anywhere            
st_OUTPUT  all  --  anywhere             anywhere            
bw_OUTPUT  all  --  anywhere             anywhere           app

Chain fw_FORWARD (1 references)
target     prot opt source               destination        ide

Chain fw_INPUT (1 references)
target     prot opt source               destination            函数

Chain fw_OUTPUT (1 references)
target     prot opt source               destination        工具

//android app层ui

INetworkManagementService networkService = INetworkManagementService.Stub.asInterface(ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE));this

    public void WhiteListMode(INetworkManagementService networkService) {
        FireWallUtils.setFirewallEnabled(networkService, true);//白名单模式
        FireWallUtils.setFirewallUidRule(networkService, 0, 1016, 0); //重置vpn uid rule
        FireWallUtils.setFirewallUidRule(networkService, 0, 1016, 1); //设置vpn uid为白名单
        FireWallUtils.setFirewallUidRule(networkService, 0, 0, 0); //重置root uid rule
        FireWallUtils.setFirewallUidRule(networkService, 0, 0, 1); //设置root uid为白名单
        
//          List<String> whitelistApp = new ArrayList<>();
//          whitelistApp.add("com.nuts.extremspeedup");
//          PackageManager pm = mContext.getPackageManager();
//            for (String pkgName : whitelistApp) {
//                int uid = FireWallUtils.getUidFromPackageName(pm, pkgName);
//                if (uid > 0) {
//                    FireWallUtils.setFirewallUidRule(networkService, 0, uid, 0); //重置 uid rule
//                    FireWallUtils.setFirewallUidRule(networkService, 0, uid, 1); //设置白名单
//                }
//            }
    }
    
    public void BlackListMode(INetworkManagementService networkService) {
        FireWallUtils.setFirewallEnabled(networkService, false); //黑名单模式
        
          List<String> whitelistApp = new ArrayList<>();
          whitelistApp.add("com.iflytek.inputmethod");//com.iflytek.inputmethod
          PackageManager pm = getPackageManager();
            for (String pkgName : whitelistApp) {
                int uid = FireWallUtils.getUidFromPackageName(pm, pkgName); //获取app的uid
                if (uid > 0) {
                    FireWallUtils.setFirewallUidRule(networkService, 0, uid, 0); //重置 uid rule
                    FireWallUtils.setFirewallUidRule(networkService, 0, uid, 2); //设置uid为黑名单
                }
            }
    }

    public void DisableMobileMode(INetworkManagementService networkService) {
        FireWallUtils.setFirewallEnabled(networkService, false); //黑名单模式
        
          List<String> whitelistApp = new ArrayList<>();
          whitelistApp.add("com.iflytek.inputmethod");//com.iflytek.inputmethod
          PackageManager pm = getPackageManager();
            for (String pkgName : whitelistApp) {
                int uid = FireWallUtils.getUidFromPackageName(pm, pkgName);
                if (uid > 0) {
                    FireWallUtils.setFirewallUidChainRule(networkService, uid, 0, false); //(networkType == 1) ? WIFI : MOBILE; , 禁止此uid连mobile
                }
            }
    }

 

import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.os.INetworkManagementService;
import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemProperties;
import android.provider.Settings;
import android.util.Log;

import java.util.ArrayList;
import java.util.List;

public class FireWallUtils  {
    private static final String TAG = "FireWallUtils";

    public static void setFirewallEnabled(INetworkManagementService networkService, boolean enable) {
        try {
            networkService.setFirewallEnabled(enable);
        } catch (RemoteException e) {
            Log.e(TAG, "setFirewallEnabled RemoteException e:" + Log.getStackTraceString(e));
        } catch (Exception e) {
            Log.e(TAG, "setFirewallEnabled Exception e:" + Log.getStackTraceString(e));
        }
    }
    
    public static void setFirewallUidRule(INetworkManagementService networkService, int chain, int uid, int rule) {
        try {
            networkService.setFirewallUidRule(chain, uid, rule);
        } catch (RemoteException e) {
            Log.e(TAG, "setFirewallUidRule RemoteException e:" + Log.getStackTraceString(e));
        } catch (Exception e) {
            Log.e(TAG, "setFirewallUidRule Exception e:" + Log.getStackTraceString(e));
        }
    }

    public static int getUidFromPackageName(PackageManager pm, String pkgName) {
        try {
            ApplicationInfo appInfo = pm.getApplicationInfo(pkgName, PackageManager.MATCH_ALL);
            if (appInfo != null) {
                return appInfo.uid;
            }
        } catch (PackageManager.NameNotFoundException e) {
            //e.printStackTrace();
        }
        return -1;
    }
}

    public static void setFirewallUidChainRule(INetworkManagementService networkService, int uid, int networkType, boolean allow) {
        try {
            networkService.setFirewallUidChainRule(uid, networkType, allow);
        } catch (RemoteException e) {
            Log.d(TAG, "setFirewallEnabled RemoteException e:" + Log.getStackTraceString(e));
        } catch (Exception e) {
            Log.d(TAG, "setFirewallEnabled Exception e:" + Log.getStackTraceString(e));
        }
    }
    public static void clearFirewallChain(INetworkManagementService networkService, String chain) {
        try {
            networkService.clearFirewallChain(chain);
        } catch (RemoteException e) {
            Log.d(TAG, "setFirewallEnabled RemoteException e:" + Log.getStackTraceString(e));
        } catch (Exception e) {
            Log.d(TAG, "setFirewallEnabled Exception e:" + Log.getStackTraceString(e));
        }
    }

frameworks/base/core/java/android/os/INetworkManagementService.aidl

    void setFirewallEnabled(boolean enabled);
    boolean isFirewallEnabled();
    void setFirewallInterfaceRule(String iface, boolean allow);
    void setFirewallUidRule(int chain, int uid, int rule);
    void setFirewallUidRules(int chain, in int[] uids, in int[] rules);
    void setFirewallChainEnabled(int chain, boolean enable);

    /**
     * agold
     * Cnfigure firewall rule by uid and chain
     * @hide
     */
    void setFirewallUidChainRule(int uid, int networkType, boolean allow);

    /** @} */

    /**
     * agold
     * Delete all rules in  chain or all chains
     * @hide
     */
    void clearFirewallChain(String chain);

frameworks/base/services/core/java/com/android/server/NetworkManagementService.java

    @Override
    public void setFirewallEnabled(boolean enabled) {
        enforceSystemUid();
        try {
            mConnector.execute("firewall", "enable", enabled ? "whitelist" : "blacklist");
            mFirewallEnabled = enabled;
        } catch (NativeDaemonConnectorException e) {
            throw e.rethrowAsParcelableException();
        }
    }

    @Override
    public void setFirewallUidRule(int chain, int uid, int rule) {
        enforceSystemUid();
        synchronized (mQuotaLock) {
            setFirewallUidRuleLocked(chain, uid, rule);
        }
    }

    private void setFirewallUidRuleLocked(int chain, int uid, int rule) {
        if (updateFirewallUidRuleLocked(chain, uid, rule)) {
            try {
                mConnector.execute("firewall", "set_uid_rule", getFirewallChainName(chain), uid,
                        getFirewallRuleName(chain, rule));
            } catch (NativeDaemonConnectorException e) {
                throw e.rethrowAsParcelableException();
            }
        }
    }

    /**
     * agold
     * @Configure firewall rule by uid and chain
     * @hide
     */
    public void setFirewallUidChainRule(int uid, int networkType, boolean allow) {
        android.util.Log.i("linyuan_NMS", "setFirewallUidChainRule uid = " + uid + ", networkType = " + networkType + ", allow = " + allow);
        //enforceSystemUid();
        final String MOBILE = "mobile";
        final String WIFI = "wifi";

        final String rule = allow ? "allow" : "deny";
        final String chain = (networkType == 1) ? WIFI : MOBILE;

        try {
            mConnector.execute("firewall", "set_uid_fw_rule", uid, chain, rule);
        } catch (NativeDaemonConnectorException e) {
            throw e.rethrowAsParcelableException();
        }
    }
    
    /**
     * agold
     * @Configure firewall rule by uid and chain
     * @hide
     */
    public void clearFirewallChain(String chain) {
        //enforceSystemUid();
        try {
            mConnector.execute("firewall", "clear_fw_chain", chain);
        } catch (NativeDaemonConnectorException e) {
            throw e.rethrowAsParcelableException();
        }
    }

system/netd/server/CommandListener.cpp

int CommandListener::FirewallCmd::runCommand(SocketClient *cli, int argc,
        char **argv) {
    if (argc < 2) {
        cli->sendMsg(ResponseCode::CommandSyntaxError, "Missing command", false);
        return 0;
    }

    if (!strcmp(argv[1], "enable")) {
        if (argc != 3) {
            cli->sendMsg(ResponseCode::CommandSyntaxError,
                        "Usage: firewall enable <whitelist|blacklist>", false);
            return 0;
        }
        FirewallType firewallType = parseFirewallType(argv[2]);

        int res = gCtls->firewallCtrl.enableFirewall(firewallType);
        return sendGenericOkFail(cli, res);
    }
    if (!strcmp(argv[1], "disable")) {
        int res = gCtls->firewallCtrl.disableFirewall();
        return sendGenericOkFail(cli, res);
    }
    if (!strcmp(argv[1], "is_enabled")) {
        int res = gCtls->firewallCtrl.isFirewallEnabled();
        return sendGenericOkFail(cli, res);
    }

    if (!strcmp(argv[1], "set_uid_rule")) {
        if (argc != 5) {
            cli->sendMsg(ResponseCode::CommandSyntaxError,
                         "Usage: firewall set_uid_rule <dozable|standby|none> <1000> <allow|deny>",
                         false);
            return 0;
        }

        ChildChain childChain = parseChildChain(argv[2]);
        if (childChain == INVALID_CHAIN) {
            cli->sendMsg(ResponseCode::CommandSyntaxError,
                         "Invalid chain name. Valid names are: <dozable|standby|none>",
                         false);
            return 0;
        }
        int uid = atoi(argv[3]);
        FirewallRule rule = parseRule(argv[4]);
        int res = gCtls->firewallCtrl.setUidRule(childChain, uid, rule);
        return sendGenericOkFail(cli, res);
    }

 
    
    //agold start
    if (!strcmp(argv[1], "set_uid_fw_rule")) {
        ALOGD("set_uid_fw_rule");
        if (argc != 5) {
            cli->sendMsg(ResponseCode::CommandSyntaxError,
                         "Usage: firewall set_uid_fw_rule <uid> <mobile|wifi> <allow|deny>",
                         false);
            return 0;
        }

        int uid = atoi(argv[2]);
        FirewallChinaRule chain = parseChain(argv[3]);
        FirewallRule rule = parseRule(argv[4]);

        int res = gCtls->firewallCtrl.setUidFwRule(uid, chain, rule);
        return sendGenericOkFail(cli, res);
    }

    if (!strcmp(argv[1], "clear_fw_chain")) {
        if (argc != 3) {
            cli->sendMsg(ResponseCode::CommandSyntaxError,
                         "Usage: firewall clear_fw_chain <chain>",
                         false);
            return 0;
        }

        FirewallChinaRule chain = parseChain(argv[2]);

        int res = gCtls->firewallCtrl.clearFwChain(chain);
        return sendGenericOkFail(cli, res);
    }
    //agold end

    cli->sendMsg(ResponseCode::CommandSyntaxError, "Unknown command", false);
    return 0;
}

system/netd/server/FirewallController.cpp

int FirewallController::enableFirewall(FirewallType ftype) {
    int res = 0;
    if (mFirewallType != ftype) {
        // flush any existing rules
        disableFirewall();

        if (ftype == WHITELIST) {
            // create default rule to drop all traffic
            std::string command =
                "*filter\n"
                "-A fw_INPUT -j DROP\n"
                "-A fw_OUTPUT -j REJECT\n"
                "-A fw_FORWARD -j REJECT\n"
                "COMMIT\n";
            res = execIptablesRestore(V4V6, command.c_str());
        }

        // Set this after calling disableFirewall(), since it defaults to WHITELIST there
        mFirewallType = ftype;
    }
    return res;
}

int FirewallController::disableFirewall(void) {
    mFirewallType = WHITELIST;
    mIfaceRules.clear();

    // flush any existing rules
    std::string command =
        "*filter\n"
        ":fw_INPUT -\n"
        ":fw_OUTPUT -\n"
        ":fw_FORWARD -\n"
        "COMMIT\n";

    return execIptablesRestore(V4V6, command.c_str());
}

int FirewallController::setUidRule(ChildChain chain, int uid, FirewallRule rule) {
    const char* op;
    const char* target;
    FirewallType firewallType = getFirewallType(chain);
    if (firewallType == WHITELIST) {
        target = "RETURN";
        // When adding, insert RETURN rules at the front, before the catch-all DROP at the end.
        op = (rule == ALLOW)? "-I" : "-D";
    } else { // BLACKLIST mode
        target = "DROP";
        // When adding, append DROP rules at the end, after the RETURN rule that matches TCP RSTs.
        op = (rule == DENY)? "-A" : "-D";
    }

    std::vector<std::string> chainNames;
    switch(chain) {
        case DOZABLE:
            chainNames = { LOCAL_DOZABLE };
            break;
        case STANDBY:
            chainNames = { LOCAL_STANDBY };
            break;
        case POWERSAVE:
            chainNames = { LOCAL_POWERSAVE };
            break;
        case NONE:
            chainNames = { LOCAL_INPUT, LOCAL_OUTPUT };
            break;
        default:
            ALOGW("Unknown child chain: %d", chain);
            return -1;
    }

    std::string command = "*filter\n";
    for (std::string chainName : chainNames) {
        StringAppendF(&command, "%s %s -m owner --uid-owner %d -j %s\n",
                      op, chainName.c_str(), uid, target);
    }
    StringAppendF(&command, "COMMIT\n");

    return execIptablesRestore(V4V6, command);
}

 

//agold start
const char* FirewallController::FIREWALL = "firewall";
const char* FirewallController::FIREWALL_MOBILE = "mobile";
const char* FirewallController::FIREWALL_WIFI = "wifi";
//agold end

int FirewallController::setupIptablesHooks(void) {
    int res = 0;
    //agold start
    std::string command = "*filter\n";
    StringAppendF(&command, "-F firewall \n");
    StringAppendF(&command, "-A firewall -o ppp+ -j mobile\n");
    StringAppendF(&command, "-A firewall -o ccmni+ -j mobile\n");
    StringAppendF(&command, "-A firewall -o ccemni+ -j mobile\n");
    StringAppendF(&command, "-A firewall -o usb+ -j mobile\n");
    StringAppendF(&command, "-A firewall -o cc2mni+ -j mobile\n");
    StringAppendF(&command, "-A firewall -o wlan+ -j wifi\n");
    StringAppendF(&command, "COMMIT\n");
    res |= execIptablesRestore(V4V6, command.c_str());
    //agold end
    res |= createChain(LOCAL_DOZABLE, getFirewallType(DOZABLE));
    res |= createChain(LOCAL_STANDBY, getFirewallType(STANDBY));
    res |= createChain(LOCAL_POWERSAVE, getFirewallType(POWERSAVE));
    return res;
}

//agold start
int FirewallController::setUidFwRule(int uid, FirewallChinaRule chain, FirewallRule rule) {
    ALOGD("setUidFwRule");
    std::string command = "*filter\n";
    char uidStr[16];
    const char* op;
    const char* fwChain;

    sprintf(uidStr, "%d", uid);

    if (rule == DENY) {
        op = "-I";
    } else {
        op = "-D";
    }

    if(chain == MOBILE) {
        fwChain = "mobile";
    }else{
        fwChain = "wifi";
    }
/*
    if(chain == MOBILE) {
        if(rule == ALLOW)
            blacklistUsers.insert(uid);
        else
            blacklistUsers.erase(uid);
    }
    */
    
    
    ALOGD("setUidFwRule op = %s, chain = %s, uid = %s", op, fwChain, uidStr);
    StringAppendF(&command, "%s %s -m owner --uid-owner %s -j REJECT\n", op, fwChain, uidStr);
    StringAppendF(&command, "COMMIT\n");

    return execIptablesRestore(V4V6, command.c_str());
}


int FirewallController::clearFwChain(FirewallChinaRule chain) {
    std::string command = "*filter\n";
    const char* fwChain;
    if(chain == MOBILE) {
        fwChain = "mobile";
    }else{
        fwChain = "wifi";
    }
    StringAppendF(&command, "-F %s \n", fwChain);
    StringAppendF(&command, "COMMIT\n");
    return execIptablesRestore(V4V6, command.c_str());
}
//agold end

 

运行到最后就是调用iptables命令去添加或删除一些规则,若是没有接口能够参考上面的接口增长,熟悉下iptables的规则就知道怎么添加了。

WhiteListMode函数其实就调的下面的iptables命令

 adb shell iptables -A fw_INPUT -j DROP ; // -A表示要添加规则到fw_INPUT链, -j DROP表示添加丢弃规则,丢弃全部输入包
 adb shell iptables -A fw_OUTPUT -j REJECT // -A表示要添加规则到fw_OUTPU链, -j REJECT表示添加拒绝规则,拒绝全部输出包
adb shell iptables -A fw_FORWARD -j REJECT

adb shell iptables -I fw_OUTPUT -m owner --uid-owner 1016 -j ACCEPT // -I表示插入一条规则到fw_OUTPUT链,是一条白名单规则,容许uid 1016的数据包经过

adb shell iptables -I fw_INPUT -m owner --uid-owner 1016 -j ACCEPT

调用后iptables张下面这个样子,Chain OUTPUT的数据包经过Chain fw_OUTPUT控制, Chain fw_OUTPUT的规则是reject全部数据包,只容许uid 1016 vpn。

$ adb shell iptables -L
Chain INPUT (policy ACCEPT)
target     prot opt source               destination         
bw_INPUT   all  --  anywhere             anywhere            
fw_INPUT   all  --  anywhere             anywhere            

Chain FORWARD (policy ACCEPT)
target     prot opt source               destination         
oem_fwd    all  --  anywhere             anywhere            
fw_FORWARD  all  --  anywhere             anywhere            
bw_FORWARD  all  --  anywhere             anywhere            
natctrl_FORWARD  all  --  anywhere             anywhere            

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination         
oem_out    all  --  anywhere             anywhere            
firewall   all  --  anywhere             anywhere            
fw_OUTPUT  all  --  anywhere             anywhere            
st_OUTPUT  all  --  anywhere             anywhere            
bw_OUTPUT  all  --  anywhere             anywhere           

Chain fw_FORWARD (1 references)
target     prot opt source               destination         
REJECT     all  --  anywhere             anywhere             reject-with icmp-port-unreachable

Chain fw_INPUT (1 references)
target     prot opt source               destination         
RETURN     all  --  anywhere             anywhere             owner UID match vpn
DROP       all  --  anywhere             anywhere            

Chain fw_OUTPUT (1 references)
target     prot opt source               destination         
RETURN     all  --  anywhere             anywhere             owner UID match vpn
REJECT     all  --  anywhere             anywhere             reject-with icmp-port-unreachable

 

BlackListMode函数其实就调的下面的iptables命令

adb shell iptables -F fw_INPUT

adb shell iptables -F fw_OUTPUT  //清除fw_OUTPUT链上全部规则,容许全部数据包经过

adb shell iptables -D fw_INPUT -m owner --uid-owner 10086 -j DROP  // -D表示删除一条规则

adb shell iptables -I fw_OUTPUT -m owner --uid-owner 10086 -j DROP // -I表示插入一条规则到fw_OUTPUT链,是一条黑名单规则,只禁止uid 10086的数据包经过

adb shell iptables -I fw_INPUT -m owner --uid-owner 10086 -j DROP

调用后iptables张下面这个样子,Chain OUTPUT的数据包经过Chain fw_OUTPUT控制, Chain fw_OUTPUT的规则是只丢弃

uid 为u11_a83(10086)的数据包,就是前面设置的app的uid 10086

~$adb shell iptables -L
Chain INPUT (policy ACCEPT)
target     prot opt source               destination         
bw_INPUT   all  --  anywhere             anywhere            
fw_INPUT   all  --  anywhere             anywhere                  

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination         
oem_out    all  --  anywhere             anywhere            
firewall   all  --  anywhere             anywhere            
fw_OUTPUT  all  --  anywhere             anywhere            
st_OUTPUT  all  --  anywhere             anywhere            
bw_OUTPUT  all  --  anywhere             anywhere          

Chain fw_INPUT (1 references)
target     prot opt source               destination         
DROP       all  --  anywhere             anywhere             owner UID match u11_a83

Chain fw_OUTPUT (1 references)
target     prot opt source               destination         
DROP       all  --  anywhere             anywhere             owner UID match u11_a83

 

若是须要单独控制wifi或移动数据,看DisableMobileMode里面setFirewallUidChainRule接口,这部分是厂商加的,若是没有能够按上面的内容进行添加,参考agold关键字,对照adb shell iptables -L的输出调试问题就不大了。

调用后iptables张下面这个样子,Chain OUTPUT的数据包经过Chain fw_OUTPUT 和Chain firewall控制, Chain fw_OUTPUT的规则为空就是不限制,Chain firewall经过mobile和wifi链控制wifi/mobile, Chain wifi为空,Chain mobile为reject owner UID match u0_a79

~$ adb shell iptables -L
Chain INPUT (policy ACCEPT)
target     prot opt source               destination         
bw_INPUT   all  --  anywhere             anywhere            
fw_INPUT   all  --  anywhere             anywhere            
        

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination         
oem_out    all  --  anywhere             anywhere            
firewall   all  --  anywhere             anywhere            
fw_OUTPUT  all  --  anywhere             anywhere            
st_OUTPUT  all  --  anywhere             anywhere            
bw_OUTPUT  all  --  anywhere             anywhere                 

Chain firewall (1 references)
target     prot opt source               destination         
mobile     all  --  anywhere             anywhere            
mobile     all  --  anywhere             anywhere            
mobile     all  --  anywhere             anywhere            
mobile     all  --  anywhere             anywhere            
mobile     all  --  anywhere             anywhere            
wifi       all  --  anywhere             anywhere            


Chain fw_INPUT (1 references)
target     prot opt source               destination         

Chain fw_OUTPUT (1 references)
target     prot opt source               destination         


Chain mobile (5 references)
target     prot opt source               destination         
REJECT     all  --  anywhere             anywhere             owner UID match u0_a79 reject-with icmp-port-unreachable

 

Chain wifi (1 references)
target     prot opt source               destination 

 

iptables命令能够参考 https://blog.csdn.net/l1028386804/article/details/47356011

https://blog.csdn.net/reyleon/article/details/12976341