0

我有一个带有 4 个网关节点的 25 个(即将超过 100 个)节点 (OpenWRT) 即席 L2 网状网络 (batman-adv)。每个节点都有一个 5ghz 和一个 2.4ghz 的无线接口。2.4 用于客户端访问,mesh 在 5ghz 接口上运行。

所有节点几乎相同(软件)。GW 对网格的广播 Internet(以太网)访问和几个节点有一个 LAN 端口(以太网)连接到为网格提供 DHCP、SQL 和 http 服务的单个服务器。根据设计,对于哪些节点物理提供对 Internet 或 DCHP/SQL/HTTP 服务器的访问没有限制(即,协同定位不是设计选项)。

目前无线客户端可以不受限制地访问GW的WAN口。该项目的下一阶段是根据托管在 LAN 服务器上的帐户信息(即数据库中的帐户信息)来限制对 WAN 接口的访问。

我想做的是远程操作 GW 节点的 iptables 以根据来自服务器的信息控制 Internet 访问,但我不确定将 iptables 命令发送到 GW 的最佳方法是什么。我的第一个想法是通过 SSH 执行批处理命令或将命令流式传输到 SSH 客户端。我还可以编写自己的简单 TCP/IP 服务器。可能还有一个 RPC 模型。

鉴于上述情况或我应该考虑的优点和缺点,是否有推荐方法。谢谢你。

编辑: iptables 是否会阻止并发调用,或者用户是否需要序列化使用?

4

2 回答 2

1

我猜你会在所有网关上采用相同的策略。您可能正在集中维护策略。在这种情况下,我只需在某个集中式服务器中以“iptables-restore”格式构建策略,并在每个 GW 上执行一个命令。您可能希望通过安全通道执行此命令。我只会使用 scp+ssh。

这与您的建议完全相同,只是用新表替换了整个表。一次添加一个规则可能会在策略中引入一些不一致并可能引入漏洞。此外,在有损环境中保持所有 GW 同步可能会很棘手。因此,对 iptables-restore 的压力很大。

于 2013-09-27T06:23:44.570 回答
0

最后,我决定在 OpenWRT 下使用 php-ssh2、php-mysqli 以及 iptables 和 ipset 的组合(包 ipset、iptables-mod-ipset 和 kmod-ipt-ipset)。

我创建了一个 ipset 来包含允许完全互联网访问的 ip 地址:

ipset create ip_whitelist hash:ip

在 OpenWRT 上,如果源 IP 不在 ip_whitelist 中,我设置 iptables 以将任何流量 http 流量重定向到我的 Internet 服务器:

iptables --table nat --new prerouting_mychain
iptables --table nat --insert PREROUTING -j prerouting_mychain
iptables --table nat --append prerouting_mychain --match set --match-set ip_whitelist src -j RETURN
iptables --table nat --append prerouting_mychain --dport 80 -j DNAT --to-destination <internal http server>

现在这将处理不在 ipset 中的 IP 的重定向,但我们仍然需要阻止其他未经授权的流量。这是在 FORWARD 链的过滤表中完成的:

iptables --table filter --new forward_mychain
iptables --table filter --insert FORWARD -j forward_mychain
iptables --table filter --insert forward_mychain -j DROP (this ends up being the last rule)
iptables --table filter --insert forward_mychain --match set --match-set ip_whitelist src -j ACCEPT

就我而言,我希望始终允许 DNS,这样浏览器就不会在查找时冻结:

iptables --table filter --insert forward_mychain -p udp --dport 53 -j ACCEPT
iptables --table filter --insert forward_mychain -p udp --sport 53 -j ACCEPT

现在 FORWARD 链设置为仅允许来自 ipset 和 DNS 流量中的 IP 的流量。

被授权的 IP 被添加到 ipset:

ipset add ip_whitelist 10.10.10.10

并删除:

ipset del ip_whitelist 10.10.10.10

在我的情况下,我简单地在条目上设置一个到期时间,以强制重定向回内部站点以进行数据库检查:

ipset add ip_whitelist 10.10.10.10 timeout 3600(秒)

当您第一次创建 ipset 时,也可以将此超时设置为默认值。

剩下要做的就是为重定向的 http 流量创建一个 php 页面,查询数据库并向网关路由器发送 ipset 命令。

首先是我的简单数据库:

CREATE DATABASE Testdb; 
CREATE TABLE `Clients` (   
`IP` varchar(15) DEFAULT NULL,   
`Created` datetime DEFAULT NULL,   
`Expiry` datetime DEFAULT NULL,   
`LastAccess` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,   UNIQUE KEY `ipidx` (`IP`) 
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

和redirect.php:

<!DOCTYPE html >
<html>
   <head>
       <title></title>
   </head>
   <body>
       <?php
       $ClientIPAddr = filter_var($_SERVER["REMOTE_ADDR"], FILTER_VALIDATE_IP);
       $sqlQuery = "";
       if (($ClientIPAddr === FALSE) || ($ClientIPAddr === NULL)) {
           echo "Failed to obtain client IP [" . $_SERVER["REMOTE_ADDR"] . "]";
           exit;
       }

数据库查询/插入:

       $mysqli = new mysqli("<database ip>", "dbuser", "dbpass", "Testdb");
       if ($mysqli->connect_errno) {
           echo "Failed to connect to MySQL: " . $mysqli->connect_error;
           exit;
       } else {
           echo "Connected (" . $mysqli->server_info . ")<br />";
       }
       echo "Now for a query on " . $ClientIPAddr . ":<br />";
       // Check to see if IP is in database and has not expired
       $sqlQuery = "SELECT IP, TIMEDIFF(Expiry, now()) from Clients where IP=\"" . $ClientIPAddr . "\";";
       if (($result = $mysqli->query($sqlQuery)) === FALSE) {
           echo "Query Error (" . $mysqli->error . ") on (" . $sqlQuery . ")<br />";
           exit;
       } else {
           echo "<h2>Query Result(" . $result->num_rows . "):</h2>";
           echo "<table>";
           while (($row = $result->fetch_array(MYSQLI_NUM)) !== NULL) {
               echo "<tr>";
               foreach ($row as $value) {
                   echo "<td>" . $value . "</td>";
               }
               echo "</tr>";
           }
           echo "</table>";
           echo "<h1>Welcome to my Test Page</h1>";
           if ($result->num_rows === 0) {
               echo "<p>New(" . $result->num_rows . "): " . $ClientIPAddr . "</p>";
               echo "<p>You now have full Internet access.</p>";
           } else {
               echo "<p>Returning(" . $result->num_rows . "): " . $ClientIPAddr . "</p>";
               echo "<p>Your Internet access has been extended.</p>";
           }
           $result->free();
           $sqlQuery = "INSERT INTO Clients (IP, Created, Expiry) VALUES (\"" . $ClientIPAddr . "\", now(),
               timestampadd(hour, 24, now())) ON DUPLICATE KEY UPDATE Expiry=timestampadd(hour, 24, now());";
           if (($result = $mysqli->query($sqlQuery)) === FALSE) {
               echo "Query Error (" . $mysqli->error . ") on (" . $sqlQuery . ")<br />";
               exit;
           } else {

给ipset添加IP授权上网:

               // **************** UPDATE IPSET *****************
               //ssh2_connect, ssh2_fingerprint, ssh2_auth_pubkey_file, ssh2_auth_password, ssh2_exec
               if (($sshConnect = ssh2_connect("<GW IP>")) === FALSE) {
                   $err = error_get_last();
                   echo $err["message"];
                   exit;
               }
               if (ssh2_auth_pubkey_file($sshConnect, "root", "/usr/share/nginx/rsa.pub", "/usr/share/nginx/rsa") === FALSE) {
                   $err = error_get_last();
                   echo $err["message"];
                   exit;
               }
               if (($stream = ssh2_exec($sshConnect, "ipset add ip_whitelist " . $ClientIPAddr)) === FALSE) {
                   $err = error_get_last();
                   echo $err["message"];
                   exit;
               }
               $stderr_stream = ssh2_fetch_stream($stream, SSH2_STREAM_STDERR);
               if ((stream_set_blocking($stream, true) === FALSE) ||
                       (stream_set_blocking($stderr_stream, true) === FALSE)) {
                   $err = error_get_last();
                   echo $err["message"];
                   exit;
               }
               if (($resultStr = stream_get_contents($stream)) === FALSE) {
                   $err = error_get_last();
                   echo $err["message"];
                   exit;
               }
               if ($resultStr === "") { // Likely an error
                   if (($resultStr = stream_get_contents($stderr_stream)) === FALSE) {
                       $err = error_get_last();
                       echo $err["message"];
                       exit;
                   }
               }
               echo "<pre>" . $resultStr . "</pre>";
           }
       }
       $mysqli->close();
       ?>
   </body>
</html>

如您所见,这非常粗糙。但是它确实满足了我需要的所有功能。我希望这对其他人有用。

于 2013-10-26T04:11:33.763 回答