Last active
September 6, 2023 00:45
-
-
Save fragtion/8402cd4989b0f0d8b86baf4da38c1dbc to your computer and use it in GitHub Desktop.
Firewall Routes/Rules Generator for Steam PoPs/Relays
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
THIS GIST CONTAINS TWO VERSIONS - A PHP SCRIPT, AND A PYTHON SCRIPT | |
### PHP SCRIPT ### | |
/* | |
* This script will generate a list of IP segments for selected Steam PoPs/Relays, in desired format (mikrotik, windows, cidr, list, or mask) | |
* Example use case is for CSGO matchmaking - you can use this to block all servers except the ones you want to play on | |
* | |
* Example query (assuming you are hosting this on a php-enabled web server at 192.168.0.1): | |
* http://192.168.0.1/steam-routes.php?type=mikrotik&telnet=true&allow=jhb,cpt,jnb | |
* | |
* Requires Linux for shell exec of the `ipcalc` command (can be ported to windows quite easily if required) | |
*/ | |
<html> | |
<head></head> | |
<body> | |
<pre> | |
<?php | |
error_reporting(E_ALL & ~E_NOTICE); | |
$type = $_GET["type"]; | |
if ($_GET["telnet"] == "true") { $telnet = 1; } | |
$allow = array_filter(explode(",",$_GET["allow"])); | |
$string = file_get_contents("https://api.steampowered.com/ISteamApps/GetSDRConfig/v1?appid=730"); | |
//$string = file_get_contents("https://steamcdn-a.akamaihd.net/apps/sdr/network_config.json"); | |
$json_a = json_decode($string, true); | |
$list = array(); | |
foreach($json_a['pops'] as $key => $pops) { | |
foreach($pops as $popkey => $pop) { | |
if ($popkey == "relays") { | |
foreach($pop as $awe) { | |
process_entry($key, $awe['ipv4']."/32"); | |
} | |
} | |
elseif ($popkey == "service_address_ranges") { | |
foreach($pop as $adr_range) { | |
if (strpos($adr_range, '-') !== false) { | |
if ($adr_cidrs = `ipcalc $adr_range | grep -v deaggregate`) { | |
$adr_cidrs = explode("\n",$adr_cidrs); | |
foreach ($adr_cidrs as $adr_cidr) { | |
if(!empty(trim($adr_cidr))) { | |
process_entry($key, $adr_cidr); | |
} | |
} | |
} | |
} | |
else { process_entry($key, $adr_range); } | |
} | |
} | |
} | |
} | |
/////// MAIN /////// | |
if ($type == "mikrotik") { | |
$output = ":foreach i in=[/ip firewall address-list find list=\"csgo-allow\"] do={ /ip firewall address-list remove \$i }\n"; | |
$output .= ":foreach i in=[/ip firewall address-list find list=\"csgo-deny\"] do={ /ip firewall address-list remove \$i }\n"; | |
$output .= "/ip firewall address-list \n"; | |
} | |
elseif ($type == "windows") { | |
$output = "netsh advfirewall firewall del rule name=\"csgo-deny\"\n"; | |
$output .= "netsh advfirewall firewall add rule name=\"csgo-deny\" dir=out localip=any protocol=any action=block remoteip="; | |
} | |
foreach ($list as $key => $types) { | |
$list[$key]['cidrs_cond'] = array_unique(merge_cidr($types['cidrs']), SORT_REGULAR); | |
if (in_array($key, $allow) || empty($allow)) { | |
foreach ($list[$key]['cidrs_cond'] as $cidr) { | |
if ($type == "cidr") { $output .= "$cidr ($key)\n"; } | |
elseif ($type == "list" || $type == "mask") { | |
$address = ipcalc($cidr, "Address"); | |
$netmask = ipcalc($cidr, "Netmask"); | |
if ($netmask != "255.255.255.255") { | |
$hostmin = ipcalc($cidr, "HostMin"); | |
$hostmax = ipcalc($cidr, "HostMax"); | |
} | |
else { | |
unset($hostmin); | |
unset($hostmax); | |
} | |
if ($type == "list") { | |
$output .= (isset($hostmin)) ? "$hostmin-$hostmax" : "$address"; | |
$output .= " ($key)\n"; | |
} | |
else { | |
$output .= "$address $netmask"; | |
$output .= " ($key)\n"; | |
} | |
} // windows is deny only (via netsh advfirewall rule) so we won't include it here | |
elseif ($type == "mikrotik") { $output .= "add list=csgo-allow address=$cidr comment=\"valve-sdr-$key\"\n"; } | |
} | |
} | |
else { | |
foreach ($list[$key]['cidrs_cond'] as $cidr) { | |
if ($type == "cidr") { $output .= "$cidr ($key - denied)\n"; } | |
elseif ($type == "list" || $type == "mask") { | |
$address = ipcalc($cidr, "Address"); | |
$netmask = ipcalc($cidr, "Netmask"); | |
if ($netmask != "255.255.255.255") { | |
$hostmin = ipcalc($cidr, "HostMin"); | |
$hostmax = ipcalc($cidr, "HostMax"); | |
} | |
else { | |
unset($hostmin); | |
unset($hostmax); | |
} | |
if ($type == "list") { | |
$output .= (isset($hostmin)) ? "$hostmin-$hostmax" : "$address"; | |
$output .= " ($key)\n"; | |
} | |
else { | |
$output .= "$address $netmask"; | |
$output .= " ($key - denied)\n"; | |
} | |
} | |
elseif ($type == "mikrotik") { $output .= "add list=csgo-deny address=$cidr comment=\"valve-sdr-$key\"\n"; } | |
elseif ($type =="windows") { $output .= "$cidr,"; } | |
} | |
} | |
} | |
////// FUNCTIONS BELOW ////// | |
function process_entry($key, $host) { | |
global $list; | |
$list[$key]['cidrs'][] = $host; | |
} | |
function ipcalc($addr, $req) { | |
$li = `ipcalc $addr`; | |
$li = preg_replace('!\s+!', ' ', $li); | |
$li = preg_replace('!: !', ':', $li); | |
preg_match_all("/".$req."(.*)$/m",$li,$val); | |
$val=explode(":",$val[0][0]); | |
$val=$val[1]; | |
$val=explode(" ",$val); | |
$val=$val[0]; | |
return $val; | |
} | |
function merge_cidr(array $cidr_or_ipv4_list) | |
{ // Main function | |
$valid_ip='0*?((?:0)|(?:2(?:(?:[0-4][0-9])|(?:5[0-5])))|(?:1?[0-9]{1,2}))'; // Build the valid ipv4 regex | |
$valid_ip.=str_repeat(".$valid_ip",3); // Finalize the ipv4 regex accepting leading zeros for each part | |
$valid_routing_prefix='(?:0*?((?:(?:0)|(?:3[0-2])|(?:[1-2]?[0-9]))))'; // Build a regex for the routing prefix (accepting leading zeros) | |
foreach($cidr_or_ipv4_list as $a) // For each entry you pass to the function | |
if (is_string($a) && preg_match("#^[^0-9]*$valid_ip(?:/$valid_routing_prefix)?[^0-9]*$#", $a, $m)) | |
{ // Extracting the valid ipv4 and optionnaly the routing prefix | |
$m[5] = ctype_digit($m[5]) ? ((int)$m[5]) : 32; // Initialize the valid routing prefix to the extracted value or 32 if mismatch | |
$c[$m[5]][] = ip2long("$m[1].$m[2].$m[3].$m[4]") & (-1 << (32 - $m[5])); // Initialize the working array with key (prefix) and value as subnet by bitwise the decimal ip | |
} | |
if ($c) // If any valid ipv4 with optional routing prefix matched | |
{ | |
foreach($c as &$unique) $unique=array_unique($unique); //Make unique as possible before processing | |
$c = merge_cidr_summarize($c); // Pass the valid array to the slave function | |
foreach($c as $d => & $e) // For each result as routing prefix => Decimal value | |
$e = array_map( | |
function ($a) use($d) | |
{ | |
return [$a, $a + (1 << (32 - $d)) - 1]; | |
} | |
, $e); // Change it to an array containing the range of ip | |
foreach($c as $f => $g) // For each result as routing prefix => array of decimal value | |
foreach($c as $h => $i) // For each result as routing prefix => array of decimal value | |
if ($f > $h) // If we are not in the same line and the second line have a lower routing prefix | |
foreach($g as $j => $k) // For each line as id => array of decimal values | |
foreach($i as $l) // For each line as decimal value in the second foreach | |
if ($k[0] >= $l[0] && $k[1] <= $l[1]) // If the block with lower routing prefix is totally including the first | |
unset($c[$f][$j]); // Unset the smaller block | |
foreach($c as $f => $g) // For each result as routing prefix => array of decimal value | |
{ | |
usort($g, | |
function (array $a, array $b) | |
{ | |
return $b[0]>$a[0]?1:($b[0]<$a[0]?-1:0); | |
}); // Order the result "naturally" inversed | |
foreach($g as $h) // For each ordered result | |
$z[] = long2ip($h[0]) . '/' . $f; // Convert it to human readable | |
} | |
return array_reverse($z); // And return the reversed result (order by routing prefix DESC and ip DESC) | |
} | |
} | |
function merge_cidr_summarize(array $a) | |
{ // Slave function | |
$b = false; // Did we make a change ? | |
$c = []; // Initialize the result to an empty array | |
krsort($a); // Order the input by routing prefix DESC | |
foreach($a as $d => $e) { // For each entry as cidr => Array of decimal values | |
sort($a[$d]); // Order the values "naturally" | |
$k = count($a[$d]); // Count the values for the loop | |
for ($i = 0; $i < $k; $i++) // Loop to check all values with this routing prefix | |
if ($a[$d][$i] == $a[$d][$i + 1]) continue; // If the subnet is the same as the next, then directly goto the next | |
elseif (($a[$d][$i] & (-1 << 33 - $d)) == ($a[$d][$i + 1] & (-1 << 33 - $d))) { // Check if subnet of this line and the next line are equals | |
$c[$d - 1][] = $a[$d][$i++] & (-1 << 33 - $d); // If yes add the new subnet in result array and skip the next line | |
$b = true; // And tell the script to run again | |
} | |
else $c[$d][] = $a[$d][$i]; // Else don't make anything | |
} | |
return $b ? merge_cidr_summarize($c) : $a; // If any change run again else return the result | |
} | |
////// MIKROTIK ROUTEROS FUNCTIONS ////// | |
class phpMikrotikTelnet | |
{ | |
//You may be able to lower this for single commands but needs to be high when running lots of commands | |
var $TimeOut=125000; | |
var $fp; | |
var $echo=true; | |
function phpMikrotikTelnet($host, $username, $password,$echo=true) | |
{ | |
$this->routeros_connect($host, $username, $password); | |
} | |
function routeros_connect($host, $username, $password) | |
{ | |
$header1=chr(0xFF).chr(0xFB).chr(0x1F).chr(0xFF).chr(0xFB).chr(0x20).chr(0xFF).chr(0xFB).chr(0x18).chr(0xFF).chr(0xFB).chr(0x27).chr(0xFF).chr(0xFD).chr(0x01).chr(0xFF).chr(0xFB).chr(0x03).chr(0xFF).chr(0xFD).chr(0x03).chr(0xFF).chr(0xFC).chr(0x23).chr(0xFF).chr(0xFC).chr(0x24).chr(0xFF).chr(0xFA).chr(0x1F).chr(0x00).chr(0x50).chr(0x00).chr(0x18).chr(0xFF).chr(0xF0).chr(0xFF).chr(0xFA).chr(0x20).chr(0x00).chr(0x33).chr(0x38).chr(0x34).chr(0x30).chr(0x30).chr(0x2C).chr(0x33).chr(0x38).chr(0x34).chr(0x30).chr(0x30).chr(0xFF).chr(0xF0).chr(0xFF).chr(0xFA).chr(0x27).chr(0x00).chr(0xFF).chr(0xF0).chr(0xFF).chr(0xFA).chr(0x18).chr(0x00).chr(0x41).chr(0x4E).chr(0x53).chr(0x49).chr(0xFF).chr(0xF0); | |
$header2=chr(0xFF).chr(0xFC).chr(0x01).chr(0xFF).chr(0xFC).chr(0x22).chr(0xFF).chr(0xFE).chr(0x05).chr(0xFF).chr(0xFC).chr(0x21); | |
$this->fp=fsockopen($host,23); | |
fputs($this->fp,$header1); | |
usleep(125000); | |
fputs($this->fp,$header2); | |
usleep(125000); | |
$this->write_to_telnet($username."+ct"); | |
$this->write_to_telnet($password); | |
$this->read_from_telnet(); | |
} | |
function routeros_cmd($command) | |
{ | |
//$command = str_replace(";\n",';',$command); | |
//echo $command."\n"; | |
$commands = explode("\n",$command); | |
reset($commands); | |
foreach ($commands as $cmd) | |
{ | |
if ($this->echo) | |
echo $cmd."\n"; | |
flush(); | |
$this->write_to_telnet(trim($cmd)); | |
$read = $this->read_from_telnet()."\n"; | |
if ($this->echo) | |
echo $read; | |
flush(); | |
} | |
return $rez; | |
} | |
# Telnet Related | |
function write_to_telnet($text) | |
{ | |
fputs($this->fp,$text."\r\n"); | |
usleep($this->TimeOut); | |
return true; | |
} | |
function read_from_telnet() | |
{ | |
$output = ""; | |
$count = 0; | |
$count2 = 0; | |
do{ | |
$char =fread($this->fp, 1); | |
$output .= $char; | |
if($char==">") $count++; | |
if($count==1) break; | |
if($char==".") $count2++; | |
if($count2==3) break; | |
} while(1==1); | |
$output=preg_replace("/^.*?\n(.*)\n[^\n]*$/","$1",$output); | |
$o=explode("\n",$output); | |
for($i=1;$i<=count($o)-2;$i++) $op.=$o[$i]."\n"; | |
return $op; | |
} | |
function close() | |
{ | |
fclose($this->fp); | |
} | |
} | |
if (($type == "mikrotik") && (isset($telnet))) { | |
$ServerList [] = "192.168.0.1"; | |
$Username = 'admin'; | |
$Pass = 'password'; | |
foreach ($ServerList as $Server) | |
{ | |
$mk = new phpMikrotikTelnet($Server, $Username, $Pass); | |
$mk->routeros_cmd($output); | |
$mk->close(); | |
} | |
} | |
elseif ($type == "mikrotik" || $type == "windows" || $type == "list" || $type == "mask" || $type == "cidr") { echo $output; } | |
// EOF | |
?> | |
</pre> | |
</body> | |
<html> | |
### PYTHON VERSION ### | |
import requests | |
import json | |
import ipaddress | |
# Function to process an entry | |
def process_entry(key, host): | |
global list | |
list[key]['cidrs'].append(host) | |
# Function to perform IP calculations | |
def ipcalc(addr, req): | |
try: | |
ip = ipaddress.IPv4Interface(addr) | |
if req == "Address": | |
return str(ip.ip) | |
elif req == "Netmask": | |
return str(ip.netmask) | |
elif req == "HostMin": | |
return str(ip.network.network_address) | |
elif req == "HostMax": | |
return str(ip.network.broadcast_address) | |
except ValueError: | |
return None | |
# Function to merge CIDR ranges | |
def merge_cidr(cidr_or_ipv4_list): | |
cidrs = [ipaddress.ip_network(item, strict=False) for item in cidr_or_ipv4_list] | |
merged_cidrs = ipaddress.collapse_addresses(cidrs) | |
return [str(cidr) for cidr in merged_cidrs] | |
# Main function | |
def main(): | |
type = input("Enter type: ") | |
allow = input("Enter allow: ").split(',') | |
# Fetch data from the Steam API | |
response = requests.get("https://api.steampowered.com/ISteamApps/GetSDRConfig/v1?appid=730") | |
data = json.loads(response.text) | |
global list | |
list = {} | |
for key, pops in data['pops'].items(): | |
list[key] = {'cidrs': []} | |
for popkey, pop in pops.items(): | |
if popkey == "relays": | |
for awe in pop: | |
process_entry(key, awe['ipv4'] + "/32") | |
elif popkey == "service_address_ranges": | |
for adr_range in pop: | |
if '-' in adr_range: | |
adr_cidrs = adr_range.split('-') | |
for adr_cidr in adr_cidrs: | |
process_entry(key, adr_cidr.strip()) | |
else: | |
process_entry(key, adr_range) | |
output = "" | |
if type == "mikrotik": | |
output += ":foreach i in=[/ip firewall address-list find list=\"csgo-allow\"] do={ /ip firewall address-list remove \$i }\n" | |
output += ":foreach i in=[/ip firewall address-list find list=\"csgo-deny\"] do={ /ip firewall address-list remove \$i }\n" | |
output += "/ip firewall address-list \n" | |
elif type == "windows": | |
output += "netsh advfirewall firewall del rule name=\"csgo-deny\"\n" | |
output += "netsh advfirewall firewall add rule name=\"csgo-deny\" dir=out localip=any protocol=any action=block remoteip=" | |
for key, item in list.items(): | |
cidrs_cond = merge_cidr(item['cidrs']) | |
if type == "mikrotik": | |
for cidr in cidrs_cond: | |
if key in allow: | |
output += f"add list=csgo-allow address={cidr} comment=\"valve-sdr-{key}\"\n" | |
else: | |
output += f"add list=csgo-deny address={cidr} comment=\"valve-sdr-{key}\"\n" | |
elif type == "windows": | |
if key not in allow: | |
for cidr in cidrs_cond: | |
output += f"{cidr}," | |
else: | |
if key in allow or not allow: | |
for cidr in cidrs_cond: | |
if type == "cidr": | |
output += f"{cidr} ({key})\n" | |
elif type == "list" or type == "mask": | |
address = ipcalc(cidr, "Address") | |
netmask = ipcalc(cidr, "Netmask") | |
hostmin = ipcalc(cidr, "HostMin") | |
hostmax = ipcalc(cidr, "HostMax") | |
if type == "list": | |
output += f"{hostmin}-{hostmax} ({key})\n" | |
else: | |
output += f"{address} {netmask} ({key})\n" | |
else: | |
for cidr in cidrs_cond: | |
if type == "cidr": | |
output += f"{cidr} ({key} - denied)\n" | |
elif type == "list" or type == "mask": | |
address = ipcalc(cidr, "Address") | |
netmask = ipcalc(cidr, "Netmask") | |
hostmin = ipcalc(cidr, "HostMin") | |
hostmax = ipcalc(cidr, "HostMax") | |
if type == "list": | |
output += f"{hostmin}-{hostmax} ({key} - denied)\n" | |
else: | |
output += f"{address} {netmask} ({key} - denied)\n" | |
print(output) | |
if __name__ == "__main__": | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment