The Multi-Layer Firewall Daemon (mlfd)

Author: James Tay
Email: james@2longbeans.net
Date: 27th June 2004
Updated: 1st Sept 2004

  • Configuration
  • Building Modules
  • Module Requests to mlfd
  • MLFD Internals


    Description and Introduction

    There are currently many solutions implementing firewalls for layers 2 to 4 (IP, TCP, UDP, etc). Many of them do a wonderful job filtering packets based on policies the administrator configures. Unfortunately, there are certain situations whereby the basis of implementing certain filter rules is not so straight forward, and thus cannot be automated. This may be due to complex business logic, changes in server workload conditions, or recognition of a certain attack pattern.

    The objective of mlfd is to perform analysis of data at layers 4 and above. Many platforms (eg Solaris, Linux, etc) have builtin packet filter capability based on rules expressing policies in terms of layer 2 and layer 3. Mlfd's job is to continue the filter process by doing analysis on layers 4 to 7. The main problem of trying to firewall at this level is, there is absolutely no fixed manner of expressing policies. It will also be almost impossible to design a syntactical means of expressing policies. Since the variables and algorithms to determine allow or deny are almost infinite, mlfd does not attempt to actually perform this analysis. Instead, it passes data from layers 2 and 3 to dynamic modules which perform the actual analysis. Based on the results of this analysis, the modules will then advise mlfd on whether to apply filter policies at layer 2/3.

    Nothing is better than a (naive) example. Let's say you are running a POP service and somebody on the Internet has decided to use your POP service to guess usernames and passwords on your server. As a responsible sysadmin, you've configured your POP server to lockout an account for 15 mins if you get more than 5 password failures in a 1 minute window. Let's say the attacker starts guessing passwords for user "john". Your POP server disables the account after 5 failures. In the meantime, the real "john" tries to POP his mail and fails. Well, as it turns out, this attacker isn't stopping and continues to throw password guesses. Poor "john" will never be able to POP his mail until the attacker stops, or you manually put an IP filter which blocks packets coming from the attacker. What you want, is a daemon which can monitor packets on the POP port, recognise POP password error messages and automatically insert packet filter policies to block out whatever IP he is coming from.

    This is what mlfd is designed to do. A dynamic module is designed to understand messages in a POP conversation. Perhaps it observes a lot of "-ERR Login failed" responses repeatedly sent to a host on the Internet. It then tells mlfd, "please block packets from IP a.b.c.d for a duration of 1 hour." A packet filter policy is inserted by mlfd, and automatically removed after 1 hour.

    Architecture of mlfd

    Mlfd begins by spawning threads for each capture expression the user wants layer 7 analysis for. Packets captured within each thread are read off the wire by libpcap. Once captured, mlfd passes the packet to dynamically loadable modules which will perform analysis. The modules themselves, upon deciding on what needs to be done, can communicate with mlfd, advising it to perform an allow or deny action. Mlfd then interacts with the OS to enforce the actual packet filter policies. A module may also indicate to mlfd that a current TCP session should be immediately terminated. Mlfd will then inject a RST packet for that particular TCP session, causing it to shutdown immediately.

    It is important to note that mlfd does not perform any analysis, but forms the infrastructure for providing a runtime environment to the modules which implement the actual firewall policies. Mlfd provides the following runtime services :

    Interaction Between Mlfd And Modules

    In layer 2/3 firewalls, IP policies form the rules by which allow/deny actions are performed. However, in mlfd, dynamic modules form the program logic which evaluates data in order to determine allow/deny actions. Thus adding or removing rules in mlfd is performed by loading or unloading modules during runtime.

    Action starts when a packet is captured by the pcap expression specified within a particular thread. If not already done, mlfd loads the specified dynamic module and executes a function in the module. The module is passed the payload of the packet. After analysis, the module returns a structure to mlfd. Within this structure there may contain instructions for mlfd to manipulate packet filter policies. After this, the thread sleeps until the next packet is captured by pcap and the process repeats itself. At times, the code within a module needs to pass data it processed to its subsequent calls. Thus, mlfd provides a pseudo malloc facility which allows buffers used within a module to be passed into subsequent calls or subsequent modules within the stack.

    Processing speed of the code within the modules is critical. Assuming a fastethernet network with small 100 byte packets, you will be receiving packets at a rate of 1 every 8 microseconds. On a 1 GHz CPU, this works out to 8000 clock cycles per packet. Since memory access probably will not always hit the CPU's L1/L2 cache, we'll assume an average of 40 cycles per memory operation. This gives you 200 cache-miss memory operations per packet. At the time of writing, mlfd has a worst case throughput of about 2Mbps. This has been tested using gcc -O2, and run on an Ultrasparc IIe. It consumes approximately 2,400 cpu cycles for every byte of data.

    Project Milestones

    Mlfd is currently under development and is not ready for deployment. It currently can function as a proof of concept but there has been insufficient testing to fully explore its behavioral characteristics under various OSes and hardware platforms.

    TaskDateStatus
    Config file parser 6th JulyCompleted
    Multithreaded packet capture is working. Config reloads on HUP 13th JulyCompleted
    MLFD_MALLOC and MLFD_FREE implemented. Pages retained after exit. 17th JulyCompleted
    Act on BLOCK requests from returning modules. 23rd JulyCompleted
    Inject RST upon request from returning modules. 3th AugCompleted
    TCP sequence tracking. 11th AugCompleted
    Packet reinjection upon modules giving the go-ahead. 31st Aug Completed
    Remove IP filter policies upon program exit.   Future work...

    Test Scenerios

    Mlfd has been tested to work under the following situations.

  • Libraries and tools used during compile time
  • C Compilers
  • Platforms
  • Mlfd does not work under the following situations.

  • libnet-1.1.2.1 is broken on Solaris. Mlfd's packet injection fails during runtime with : InjectPkt() libnet_init() failed. libnet_open_raw4(): set SO_SNDBUF failed: No such file or directory. It seems that libnet is trying to find the biggest socket buffer which can be used. In the libnet source libnet_raw.c:142 the code calls setsockopt() with increasingly bigger buffer sizes until it fails with ENOBUFS. Interestingly, a truss shows that the correct errno is returned but the code reports "Bad file number" and gives up.
  • libnet-1.1.1 (and onwards) is broken. TCP RST packets injected by mlfd keep getting TCP header checksum errors.
  • On all x86 machines, whenever mlfd is required to inject a packet whose final destination is the same machine, the packet does not get delivered. So far, self injected packets only work on sparc with mlfd built with libnet-1.1.2.1.
  • Bugs (known ones)

  • Packet injection to VMWARE interfaces does not seem to work correctly. The packet does not seem to arrive at the virtual machine's interface.
  • Listening on the loopback interface does not work. This is because mlfd expects packets to come with ethernet frames.
  • If mlfd receives a signal, threads may (not proven) deadlock because they exit while holding on to mutexes that are still locked.
  • Packet filter policies inserted my mlfd are not removed when mlfd exits.
  • Mlfd should be used on routers, not end points. This is because packet injection has not been sucessfully tested injecting packets meant for the host itself.
  • Bit-masked requests from modules to mlfd are not yet implemented. Thus modules can only make single requests to mlfd for now.