Introduction

Today a colleague of mine asked for some help on an assignment he is working on. He needs the ability to identify if a client is in an IP block. He is using data from blockacountry.com and wants a service to perform different actions based on the client IP.

In our example solution, we will create a reusable WCF service behavior that can be used to deny or allow use of a service based on the client IP address. The service behavior allows us to declaratively add our IPFilter via configuration to any service. We will also create configuration section to support allow/deny by IP, similar to the authorize configuration element in ASP.NET.

Blocking IP addresses should not be done at the application level. If possible, you should use a firewall. If your service/application is hosted in IIS, you can use the built in IP filtering it provides. You can learn more about the IIS IP restrictions here. If you are hosting a WCF service outside of IIS or developing a socket based application, this might be useful to you too.

Background

We first need to go over some IP Address basics. IP addresses are numbers used to uniquely identify computers in a network. IP version 4 address are 32 bits wide but will soon be replaced by IP version 6 which uses 128-bit number for addresses. Today we are going to look at IPV4 only. Even though an IP address is a 32-bit number writing the number as a decimal and separating each byte of the number with a period is the most common way of writing it. This is called dot decimal notation.

Dot Decimal

Hex

127.0.0.1

0x100007F

24.1.5.1

0x1050118

192.168.0.23

0x1700A8C0

An IP address in not only an identifier for a particular host/computer but also an identifier for the network the host is on. IPV4 allows us to segment traffic into smaller groups called subnets. The start of an address is the group/subnet identifier and the end of the address is the host identifier. Each IPV4 address has a corresponding subnet mask or netmask. A netmask is a bitmask used to identify the subnet identifier portion of an IP address.

The netmask can be used to determine how many hosts can be in a subnet. The subnet 255.255.255.0 or 0xFFFFFF00 in leaves us 1 byte or 256 possible addresses (including zero). Some of these are reserved broadcast addresses and cannot be used.

A more efficient way of describing a subnet without including the netmask is CIDR (Classless Inter-Domain Routing) notation. Since the subnet identifier is always at the start of the IP address and must be contiguous, we just need to know the number of bits. Using CIDR notation, we write out the subnet identifier portion of the IP address in dot decimal notation and then a forward slash and the number of bits used by the subnet identifier.

CIDR is not just a notation. Back in the olden days (pre 1993), an IP address network and host identifiers could only be segmented along 8 bit boundaries called classes. The Classless in CIDR comes from the fact that you are not bound by 8 bit classes and can split the bits however you like. You can learn more about it here.

IP Validation Code

Our new code is going to center around a struct called IPRange. This struct will describe the IP ranges. The code for this struct is given below:

We need to support wildcards in the middle of an address. An example would be 192.168.*.11 or 10.*.11.*. My colleague said this is a requirement so we had to implement it. Because of this late addition, I introduced a mode flag to switch between "Class" mode and Classless mode.

Testing for a Match

We test for matches differently depending on the mode. In classless, we simply shift over the host identifier portion of the IP address so we only have the network identifier left and compare. Because class modes wildcards are not tied to the network and host identifiers (ex 192.168.*.1) we need to compare them differently. In class mode we use a bitmask but we also have to check for zeros in the address. Another possible approach is to enumerate through each bit, check to see if it is in a network/wildcard mask, and compare bits if it is. This would give us a uniform solution.

To get all possible IPAddresses in the IPRange, we need to identify the wildcard bits and enumerate through all possible values. In classless mode, we get the count of addresses and loop through these shifting over the index and doing a bitwise OR with the mask. Class mode is a bit more code since the wildcards can be anywhere. In class mode, we enumerate the possible values for each class and do a bitwise OR to produce the final result.

IPFilter

The IPFilter class takes multiple IPRanges and associates Allow/Deny behavior with them. This allows us to validate one IP address against multiple IPRanges. These are evaluated in top down order. The default behavior is returned when no match is found.

The IPFilter is also configurable. Below is some example configuration:

<?xmlversion="1.0"encoding="utf-8"?><!-- Example configuration --><configuration><configSections><sectionname="IPFilter"type="IPFilter.Configuration.IPFilterConfiguration,IPFilter"/></configSections><IPFilter><HttpModuleFilterName="Default"/><Filters><addName="Default"DefaultBehavior="Deny"><denyhosts="192.168.11.12,192.168.1.*"/><allowhosts="192.168.0.0/16"/><denyhosts="*"/></add><!-- A filter than only allows traffic from local network --><addName="LocalOnly"><allowhosts="10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,127.0.0.1/8"/><denyhosts="*"/></add><!-- A filter than denies traffic from local network --><addName="DenyLocal"><denyhosts="10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,127.0.0.1/8"/><allowhosts="*"/></add><!-- A filter than only allows traffic from loopback --><addName="LoopbackOnly"><allowhosts="127.0.0.1/8"/><denyhosts="*"/></add><!-- A filter than denies traffic from loopback --><addName="DenyLoopback"><denyhosts="127.0.0.1/8"/><allowhosts="*"/></add></Filters></IPFilter></configuration>

WCF Service Behavior

A service behavior allows us to hook in to various parts of a WCF service and modify its behavior. Below is the IServiceBehavior interface:

The IDispatchMessageInspector has only two methods, AfterReceiveRequest and BeforeSendReply. AfterReceiveRequest fires when a message first comes in. The message is passed in by ref allowing to replace or set it to null. When the message is set to null, the service method is not invoked. We are going to check the IP Address and if it is in our deny list, we will set the message to null. This method returns an object that is passed in to BeforeSendReply after the service method is invoked allowing us to persist some state between the two methods.

publicobject AfterReceiveRequest(ref Message request,
IClientChannel channel, InstanceContext instanceContext)
{
// RemoteEndpointMessageProperty new in 3.5 allows us
// to get the remote endpoint address.
RemoteEndpointMessageProperty remoteEndpoint = request.Properties
[RemoteEndpointMessageProperty.Name] as RemoteEndpointMessageProperty;
// The address is a string so we have to parse to get as a number
IPAddress address = IPAddress.Parse(remoteEndpoint.Address);
// If IP address is denied clear the request message
// so service method does not get execute
if (_verifier.CheckAddress(address) == IPFilterType.Deny)
{
request = null;
return (channel.LocalAddress.Uri.Scheme.Equals(Uri.UriSchemeHttp) ||
channel.LocalAddress.Uri.Scheme.Equals(Uri.UriSchemeHttps)) ?
_httpAccessDeined : _accessDenied;
}
returnnull;
}

We are not really doing anything in our BeforeSendReply method. If the channel is http, then we set a 401 status code.

Service Behaviors can be applied via code but the ideal way is application configuration. WCF allows us to create strongly typed configuration section for our service behaviors. These configuration sections should inherit from BehaviorExtensionElement. It inherits from ServiceModelExtensionElement which in turn inherits from ConfigurationElement. BehaviorExtensionElement has an abstract method and property that we need to implement. The property returns the type of the class that implements IServiceBehavior. The abstract method should return a new instance of this object. BehaviorExtensionElement sections can be nested under the extensions element in the system.serviceModel configuration section.

Below is some example configuration and our BehaviorExtensionElement in its entirety.

About the Author

Comments and Discussions

Hi, Thankyou for the nice piece of code, i really like this.
My question though, How can we add a RegistryFilterModule for the registry to store the settings instead of app.config ?
I will look forward to your reply.