Skip to content

Instantly share code, notes, and snippets.

@fragtion
Last active September 6, 2023 00:45
Show Gist options
  • Save fragtion/8402cd4989b0f0d8b86baf4da38c1dbc to your computer and use it in GitHub Desktop.
Save fragtion/8402cd4989b0f0d8b86baf4da38c1dbc to your computer and use it in GitHub Desktop.
Firewall Routes/Rules Generator for Steam PoPs/Relays
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