#!/usr/bin/php array(), 'tunnel' => array(), 'vlan' => array(), 'hlsw' => array(), 'forward' => array() ); } function save($arr) { if ($arr == self::getEmpty()) return; if (!$fp = @fopen(OLDFILE, "w")) { echo "write error\n"; return; } fwrite($fp, @serialize($arr)); fclose($fp); } } class BaseTables { function init($bridge) { // bridge present? if (!is_dir("/sys/class/net/br0")) { // create bridge Cmd::brctl("addbr br0"); } else { // delete all subdevices $dir = opendir("/sys/class/net/br0/brif"); while ($dir !== false && (($file = readdir($dir)) !== false)) { if ($file == "." || $file == "..") continue; Cmd::brctl("delif br0 {$file}"); } closedir($dir); } // bridge init Cmd::brctl("setfd 2"); Cmd::ifconfig("br0 0.0.0.0 up"); // delete all rules & chains Cmd::ebtables("-t nat -F"); Cmd::ebtables("-t nat -X"); Cmd::ebtables("-t filter -F"); Cmd::ebtables("-t filter -X"); Cmd::ebtables("-t nat -N loop_pkts"); Cmd::ebtables("-t nat -A loop_pkts -j DROP"); // TODO: tap_block wird pakete * interfaces durchlaufen Cmd::ebtables("-t filter -N tap_block"); Cmd::ebtables("-t filter -A tap_block -j RETURN"); // eigene pakete empfangen -> loop Cmd::ebtables("-t nat -A PREROUTING -s {$bridge['macmask']} -j loop_pkts"); } function add_tunnel($tunnels) { foreach ($tunnels as $tunnel) { // pakete durch tunnel von anderer Bridge -> ok // pakete von anderen Bridges auf normalen ports -> loop Cmd::ebtables("-t nat -A PREROUTING -i {$tunnel['device']} -j ACCEPT"); Cmd::ebtables("-t nat -A PREROUTING -s {$tunnel['macmask']} -j loop_pkts"); // wenns ausm tunnel kommt, nicht in nen tunnel stecken Cmd::ebtables("-t filter -A FORWARD -i {$tunnel['device']} -j tap_block"); Cmd::ebtables("-t filter -I tap_block 1 -o {$tunnel['device']} -j DROP"); } // ansonsten alles in den tunnel packen foreach ($tunnels as $tunnel) Cmd::ebtables("-t filter -A FORWARD -o {$tunnel['device']} -j ACCEPT"); } } class ForwardTables { function init() { // delete all vlan devices $dir = opendir("/sys/class/net/"); while ($dir !== false && (($file = readdir($dir)) !== false)) { if ($file == "." || $file == "..") continue; if (strncmp($file, "eth", 3)) continue; $part = explode('.', $file); if (!isset($part[1])) continue; $address = Cmd::ip("-4 addr show dev ${file}"); if (count($address)) continue; Cmd::ifconfig("{$file} down"); Cmd::vconfig("rem {$file}"); } closedir($dir); Cmd::ebtables("-t nat -N hlswmaster"); Cmd::ebtables("-t nat -A hlswmaster -j DROP"); Cmd::ebtables("-t nat -N arp_pkts"); Cmd::ebtables("-t nat -A arp_pkts -j DROP"); Cmd::ebtables("-t nat -N nonip_pkts"); Cmd::ebtables("-t nat -A nonip_pkts -j DROP"); Cmd::ebtables("-t nat -N udp_blocked"); Cmd::ebtables("-t nat -A udp_blocked -j DROP"); Cmd::ebtables("-t nat -N udp_forwarded"); Cmd::ebtables("-t nat -A udp_forwarded -j ACCEPT"); Cmd::ebtables("-t nat -N fwdfilter"); Cmd::ebtables("-t nat -A fwdfilter -j udp_blocked"); Cmd::ebtables("-t filter -N hlswmaster"); Cmd::ebtables("-t filter -A hlswmaster -j DROP"); // allow hlsw-master-querys from all clients // but forward them only to our masters Cmd::ebtables("-t nat -A PREROUTING -p ipv4 --ip-proto udp --ip-dport 7140 -j ACCEPT"); Cmd::ebtables("-t filter -A FORWARD -p ipv4 --ip-proto udp --ip-dport 7140 -j hlswmaster"); // only allow hlsw-querys from master servers Cmd::ebtables("-t nat -A PREROUTING -p ipv4 --ip-proto udp --ip-sport 7130:7139 -j hlswmaster"); // udp broadcast und multicast filtern Cmd::ebtables("-t nat -A PREROUTING -p ipv4 --ip-proto udp --ip-dst 255.255.255.255 -j fwdfilter"); Cmd::ebtables("-t nat -A PREROUTING -p ipv4 --ip-proto udp --ip-dst 224.0.0.0/8 -j fwdfilter"); // udp unicast (falsche netmask beim client) blocken Cmd::ebtables("-t nat -A PREROUTING -p ipv4 --ip-proto udp -j udp_blocked"); // andere protos fuer stats auseinandernehmen Cmd::ebtables("-t nat -A PREROUTING -p arp -j arp_pkts"); Cmd::ebtables("-t nat -A PREROUTING -j nonip_pkts"); } private function add_hlsw($hlsw) { Cmd::ebtables("-t nat -I hlswmaster 1 -p ipv4 --ip-src {$hlsw['ip']} -j ACCEPT"); Cmd::ebtables("-t filter -I hlswmaster 1 -o {$hlsw['dev']} -j ACCEPT"); } private function del_hlsw($hlsw) { Cmd::ebtables("-t nat -D hlswmaster -p ipv4 --ip-src {$hlsw['ip']} -j ACCEPT"); Cmd::ebtables("-t filter -D hlswmaster -o {$hlsw['dev']} -j ACCEPT"); } private function add_vlan($vlan) { // vlan 0 and vlan 1 are invalid -> use basename (eth1.1 -> eth1) $part = explode('.', $vlan['name']); if (isset($part[1]) && ($part[1] >= 2)) { Cmd::ifconfig("{$part[0]} 0.0.0.0 up"); Cmd::vconfig("add {$part[0]} {$part[1]}"); } // add device to bridge Cmd::ifconfig("{$vlan['name']} 0.0.0.0 up"); Cmd::brctl("addif br0 {$vlan['name']}"); // TODO: bei echten devices kann qdisc noch vorhanden sein Cmd::tc("qdisc add dev {$vlan['name']} root tbf rate {$vlan['bw']}kbit burst 4k latency 100ms"); // stats Cmd::ebtables("-t nat -I loop_pkts 1 -i {$vlan['name']} -j DROP"); Cmd::ebtables("-t nat -I arp_pkts 1 -i {$vlan['name']} -j DROP"); Cmd::ebtables("-t nat -I nonip_pkts 1 -i {$vlan['name']} -j DROP"); Cmd::ebtables("-t nat -I udp_blocked 1 -i {$vlan['name']} -j DROP"); Cmd::ebtables("-t nat -I udp_forwarded 1 -i {$vlan['name']} -j ACCEPT"); // ausgehende src-mac anpassen fuer loop detection // und damit non-cisco HW funktionieren wuerde :) Cmd::ebtables("-t nat -I POSTROUTING 1 -o {$vlan['name']} -j snat --to-source {$vlan['mac']}"); } private function del_vlan($vlan) { // remove from bridge Cmd::brctl("delif br0 {$vlan['name']}"); Cmd::ifconfig("{$vlan['name']} down"); // remove vlan $part = explode('.', $vlan['name']); if (isset($part[1]) && ($part[1] >= 2)) Cmd::vconfig("rem {$vlan['name']}"); // stats Cmd::ebtables("-t nat -D loop_pkts -i {$vlan['name']} -j DROP"); Cmd::ebtables("-t nat -D arp_pkts -i {$vlan['name']} -j DROP"); Cmd::ebtables("-t nat -D nonip_pkts -i {$vlan['name']} -j DROP"); Cmd::ebtables("-t nat -D udp_blocked -i {$vlan['name']} -j DROP"); Cmd::ebtables("-t nat -D udp_forwarded -i {$vlan['name']} -j ACCEPT"); // snat mac Cmd::ebtables("-t nat -D POSTROUTING -o {$vlan['name']} -j snat --to-source {$vlan['mac']}"); } private function add_fwd($fwd) { $port = $fwd['portlo']; if ($fwd['porthi'] != 0) $port .= ':'.$fwd['porthi']; $target = ($fwd['active'] ? "udp_forwarded" : "udp_blocked"); Cmd::ebtables("-t nat -I fwdfilter 1 -p ipv4 --ip-proto udp --ip-dport {$port} -j {$target}"); } private function del_fwd($fwd) { $port = $fwd['portlo']; if ($fwd['porthi'] != 0) $port .= ':'.$fwd['porthi']; $target = ($fwd['active'] ? "udp_forwarded" : "udp_blocked"); Cmd::ebtables("-t nat -D fwdfilter -p ipv4 --ip-proto udp --ip-dport {$port} -j {$target}"); } function checkDiff($old, $new) { $oldarr = $old['vlan']; $newarr = $new['vlan']; foreach ($newarr as $id => $tmp) { if (!isset($oldarr[$id])) { self::add_vlan($tmp); } else if ($tmp !== $oldarr[$id]) { // TODO: this is slow self::del_vlan($oldarr[$id]); self::add_vlan($tmp); } } foreach ($oldarr as $id => $tmp) { if (!isset($newarr[$id])) self::del_vlan($tmp); } $oldarr = $old['forward']; $newarr = $new['forward']; foreach ($newarr as $id => $tmp) { if (!isset($oldarr[$id])) { self::add_fwd($tmp); } else if ($tmp !== $oldarr[$id]) { self::del_fwd($oldarr[$id]); self::add_fwd($tmp); } } foreach ($oldarr as $id => $tmp) { if (!isset($newarr[$id])) self::del_fwd($tmp); } $oldarr = $old['hlsw']; $newarr = $new['hlsw']; foreach ($newarr as $id => $tmp) { if (!isset($oldarr[$id])) { self::add_hlsw($tmp); } else if ($tmp !== $oldarr[$id]) { self::del_hlsw($oldarr[$id]); self::add_hlsw($tmp); } } foreach ($oldarr as $id => $tmp) { if (!isset($newarr[$id])) self::del_hlsw($tmp); } } } class Vtund { function getBaseConfig() { return " options { type stand; port 5000; timeout 60; syslog daemon; ifconfig /sbin/ifconfig; } default { type ether; proto udp; timeout 60; compress no; encrypt no; keepalive yes; stat no; speed 0; multi kill; up { ifconfig \"%d 0.0.0.0 up\"; program /usr/sbin/brctl \"addif br0 %d\"; }; down { program /usr/sbin/brctl \"delif br0 %d\"; ifconfig \"%d down\"; }; } "; } function getSession($tunnel) { return " {$tunnel['name']} { # partner {$tunnel['dest']} # our mode: {$tunnel['mode']} device {$tunnel['device']}; password whiterabbit; } "; } function buildConfig($arr) { if (!$fp = @fopen("/etc/vtund.conf", "w")) { echo "could not write vtund config"; return; } // basic config fwrite($fp, self::getBaseConfig()); // add sessions foreach ($arr as $tunnel) fwrite($fp, self::getSession($tunnel)); fclose($fp); } function init($arr) { self::buildConfig($arr); // check if vtund server runs $srv_running = false; if (file_exists("/var/run/vtund.pid")) { $pid = file_get_contents("/var/run/vtund.pid"); // send SIGHUP, to reread the config $srv_running = posix_kill($pid, 1); } // start the server if (!$srv_running) Cmd::vtund("-f /etc/vtund.conf -s"); // kill all clients (_our_ clients, not server childs!) $dir = opendir("/var/run/"); while ($dir !== false && (($file = readdir($dir)) !== false)) { if ($file == "." || $file == "..") continue; if (strncmp($file, "vtund.tap", 9)) continue; // send SIGTERM to client vtund $pid = file_get_contents("/var/run/".$file); posix_kill($pid, 15); } closedir($dir); // start all clients foreach ($arr as $tunnel) { if ($tunnel['mode'] == "client") Cmd::vtund("-f /etc/vtund.conf -p {$tunnel['name']} {$tunnel['dest']}"); } } } class Stats { private $sock; private $files = array(); private function write($msg) { socket_write($this->sock, $msg."\r\n", strlen($msg."\r\n")); } private function read() { while (true) { $fdset = array($this->sock); $sel = socket_select($fdset, $write = NULL, $except = NULL, 0, 100000); if ($sel == 0 || $sel === false) return false; return socket_read($this->sock, 1024, PHP_NORMAL_READ); } } private function waitcmd() { $retval = array(); while (($line = $this->read()) !== false && strlen($line) > 0) { if (!strncmp($line, 'OK', 2)) break; if (!strncmp($line, 'ERROR', 2)) { echo $line; break; } $retval[] = $line; } return $retval; } function connect($id) { $this->sock = socket_create(AF_INET, SOCK_STREAM, SOL_TCP); socket_connect($this->sock, RRDSRV, 5001); self::write('ls'); $list = self::waitcmd(); foreach ($list as $num => $name) $list[$num] = substr(trim($name), 2, 32); $dirname = 'bridge-'.$id; if (!in_array($dirname, $list)) { self::write('mkdir '. $dirname); self::waitcmd(); } self::write('cd '. $dirname); self::waitcmd(); self::write('ls'); $list = self::waitcmd(); foreach ($list as $num => $name) $list[$num] = substr(trim($name), 2, 32); $this->files = $list; } function get_vlans() { $result = array(); unset($lines); $lines = Cmd::ebtables('-t nat -L loop_pkts --Lc'); foreach ($lines as $line) { if (strpos($line, '-i') !== false) { $parts = explode(' ', $line); $result[$parts[1]]['loop'] = $parts[11]; } } unset($lines); $lines = Cmd::ebtables('-t nat -L arp_pkts --Lc'); foreach ($lines as $line) { if (strpos($line, '-i') !== false) { $parts = explode(' ', $line); $result[$parts[1]]['arp'] = $parts[11]; } } unset($lines); $lines = Cmd::ebtables('-t nat -L nonip_pkts --Lc'); foreach ($lines as $line) { if (strpos($line, '-i') !== false) { $parts = explode(' ', $line); $result[$parts[1]]['nonip'] = $parts[11]; } } unset($lines); $lines = Cmd::ebtables('-t nat -L udp_blocked --Lc'); foreach ($lines as $line) { if (strpos($line, '-i') !== false) { $parts = explode(' ', $line); $result[$parts[1]]['blocked'] = $parts[11]; } } unset($lines); $lines = Cmd::ebtables('-t nat -L udp_forwarded --Lc'); foreach ($lines as $line) { if (strpos($line, '-i') !== false) { $parts = explode(' ', $line); $result[$parts[1]]['forwarded'] = $parts[11]; } } unset($lines); $lines = Cmd::ebtables('-t nat -L POSTROUTING --Lc'); foreach ($lines as $line) { if (strpos($line, '-o') !== false) { $parts = explode(' ', $line); $result[$parts[1]]['output'] = $parts[14]; } } foreach ($result as $name => $data) { $fullname = "vlan-$name.rrd"; if (!in_array($fullname, $this->files)) { $cmd = "create $fullname --start now -s 60 ". 'DS:loop:DERIVE:60:0:10000000 '. 'DS:arp:DERIVE:60:0:10000000 '. 'DS:nonip:DERIVE:60:0:10000000 '. 'DS:block:DERIVE:60:0:10000000 '. 'DS:fwd:DERIVE:60:0:10000000 '. 'DS:out:DERIVE:60:0:10000000 '. 'RRA:AVERAGE:0.5:1:720 RRA:AVERAGE:0.5:5:576 '. 'RRA:MIN:0.5:1:720 RRA:MIN:0.5:5:576 '. 'RRA:MAX:0.5:1:720 RRA:MAX:0.5:5:576 '; self::write($cmd); self::waitcmd(); } $cmd = "update $fullname N:{$data['loop']}:{$data['arp']}:{$data['nonip']}:{$data['blocked']}:{$data['forwarded']}:{$data['output']}"; self::write($cmd); self::waitcmd(); } } function get_forwards($forwards) { $names = array(); foreach ($forwards as $id => $fwd) { $key = $fwd['portlo']; if ($fwd['porthi'] != 0) $key .= ':'.$fwd['porthi']; $names[$key] = "forward-$id.rrd"; } $result = array(); $lines = Cmd::ebtables('-t nat -L fwdfilter --Lc'); foreach ($lines as $line) { if (strpos($line, '-p') !== false) { $parts = explode(' ', $line); if (!isset($names[$parts[5]])) continue; switch ($parts[7]) { case 'udp_forwarded,': $result[$names[$parts[5]]] = "N:{$parts[14]}:0"; break; case 'udp_blocked,': $result[$names[$parts[5]]] = "N:0:{$parts[14]}"; break; } } } foreach ($result as $fullname => $data) { if (!in_array($fullname, $this->files)) { $cmd = "create $fullname --start now -s 60 ". 'DS:fwd:DERIVE:60:0:10000000 '. 'DS:block:DERIVE:60:0:10000000 '. 'RRA:AVERAGE:0.5:1:720 RRA:AVERAGE:0.5:5:576 '. 'RRA:MIN:0.5:1:720 RRA:MIN:0.5:5:576 '. 'RRA:MAX:0.5:1:720 RRA:MAX:0.5:5:576 '; self::write($cmd); self::waitcmd(); } self::write("update $fullname $data"); self::waitcmd(); } } function get_masters($masters) { $lines_from = Cmd::ebtables('-t nat -L hlswmaster --Lc'); $lines_to = Cmd::ebtables('-t filter -L hlswmaster --Lc'); $result = array(); foreach ($masters as $id => $hlsw) { foreach ($lines_from as $line) { if (strpos($line, $hlsw['ip'])) { $parts = explode(' ', $line); $from = $parts[13]; break; } } foreach ($lines_to as $line) { if (strpos($line, $hlsw['dev'])) { $parts = explode(' ', $line); $to = $parts[11]; break; } } $result['hlsw-'.$id.'.rrd'] = "N:{$from}:{$to}"; } foreach ($result as $fullname => $data) { if (!in_array($fullname, $this->files)) { $cmd = "create $fullname --start now -s 60 ". 'DS:from:DERIVE:60:0:10000000 '. 'DS:to:DERIVE:60:0:10000000 '. 'RRA:AVERAGE:0.5:1:720 RRA:AVERAGE:0.5:5:576 '. 'RRA:MIN:0.5:1:720 RRA:MIN:0.5:5:576 '. 'RRA:MAX:0.5:1:720 RRA:MAX:0.5:5:576 '; self::write($cmd); self::waitcmd(); } self::write("update $fullname $data"); self::waitcmd(); } } function close() { socket_close($this->sock); } } /* teh main */ $init = (isset($_SERVER['argv'][1]) && $_SERVER['argv'][1] == "init"); // get old config, fallback to the empty config $oldarr = Config::getOld(); if ($oldarr === false) $oldarr = Config::getEmpty(); // get new config, fallback to old config $newarr = Config::fetch(); if ($newarr === false) { echo "Fetch failed!\n"; $newarr = $oldarr; } // main config changed -> reset everything if ($oldarr['bridge'] != $newarr['bridge']) $init = true; // tunnel config changed -> reset everything if ($oldarr['tunnel'] != $newarr['tunnel']) $init = true; if ($init) { // basic bridge layout, loop detection BaseTables::init($newarr['bridge']); // add bridge rules for tunnels BaseTables::add_tunnel($newarr['tunnel']); // vlan + port stuff ForwardTables::init(); // setup tunnels Vtund::init($newarr['tunnel']); // we do a init, so diff between $empty -> $new $oldarr = Config::getEmpty(); } // add VLANs, Forward-Ports and HLSW Master Server ForwardTables::checkDiff($oldarr, $newarr); // gather stats and send them to rrdtool server via tcp $stats = new Stats(); $stats->connect($newarr['bridge']['id']); $stats->get_vlans(); $stats->get_forwards($newarr['forward']); $stats->get_masters($newarr['hlsw']); $stats->close(); // save the config Config::save($newarr); ?>