Having access to multiple internet connections at home, I found it hard to balance the usage in Windows especially when I want to download multiple files through them.
The thing is that in Windows OS you can't in any way force a program to use a specific network interface by modifying the routing table nor you have open access to other parts of the system to create a special scenario for your process.
Fortunately, tho, each program initializing a socket connection can bind the newly created socket to a specific network interface if they desire to. The problem is that with the increasing number of frameworks and interest for using higher level programming languages, most of the programmers are not aware of that or simply don't care much to put a setting in their application for this. In my case, for example, I really hoped that uTorrent and Internet Download Manager had this option.
As there is no other way to force bind a program to a network interface except what described earlier, the only way to get the desired outcome is to find a way to forge a request for this binding using the main program's process. But to do so, we first need to somehow put our code inside an already compiled and executing the program in the memory.
Injecting a code inside another program and listening for different calls of Windows API in it, is called Code Injection and API Hooking. Windows' C++ developers are no stranger to these techniques; in-fact they used to do so a lot in old days. Yet currently this method is not very popular as new technologies got so far from the machine code that they can't be injected inside the memory of another program easily. And you know, everything is in the cloud now times; so why even bother with Windows programming?
Anyway, I found a program tat does exactly that, named ForceBindIP, a fine piece of code actually. Unfortunately, I had some problems with it:
1st, I had no idea which program uses which network interface after running the program with ForceBindIP. No indication, at all.
2nd, ForceBindIP couldn't handle connections made with WSAConnect function.
3rd, It was hard to run a program with ForceBindIP in everyday usage. Especially when IP address of the network adapter was dynamic.
4th, ForceBindIP couldn't detect when IP of the network interface changes. In fact, it seems that it also does ignore the return value of bind call resulting in not applying the change to some connections if bind fails. Which mean you may end up using the main internet connection if something happens with the selected network adapter making the bind requests to fail.
So the only other way around it was to rewrite the program. But then I wanted to do something new and I wasn't really in the mood for writing C++. So I tried to do it using C# and dot-Net.
There is a library named EasyHook which I had some experience with a couple of years back after Detours died. It was so buggy and crashy back then, but I thought I have to give it another shot. Other than the possibility to write the code in C# and having access to all those shiny libraries that comes with .Net Framework, EasyHook saves you from a lot of troubles with its methods and helper classes to inject your code, hook the functions, inter-process communication (IPC), multithreading and stability of host program (if you do your part correct). And with .Net 4 being around for some years now, you could hook any managed and un-managed program from one library compiled for both x86 and x64 architects (AnyCPU). In fact, you can even incorporate the injected library with the injector itself resulting in one executable file as for everything. Until .Net 4, it wasn't possible to inject the code into some managed programs due to the impossibility of loading of different versions of run-time by one process. But that's the old story now.
Using EasyHook is relatively easy. For injecting your code into the program, all you have to do is simply using RemoteHooking.Inject method. There is another method called RemoteHooking.CreateAndInject but I found it somehow useless as it couldn't inject the code inside managed code due to run-time stealing the first thread from EasyHook. So you better run the program yourself and then use the Process.Id of the program to inject the code. You may loose some of the first connections, but nothing to worry about too much.
processId = Process.Start(CommandLineOptions.Default.Execute, CommandLineOptions.Default.Arguments)?.Id ?? 0; . . . . . . RemoteHooking.Inject(processId, injectorAddress, injectorAddress, networkId, injectorAddress, CommandLineOptions.Default.Delay, CommandLineOptions.Default.Debug);
Using EasyHook you can easily send your desired arguments to the injected code. For example, in the above sample, I sent the selected networkId and some other options to the injected code.
This is all you need to do for injecting your code inside the program; now you need to write the injected code. To do so you simply need to create a class implementing the IEntryPoint interface. EasyHook uses that class automatically after injecting the code.
public class Guest : IEntryPoint {
Two key parts of this class are the constructor and the Run method. Both accepting all arguments sent to them with Run method being in charge of hooking the functions and keeping the injected thread alive.
To hook a function you need to know exactly where it is located and the exact name of it, including all the affixes. You also need to indicate which thread you want to accept calls from. Here is the method I wrote for adding a new hook:
private void AddHook(string libName, string entryPoint, Delegate inNewProc) { try { var localHook = LocalHook.Create(LocalHook.GetProcAddress(libName, entryPoint), inNewProc, null); // Exclude current thread (EasyHook) localHook.ThreadACL.SetExclusiveACL(new[] { 0 }); lock (_hooks) _hooks.Add(localHook); DebugMessage(entryPoint + "@" + libName + " Hooked Successfully"); } catch (Exception e) { DebugMessage("Failed to Hook " + entryPoint + "@" + libName); DebugMessage(e.ToString()); } }
This will create a hook and assigns a callback to it. Then it will set an excluding rule for threads indicating that the hook should not capture the calls from the current thread. To do so we used an integer array with one element, being 0, means current thread for the LocalHook.ThreadACL.SetExclusiveACL method. Please note that the current thread, if executed from the Run method, is the EasyHook's thread, executing OUR code. So yeah, we don't really want to (re)capture our calls.
I also found that in some rare cases, we may end up hooking a function before the main program even loads the library containing it, making the hook registration impossible. You can't take a fish if there is no water in the lake. In that case, we can load the library our-self.
But LoadLibrary is tricky; the thing is that the second call to the LoadLibrary returns null. So if we load the library before the main executable, we may end up with undefined consequences as the main program tries to load the library after that. Fortunately, tho, it seems that a registered hook still works if you unload the library and load it again. So I wrote another piece of code to do so. Load the desired library, hook the functions and then unload the library if only we were the first one who did load it.
But then, there should be some sort of race condition here between our code and the main program. What if we load the library first, but while hooking the functions, the main program tries to load it again and ends up with a null handler? What if we unload the library while the main program still expects it to be there. But that didn't happen to my code yet. So I think you need to choose between bad or worse sometimes. The only other way that I can think of to solve this problem is to hook the LoadLibrary function.
Anyway, here is the method we currently use:
private void LoadLibrary(string libraryName, Action code) { // Forcing the hook by pre-loading the desired library var library = Library.LoadLibrary(libraryName); code(); // Unload the library if only we were the ones loading it for the first time if (!library.Equals(IntPtr.Zero)) { Library.FreeLibrary(library); } }
Now, let's take a look at the Run method:
LoadLibrary(@"ws2_32.dll", () => { AddHook(@"ws2_32.dll", "connect", new Delegates.ConnectDelegate(Do_Connect)); AddHook(@"ws2_32.dll", "WSAConnect", new Delegates.WsaConnectDelegate(Do_WsaConnect)); AddHook(@"ws2_32.dll", "bind", new Delegates.BindDelegate(Do_Bind)); }); AddHook(@"kernel32.dll", "CreateProcessW", new Delegates.CreateProcessDelegate( (IntPtr lpApplicationName, IntPtr lpCommandLine, IntPtr lpProcessAttributes, IntPtr lpThreadAttributes, bool bInheritHandles, uint dwCreationFlags, IntPtr lpEnvironment, IntPtr lpCurrentDirectory, IntPtr lpStartupInfo, out ProcessInformation lpProcessInformation) => Do_CreateProcess(lpApplicationName, lpCommandLine, lpProcessAttributes, lpThreadAttributes, bInheritHandles, dwCreationFlags, lpEnvironment, lpCurrentDirectory, lpStartupInfo, out lpProcessInformation, true))); AddHook(@"kernel32.dll", "CreateProcessA", new Delegates.CreateProcessDelegate( (IntPtr lpApplicationName, IntPtr lpCommandLine, IntPtr lpProcessAttributes, IntPtr lpThreadAttributes, bool bInheritHandles, uint dwCreationFlags, IntPtr lpEnvironment, IntPtr lpCurrentDirectory, IntPtr lpStartupInfo, out ProcessInformation lpProcessInformation) => Do_CreateProcess(lpApplicationName, lpCommandLine, lpProcessAttributes, lpThreadAttributes, bInheritHandles, dwCreationFlags, lpEnvironment, lpCurrentDirectory, lpStartupInfo, out lpProcessInformation, false))); // Ansi version of the SetWindowText function AddHook(@"user32.dll", "SetWindowTextA", new Delegates.SetWindowTextDelegate((handle, text) => Do_SetWindowText(handle, text, false))); // Unicode (Wide) version of the SetWindowText function AddHook(@"user32.dll", "SetWindowTextW", new Delegates.SetWindowTextDelegate((handle, text) => Do_SetWindowText(handle, text, true)));
As you can see in the above piece of code, we hooked connect, WSAConnect and bind functions from the wsa2_32 library for the purpose of detecting new connections and manual binds. We did also hook the CreateProcessW and CreateProcessA functions from the kernal32 library to detect new child processes and inject our little code into them as well.
For the sake of user experience, we did hook the SetWindowTextA and SetWindowTextW functions from the user32 library so we can detect any changes in the title bar of the application and show the selected network interface for the current process.
Another good thing about EasyHook (C# actually) is that you can use anonymous methods, so I used one function to handle both ANSI and WideWord (Unicode) version of hooked functions. Now, isn't it beautiful?
Let's see what we did in the connect callback:
private SocketError Do_Connect(IntPtr socket, ref SocketAddressIn address, int addressSize) { if (!BindSocket(socket, address.Family, address.IPAddress.IpAddress)) { DebugMessage(Socket.WSAGetLastError().ToString()); return SocketError.SocketError; } var returnValue = Socket.Connect(socket, ref address, addressSize); if (returnValue == SocketError.SocketError && Socket.WSAGetLastError() == SocketError.Success) { returnValue = SocketError.Success; } return returnValue; }
While in the body of this method, we are sure that there was a call for connecting to a remote (or local) address; so we should bind it to the network interface we want, then if binding goes well we will ask for the actual connect function from ws2_32 library to do the rest.
It seems that sometimes, the return value of connect function indicates a failure while the last error of the WinSock shows a success. In that case, the host program may get confused; so I found the best way to avoid this is to modify the return value of the callback to success. But then I am not sure why this only happens when my code is in place.ace.
[DllImport("ws2_32", SetLastError = true, EntryPoint = "connect")] public static extern SocketError Connect(IntPtr socket, ref SocketAddressIn address, int addressSize);
Now let's take a look at the Bind method we called from the Do_Connect method:
private bool BindSocket(IntPtr socket, AddressFamily addressFamily, IPAddress ipAddress, int portNumber = 0) { switch (addressFamily) { case AddressFamily.InterNetwork: if (ipAddress.Equals(IPAddress.Any)) { return true; } if (IsIpInRange(ipAddress, IPAddress.Parse("127.0.0.0"), IPAddress.Parse("127.255.255.255"))) { return true; // Loopback } if (IsIpInRange(ipAddress, IPAddress.Parse("10.0.0.0"), IPAddress.Parse("10.255.255.255"))) { return true; // Private Network } if (IsIpInRange(ipAddress, IPAddress.Parse("172.16.0.0"), IPAddress.Parse("172.31.255.255"))) { return true; // Private Network } if (IsIpInRange(ipAddress, IPAddress.Parse("192.168.0.0"), IPAddress.Parse("192.168.255.255"))) { return true; // Private Network } if (IsIpInRange(ipAddress, IPAddress.Parse("224.0.0.0"), IPAddress.Parse("239.255.255.255"))) { return true; // Multicast } var networkIp = GetNetworkInterfaceIPAddress(addressFamily); if (networkIp == null) { return false; } if (networkIp.Equals(ipAddress)) { return true; } var bindIn = new SocketAddressIn { IPAddress = new SocketAddressIn.AddressIn { IpAddress = networkIp }, Family = networkIp.AddressFamily, Port = portNumber }; var addressIn = bindIn; ContinueExecution(() => DebugMessage("Auto Bind: " + addressIn.IPAddress.IpAddress)); return Socket.Bind(socket, ref bindIn, Marshal.SizeOf(bindIn)) == SocketError.Success; default: return true; } }
As we do only support IPv4 in the current version of the code, we ignore the binding part for all other connection addresses, including IPv6. But even then, we do ignore the binding for all local addresses as well as all known private IP spaces. We do also ignore the binding for the reserved multicast IP space. But if the request was for other destinations, then we do try to bind the socket using the bind function of the wsa2_32 library.
[DllImport("ws2_32", SetLastError = true, EntryPoint = "bind")] public static extern SocketError Bind(IntPtr socket, ref SocketAddressIn address, int addressSize);
This is actually all you need to do to hook the connect function of WinSock and bind it before actually requesting a connection. But I did hook the WSAConnect as well as bind function to be able to capture all connection requests and even bind the listening sockets as well as forcing programs that are aware of binding.
Now our program is complete. But how should the user, use our program?
We can go the same way ForceBindIP has been gone and let the user pass the network interface identification as well as the program address to our application. But that's hard for average users. So we should find an easier way.
Well, I do like the idea of Shell Extensions. A nice drop down menu incorporated into the Windows Explorer and Windows Desktop to let the user selects which network interface a program should use. The idea behind Shell Extensions is very similar to the Injection and Hooking. But in this case, you don't actually need to force feed your code into the program. All you need to do is to provide your code as a COM Server and then the Explorer itself is in charge of loading your code and executing it. You don't actually hook any functions, but you are still inside the main process; hence the name, In-Process Extension.
Writing an In-Process Shell Extension using C# is not actually recommended. Apart from the problems that we had with classic scenario of Injecting a piece of code, you may end up with serious performance downgrades too. But since our code is very simple and Explorer's processes are very few at any given time (usually less than 3 active Explorer process), I found it accepting to use the C# for writing such an extension. Personally. Still, the performance hit is noticeable, but only the first time you open the Context Menu in an Explorer process. It seems that the performance hit is the result of loading .Net run-time; as it won't happen again until you restart the Explorer process.
SharpShell is a great library out there for writing these extensions. In fact, you write a lot less code using SharpShell than you had to write with C++. And again, unlike C++ you don't need to compile the library two times for x86 and x64 compatibility. Let's take a look at the whole code of our shell extension:
[ComVisible(true)] [COMServerAssociation(AssociationType.ClassOfExtension, @".exe")] internal class DynamicMenuExtension : SharpContextMenu { protected override bool CanShowMenu() { // Only show the menu when application is installed, there is just one executable // file selected and some network interface to select from return !string.IsNullOrWhiteSpace(GetNetworkAdapterSelectorAddress()) && SelectedItemPaths.Count() == 1 && NetworkInterface.GetAllNetworkInterfaces() .Any( networkInterface => networkInterface.SupportsMulticast && networkInterface.OperationalStatus == OperationalStatus.Up) && Path.GetExtension(SelectedItemPaths.First())?.ToLower() == ".exe"; } protected override ContextMenuStrip CreateMenu() { var explorerMenu = new ContextMenuStrip(); var extensionMenu = new ToolStripMenuItem(Resources.DynamicMenuExtension_CreateMenu_Bind_to, Resources.x16); var i = 0; // Going through all network interfaces and add them to the list foreach ( var networkInterface in NetworkInterface.GetAllNetworkInterfaces() .Where( networkInterface => networkInterface.SupportsMulticast && networkInterface.OperationalStatus == OperationalStatus.Up)) { // Copying the network id to a variable var networkId = networkInterface.Id; // Add normal execution item extensionMenu.DropDownItems.Insert(i, new ToolStripMenuItem(networkInterface.Name, null, (sender, args) => BindTo(networkId, false))); // Add separator, only once if (i == 0) extensionMenu.DropDownItems.Add(new ToolStripSeparator()); i++; // Add run as administrator execution item extensionMenu.DropDownItems.Add( new ToolStripMenuItem( string.Format(Resources.DynamicMenuExtension_CreateMenu_Run_as, networkInterface.Name), null, (sender, args) => BindTo(networkId, true))); } explorerMenu.Items.Add(extensionMenu); return explorerMenu; } private void BindTo(string networkInterfaceId, bool asAdmin) { try { var executableAddress = GetNetworkAdapterSelectorAddress(); if (string.IsNullOrWhiteSpace(executableAddress)) { return; } // Executing the "NetworkAdapterSelector.Hook" and passing the address of executable file. var processInfo = new ProcessStartInfo(executableAddress, $"-n \"{networkInterfaceId}\" -e \"{SelectedItemPaths.First()}\"") { WindowStyle = ProcessWindowStyle.Hidden, CreateNoWindow = true, UseShellExecute = true }; if (asAdmin) { // Forcing administrator privileges processInfo.Verb = @"runas"; } Process.Start(processInfo); } catch (Exception e) { // Check if operation canceled by user if ((e as Win32Exception)?.NativeErrorCode == 1223) { return; } // Otherwise, show an error message to the user MessageBox.Show(e.ToString(), Resources.DynamicMenuExtension_BindTo_Network_Adapter_Selector, MessageBoxButtons.OK, MessageBoxIcon.Error); } } }
By inheriting the SharpContextMenu class, you have access to the methods that Explorer calls when it expect an answer. In our case, only two methods are important, CanShowMenu and CreateMenu; and as you can clearly see, it is unbelievably easy to modify the context menu of the Explorer.
Please note that in the above example we did register our extension to be used only with files registered under the executable class. Limiting the times our code will be executed.
Simple as that, we have written a nice Windows Explorer Shell Extension in pure C#.
Now if you are interested in the source code or the program itself, check the project's github page.