Skip to content

Instantly share code, notes, and snippets.

@ngorchilov
Last active December 23, 2015 02:59
Show Gist options
  • Save ngorchilov/6570413 to your computer and use it in GitHub Desktop.
Save ngorchilov/6570413 to your computer and use it in GitHub Desktop.
Kernel auto-port assignment error reproduction
#!/usr/bin/env php
<?php
$broken = TRUE; // TRUE: auto-assign port (doesn't work); FALSE: select port explicitely (works)
$local = TRUE; // TRUE: assing IPs to local interface; FALSE: use TPROXY mode. Doesn't make any difference in kernel behaviour
$ip_range = array('10.254.254.251', '10.254.254.252', '10.254.254.253', '10.254.254.254'); // put minimum two IPs that DO NOT belong to this machine already
$counters = $socks = array();
// constants needed for setting a socket in TPROXY mode
define('SOL_IP', 0);
define('IP_TRANSPARENT', 19);
// parsing of /proc/sys/net/ipv4/ip_local_port_range
$port_range = file_get_contents('/proc/sys/net/ipv4/ip_local_port_range') or die("error loading /proc/sys/net/ipv4/ip_local_port_range\n");
preg_match('/(\d+)\s+(\d+)/', $port_range, $matches) or die("error parsing /proc/sys/net/ipv4/ip_local_port_range\n");
$port_min = $matches[1];
$port_max = $matches[2];
$port_cnt = $port_max - $port_min + 1;
echo "available ports: {$port_min} - {$port_max} ({$port_cnt} total)\n";
// check max open files limit
$max_open_files = exec('ulimit -n') or die("can't get maximum open files\n");
$open_files_needed = count($ip_range) * $port_cnt + 1000; // adding 1000 files extra just in case
if ($max_open_files < $open_files_needed) {
die("insufficient max open files ({$max_open_files}). run 'ulimit -n {$open_files_needed}; {$_SERVER['PHP_SELF']}'\n");
}
// setup ips if needed
foreach($ip_range as $ip) {
if ($local) {
// assign IPs
passthru("ip a a {$ip}/32 dev eth0");
}
$counters[$ip] = 0;
}
// creating sockets
echo "creating sockets\n";
try {
for ($port = $port_min; $port <= $port_max; $port++) {
foreach ($ip_range as $ip) {
$counters[$ip]++;
$socks[] = socket_setup($ip, $broken ? 0 : $port);
}
}
echo "done...\n";
} catch(Exception $e) {
echo 'exception on socket ' . (count($socks) + 1). ': ' . $e->getMessage() . PHP_EOL;
}
// remove ips
echo "port usage per ip:\n";
foreach($ip_range as $ip) {
if ($local) {
// delete IPs
passthru("ip a d {$ip}/32 dev eth0");
}
echo "\t$ip: {$counters[$ip]} ports assigned\n";
}
// closing sockets
echo 'closing the remaining ' . count($socks) . " sockets\n";
foreach($socks as $socket) {
@socket_close($socket);
}
echo "finish\n";
function socket_setup($ip, $port = 0) {
global $local;
if(($socket = @socket_create(AF_INET, SOCK_STREAM, SOL_TCP)) === FALSE) {
throw new Exception(socket_strerror(socket_last_error()) . ' [' . socket_last_error() . '] (socket_create)');
} elseif (@socket_set_option($socket, SOL_SOCKET, SO_REUSEADDR, 1) === FALSE) {
throw new Exception(socket_strerror(socket_last_error($socket)) . ' [' . socket_last_error($socket) . '] (socket_set_option: SO_REUSEADDR)');
} elseif (!$local && @socket_set_option($socket, SOL_IP, IP_TRANSPARENT, 1) === FALSE) {
throw new Exception(socket_strerror(socket_last_error($socket)) . ' [' . socket_last_error($socket) . '] (socket_set_option: IP_TRANSPARENT)');
} elseif (@socket_bind($socket, $ip, $port) === FALSE) {
throw new Exception(socket_strerror(socket_last_error($socket)) . ' [' . socket_last_error($socket) . '] (socket_bind)');
} elseif (@socket_listen($socket, 0) === FALSE) {
throw new Exception(socket_strerror(socket_last_error($socket)) . ' [' . socket_last_error($socket) . '] (socket_listen)');
} else {
return $socket;
}
}
?>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment