upnpsync是个小脚本。主要用来同步路由器的UPnP转发信息和Cable Modem的转发信息。

最近在Time Warner的Cable Modem后面搞了个无线路由。之后又给这个路由刷了dd-wrt。 虽然一切都不错,但是对于要用UPnP进行端口转发的程序来说,因为它只会通过UPnP让路由转发,没有通知Cable Modem,导致它即使设置了端口转发,外网还是无法连接到它监听的端口。

网络结构是:

Internet <-> <74.73.xx.xx> [Time Warner Cable Modem] <192.168.0.1> <-> <192.168.0.x> [DLink Wireless Router] <192.168.1.1> <-> <192.168.1.x> [My Computer]

Time Warnar那个破Modem居然连配置都没法配,所有配置界面都锁死了,直接filter了23,80端口……

幸好它居然留着UPnP。端口扫描扫出来5555和8081俩端口,尝试了一下,http://192.168.0.1:5555/ 就是它的UPnP地址……

于是搞了个小脚本,可以根据无线路由上UPnP的端口转发信息,在Cable Modem上用UPnP自动配好对应的端口转发~

后来又加了个小功能,会自动把在本地找不到的Modem上的转发信息干掉,这样在程序结束后能自动清理。

#!/usr/bin/perl

sub get_external($) {
	my ($url) = @_;
	$_ = `/usr/local/bin/upnpc -u $url -s | grep External`;
	/ExternalIPAddress = ([0-9\.]+)+/;

	return $1;

}

sub get_fwd_list($) {
	my ($url) = @_;
	$_ = `/usr/local/bin/upnpc -u $url -l | grep '\\\->'`;

	@result = ();

	foreach $line(split(/\n/))
	{
		if ($line =~ m/\s*(\d+)\s+([A-Za-z]+)\s+(\d+)->([0-9\.]+):(\d+)\s+'(.*)'\s+'(.*)'/)
		{
#			print "No.$1 Proto:$2 Ext.Port:$3 LocalIP:$4 LocalPort:$5 Desc:$6 Note:$7\n";

			$result[$1] = {"proto"=>$2, "extport"=>$3, "localip"=>$4, "localport"=>$5, "desc"=>$6, "note"=>$7};
		}
	}
	return @result;
}

$router_ip = "192.168.1.1";

$modem_ip = "192.168.0.1";

$router_upnp = "http://192.168.1.1:1780/InternetGatewayDevice.xml";

$modem_upnp = "http://192.168.0.1:5555/";

$router_external = get_external($router_upnp);
$modem_external = get_external($modem_upnp);

@router_fwd = get_fwd_list($router_upnp);
@modem_fwd = get_fwd_list($modem_upnp);

print "Modem External IP: $modem_external\n";
print "Router External IP: $router_external\n";

foreach $router_pair (@router_fwd)
{
	$proto = $router_pair->{"proto"};
	$extport = $router_pair->{"extport"};
	$found = 0;
	$good_pair = "";
	print "Router: $proto $router_external:$extport -> $router_pair->{'localip'}:$router_pair->{'localport'}";
	foreach $modem_pair (@modem_fwd)
	{
		if ($modem_pair->{"proto"} eq $proto 
				and $modem_pair->{"localport"} == $extport)
		{
			$found = 1;
			$good_pair = $modem_pair;
			$modem_pair->{"router_pair"} = $router_pair;
			if ($modem_pair->{"localip"} ne $router_external)
			{
				$modem_local = $modem_pair->{"localip"};
				print "\nWarning: Modem forwarding to strange IP: $modem_local\n";
			}
		}
	}

	if (not $found)
	{
		print "... Not found!\n";
		$ret = `/usr/local/bin/upnpc -u $modem_upnp -a $router_external $extport $extport $proto`;
		if ($? != 0)
		{
			print "ERROR adding port forwarding pair!\n";
			print $ret;
		} else {
			print "Added forwarding $proto $modem_external:$extport->$router_external:$extport\n";
		}
	} else {
		print "... Found\n";
		print "Modem: $good_pair->{'proto'} $modem_external:$good_pair->{'extport'} -> $good_pair->{'localip'}:$good_pair->{'localport'}\n";
	}
}

foreach $modem_pair (@modem_fwd)
{
	if (not $modem_pair->{"router_pair"})
	{
		$proto = $modem_pair->{'proto'};
		$extport = $modem_pair->{'extport'};
		print "Found extra modem pair: $proto $modem_external:$extport -> $modem_pair->{'localip'}:$modem_pair->{'localport'}\n";
		$ret = `/usr/local/bin/upnpc -u $modem_upnp -d $extport $proto`;
		if ($? != 0)
		{
			print "ERROR deleting modem pair!\n";
			print $ret;
		} else {
			print "Extra pair deleted.\n";
		}
	}
}

这里的router_ip和modem_ip都是对内IP,应该都很清楚。

router_upnp和modem_upnp都是对应设备的UPnP地址。可以通过在计算机与该设备直连的情况下,用upnpc -l发现出来(自动发现还是不太靠谱啊……)。