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

speed test

According to the log, we can speculate the necessary steps that the program took:

  1. Read the configuration from speedtest.net.
  2. Based on the configuration, find a server list.
  3. 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", 
"http://www.codepool.biz/wp-content/uploads/2015/06/django_dwt.png", 
"http://www.codepool.biz/wp-content/uploads/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))
  1. Read images in threads
  2. Queue the threads
  3. Get thread results from queue
  4. 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:

  1. Right-click on the target network adapter.
  2. Click Configure.
  3. Choose Details.
  4. Select a suitable property and check its value.

I decided to use Device description – Realtek PCIe GBE Family Controller.

network adapter

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

Py2exe

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.

py2exe

Do not forget that you should run the EXE file as administrator.

Source Code

https://github.com/yushulx/switch-windows-gateway