【Android】(三) iptables 详解

iptables 源码分析 - 内核源码-Chinaunix

【iptables交流贴】iptables执行的流程分析 - 内核源码-Chinaunix

iptables 命令

规则 rules

用于定义 IP 数据包的识别和处理规则,每条 Rule 都包含了 “匹配" 和 “动作" 这 2 个元素。其中,动作包括有:修改或跳转。跳转可以用于处理接受该数据包、拒绝该数据包,也可以跳转到其他 Chain 中继续进行匹配,或者从当前 Chain 中返回到调用者 Chain。

每条 Rule 都由下列 2 个元素组成:

  1. 若干个匹配条件(Xmatch):与 IP 数据包进行匹配,具有以下匹配条件类型:
    1. **Interface(接口,**e.g. eth0、eth1)
    2. Protocol(协议类型,e.g. ICMPTCPUDP
    3. Source IP / Destination IP
    4. Source Port / Destination Port
  2. 一个执行动作(Action):数据包匹配所有条件后所需要执行的动作。具有以下动作类型:
    1. ACCEPT:运行通过。
    2. DROP:直接丢弃。
    3. REJECT:拒绝通过。
    4. SNAT:源地址转换。
    5. DNAT:目标地址转换。
    6. LOG:记录日志信息。
    7. QUEUE:将数据包移交到用户空间。
    8. RETURN:防火墙停止执行当前链中的后续规则,并返回到调用链。
    9. REDIRECT:端口重定向。
    10. iptablMARK:做防火墙标记。

链 chains

链(Chains)的本质是一个有序的 Rules 列表。在复杂的网络环境中,用户可以通过配置 Rules 在 Chain 中的顺序来灵活实现多种效果。

也因为 Chain 中 Rules 的次序非常关键,执行 Rules 时,会按照从上往下的顺序进行。所以越严格的 Rule,就越应该放在越靠前,而 Default Rule 则总是在最后生效。

此外,Netfilter 提供了 5 条内建的 Chains,用户也可以新建自定义的 Chains。

  1. INPUT:发往本机的数据包通过此链,并执行此链上关于 INPUT 的 Rules,例如:DDoS 攻击防御规则。
  2. OUTPUT:从本机发出的数据包通过此链,并执行此链上关于 OUTPUT 的 Rules。
  3. PORWARD:由本机转发的数据包通过此链,并执行此链上关于 PORWARD 的 Rules。例如:作为 IP 路由器。
  4. PREROUTING:在处理 IP 路由规则前(Pre-Routing)通过此链,并执行此链上关于 Pre-Routing 的 Rules。例如:DNAT。
  5. POSTOUTING:在处理 IP 路由规则后(Post-Routing)通过此链,并执行此链上关于 Post-Routing 的 Rules。例如:SNAT。

表 tables

表(Table)是面向应用场景的管理方式,每张表被赋予了不同的应用场景,所以也内含了不同的 Chains 和 Rules。

用户在实际使用 Netfilter 时,往往是通过 Table 作为操作入口,然后对 Chains 和 Rules 进行定义。

Netfilter 内建了以下五张表:

  1. filter 表(默认):提供数据包的过滤功能,例如:用于防火墙规则。
  2. nat :提供了 NAT、NAPT 功能,例如:用于网关路由器。
  3. mangle 表:提供了数据包修改功能,例如:更改 IP Header 的 TOS、DSCP、ECN 位。
  4. raw :用来提前标记报文不需要执行一些流程,例如:不需要建立会话。
Usage: iptables -[ACD] chain rule-specification [options]
       iptables -I chain [rulenum] rule-specification [options]
       iptables -R chain rulenum rule-specification [options]
       iptables -D chain rulenum [options]
       iptables -[LS] [chain [rulenum]] [options]
       iptables -[FZ] [chain] [options]
       iptables -[NX] chain
       iptables -E old-chain-name new-chain-name
       iptables -P chain target [options]
       iptables -h (print this help information)

Commands:
Either long or short options are allowed.
  --append  -A chain    Append to chain
  --check   -C chain    Check for the existence of a rule
  --delete  -D chain    Delete matching rule from chain
  --delete  -D chain rulenum
        Delete rule rulenum (1 = first) from chain
  --insert  -I chain [rulenum]
        Insert in chain as rulenum (default 1=first)
  --replace -R chain rulenum
        Replace rule rulenum (1 = first) in chain
  --list    -L [chain [rulenum]]
        List the rules in a chain or all chains
  --list-rules -S [chain [rulenum]]
        Print the rules in a chain or all chains
  --flush   -F [chain]    Delete all rules in  chain or all chains
  --zero    -Z [chain [rulenum]]
        Zero counters in chain or all chains
  --new     -N chain    Create a new user-defined chain
  --delete-chain
            -X [chain]    Delete a user-defined chain
  --policy  -P chain target
        Change policy on chain to target
  --rename-chain
            -E old-chain new-chain
        Change chain name, (moving any references)
Options:
    --ipv4  -4    Nothing (line is ignored by ip6tables-restore)
    --ipv6  -6    Error (line is ignored by iptables-restore)
[!] --protocol  -p proto  protocol: by number or name, eg. tcp'
[!] --source  -s address[/mask][...]
        source specification
[!] --destination -d address[/mask][...]
        destination specification
[!] --in-interface -i input name[+]
        network interface name ([+] for wildcard)
 --jump -j target
        target for rule (may load target extension)
  --goto      -g chain
                              jump to chain with no return
  --match -m match
        extended match (may load extension)
  --numeric -n    numeric output of addresses and ports
[!] --out-interface -o output name[+]
        network interface name ([+] for wildcard)
  --table -t table  table to manipulate (default: filter')
  --verbose -v    verbose mode
  --wait  -w [seconds]  maximum wait to acquire xtables lock before give up
  --wait-interval -W [usecs]  wait time to try to acquire xtables lock
        default is 1 second
  --line-numbers    print line numbers when listing
  --exact -x    expand numbers (display exact values)
[!] --fragment  -f    match second or further fragments only
  --modprobe=<command>    try to insert modules using this command
  --set-counters PKTS BYTES set the counter during insert/append
[!] --version -V    print package version.

MARK target options:
  --set-xmark value[/mask]  Clear bits in mask and XOR value into nfmark
  --set-mark value[/mask]   Clear bits in mask and OR value into nfmark
  --and-mark bits           Binary AND the nfmark with bits
  --or-mark bits            Binary OR the nfmark with bits
  --xor-mark bits           Binary XOR the nfmark with bits
iptables -N 1000

iptables -I OUTPUT -p tcp --dport 47000:48000 -j 1000

iptables -F 1000

iptables -A 1000 -m owner --uid-owner 1000 -j ACCEPT

iptables -A 1000 -j REJECT

external/iptables

iptables_main -> do_command4 -> iptc_commit

Netd

IptablesRestoreController

forkAndExec
//system/netd/server/IptablesRestoreController.cpp
IptablesProcess* IptablesRestoreController::forkAndExec(const IptablesProcessType type) {
    const char* const cmd = (type == IPTABLES_PROCESS) ?
        IPTABLES_RESTORE_PATH : IP6TABLES_RESTORE_PATH;

    // Create the pipes we'll use for communication with the child
    // process. One each for the child's in, out and err files.
    int stdin_pipe[2];
    int stdout_pipe[2];
    int stderr_pipe[2];

    if (pipe2(stdin_pipe,  O_CLOEXEC) == -1 ||
        pipe2(stdout_pipe, O_NONBLOCK | O_CLOEXEC) == -1 ||
        pipe2(stderr_pipe, O_NONBLOCK | O_CLOEXEC) == -1) {

        ALOGE("pipe2() failed: %s", strerror(errno));
        return nullptr;
    }

    const auto& sys = sSyscalls.get();
    StatusOr<pid_t> child_pid = sys.fork();
    if (!isOk(child_pid)) {
        ALOGE("fork() failed: %s", strerror(child_pid.status().code()));
        return nullptr;
    }

    if (child_pid.value() == 0) {
        // The child process. Reads from stdin, writes to stderr and stdout.

        // stdin_pipe[0] : The read end of the stdin pipe.
        // stdout_pipe[1] : The write end of the stdout pipe.
        // stderr_pipe[1] : The write end of the stderr pipe.
        if (dup2(stdin_pipe[0], 0) == -1 ||
            dup2(stdout_pipe[1], 1) == -1 ||
            dup2(stderr_pipe[1], 2) == -1) {
            ALOGE("dup2() failed: %s", strerror(errno));
            abort();
        }

        if (execl(cmd,
                  cmd,
                  "--noflush",  // Don't flush the whole table.
                  "-w",         // Wait instead of failing if the lock is held.
                  "-v",         // Verbose mode, to make sure our ping is echoed
                                // back to us.
                  nullptr) == -1) {
            ALOGE("execl(%s, ...) failed: %s", cmd, strerror(errno));
            abort();
        }

        // This statement is unreachable. We abort() upon error, and execl
        // if everything goes well.
        return nullptr;
    }

    // The parent process. Writes to stdout and stderr and reads from stdin.
    // stdin_pipe[0] : The read end of the stdin pipe.
    // stdout_pipe[1] : The write end of the stdout pipe.
    // stderr_pipe[1] : The write end of the stderr pipe.
    if (close(stdin_pipe[0]) == -1 ||
        close(stdout_pipe[1]) == -1 ||
        close(stderr_pipe[1]) == -1) {
        ALOGW("close() failed: %s", strerror(errno));
    }

    return new IptablesProcess(child_pid.value(), stdin_pipe[1], stdout_pipe[0], stderr_pipe[0]);
}
  1. 根据IptablesProcessType的值确定要执行的命令:iptables-restore 或者 ip6tables-restore
  2. 使用fork函数创建子进程。通过 stdin_pipe、stdout_pipe 和 stderr_pipe 进行父子进程的通信。
  3. 在子进程中,通过dup2函数将标准输入重定向到 stdin_pipe 读端,标准输出和标准错误重定向到 stdout_pipe 和 stderr_pipe 写端。然后使用execl函数执行iptables-restore命令。
  4. 在父进程中,关闭 stdin_pipe 的读端,关闭 stdout_pipe 和 stderr_pipe 的写端。
  5. 最后,创建一个IptablesProcess对象,IptablesProcess对象封装了子进程的PID以及与子进程通信的管道的文件描述符。

execIptablesRestoreWithOutput

//system/netd/server/NetdConstants.cpp
int execIptablesRestoreWithOutput(IptablesTarget target, const std::string& commands,
                                  std::string *output) {
    return android::net::gCtls->iptablesRestoreCtrl.execute(target, commands, output);
}
//system/netd/server/IptablesRestoreController.cpp
int IptablesRestoreController::execute(const IptablesTarget target, const std::string& command,
                                       std::string *output) {
    std::lock_guard lock(mLock);

    std::string buffer;
    if (output == nullptr) {
        output = &buffer;
    } else {
        output->clear();
    }

    int res = 0;
    if (target == V4 || target == V4V6) {
        res |= sendCommand(IPTABLES_PROCESS, command, output);
    }
    if (target == V6 || target == V4V6) {
        res |= sendCommand(IP6TABLES_PROCESS, command, output);
    }
    return res;
}

// TODO: Return -errno on failure instead of -1.
// TODO: Maybe we should keep a rotating buffer of the last N commands
// so that they can be dumped on dumpsys.
int IptablesRestoreController::sendCommand(const IptablesProcessType type,
                                           const std::string& command,
                                           std::string *output) {
   std::unique_ptr<IptablesProcess> *process =
           (type == IPTABLES_PROCESS) ? &mIpRestore : &mIp6Restore;

    // We might need to fork a new process if we haven't forked one yet, or
    // if the forked process terminated.
    //
    // NOTE: For a given command, this is the last point at which we try to
    // recover from a child death. If the child dies at some later point during
    // the execution of this method, we will receive an EPIPE and return an
    // error. The command will then need to be retried at a higher level.
    IptablesProcess *existingProcess = process->get();
    if (existingProcess != nullptr && !existingProcess->outputReady()) {
        existingProcess->stop();
        existingProcess = nullptr;
    }

    if (existingProcess == nullptr) {
        // Fork a new iptables[6]-restore process.
        IptablesProcess *newProcess = IptablesRestoreController::forkAndExec(type);
        if (newProcess == nullptr) {
            LOG(ERROR) << "Unable to fork ip[6]tables-restore, type: " << type;
            return -1;
        }

        process->reset(newProcess);
    }

    if (!android::base::WriteFully((*process)->stdIn, command.data(), command.length())) {
        ALOGE("Unable to send command: %s", strerror(errno));
        return -1;
    }

    if (!android::base::WriteFully((*process)->stdIn, PING, PING_SIZE)) {
        ALOGE("Unable to send ping command: %s", strerror(errno));
        return -1;
    }

    if (!drainAndWaitForAck(*process, command, output)) {
        // drainAndWaitForAck has already logged an error.
        return -1;
    }

    return 0;
}

根据target参数的值,判断要执行的命令类型。如果target是V4或V4V6,则调用sendCommand函数执行iptables-restore命令,并将结果存储在output中。如果target是V6或V4V6,则调用sendCommand函数执行ip6tables-restore命令,并将结果存储在output中。

在IptablesRestoreController类中的sendCommand函数中,执行了具体的命令发送和处理逻辑:

  1. 首先,根据type参数的值,确定要执行的命令类型。如果type是IPTABLES_PROCESS,则使用mIpRestore指针,否则使用mIp6Restore指针。
  2. 检查当前已经存在的进程。如果存在进程且输出结果未准备好,则停止当前进程。如果当前进程为空,则通过调用forkAndExec函数创建一个新的进程,并将其赋值给相应的指针。
  3. 使用WriteFully函数将命令数据写入到进程的标准输入中。
  4. 使用WriteFully函数将一个特殊的"PING"命令写入到进程的标准输入中,用于标识命令的结束。
  5. 调用drainAndWaitForAck函数来读取进程的输出,并等待命令的确认。

FirewallController

binder::Status NetdNativeService::firewallSetFirewallType(int32_t firewallType)
binder::Status NetdNativeService::firewallSetInterfaceRule(const std::string& ifName,
                                                           int32_t firewallRule)
binder::Status NetdNativeService::firewallEnableChildChain(int32_t childChain, bool enable)
binder::Status NetdNativeService::firewallSetUidRule(int32_t childChain, int32_t uid,
                                                     int32_t firewallRule)
binder::Status NetdNativeService::firewallReplaceUidChain(const std::string& chainName,
                                                          bool isAllowlist,
                                                          const std::vector<int32_t>& uids,
                                                          bool* ret)                                                    

TetherController

binder::Status NetdNativeService::tetherAddForward(const std::string& intIface,
                                                   const std::string& extIface)
binder::Status NetdNativeService::tetherRemoveForward(const std::string& intIface,
                                                      const std::string& extIface)
** binder::Status NetdNativeService::tetherGetStats(
        std::vector<TetherStatsParcel>* tetherStatsParcelVec)

BandwidthController

binder::Status NetdNativeService::bandwidthEnableDataSaver(bool enable, bool *ret)
** binder::Status NetdNativeService::bandwidthSetInterfaceQuota(const std::string& ifName,
                                                             int64_t bytes)
** binder::Status NetdNativeService::bandwidthRemoveInterfaceQuota(const std::string& ifName)
binder::Status NetdNativeService::bandwidthSetGlobalAlert(int64_t bytes)
binder::Status NetdNativeService::bandwidthSetInterfaceAlert(const std::string& ifName,
                                                             int64_t bytes)
binder::Status NetdNativeService::bandwidthRemoveInterfaceAlert(const std::string& ifName)
"bw_happy_box",
"bw_penalty_box",
"bw_data_saver",
"bw_costly_shared",
  • bw_happy_box:可能用于处理和限制优先级较高的数据流,例如优先处理实时视频、音频等流量。
  • bw_penalty_box:可能用于处理和限制优先级较低的数据流,例如限制下载、非关键任务等流量。
  • bw_data_saver:可能用于实现数据节省功能,例如启用数据压缩、禁止某些应用程序的后台数据传输等。
  • bw_costly_shared:可能用于处理和限制消耗资源较多的共享连接,例如限制对公共Wi-Fi网络的带宽使用。

IdletimerController

binder::Status NetdNativeService::idletimerAddInterface(const std::string& ifName, int32_t timeout,
                                                        const std::string& classLabel)
binder::Status NetdNativeService::idletimerRemoveInterface(const std::string& ifName,
                                                           int32_t timeout,
                                                           const std::string& classLabel)

StrictController

binder::Status NetdNativeService::strictUidCleartextPenalty(int32_t uid, int32_t policyPenalty)
const char* StrictController::LOCAL_CLEAR_DETECT = "st_clear_detect";
const char* StrictController::LOCAL_CLEAR_CAUGHT = "st_clear_caught";
const char* StrictController::LOCAL_PENALTY_LOG = "st_penalty_log";
const char* StrictController::LOCAL_PENALTY_REJECT = "st_penalty_reject";
  1. st_clear_detect:可能用于检测和清除违规行为或异常流量的链。它可能包含规则来检测和标记违规的数据流量。
  2. st_clear_caught:可能用于处理和记录已被捕获的违规行为的链。它可能包含规则来记录违规行为、生成日志或发送警报。
  3. st_penalty_log:可能用于记录违规行为处罚结果的链。它可能包含规则来记录处罚行为、生成日志或发送警报。
  4. st_penalty_reject:可能用于拒绝违规行为的链。它可能包含规则来拒绝违规的数据流量,例如通过使用rejectdrop指令。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值