![]() |
![]() |
![]() |
The improved security brought by Microsoft Windows XP Service Pack 2 (SP2) also brings a new complication for multiplayer games: the Windows Firewall. This article describes the new Windows Firewall, why it exists, what it accomplishes, and how it does so. Most importantly, it describes how to configure your application to work well with the Windows Firewall.
A firewall provides a barrier against network-based intrusions. It blocks unsolicited incoming traffic, and makes the system mostly invisible on the internet by rejecting Internet Control Message Protocol (ICMP) requests. This means that ping and tracert will not work. The firewall also looks out for invalid packets and rejects those it finds.
This barrier prevents opportunistic attacks. Attacks spread by finding many systems with the same vulnerability. The firewall can thwart many attacks by putting up a "do not disturb" sign for those features not currently in use; the attack is ignored and does not strike home. The essential benefit of the new Windows Firewall is that features and applications that are not in use cannot be avenues for attack.
The user configures the system to identify what applications and features are needed and should be open to the network. This happens either when an application is installed or when it tries to open itself to the network.
With the arrival of Windows XP SP2, firewalls are going to be widely deployed and an open connection between endpoints will be the exception. Asking users to choose between being secure and having fun is bad business.
All multiplayer games are affected to some degree. While clients are generally in good shape, servers, hosts, and peers in peer-to-peer all need the Windows Firewall configured to continue working. Specifically, incoming unsolicited traffic is dropped. The Windows Firewall intercepts network traffic filter packets based on the packet contents and recent network activity. The Windows Firewall uses the contents and activity to decide if a packet is forwarded or dropped.
Once the Windows Firewall is properly configured, a game will be able to accept inbound unsolicited traffic as before.
Who has inbound unsolicited traffic?
Clients are generally in good shape. Their outgoing Transmission Control Protocol/Internet Protocol (TCP/IP) connections will work fine, as will outgoing User Datagram Protocol (UDP) messages along with timely responses to those messages - the Windows Firewall keeps the port open for 90 seconds after each outgoing message in anticipation of a reply.
The Internet Connection Firewall (ICF) in Windows XP and Windows Server 2003 is a stateful packet filter, and handles both Internet Protocol, version 4 (IPv4) and Internet Protocol, version 6 (IPv6). However, it is not on by default and does not support non-Microsoft network stacks, of which there are a significant number in the world, such as large internet providers including national telephone companies.
For those not on Windows XP SP2, turning ICF on is highly recommended. "Use a Firewall" is listed on http://www.microsoft.com/security/protect as the first step to securing your PC. The ICF provides port mapping to override the packet filter. Essentially, you specify the port to open and it remains opened until you close it. If the user reboots before it is closed, it will remain open until specifically closed. The control of the ICF and the management of the port mappings is done via the INetSharingManager (IPv4) and the INetFwV6Mgr (IPv6).
The Windows Firewall for Windows XP SP2 is a more comprehensive solution that does support non-Microsoft network stacks, and does much more intelligent port handling: ports are kept open only so long as the application that needs them is still active.
The new firewall is available on both Windows XP SP2 and Windows Server 2003 Service Pack 1 (SP1). Like the Windows Firewall, it is a software firewall that supports both IPv4 and IPv6, but unlike Windows Firewall it:
In the "on with no exceptions" mode, all static holes are closed. application programming interface (API) calls to open a static hole are allowed but deferred; that is, they are not applied until the Windows Firewall switches back to normal operation. All listen requests by applications will also be ignored. Outbound connections will still succeed. For new applications, add your application to the "Exceptions List" during installation. After getting the user's agreement, add the application using the INetFwAuthorizedApplications interface, supplying the full path. We'll cover the details in the sample.
Your old applications, including applications installed before the user upgraded to Windows XP SP2, are handled with the Exceptions List popup (see Figure 1). This dialog shows when an application tries to open a port for incoming traffic - either when calling bind() with a non-zero port for UDP, or accept() for TCP/IP protocol. You must be running as an Administrator to "Unblock" applications, while "Ask Me Later" disallows this time around but asks again next time.
This is a non-blocking, system modal dialog unless the Windows Firewall detects a fullscreen Microsoft Direct3D application. When we tested with a blocking version of the dialog, many fullscreen applications crashed when the dialog popped up. To avoid these crashes, the dialog comes in underneath; the user can then handle it when the application exits fullscreen mode.
Figure 1. Exceptions List Popup dialog
The popup displays the name and publisher of the application which it extracts from the executable's version information. This information is an important system administration tool; it is even used for ongoing application compatibility work. Some applications neglect to keep this version information up-to-date.
Users can also add their applications through the user interface (UI) (see Figure 2), but for your new applications it's best to handle it during setup, add the application on install, and remove the application on uninstall. We'll show you how in Managing the Windows Firewall below.
Figure 2. Configuring the Windows Firewall
Figure 3. Application Exceptions List
Windows XP SP2 puts the security choices and settings out front. The goal is to protect by default, and allow the users to make informed choices about what applications are allowed to run on their machine.
The sample hits the two big scenarios: Installation (including the uninstall) and multiplayer launch. The Windows Firewall is configured during install, and the state of the Windows Firewall is detected when multiplayer is launched. The sample deals with these two scenarios in OnInstallApplication and in OnLaunchMultiplayer. The complete sample includes error checking left out of these snippets in the interest of brevity.
The install case is crucial (see Figure 4). Once the Windows Firewall is properly configured, the Windows Firewall takes care of the rest and is invisible to the application.
hr = OnInstallApplication( L"%ProgramFiles%\\Combat Flight Simulator\\COMBATFS.EXE ", L" Combat Flight Simulator" ); if( FAILED(hr) ) printf( "OnInstallApplication failed: 0x%08lx\n", hr );
Figure 4. The first basic scenario: Install
We invoke OnInstallApplication passing the complete path to the executable and a "friendly name" that will appear in the Windows Firewall exception list (see Figure 4 and Figure 5). OnInstallApplication creates an instance of our FirewallWrapper class, and adds the executable to the Windows Firewall's exception list using the AddAuthorizedApp method.
BOOL OnInstallApplication( IN const wchar_t* szFwProcessImageFileName, IN const wchar_t* szFwFriendlyName ) { FirewallWrapper* pfw = FirewallWrapper::Create(); if( !pfw ) return FALSE; HRESULT hr = pfw->AddAuthorizedApp( szFwProcessImageFileName, szFwFriendlyName ); return SUCCEEDED(hr); }
Figure 5. OnInstallApplication
class FirewallWrapper { INetFwProfile* m_pFwProfile; HRESULT InitFirewallProfile(); FirewallWrapper(); public: static FirewallWrapper* Create(); ~FirewallWrapper(); BOOL FirewallPresent() { return m_pFwProfile != NULL; } HRESULT AddAuthorizedApp( IN const wchar_t* szFwProcessImageFileName, IN const wchar_t* szFwFriendlyName ); HRESULT RemoveAuthorizedApp( IN const wchar_t* szFwProcessImageFileName ); BOOL IsAppEnabled( IN const wchar_t* szFwProcessImageFileName ); BOOL AreExceptionsAllowed(); };
Figure 6. The FirewallWrapper class
FirewallWrapper::FirewallWrapper() : m_pFwProfile(NULL) , m_hrComInit(E_FAIL) {} FirewallWrapper* FirewallWrapper::Create() { FirewallWrapper* pfw = new FirewallWrapper; if( pfw ) { pfw->InitFirewallProfile(); if( !pfw->FirewallPresent() ) { // Failed to initialize firewall profile delete pfw; pfw = NULL; } } return pfw; }
Figure 7. Initialize FirewallWrapper
Digging inside the FirewallWrapper class initialization, the first order of business is to initialize a profile (Figure 7). We create an instance of the Firewall Settings Manager (Figure 8), use it to retrieve the local Windows Firewall policy, then use the policy to fetch the intended profile. Because all interactions go through the profile, it is stored in a member variable for future use. From now on, the FirewallWrapper will use the profile to check and modify Windows Firewall settings.
On older systems that lack the new Windows Firewall, no profile will be created and the FirewallWrapper::Create method will return NULL.
HRESULT FirewallWrapper::InitFirewallProfile() { HRESULT hr = S_OK; INetFwMgr* pFwMgr = NULL; INetFwPolicy* pFwPolicy = NULL; m_pFwProfile = NULL; // Create an instance of the firewall settings manager. hr = CoCreateInstance( __uuidof(NetFwMgr), NULL, CLSCTX_INPROC_SERVER, __uuidof(INetFwMgr), (void**)&pFwMgr ); _ASSERT( hr != CO_E_NOTINITIALIZED && "COM has not been initialized" ); if( FAILED(hr) ) goto cleanup; // Retrieve the local firewall policy. hr = pFwMgr->get_LocalPolicy( &pFwPolicy ); if( FAILED(hr) ) goto cleanup; // Retrieve the firewall profile currently in effect. hr = pFwPolicy->get_CurrentProfile( &m_pFwProfile ); if( FAILED(hr) ) printf( "get_CurrentProfile failed: 0x%08lx\n", hr ); cleanup: if( pFwPolicy != NULL ) pFwPolicy->Release(); if( pFwMgr != NULL ) pFwMgr->Release(); return hr; }
Figure 8. InitFirewallProfile
Adding the authorized application (Figure 9) works through the Authorized Applications list. An instance of an authorized application is created to add to the list, BSTRs are used to pass in both the fully-qualified file name and the friendly name, and the application is added to the list. Manipulating the Windows Firewall is limited to Administrators on the system, so this call will fail when running as a limited user. This does not pose a problem because installs already require Administrator privilege.
HRESULT FirewallWrapper::AddAuthorizedApp( IN const wchar_t* szFwProcessImageFileName, IN const wchar_t* szFwFriendlyName ) { HRESULT hr = S_OK; BSTR fwBstrName = NULL; BSTR fwBstrProcessImageFileName = NULL; INetFwAuthorizedApplication* pFwApp = NULL; . INetFwAuthorizedApplications* pFwApps = NULL; hr = m_pFwProfile->get_AuthorizedApplications( &pFwApps ); // Create an instance of an authorized application. hr = CoCreateInstance( __uuidof(NetFwAuthorizedApplication), NULL, CLSCTX_INPROC_SERVER, __uuidof(INetFwAuthorizedApplication), (void**)&pFwApp ); // Set the process image file name. fwBstrProcessImageFileName = SysAllocString( szFwProcessImageFileName ); hr = pFwApp->put_ProcessImageFileName( fwBstrProcessImageFileName ); // Set the application friendly name. fwBstrName = SysAllocString( szFwFriendlyName ); hr = pFwApp->put_Name( fwBstrName ); // Add the application to the collection. hr = pFwApps->Add( pFwApp ); cleanup: // Free the BSTRs. SysFreeString( fwBstrName ); SysFreeString( fwBstrProcessImageFileName ); // Release the authorized application instance. if( pFwApp != NULL ) pFwApp->Release(); if( pFwApps != NULL ) pFwApps->Release(); return hr; }
Figure 9. AddAuthorizedApp
Because removing an authorized application is so similar to adding one, the details are omitted here. The wrapper handles it in the RemoveAuthorizedApp method (see Figure 10 and the sample application).
BOOL OnUninstallApplication( IN const wchar_t* szFwProcessImageFileName ) { FirewallWrapper* pfw = FirewallWrapper::Create(); if( !pfw ) return FALSE; HRESULT hr = pfw->RemoveAuthorizedApp( szFwProcessImageFileName ); return SUCCEEDED(hr); }
Figure 10. OnUninstallApplication
Now to launch, the second basic scenario (see Figure 11). The launch case is interesting because connectivity problems are a real headache for gamers. The Windows Firewall state needs to be checked on launch to proactively detect connection problems and give better guidance through better error messages.
if( CanHostMultiplayer( L"%ProgramFiles%\\Combat Flight Simulator\\COMBATFS.EXE") ) printf( "Okay to launch multiplayer.\n" );
Figure 11. The second basic scenario: Launch
First, check for changes since installation. The application may have been disabled or removed from the exceptions list. This checks to ensure the work from OnInstallApplication still holds. The second check is even more interesting in its own way, because users can request the special "on with no exceptions" mode at any time. Hosting will not work if either of these checks fails, including the peer-to-peer scenario as discussed earlier.
BOOL CanHostMultiplayer( IN const wchar_t* szFwProcessImageFileName ) { FirewallWrapper* pfw = FirewallWrapper::Create(); if( !pfw ) return TRUE; if( !pfw->IsAppEnabled(szFwProcessImageFileName) ) { // Application is not enabled in the firewall. // You will not be able host games or join a peer-to-peer game. // Depending on the game, you may be able to join. return FALSE; } if( !pfw->AreExceptionsAllowed() ) { // Firewall is on with no exceptions. // You will not be able host games or join a peer-to-peer game. // Depending on the game, you may be able to join. return FALSE; } return TRUE; }
Figure 12. CanHostMultiplayer
With the wrapper, it's simple to check if exceptions are allowed as seen in Figure 13. The wrapper uses the profile to call get_ExceptionsNotAllowed, fixes up the negative logic, and returns the result.
BOOL FirewallWrapper::AreExceptionsAllowed() { VARIANT_BOOL vbNotAllowed = VARIANT_FALSE; HRESULT hr = S_OK; hr = m_pFwProfile->get_ExceptionsNotAllowed( &vbNotAllowed ); if( SUCCEEDED(hr) && vbNotAllowed != VARIANT_FALSE ) return FALSE; return TRUE; }
Figure 13. AreExceptionsAllowed
Discovering if an application is on the enabled list is more involved (see Figure 14). Get the set of authorized applications, and check for the executable image's file name using a BSTR; if present, check to see if it is enabled. Finally, complete the process by cleaning up.
BOOL FirewallWrapper::IsAppEnabled( IN const wchar_t* szFwProcessImageFileName ) { HRESULT hr = S_OK; BSTR fwBstrProcessImageFileName = NULL; VARIANT_BOOL vbFwEnabled; INetFwAuthorizedApplication* pFwApp = NULL; INetFwAuthorizedApplications* pFwApps = NULL; BOOL bFwAppEnabled = FALSE; // Retrieve the collection of authorized applications. hr = m_pFwProfile->get_AuthorizedApplications( &pFwApps ); if( FAILED(hr) ) goto cleanup; fwBstrProcessImageFileName = SysAllocString( szFwProcessImageFileName ); if( SysStringLen( fwBstrProcessImageFileName ) == 0 ) goto cleanup; // Retrieve the authorized application, find if it is enabled hr = pFwApps->Item( fwBstrProcessImageFileName, &pFwApp ); if( SUCCEEDED(hr ) ) { hr = pFwApp->get_Enabled( &vbFwEnabled ); if( SUCCEEDED(hr) && vbFwEnabled != VARIANT_FALSE ) bFwAppEnabled = TRUE; } cleanup: SysFreeString( fwBstrProcessImageFileName ); if( pFwApp != NULL ) pFwApp->Release(); if( pFwApps != NULL ) pFwApps->Release(); return bFwAppEnabled; }
Figure 14. IsAppEnabled
If you run afoul of the Windows Firewall, the log can come in handy. We made extensive use of it during our development cycle; you can see the Windows Firewall activity in this text log including what packets are dropped and what connections succeed. At the top of the log is a header describing the columns. Turn it on from the Control Panel by double-clicking Windows Firewall, then selecting the Advanced tab, and clicking the Settings button in the Security Logging area. By default, the log file goes to the following location and may be found there:
C:\windows\pfirewall.log
You may be wondering if it is a security risk that applications can add and remove applications from the exceptions list without a popup, or perhaps you think that the bigger risk is that applications can disable the Windows Firewall altogether. To perform these feats, the application must be running in administrator mode. If you have malicious code running in administrator mode on your system, the game is already over. The hacker has already won. Your system is compromised. The hacker's ability to disable the Windows Firewall would merit little more than a footnote.
The Windows Firewall is here to stay as long as the Internet is under attack. With our customers under attack, don't tell them to disable the Windows Firewall. They need an active firewall. Easy is good. Make the firewall seamless for your users by adding your application to the exceptions list during installation. Make the user aware you are doing so; remember, the system belongs to the user. Put the user in the driver's seat. When you uninstall, remove your application from the exceptions list. Give good feedback if your multiplayer code is blocked by the Windows Firewall state. Disable networking features if they won't work either because the application is disallowed or because the system is in the "no exceptions" mode.
Existing applications will be handled through the popup mechanism; you can also patch or supply instructions on your Web site to guide them through the Control Panel dialog. That's what they'll need to do if they want to change their mind after allowing or disallowing a given application.