Domanda

I have a 25 (soon to be 100+) node (OpenWRT) adhoc L2 mesh network (batman-adv) with 4 gateway nodes. Each node has a 5ghz and a 2.4ghz wireless interface. The 2.4 is for client access and the mesh operates on the 5ghz interface.

All nodes are near identical (software). The GW's broadcast Internet (Ethernet) access to the mesh and a couple nodes have a LAN port (Ethernet) connected to a single server providing DHCP, SQL and http services to the mesh. By design, there is no restriction as to which nodes physically provide access to the Internet or to the DCHP / SQL / HTTP server (i.e. co-location is not a design option).

Currently wireless clients have access the WAN ports of the GW's without restriction. The next phase of the project is to restrict access to the WAN interface based on account information hosted on the LAN server (i.e. account information in the database).

What I would like to do is remotely manipulate the iptables of GW nodes to control Internet access based on information from the servers but I am not sure what is the best method to get iptables commands to the GWs. My first thought was to do batch commands via SSH or stream commands to the SSH client. I could also write my own simple TCP/IP server. Likely there is a RPC model as well.

Is there a recommend method given the above or Pros and Cons I should consider. Thank you.

EDIT: Does iptables block on concurrent calls or is it necessary for the user to serialize use?

È stato utile?

Soluzione 2

In the end I settled on using the php-ssh2, php-mysqli and a combination of iptables and ipset under OpenWRT (packages ipset, iptables-mod-ipset and kmod-ipt-ipset).

I created a ipset to contain the ip addresses that are allowed full internet access:

ipset create ip_whitelist hash:ip

On OpenWRT I set up the iptables to redirect any traffic http traffic to my internet server if the source IP is not in ip_whitelist:

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>

Now this will handle the redirection for IPs not in the ipset, but we still need to block other unauthorized traffic. This is done in the filter table of the FORWARD chain:

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

In my case I want to always allow DNS so the browser doesn't freeze on a lookup:

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

Now the FORWARD chain is setup to only allow traffic from IPs in the ipset and DNS traffic.

IPs that are authorized are added to the ipset:

ipset add ip_whitelist 10.10.10.10

and removed with:

ipset del ip_whitelist 10.10.10.10

In my case I simple set an expiry on the entry to force a redirect back to the internal site for a database check:

ipset add ip_whitelist 10.10.10.10 timeout 3600 (seconds)

This timeout can also be set as a default when you first create the ipset.

All that is left to do is create a php page for the redirected http traffic, query the database and send ipset commands to the gateway routers.

First my simple database:

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;

And the 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;
       }

Database query / insert:

       $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 {

Add IP to ipset to authorize Internet access:

               // **************** 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>

As you can see this is very rough. However it does hit all the functions I need. I hope this proved useful to someone else.

Altri suggerimenti

I am guessing that you would have the same policy on all the gateways. You are probably maintaining the policy centrally. In that case, I would just build the policy in "iptables-restore" format in some centralized server and execute one single command on each GW. You probably want to execute this command over secure channel. I would just use scp+ssh.

This is exactly similar to what you suggested except for replacing the full table with a new table. Adding one rule at a time could introduce some inconsistency in the policy and may introduce holes.Also, keeping all GW in synch could be tricky in lossy environment. Hence the stress on iptables-restore.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top