How to Change Windows Gateway by Broadband Speed in Python
Plenty of enterprises allocate static IPs and gateways to employees’ devices for better network management. For example, limiting the internet connection speed, monitoring intranet activities and so on. In my company, the network administrator sets up two gateways for accessing different broadband services. During my working time, I often need to switch the gateways to obtain better connection speed. Is there any good tool for dynamically changing gateways based on network speed? With the question, I spend some time googling relevant information and finally find out a solution in Python. Let’s take a glimpse of how to make it.
Testing Broadband Speed with speedtest-cli
When searching for keywords “speed test python”, the first returned result is speedtest-cli, which is a Python open-source project maintained on GitHub. It examines Internet bandwidth using speedtest.net.
Installation
pip install speedtest-cli
or
easy_install speedtest-cli
Testing Internet Speed
You can test the download speed and upload speed with following command:
speedtest-cli --bytes
According to the log, we can speculate the necessary steps that the program took:
- Read the configuration from speedtest.net.
- Based on the configuration, find a server list.
- Choose the best server for testing speed.
However, my goal is to compare the gateways, and thus I just need to pick an arbitrary server without relying on speedtest.net.
Customized Function for Testing Connection Speed
How to write our Python program based on speedtest-cli? We can refer to the source code file, which is located at {Python Installation Directory}\Lib\site-packages\speedtest_cli.py. After learning the function speedtest(), I created a simple function for testing download speed:
def testSpeed(urls):
speedtest_cli.shutdown_event = threading.Event()
signal.signal(signal.SIGINT, speedtest_cli.ctrl_c)
print "Start to test download speed: "
dlspeed = speedtest_cli.downloadSpeed(urls)
dlspeed = (dlspeed / 1000 / 1000)
print('Download: %0.2f M%s/s' % (dlspeed, 'B'))
return dlspeed
The original source code generated the URLs based on the best server, whereas the customized function can take any target URLs we like:
urls = ["http://www.dynamsoft.com/assets/images/logo-index-dwt.png",
"http://www.dynamsoft.com/assets/images/logo-index-dnt.png",
"http://www.dynamsoft.com/assets/images/logo-index-ips.png",
"./img/2015/06/django_dwt.png",
"./img/2015/07/drag_element.png"]
How to calculate the download speed?
def downloadSpeed(files, quiet=False):
"""Function to launch FileGetter threads and calculate download speeds"""
start = timeit.default_timer()
def producer(q, files):
for file in files:
thread = FileGetter(file, start)
thread.start()
q.put(thread, True)
if not quiet and not shutdown_event.isSet():
sys.stdout.write('.')
sys.stdout.flush()
finished = []
def consumer(q, total_files):
while len(finished) < total_files:
thread = q.get(True)
while thread.isAlive():
thread.join(timeout=0.1)
finished.append(sum(thread.result))
del thread
q = Queue(6)
prod_thread = threading.Thread(target=producer, args=(q, files))
cons_thread = threading.Thread(target=consumer, args=(q, len(files)))
start = timeit.default_timer()
prod_thread.start()
cons_thread.start()
while prod_thread.isAlive():
prod_thread.join(timeout=0.1)
while cons_thread.isAlive():
cons_thread.join(timeout=0.1)
return (sum(finished) / (timeit.default_timer() - start))
- Read images in threads
- Queue the threads
- Get thread results from queue
- speed = total file sizes / total time cost
Querying Network Adapter Configurations and Setting Gateways
How to get the default IP and gateway in Python? I found a similar question from StackOverflow. The recommended solution is to use WMI (Windows Management Instrumentation).
Installation
WMI Python for Windows Extensions
Win32 Network Adapter Configuration
There are many network adapters on my Windows, how can I find the target? Microsoft defines many attributes for network adapter, take a look at Win32_NetworkAdapterConfiguration class:
[Provider("CIMWin32")]class Win32_NetworkAdapterConfiguration : CIM_Setting
{
boolean ArpAlwaysSourceRoute;
boolean ArpUseEtherSNAP;
string Caption;
string DatabasePath;
boolean DeadGWDetectEnabled;
string DefaultIPGateway[];
uint8 DefaultTOS;
uint8 DefaultTTL;
string Description;
boolean DHCPEnabled;
datetime DHCPLeaseExpires;
datetime DHCPLeaseObtained;
string DHCPServer;
string DNSDomain;
string DNSDomainSuffixSearchOrder[];
boolean DNSEnabledForWINSResolution;
string DNSHostName;
string DNSServerSearchOrder[];
boolean DomainDNSRegistrationEnabled;
uint32 ForwardBufferMemory;
boolean FullDNSRegistrationEnabled;
uint16 GatewayCostMetric[];
uint8 IGMPLevel;
uint32 Index;
uint32 InterfaceIndex;
string IPAddress[];
uint32 IPConnectionMetric;
boolean IPEnabled;
boolean IPFilterSecurityEnabled;
boolean IPPortSecurityEnabled;
string IPSecPermitIPProtocols[];
string IPSecPermitTCPPorts[];
string IPSecPermitUDPPorts[];
string IPSubnet[];
boolean IPUseZeroBroadcast;
string IPXAddress;
boolean IPXEnabled;
uint32 IPXFrameType[];
uint32 IPXMediaType;
string IPXNetworkNumber[];
string IPXVirtualNetNumber;
uint32 KeepAliveInterval;
uint32 KeepAliveTime;
string MACAddress;
uint32 MTU;
uint32 NumForwardPackets;
boolean PMTUBHDetectEnabled;
boolean PMTUDiscoveryEnabled;
string ServiceName;
string SettingID;
uint32 TcpipNetbiosOptions;
uint32 TcpMaxConnectRetransmissions;
uint32 TcpMaxDataRetransmissions;
uint32 TcpNumConnections;
boolean TcpUseRFC1122UrgentPointer;
uint16 TcpWindowSize;
boolean WINSEnableLMHostsLookup;
string WINSHostLookupFile;
string WINSPrimaryServer;
string WINSScopeID;
string WINSSecondaryServer;
};
It will be handy if we can find an useful value like id or device name:
- Right-click on the target network adapter.
- Click Configure.
- Choose Details.
- Select a suitable property and check its value.
I decided to use Device description - Realtek PCIe GBE Family Controller.
Querying Windows IP and Gateway
wmiObj = wmi.WMI()
sql = "select IPAddress,DefaultIPGateway from Win32_NetworkAdapterConfiguration where Description="Realtek PCIe GBE Family Controller" and IPEnabled=TRUE"
configurations = wmiObj.query(sql)
Setting Windows Gateway
configurations = wmiObj.Win32_NetworkAdapterConfiguration(Description="Realtek PCIe GBE Family Controller", IPEnabled=True)
configuration = configurations[0]
ret = configuration.SetGateways(DefaultIPGateway=[gateway])
Be careful. If you do not run the script as administrator, it will fail to set the gateway. Make sure the returned value is 0.
for gateway in gateways:
settingReturn = setGateway(wmiObj, gateway)
if (settingReturn[0] != 0):
print "Setting failed"
return
print "Set gateway: " + gateway
dlspeed = testSpeed(urls)
option = (gateway, dlspeed)
print "Network option: " + str(option)
if (option[1] > bestChoice[1]):
bestChoice = option
Converting Python Script to Executable Program
I’d like to share my program to colleagues, what if they do not have Python installed? The way is to pack all Python dependencies. Py2exe can convert Python scripts to executable files.
Installation
Converting Python Script to Windows Executable File
Create a Python script setup.py:
from distutils.core import setup
import py2exe
setup(console=['network.py'])
Run the script:
python setup.py py2exe
Now I see a dist directory with all relevant files.
Do not forget that you should run the EXE file as administrator.