[net] Hacking the wholism of GNU/Linux net*

Shawn citypw at gmail.com
Thu Jul 28 10:31:10 EDT 2011


hi guys,

   I have written an article[1] for newbies who are willing to learn
networking stuff in linux kernel. I hope this introduction-level
article can help you understand the mechanism of netfilter and NIC
driver.

For this article, any critical comments are welcomed!


[1] http://hfg-resources.googlecode.com/files/hacking_the_wholism_of_linux_net.txt


--
GNU powered it...
GPL protect it...
God blessing it...

regards
Shawn
-------------- next part --------------
root at slackware-13.1:/home/forsaken# uname -a
Linux common-slackware 2.6.33.4-smp #2 SMP Wed May 12 22:47:36 2044
i686 Intel(R) Core(TM)2 Duo CPU     E7500  @ 2.93GHz GenuineIntel
GNU/Linux


|=-----------------------------------------------------------------=|
|=------------=[Hacking the wholism of GNU/Linux net*]=------------=|
|=----------------=[ Netfilter <====> NIC driver ]=----------------=|
|=-----------------------------------------------------------------=|
|=-----------------------------------------------------------------=|
|=-------------------=[ By Shawn the R0ck   ]=---------------------=|
|=-------------------=[ <citypw at gmail.com>  ]=---------------------=|
|=-----------------------------------------------------------------=|
|=------------------------=[ July 7 2011  ]=-----------------------=|
|=-----------------------------------------------------------------=|


--[ Contents

0. Introduction

1. What is iptables/Netfilter framework

   1.1 Hook your packet

       1.1.1 Iptables sample

   1.2 Write your own hook function

   1.3 What can Netfilter do

       1.3.1 Other important components in Netfilter

2. Linux networking systems: A monkey-coder's perspective

   2.1 Initialization of the NIC driver

   2.2 RX packets

   2.3 TX packets

   2.4 Three ways of packet's traveling

       2.4.1 Network to Host

       2.4.2 Network to Network

       2.4.3 Host to Network
       
   2.5 A bigger picture

3. Conclusion

   3.1 Gratitude

4.  References



--[ 0. Introduction

This article will discuss 3 topics of GNU/Linux networking system at
the introduction-level. In the 1st part I will give you a simple way
to introduce Netfiler and iptables. I'm not going further into
userspace tools and just give some cmd-lines of iptables as examples
that you can understand the relationship between iptables and
netfilter. I will also provide some source codes which the original
ones come from one of the great papers in phrack magazine[1]. You can
see bunch of encoded stuff at bottom of this article, but you must try
to use the tools "uudecode"(Phrack guys would not tell you this
because they treated you as hackers || I'm not implying that I treat
you as "users" -_-) to get source code of the netfilter samples which
tested in GNU/Linux kernel 2.6.33[2].

In the 2nd part of this article I will talk about how a packet travels
around in GNU/Linux kernel from NIC driver layer to network stack and
use the REALTEK 8169 NIC driver source code as example, which can be
found in /usr/src/linux-2.6.33.4/drivers/net/r8169.c.

I will follow the principle "read the fucking source code" in
discussion.

I have been using GNU/Linux for 4 years. And I started hacking on
network system( both of user space and kernel space) of GNU/Linux one
year ago. So I'm trying to make the pieces of my notes into one
article you are reading now. Share the information, be free and
opening. "By the community, for the community." should be a hacker's
creed.


--[ 1. What is iptables/netfilter framework

Netfilter is a framework that provides hook mechanism for those who
need to write their own functions for mangling packets within
GNU/Linux kernel.  Iptables is the userspace command-line program to
configure the filtering rules of the GNU/linux kernel. Both of them
are free softwares.


----[ 1.1 Hook your packet

Netfilter can filter the packet of the kernel network stack by
inserting your own kernel modules. Netfilter has 4
tables(filter,nat,mangle,raw) and 5 chains. The hooks is the
implementation of chains in protocol stack.  The different protocol
families(IPv4, IPv6, etc) of hooks linked each other by linked
list. Every table may have multiple policies stored in an array.

The declaration of the symbols can be found in
/usr/src/linux-2.6.33.4/include/linux. These hooks are displayed in
the table below:

Table 1: Available IPv4 hooks

   Hook                 Called
NF_IP_PRE_ROUTING   After sanity checks, before routing decisions. 
		    invoked in ip_rcv().

NF_IP_LOCAL_IN      After routing decisions if packet is for this host.
		    invoked in ip_local_deliver().

NF_IP_FORWARD       If the packet is destined for another interface.
		    invoked in ip_forward().

NF_IP_LOCAL_OUT     For packets coming from local processes on their way out.
		    invoked in __ip_local_out().

NF_IP_POST_ROUTING  Just before outbound packets "hit the wire".
		    invoked in ip_output().


The NF_IP_PRE_ROUTING hook is the first one that will be invoked when
a packet arrive. The different tables will invoke different functions
for different hooks. Finnaly, each function has to deal with the
policy matched by invoking ipt_do_tables() which can be found in
/usr/src/linux-2.6.33.4/net/ipv4/netfilter/ip_tables.c. Netfilter
defined 3 default tables for different uses. You can see the Table
below:

Table 2: Tables, Hooks and Policy

*-------------------------------------------------------------------------------------------*
|-[Table Name]-|----[ Hook Name]----|---[ Policy Functions]---|----[ Description ]----------|
|              |                    | linux-2.6.33.4/net/ipv4/netfilter/nf_nat_standalone.c |              
|              | NF_IP_PRE_ROUTING  | nf_nat_in               | Translation work of ip      |
|----=[nat]=---| NF_IP_POST_ROUTING | nf_nat_out              | address and network port    |
|              | NF_IP_LOCAL_OUT    | nf_nat_local_in         |                             |
|-------------------------------------------------------------------------------------------|
|              |                    | linux-2.6.33.4/net/ipv4/netfilter/iptable_filter.c    |
|              | NF_IP_LOCAL_IN     | ipt_local_in_hook       | Access control for packet   |
|--=[filter]=--| NF_IP_FORWARD      | ipt_hook                |                             |
|              | NF_IP_LOCAL_OUT    | ip_local_out_hook       |                             |
--------------------------------------------------------------------------------------------|
|              |                    | linux-2.6.33.4/net/ipv4/netfilter/iptable_mangle.c    |
|              | NF_IP_PRE_ROUTING  | ipt_pre_routing_hook    |                             |
|--=[mangle]=--| NF_IP_LOCAL_IN     | ipt_local_in_hook       | Tagging the packet to       |
|              | NF_IP_FORWARD      | ipt_forward_hook        | mangling the options like   |
|              | NF_IP_LOCAL_OUT    | ipt_local_hook          | TTL, TOS, etc               |
|              | NF_IP_POST_ROUTING | ipt_post_routing_hook   |                             |
*-------------------------------------------------------------------------------------------*

the filter table Description: What's the most important feature of
Netfilter? Filtering the packet. That's why this table is the most
important. If you want a effective filter rule, insert it to the
first.

the nat table Description: It does exist in 3 chains. The
implementation of Netfilter's nat table is based on connection
tracking for the supporting source NAT, Destination NAT and some
address translation mode including 1-to-1, many-to-1,
many-to-many. Because it's based on connection tracking, so the nat
table would only process new/related packet. The other state's packet
will direct to address translation according to NAT information of
connection tracking. NAT uses different methods to deal with different
protocols. NAT would only modify a few of source IP, destination IP,
source port or destination port if the packet is tcp or udp. But it
will modify the segments of id, type or code if the packet is icmp.

the mangle table's Description: It does exist in 5 chains. Netfilter
does not use the table mangle usually. The mangle table is used to
modify the TTL, TOS or tagging the mark for packet. TTL is used to
calculate how many routers the packet will pass in transportation. TOS
decides the priority of the packet. Tagging mark is used to deal with
policy route when you have more than 1 ISP wired in.

The process of a packet traversing the Netfilter is displayed in the
Figure below:

Figure 1: Traverse the Netfilter

                               +--------------+
                            /->| local socket |--\
  User space              /    +--------------+    \
------------------------/----------------------------\----------------------------
  Kernel space        /                               |
                     |                               \*/                                   
                 +----------------+           +-----------------+
                 | NF_IP_LOCAL_IN |           | NF_IP_LOCAL_OUT |
                 +----------------+           +-----------------+
                              /*\                              |
                               |                               | 
packet-in                      |                               |
   *-------------------*     $--------$                        |                   packet-out
-->|    SNAT           |     | route  |    +---------------+  \*/  *-------------* 
   | NF_IP_PRE_ROUTING | --->| decsion|--->| NF_IP_FORWARD | ----->|    DNAT     |--->
   *-------------------*     $--------$    +---------------+       | POSTROUTING |
                                                                   *-------------*



The hook functions will return some values to tell Netfilter what to
do then, when the hook functions are done. These values are displayed
in the Table below:

Table 3: Return code of hook function

Return Code          Meaning
  NF_DROP        Discard the packet.
  NF_ACCEPT      Keep the packet.
  NF_STOLEN      Forget about the packet.
  NF_QUEUE       Queue packet for userspace.
  NF_REPEAT      Call this hook function again.


------[ 1.1.1 Iptables samples

Iptables is a user-space tool that you can use it for
adding/removing/modifying firewall rules. As I said in the beginning
of this article, I'm not going to dig deeper into it. Read your "man
iptables" if you want details of how to use.

Case 1: Append a rule to the NF_IP_LOCAL_IN hook of the filter table,
which the rule is to drop all packets that source IP address is
192.168.0.10 trying to pass to the Host. Then list the filter table's
rules.

root at slackware-13.1:/home/forsaken# iptables -A INPUT -s 192.168.0.10 -j DROP
root at slackware-13.1:/home/forsaken# iptables -L
Chain INPUT (policy ACCEPT)
target     prot opt source               destination         
DROP       all  --  192.168.0.10        anywhere            

Chain FORWARD (policy ACCEPT)
target     prot opt source               destination         

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination  


Case 2: Insert a rule to the NF_IP_POST_ROUTING hook of the mangle
table, which the rule is to drop all packets that destination IP
address is 192.168.0.10.

root at slackware-13.1:/home/forsaken# iptables -t mangle -F
root at slackware-13.1:/home/forsaken# iptables -t mangle -I POSTROUTING -d 192.168.0.10 -j DROP
root at slackware-13.1:/home/forsaken# iptables -t mangle -L
Chain PREROUTING (policy ACCEPT)
target     prot opt source               destination         

Chain INPUT (policy ACCEPT)
target     prot opt source               destination         

Chain FORWARD (policy ACCEPT)
target     prot opt source               destination         

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination         

Chain POSTROUTING (policy ACCEPT)
target     prot opt source               destination         
DROP       all  --  anywhere             slackware-13.1.org 


----[ 1.2 Write your own hook function

Before invoking the function nf_register_hook(), we need to declare a
structure and initialize it. The declaration of the structure is
nf_hook_ops which can be found it in
/usr/src/linux-2.6.33.4/include/linux/netfilter.h.

struct nf_hook_ops {
	struct list_head list;

	/* a pointer to function */
	nf_hookfn *hook;
	struct module *owner;
	/* protocol family, we use IPv4 in case */
	u_int8_t pf;
	/* which hook point we hook up */
	unsigned int hooknum;
	/* Hooks are ordered in ascending priority. */
	int priority;
};

Your hook function's prototype is like below:

typedef unsigned int nf_hookfn(unsigned int hooknum,
			       struct sk_buff *skb,
			       const struct net_device *in,
			       const struct net_device *out,
			       int (*okfn)(struct sk_buff *));


Let's read the source code to initialize the structure. This snippet
code is part of our source code samples. You might be interested in
reading the complete one. It's quiet easy to understand!

static int init_filter_if()
{
	printk("initializing the hooks!\n");

	/* remember which hook you specified */
	nfho.hook = check_tcp_packet;
	nfho.hooknum = NF_IP_PRE_ROUTING;
	nfho.pf = PF_INET; /* ipv4 protocols */
	nfho.priority =NF_IP_PRI_FIRST;

	nf_register_hook(&nfho);

	return 0;
}


----[ 1.3 What can Netfilter do

Netfilter can do a lot of hacks that will depend on how brilliant
ideas you have. You can do:

---> Implementation of firewall, eg: netfilter/iptables[4] are the best case.
---> Implementation of KIDS[5]
---> Protocol-based and application-based systems, eg: Bioforge's ftp-sniffer is a good example.

I'm a man who is lack of creative imagination. So I just listed these
I knew. I believe you can do more hacks on Netfilter-_-


------[ 1.3.1 Other important components in Netfilter

Of course, connection tracking is one of the important components in
Netfilter framework. Connection tracking provides a kind of mechanism
to track the network connections. Connection tracking is the key of
implementation of the NAT and stateful firewall. The connection state
is completely independent of any upper-level state, such as TCP's
state. Because connection tracking only concerns about packets which
are passing the hooks of PREROUTING and POSTROUTING. Netfilter
connection can be manipulated by the user-space tool "conntrack" and
be used of checking the states with Iptables. Here I list some common
states (Referenced from Wikipedia):

*-------------------------------------------------------------------------------*
|NEW          | trying to create a new connection                               |
|-------------------------------------------------------------------------------|
|ESTABLISHED  | part of an already-existing connection                          |
|-------------------------------------------------------------------------------|
|             | assigned to a packet that is initiating a new connection and    |
| RELATED     | which has been "expected". The aforementioned mini-ALGs set up  |
|             | these expectations, for example, when the nf_conntrack_ftp      |
|             | module sees an FTP "PASV" command.                              |
|-------------------------------------------------------------------------------|
|INVALID      | the packet was found to be invalid, e.g. it would not adhere    |
|             | to the TCP state diagram.                                       |
|-------------------------------------------------------------------------------|
|UNTRACKED    | is a special state that can be assigned by the administrator to |
|             | bypass connection tracking for a particular packet (raw table)  |
*-------------------------------------------------------------------------------*


--[ 2. Linux networking systems: A monkey-coder's perspective

This part will discuss the linux kernel's network sub-systems
including the NIC driver's initialization, delivery/receipt of
packets, IP packet's processing.


----[ 2.1 Initialization of the NIC driver

As ELDD[6] said, "NIC drivers are different from other driver classes
in that they do not rely on /dev or /sys to communicate with user
space. Rather, applications interact with a NIC driver via a network
interface (for example, eth0 for the first Ethernet interface) that
abstracts an underlying protocol stack.". By using the userspace tool
"ifconfig" can manipulate the NIC driver, which provides a set of
interfaces to communicate with NIC hardware (eg: rtl8169_open will be
invoked after running the command "ifconfig eth0 up").

Network Interface Cards usually are treated as the PCI (or USB in a
few cases) device objects in the linux kernel. There's a simple way to
understand the process of initialization of the RealTek 8169 NIC
driver that is displayed in the figure below:

Figure 2: Initialization of the NIC driver

    +---------------------------+
    | Loading the driver module |
    +---------------------------+
                |
                |
               \*/                                                *------*
       +-----------------------+                                  | exit |
       | rtl8169_init_module() |                                  *------*
       +-----------------------+                                     /|\
                |                                                     |
                |                                                     | N
               \*/                                                    |
         +-----------------------+    +-------------------+    $=============$
         | pci_register_driver() |--->| driver_register() |--->$ driver_find $
         +-----------------------+    +-------------------+    $=============$
                                                                      |
                                                                      |
                                                                     \|/
                                                            +------------------+
                                                            | bus_add_driver() |
                                                            +------------------+
                                                                      |
                                                                     \|/
                                                             +-----------------+
                                                             | driver_attach() |
                                                             +-----------------+
                                      Iteration                        |
                                  +--------------------+               |
                                  |  __driver_attach() |               |
                                  +--------------------+               |
                                      |           /|\                 \|/
                                      |            |         $====================$
                                      |            +---------$ bus_for_each_dev() $
                                     \|/                     $====================$
                              +--------------------+                                   
                              | driver_probe_dev() |
                              +--------------------+
                                      |
                                      |
                                     \|/
                              +--------------------+                                   
                              | pci_device_probe() |
                              +--------------------+
                                      |
                                      |
                                     \|/
                             +----------------------+    +------------------+    +-------------------+
                             | __pci_device_probe() |--->| pci_call_probe() |--->| local_pci_probe() |
                             +----------------------+    +------------------+    +-------------------+
                                                                                          |
                                                                                          |
                                                                                         \|/
                                                                                  *--------------------*
                                                                                  | rtl8169_init_one() |
                                                                                  *--------------------*

I suggest that you should read the kernel's Documentation[7], while
looking into the source code. The entry point and exit point of the
implementation in rtl8169 dirver are rtl8169_init_module() and
rtl8169_cleanup_module() defined in the source code:

module_init(rtl8169_init_module);
module_exit(rtl8169_cleanup_module);

The rtl8169_init_module() function will be invoked after "insmod" your
driver module. And you can see the function is only doing one thing:

static int __init rtl8169_init_module(void)
{
	return pci_register_driver(&rtl8169_pci_driver);
}

The argument "rtl8169_pci_driver" is a structure of the pci_driver
which can be found in src/include/linux/pci.h

struct pci_driver {
	struct list_head node;
	char *name;
	const struct pci_device_id *id_table;	/* must be non-NULL for probe to be called */
	int  (*probe)  (struct pci_dev *dev, const struct pci_device_id *id);	/* New device inserted */
	void (*remove) (struct pci_dev *dev);	/* Device removed (NULL if not a hot-plug capable driver) */
	int  (*suspend) (struct pci_dev *dev, pm_message_t state);	/* Device suspended */
	int  (*suspend_late) (struct pci_dev *dev, pm_message_t state);
	int  (*resume_early) (struct pci_dev *dev);
	int  (*resume) (struct pci_dev *dev);	                /* Device woken up */
	void (*shutdown) (struct pci_dev *dev);
	struct pci_error_handlers *err_handler;
	struct device_driver	driver;
	struct pci_dynids dynids;
};

As you see the above structure contains some function pointers to your
own implementations for the NIC drivers. Let's see the rtl8169
driver's initialization for the structure:

static struct pci_driver rtl8169_pci_driver = {
	.name		= MODULENAME,
	.id_table	= rtl8169_pci_tbl,
	.probe		= rtl8169_init_one,
	.remove		= __devexit_p(rtl8169_remove_one),
	.shutdown	= rtl_shutdown,
	.driver.pm	= RTL8169_PM_OPS,
};

The information of the id table "rtl8169_pci_tbl" is related with the
implemenation of PCI architecture in linux kernel. And the prototype
of the rtl8169_init_one() function is displayed below:

static int __devinit
rtl8169_init_one(struct pci_dev *pdev, const struct pci_device_id *ent);

Where did you get these 2 parameters? Don't forget which function is
the caller: As you can see in above figure, it's
local_pci_probe(). These 2 parameters are created in PCI bus
enumerations.

static long local_pci_probe(void *_ddi)
{
        struct drv_dev_and_id *ddi = _ddi;

        return ddi->drv->probe(ddi->dev, ddi->id);
}

The declaration of the _ddi's structure can be found in
src/drivers/pci/pci-driver.c

struct drv_dev_and_id {
	struct pci_driver *drv;
	struct pci_dev *dev;
	const struct pci_device_id *id;
};

The driver will do a lot of things in rtl8169_init_one(), such as
memory mapping, allocation for network device, setting up DMA,
etc. But We have to care about one line of source in
rtl8160_init_one():

	netif_napi_add(dev, &tp->napi, rtl8169_poll, R8169_NAPI_WEIGHT);

This one is about the softirq we will talk about it later.


----[ 2.2 RX packets

While you are trying to turn on (eg: ifconfig eth0 up) a Ethernet
interface, it will try to register a interrupt number by invoking
request_irq() in the rtl8169_open() funcion:

retval = request_irq(dev->irq, rtl8169_interrupt, (tp->features &
			     RTL_FEATURE_MSI) ? 0 : IRQF_SHARED,
			     dev->name, dev);

The NIC hardware will rise a interrupt to CPU when the NIC recieves
packets from network and then linux kernel start to execute the
interrupt handler (we use rtl8169_interrupt() in case) for processing
packets. In this article, we need to know a little concepts of
hardware interrupt. The other work flow of hardware interrupt is a
tough topic that beyond the range of this article. There is another
great paper[8] from phrack, which is worth reading.

After initializing the IDT (Interrupt Descriptor Table) in the kernel
booting stage. When a hardware device raises an interrupt to CPU, the
assembly code will execute at first, then it will jump to the familiar
C code function do_IRQ() which can be found in
src/arch/x86/kernel/entry_32.S:

/*
 * Build the entry stubs and pointer table with some assembler magic.
 * We pack 7 stubs into a single 32-byte chunk, which will fit in a
 * single cache line on all modern x86 implementations.
 */
.section .init.rodata,"a"
ENTRY(interrupt)
.text
	.p2align 5
	.p2align CONFIG_X86_L1_CACHE_SHIFT
ENTRY(irq_entries_start)
	RING0_INT_FRAME
vector=FIRST_EXTERNAL_VECTOR
.rept (NR_VECTORS-FIRST_EXTERNAL_VECTOR+6)/7
	.balign 32
  .rept	7
    .if vector < NR_VECTORS
      .if vector <> FIRST_EXTERNAL_VECTOR
	CFI_ADJUST_CFA_OFFSET -4
      .endif
1:	pushl $(~vector+0x80)	/* Note: always in signed byte range */
	CFI_ADJUST_CFA_OFFSET 4
      .if ((vector-FIRST_EXTERNAL_VECTOR)%7) <> 6
	jmp 2f
      .endif
      .previous
	.long 1b
      .text
vector=vector+1
    .endif
  .endr
2:	jmp common_interrupt
.endr
END(irq_entries_start)

See, the common code starts at label common_interrupt and consists of
the following assembly language macros and instructions:

/*
 * the CPU automatically disables interrupts when executing an IRQ vector,
 * so IRQ-flags tracing has to follow that:
 */
	.p2align CONFIG_X86_L1_CACHE_SHIFT
common_interrupt:
	addl $-0x80,(%esp)	/* Adjust vector into the [-256,-1] range */
	SAVE_ALL
	TRACE_IRQS_OFF
	movl %esp,%eax
	call do_IRQ
	jmp ret_from_intr
ENDPROC(common_interrupt)
	CFI_ENDPROC

Then, we get to the C code do_IRQ() function after executing "call
do_IRQ". By invoking the fuc handle_irq() in do_IRQ() which can be
found in src/arch/x86/kernel/irq.c:

/*
 * do_IRQ handles all normal device IRQ's (the special
 * SMP cross-CPU interrupts have their own specific
 * handlers).
 */
unsigned int __irq_entry do_IRQ(struct pt_regs *regs)
{
	struct pt_regs *old_regs = set_irq_regs(regs);

	/* high bit used in ret_from_ code  */
	unsigned vector = ~regs->orig_ax;
	unsigned irq;

	exit_idle();
	irq_enter();

	irq = __get_cpu_var(vector_irq)[vector];

	if (!handle_irq(irq, regs)) {
		ack_APIC_irq();

		if (printk_ratelimit())
			pr_emerg("%s: %d.%d No irq handler for vector (irq %d)\n",
				__func__, smp_processor_id(), vector, irq);
	}

	irq_exit();

	set_irq_regs(old_regs);
	return 1;
}

Finally, the handle_irq() function calls the rtl8169_interrupt()
function by a function pointer "desc->handle_irq(irq, desc)", which
can be found in src/arch/x86/kernel/irq_32.c. Now the packet got into
the interrupt implementation of the NIC driver:

bool handle_irq(unsigned irq, struct pt_regs *regs)
{
	struct irq_desc *desc;
	int overflow;

	overflow = check_stack_overflow();

	desc = irq_to_desc(irq);
	if (unlikely(!desc))
		return false;

	if (!execute_on_irq_stack(overflow, desc, irq)) {
		if (unlikely(overflow))
			print_stack_overflow();
		desc->handle_irq(irq, desc);
	}

	return true;
}


Many NIC drivers now are using NAPI's strategy that uses polling mode
while many hardware interrupts are rarising in period of time, and
then turn back to interrupt mode when not many packets need
processing. This is the best solution to avoid the large number of
hardware interrupts which might exhaust the CPU. There's a few steps
to go through with it:

1, In interrupt mode, the interrupt handler rtl8169_interrupt() posts
receive packets to protocol layers by scheduling NET_RX_SOFTIRQ:

	if (likely(napi_schedule_prep(&tp->napi)))
		__napi_schedule(&tp->napi);

It then disables NIC intetrrupts and switches to polling mode by
invoking __napi_schedule() to add the devices to a poll list:

/**
 * __napi_schedule - schedule for receive
 * @n: entry to schedule
 *
 * The entry's receive function will be scheduled to run
 */
void __napi_schedule(struct napi_struct *n)
{
	unsigned long flags;

	local_irq_save(flags);
	list_add_tail(&n->poll_list, &__get_cpu_var(softnet_data).poll_list);
	__raise_softirq_irqoff(NET_RX_SOFTIRQ);
	local_irq_restore(flags);
}

Both receipt and transmission methods of softirqs are registered in
net_dev_init() which can be found in src/net/core/dev.c:

        open_softirq(NET_TX_SOFTIRQ, net_tx_action);
        open_softirq(NET_RX_SOFTIRQ, net_rx_action);

2, By invoking rtl8169_poll() in the net_rx_action() function which
can be found in src/net/core/dev.c:

	if (test_bit(NAPI_STATE_SCHED, &n->state)) {
		work = n->poll(n, weight);
		trace_napi_poll(n);
	}

3, In the polling mode, the rtl8169_poll() processes packets in the
ingress queue. When the queue becomes empty, the driver re-enables
interrupts and switches back to interrupt mode by calling
napi_complete():

	if (unlikely(work == weight)) {
		if (unlikely(napi_disable_pending(n))) {
			local_irq_enable();
			napi_complete(n);
			local_irq_disable();
		} else
			list_move_tail(&n->poll_list, list);
	}


Figure 3: The process of RX packets

---------------------------------------------------------------------------------------------------------------------------
       +-----+                                              |
       | NIC |                                              |
       +-----+                                              |
          |                                                 |
          | Raise a interrupt                               |
         \|/                                                |
    *-----------------------*                               |
    | CPU-1 | CPU-2 | CPU-N |                               |
    *-----------------------*                               |
                        |                                   |
                        | Interrupt[n]                      |
                       \|/                                  |    *---------------------*
                  +-----------+                             |    | softnet_data[CPU-n] |-----------+
                  | do_IRQ(n) |                             |    *---------------------*           |
                  +-----------+                             |            /*\                       |
                        |                                   |             | add to poll list       |
                       \|/                                  |             |                        | raise softirq
                +--------------+    +---------------------+ |    +-------------------+             |
                | handle_irq() |--->| rtl8169_interrupt() |----> | __napi_schedule() |             |
                +--------------+    +---------------------+ |    +-------------------+             |
                                                            |                                     \|/
                                                            |                             +-----------------+
                                                            |                             | net_rx_action() |          
                                                            |                             +-----------------+
                         $================$                 |  $==============$                    |
                         $ Interrupt mode $                 |  $ Polling mode $                    |
                         $================$                 |  $==============$                   \|/
                                /*\                         |                            +----------------+
                                 |                          |                            | rtl8169_poll() |          
                                 |                          |                            +----------------+
                                 |                          |                                     |
                                 |                          |                          Work of RX |
                                 |                          |                                    \|/  
                                 |                          |                            +------------------+
                                 |                          |            +---------------| eth_type_trans() |
                                 |                          |            |               +------------------+
                                 |                          |           \|/
                                 |                          |      $===========$   Y   +-----------------------+            
                                 |                          |      $  IS_VLAN  $------>| rtl8169_rx_vlan_skb() |
                                 |                          |      $===========$       +-----------------------+
                                 |                          |            |                        |
                                 |                          |          N |                        |
                                 |                          |           \|/                       |
                                 |                          |   +---------------------+           |
                                 |                          |   | netif_receive_skb() |---------->|
                                 |                          |   +---------------------+           |
                                 |                                                                |
                                 |                                                               \|/
                                 |              Yes, done the packet processing         $-----------------$
                                 +------------------------------------------------------| napi_complete() |
                                                                                        $-----------------$


There will be 5 steps to start running when the packet travels to
rtl8169_rx_interrupt:

1, netdev_alloc_skb() in rtl8169_alloc_rx_skb(), allocate a
receive buffer

2, skb_reserve(), add a 2-byte padding between the start of the packet
buffer and the beginning of the payload for align with IP header which
is 16-byte.

3, NIC hardware maps a memory space from DMA to memory. Copy the data
in DMA into a preallocated sk_buff when the data arrives.

4, skb_put(), extend the used data area of the buffer.

5, netif_receive_skb(), enqueue the packet for upper protocols/levels
to process.


----[ 2.3 TX packets

This part is not going to discuss the whole protocol stacks in linux
kernel. We intend to focus on how the driver layer works in the
process of transmitting packets when it crosses the POSTROUTING.

Each NIC has its own buffer for packets (ring buffer). Kernel will
write packets into the buffer and send TX instructions to control
register. The NIC takes packets from the buffer and hits the wire. The
linux kernel will copy the packets to kernel space by invoking the
memcpy_fromiovec() function which is invoked by packet_snd() function,
which invoked is by the packet_sendmsg() function, which the source
code can be found in src/net/packet/af_packet.c, when upper-level
protocols have already prepared the packet:

	err = memcpy_fromiovec(skb_put(skb, len), msg->msg_iov, len);

The initialization of the structure proto_ops is in
src/net/packet/af_packet.c. This structure includes function pointer
to kernel implementations:

static const struct proto_ops packet_ops = {
	.family =	PF_PACKET,
	.owner =	THIS_MODULE,
	.release =	packet_release,
	.bind =		packet_bind,
	.connect =	sock_no_connect,
	.socketpair =	sock_no_socketpair,
	.accept =	sock_no_accept,
	.getname =	packet_getname,
	.poll =		packet_poll,
	.ioctl =	packet_ioctl,
	.listen =	sock_no_listen,
	.shutdown =	sock_no_shutdown,
	.setsockopt =	packet_setsockopt,
	.getsockopt =	packet_getsockopt,
	.sendmsg =	packet_sendmsg,
	.recvmsg =	packet_recvmsg,
	.mmap =		packet_mmap,
	.sendpage =	sock_no_sendpage,
};

Linearize the buffer and do the checksum by invoking dev_queue_xmit(),
which can be found in src/net/core/dev.c:

	/* GSO will handle the following emulations directly. */
	if (netif_needs_gso(dev, skb))
		goto gso;

	if (skb_has_frags(skb) &&
	    !(dev->features & NETIF_F_FRAGLIST) &&
	    __skb_linearize(skb))
		goto out_kfree_skb;

	/* Fragmented skb is linearized if device does not support SG,
	 * or if at least one of fragments is in highmem and device
	 * does not support DMA from it.
	 */
	if (skb_shinfo(skb)->nr_frags &&
	    (!(dev->features & NETIF_F_SG) || illegal_highdma(dev, skb)) &&
	    __skb_linearize(skb))
		goto out_kfree_skb;

	/* If packet is not checksummed and device does not support
	 * checksumming for this protocol, complete checksumming here.
	 */
	if (skb->ip_summed == CHECKSUM_PARTIAL) {
		skb_set_transport_header(skb, skb->csum_start -
					      skb_headroom(skb));
		if (!dev_can_checksum(dev, skb) && skb_checksum_help(skb))
			goto out_kfree_skb;
	}

And queue a buffer for transmission to a network device by invoking
the __dev_xmit_skb() function, as you can see below code snippet:

		/*
		 * This is a work-conserving queue; there are no old skbs
		 * waiting to be sent out; and the qdisc is not running -
		 * xmit the skb directly.
		 */
		__qdisc_update_bstats(q, skb->len);
		if (sch_direct_xmit(skb, q, dev, txq, root_lock))
			__qdisc_run(q);

Finnaly, raise a softirq of TX by invoking the __netif_schedule()
function in __qdisc_run():

	while (qdisc_restart(q)) {
		/*
		 * Postpone processing if
		 * 1. another process needs the CPU;
		 * 2. we've been doing it for too long.
		 */
		if (need_resched() || jiffies != start_time) {
			__netif_schedule(q);
			break;
		}
	}

In the func __netif_reschedule(), the softirq has been raised:

	raise_softirq_irqoff(NET_TX_SOFTIRQ);

The softirq handler is registered, while kernel is booting, which can
be found in the func net_dev_init() in src/net/core/dev.c:

	open_softirq(NET_TX_SOFTIRQ, net_tx_action);

net_tx_action() calls qdisc_restart() which has:

	HARD_TX_LOCK(dev, txq, smp_processor_id());
	if (!netif_tx_queue_stopped(txq) && !netif_tx_queue_frozen(txq))
		ret = dev_hard_start_xmit(skb, dev, txq);

	HARD_TX_UNLOCK(dev, txq);

dev_hard_start_xmit() will call the func rtl8169_start_xmit() by
invoking *->ndo_start_xmit():

		rc = ops->ndo_start_xmit(skb, dev);

Then your dirver's implementation of transmission
function(rtl8169_start_xmit()) will be invoked. The process of the
figure is displayed below:


Figure 4: The process of TX packets


                       $=======================$
-----------------------$ Upper-level protocols $-------------------------------------------
                       $=======================$
                                   |
                                   |
                                  \|/
                         +------------------+
                         | packet_sendmsg() |
                         +------------------+
                                   |
                                  \|/
                         +--------------+
                         | packet_snd() |
                         +--------------+
                                   |
                                  \|/
                         +--------------------+      +------------------+      No queue
                         | memcpy_fromiovec() |----->| dev_queue_xmit() |----------------+
                         +--------------------+      +------------------+                | 
                                                             |                           |
                                                             | Y                         |
                                                            \|/                          |
                                                     +------------------+                |
                                                     | __dev_xmit_skb() |                |
                                                     +------------------+                |
                                                             |                           |
                                                            \|/                          |
                           +-----------------+          +---------------+                |
                +--------> | qdisc_restart() |<---------| __qdisc_run() |                |
                |          +-----------------+          +---------------+                |
                |                  |                                                     |
                |                  |                                                     |
                |                 \|/                                                    |
                |      +--------------------+                                            |
                |      | __netif_schedule() |                                            |
                |      +--------------------+                                            |
                |                  |                                                    \|/
                |                  |           +-----------------+     +-----------------------+
                |                  +---------->| net_tx_action() |---->| dev_hard_start_xmit() |
                |              Raise a softirq +-----------------+     +-----------------------+
                |                                      |                           |
                |                                      |                          \|/
                |                                      |                 +----------------------+
                |              If Queue is not empty   |                 | rtl8169_start_xmit() |
                +--------------------------------------+                 +----------------------+



----[ 2.4 Three ways of packet's traveling

Linux kernel supports many network protocols which have different
implementations. We will only use IPv4 to descrbe 3 ways of packet
flows which bypass the netfilter.


------[ 2.4.1 Network to Host

Firstable, register the handlers for different protocols by using the
dev_add_pack() function while kernel is booting, such as IPv4's
handlers registration in the inet_init() function which can be found
in src/net/ipv4/af_inet.c:

	dev_add_pack(&ip_packet_type);

Which the structure has defined in the same source file:

/*
 *	IP protocol layer initialiser
 */

static struct packet_type ip_packet_type __read_mostly = {
	.type = cpu_to_be16(ETH_P_IP),
	.func = ip_rcv,
	.gso_send_check = inet_gso_send_check,
	.gso_segment = inet_gso_segment,
	.gro_receive = inet_gro_receive,
	.gro_complete = inet_gro_complete,
};

After registering the protocol handlers, the .func function pointer
will be invoked by netif_receive_skb() in driver's softirq handler (we
use rtl8169_rx_interrupt() in case):

	if (pt_prev) {
		ret = pt_prev->func(skb, skb->dev, pt_prev, orig_dev);

After packet's sanity checking, the packet goes to the
NF_IP_PRE_ROUTING hook for filtering rules. Then it will enter into
the ip_rcv_finish() function, which looks up the route depending on
destination IP address. If the destination IP address is matches with
local NIC's IP address, the dst_input() function will brings the packets
into the ip_local_deliver(), which will defrag the packet and pass it
to the NF_IP_LOCAL_IN hook:

/* Input packet from network to transport.  */
static inline int dst_input(struct sk_buff *skb)
{
	return skb_dst(skb)->input(skb);
}

In the end, invoke the protocol handler in the
ip_local_deliver_finish() function:

			ret = ipprot->handler(skb);

Then, the upper-level protocol will continue to process the packet.
 

------[ 2.4.2 Network to Network

After the filtering of the NF_IP_PRE_ROUTING hook, look up the route
by invoking the ip_rcv_finish() function, and through the
skb_dst(skb)->input() enter into the ip_forward() function which does
validate checks including checking the packet type:

	if (skb->pkt_type != PACKET_HOST)
		goto drop;

Decrease the TTL, check whether the packet is allowed to defragment,
check the length of the packet which should not be bigger than MTU,
etc:

	/*
	 *	According to the RFC, we must first decrease the TTL field. If
	 *	that reaches zero, we must reply an ICMP control message telling
	 *	that the packet's lifetime expired.
	 */
	if (ip_hdr(skb)->ttl <= 1)
		goto too_many_hops;

	if (!xfrm4_route_forward(skb))
		goto drop;

	rt = skb_rtable(skb);

	if (opt->is_strictroute && rt->rt_dst != rt->rt_gateway)
		goto sr_failed;

	if (unlikely(skb->len > dst_mtu(&rt->u.dst) && !skb_is_gso(skb) &&
		     (ip_hdr(skb)->frag_off & htons(IP_DF))) && !skb->local_df) {
		IP_INC_STATS(dev_net(rt->u.dst.dev), IPSTATS_MIB_FRAGFAILS);
		icmp_send(skb, ICMP_DEST_UNREACH, ICMP_FRAG_NEEDED,
			  htonl(dst_mtu(&rt->u.dst)));
		goto drop;
	}

Then, by invoking the ip_forward_finish() call the skb_dst(skb)->ouput
entering into the ip_output() after the NF_IP_FORWARD hook. Finally,
it will arrive the NF_IP_POST_ROUTING hook.


------[ 2.4.3 Host to Network

This is the last type of the direction of packet's traveling. When
userspace program uses socket to send the packet, the packet will
traverse a lot of functions into the NIC driver in the end.

The ip_queue_xmit() function is the key function in network layer
which is provided by linux kernel. The packet looks up the route in the
ip_queue_xmit() and sets some segments, such as defragment flag,
then passes it to the NF_IP_LOCAL_OUT hook for filtering:

	/* OK, we know where to send it, allocate and build IP header. */
	skb_push(skb, sizeof(struct iphdr) + (opt ? opt->optlen : 0));
	skb_reset_network_header(skb);
	iph = ip_hdr(skb);
	*((__be16 *)iph) = htons((4 << 12) | (5 << 8) | (inet->tos & 0xff));
	if (ip_dont_fragment(sk, &rt->u.dst) && !ipfragok)
		iph->frag_off = htons(IP_DF);
	else
		iph->frag_off = 0;
	iph->ttl      = ip_select_ttl(inet, &rt->u.dst);
	iph->protocol = sk->sk_protocol;
	iph->saddr    = rt->rt_src;
	iph->daddr    = rt->rt_dst;

Then, invoke the ip_output() function by dst_output() function which
can be found in src/include/net/dst.h:

/* Output packet to network from transport.  */
static inline int dst_output(struct sk_buff *skb)
{
	return skb_dst(skb)->output(skb);
}

After filtering in the NF_IP_POST_ROUTING hook, the packet will
deliver to the lower layer handlers.


----[ 2.5 A bigger picture

Now, hope the last figure can help you understand the complete process
of a packet's rx/tx:


+-------------------------------------------------------------------------------------------------------------+
|                       A P P L I C A T I O N                L A Y E R                                        |  
+-------------------------------------------------------------------------------------------------------------+
                        /*\                                                                      \|/
                         |                                                               +-----------------+
                         |                                                               | ip_queue_xmit() |
                         |                                                               +-----------------+
                         |                                                                        |
   ipprot->handler(skb)  |                                                                       \|/
          +---------------------------+                                                  +----------------+             
          | ip_local_deliver_finish() |                                                  | ip_local_out() |
          +---------------------------+                                                  +----------------+
                      /*\                         +---------------------+                         |
                       |                          | ip_forward_finish() |------+                 \|/
               $================$                 +---------------------+      |          $=================$
               $ NF_IP_LOCAL_IN $    +-------------+     /*\                   |          $ NF_IP_LOCAL_OUT $
               $================$<---| ip_defrag() |      |                    |          $=================$
                       /*\           +-------------+      |                    |                   |
                        |               /*\          $===============$         |                  \|/
               +--------------------+    |           $ NF_IP_FORWARD $         |            +--------------+
               | ip_local_deliver() |----+           $===============$         |            | dst_output() |
               +--------------------+                   /*\                    |            +--------------+
                       /*\                               |                     |                   |
        dst_input(skb)  |                      +--------------+                |                  \|/
                +-----------------+            | ip_forward() |                +---------> +-------------+
                | ip_rcv_finish() |-------+    +--------------+                            | ip_output() |
                +-----------------+       |        /*\                                     +-------------+
                       /*\               \|/        |                                             |
                        |            +------------------+                                        \|/
               $===================$ | ip_route_input() |                               $====================$
               $ NF_IP_PRE_ROUTING $ +------------------+               +---------------$ NF_IP_POST_ROUTING $
               $===================$                                    |               $====================$
                         /*\                                           \|/
                          |                                    +--------------------+     
+-----------+          +----------+                            | ip_finish_output() |
| arp_rcv() |          | ip_rcv() |                            +--------------------+
+-----------+          +----------+                                     |
  /*\                     /*\                                          \|/
   |                       |                                   +---------------------+
   +----+             +----+                                   | ip_finish_output2() |
        |             |                                        +---------------------+
        |             |         L A Y E R  III                          |
        |             |                                                 |
--------|-------------|-------------------------------------------------|-----------------------------------------------
        |             |                                                 |
     +-----------------+                                       +------------------+
     | net_rx_action() |                                       | dev_queue_xmit() |
     +-----------------+                                       +------------------+
             /|\                                                        |
              |                                                        \|/
              |                                                +------------------+
     +-----------------+                                       | __dev_xmit_skb() |
     | __napi_schedule |                                       +------------------+
     +-----------------+                                                |
              /|\                                                      \|/              
               |                                              +---------------+       +--------------------+
      +---------------------+                                 | __qdisc_run() |------>| __netif_schedule() |
      | netif_receive_skb() |                                 +---------------+       +--------------------+
      +---------------------+                                                                  |
              /|\                                                                             \|/
               | Got a packet(s)                                                  +-----------------------+
      +----------------------+                                       +------------| dev_hard_start_xmit() | 
      | rtl8169__interrupt() |                                       |            +-----------------------+
      +----------------------+                                      \|/
             /|\                                              +----------------------+
              |                                               | rtl8169_start_xmit() |
              |               L A Y E R  II                   +----------------------+      
              |                                                      |
--------------|------------------------------------------------------|----------------------------------------
              |                                                      |
              |                                                      |
              |                                                     \|/
$------------------------------------------------------------------------------------------------------------$
|           H A R D W A R E                  L E V E L                                                       |
$------------------------------------------------------------------------------------------------------------$




--[ 3. Conclusion

Netfilter is an excellent framework on both design and implementation
for networking. But netfilter has one well-known flaw is that the
connection tracking costs too much. I've tested a machine with two
4-cores CPU, 3 Gigabytes memory, Intel 8xxxx NICs. The result of the
testing was that processing the bidirectional 64-byte packets was
640Mbps without connection tracking and 330Mbps with connection
tracking. It seems to need optimization if your requirements are
performance-sensitive.

Nowadays, many commercial companies are using a cheap combination of
X86 hardware and GNU/Linux to develop their networking products. But
there are still many commercial networking products violating the
GPL. That's shame!


----[ 3.1 Gratitude

This article is dedicated to my neurons. I can't do nothing without
their faithful support. And, I must thank those great hackers
including Phrack's authors, RMS, John Carmack, LulzSec, etc. You guys
really inspired me to keep hacking on Purpose/Hack/Life. Finally, I
thank my beautiful wife for proofreading the article and helping me
fix the grammar errors.


May L0rd's hacking spirit guide us!!!


--[ 4 - References

[1] Hacking the Linux Kernel Network Stack
     bioforge. Phrack Vol 0x0b, Issue 0x3d, Phile #0x0d of 0x0f
     http://www.phrack.org/issues.html?issue=61&id=13#article

[2] GNU/Linux kernel 2.6.33.4 source code

[3] Understanding the Kernel Network Layer
    Breno Leitao& Arnaldo Carvalho
    http://stoa.usp.br/leitao/files/-1/3689/network.pdf

[4] Netfilter/Iptables Firewall
    http://www.netfilter.org/

[5] Kernel Intrusion Detection System
    http://sourceforge.net/projects/ids-kids/

[6] Sreekrishnan Venkateswaran
    "Essential Linux Device Driver", Chapter 15: Network Interface Cards

[7] Kernel Documentation
    /usr/src/linux-2.6.33.4/Documentation/driver-model/*.txt
    /usr/src/linux-2.6.33.4/Documentation/PCI/pci.txt

[8] Handling Interrupt Descriptor Table for fun and profit
    kad. Phrack Vol 0x0b, Issue 0x3b, Phile #0x04 of 0x12 
    http://www.phrack.org/issues.html?issue=59&id=4#article


begin 644 netfilter_hacks.tar.gz
M'XL(`,($"4X``^P\:W/;.)+Y*E7E/R#>22+:LBS)KSDK]J[CR(EJ'%MK.YO:
MFZ18-`5)7%$DEZ#L:&=\O_VZ&R`)/B0GMTYV[TY,8DM$H]%`/]#=:,3CT=!Q
M(QZ:8\N>B*TGW^%IPK._NTN_X<G_IL^MUEZSW8:_^]M/FJWV?FOG"=O]'L3D
MGYF(K)"Q)Z'O1\O@'FK_7_IX.?Y[0^$YP^&CRL'7\K^UO[N_B^];.]N[*_[_
MD&<1_]];$PX-_#'&>(C_.SL[Q/_F;GM[>[L-_`=9:#]AS<<8_*'G_SG_?^E>
MGG?/S#>]R\.MF0BW1&AON8XW^[+9;NPUMK<;.U4%\JY[_*9[><4.V:;[4RWM
M9VPYGNW.!KQ:]6_^MCD].%0RU/"K_8]O`/ZGFAASUV7!W<"H5BW7/:A6?JJ]
M/_ZE:[#-$Y9!QMX?_E2#;@:;^H.9RT6U,K)MMNFS$8^"._FS85>KMLLM#Q"%
M4[89#MEZ8^)K7_3/@*BQGGQ_3VBU%VJ<^,U0#I$TP_M_-8^^Y[-(_V,>VH\P
MQG+];S=W]YJ)_K>D_K=V]E;Z_R.>K75V/78$"T)_%%I3=N>`HHJQ?\?F_HQ9
MS)D&+I]R+[(BQ_>8/V3#*&`D&SQD=V/''K,;2_!!E:VS1)C8$'#Q.S^<--@[
M*PCF#*7+\4;/``PAI[XWX?--VQ_P\.!J;-UY+!IS=MFT)W^R_6#N\F%49Z]L
M)YH'=W\:32W';=C^]`C[XC^BV0^=D>-9+D,TT-^*F!.]%#"X/X5A:4!">X8&
MC?W"0X^[[)Q'2!BAN8H`2O:\"YTHXAZ[F;,;QQ_ZX8 at WKL>6-Q')]SJS;OQ9
MA"L3LE'(H9<51 at Y8HH::5@^MB3-T^(#&%0!H<TG>%#94((\-X9\C9SLA at LC0
MLF,/NG`104]+$&UW:#(!\NWY!VF1$:Z]6V?T^S\:>\R"/K&55A1L5:M_4.:8
MO:)>6]*^-<9'A18Y?EF+F-S,0/E+6ARO]&U0]C:R2U\[]K3T/4C/@-\Z=BFM
MB6C)QN&`#YEIJHW#K/YAYN7?+$)@.L'M#F'A'O`*%@QZ.AY?UMD9FE98/IFA
MR8&7X8*V`.2+1]A8?7_QYL-9USSKG73/K[JUM;?]LS6CDP[__OAM[\0\N7C3
M98PUO^R^3EHNN_VSOYI7O?_$ENV]M$OOY'W?[!__]>SB^(UJKXTCWQ,U)S#'
M@[`F;HS-H\B/3)=[!OM4K50J3#Z;3#C_X/ZP)J)P9H-(!@#_``CP#8%@#T>K
M\:Y[U66WECOC`M2`LYE`J?=!JGE`XOWAJGOI at 1D@.>T?7UV!V at W8S(L<%^4;
M0.;4\>\S'H+&--B%Y\Z9[\F>6]B#!9832IMTPQDX$:`=$8(0`D?ACMO1)0B!
M"-^S-:R@$P*ME\WL,5CZ=:`S)+(.V?F'L[-.MC6PA"`Z<ZV.%S$P8K?<))(.
M6;-38;`,[ZT0+(0S!'5EE at LV83`G,#"=!(@:B:L5*G/D>&!*IF1,U81=?T03
M3I8)[,5T"E^$-$PC'U<55 at OG+)#P7I]9 at T'(A:`^UR=]%OAAI$]UY at EGY,%:
M(-VPP8%/`W)/9!=`P-J'"1`BDF!(]@?%TP$7=NC`$J/E.T^L_-CW8?(T*DF(
M-S3QE>D'@L&.PNE;)Y8GP-?S8&ZX#(OZ^"(J=+J812-?=2*J3F>>3;L1K8]+
M1*`M]IB8F&BXU%X at V,3S<6?Q43J@^?0:5DIJ)"[F&?4$?B3BFO(`;+@[D.N+
MEELP,0NYE-D;/QHCCSAM-$J426['0#Z:;P<,BVU%M/S4J!;WRY<O,>*45;>^
M,P#AX_;$A*TU5K9X)NM at BHWJ;U58"M4"5A6TD*W#[PZ^EG([L"*+OB+#0=LE
M#]5W)_F&_Z`C?*_ET!DU&&GS"/&P#988D`E:$&?LPBQW#(-P$`@@D`,;M1H,
M82!2[`8?:_`9$/E(O>Q4E8P\]^_J2E64BB2ZJ%2GCJOE:0")Z#80!T-]@VU4
M\0)X')'O0BQ%?H.<1@[*=,Q4TA=D"RD'+L>0U>)1#<))KS+3':!VL6>'FN+\
M_CNC6:D=/6U#?5%X&`MY-`N]9*'EE%D4SE$$!><X%%$,?RTI<$"G,G0TAY1(
M8(\'!K>&JUUG:P2\5F>[!CL$9AKL-Y8\4E'E0DH$,9<V#MEN1[U0OS(+D"$Y
MA0&WSN6LMEY3TN`8...7G\*7[,4+5GCMO:Q*=2UK;%(?A[UB+:#]-X`$Z=S8
MZ,`'!W_)$>^+1-8T0SV90M3HVS44[`W6KK.WIWVU6QNT(&BK#8C9,K.9\JG@
M48*G#KMJLUEG"HFA@=G!7`.32XY;9 at RRGA*S00U,3DTW4T@"N'`AV#=0?<6(
M>\9=P<L82ES/,Q30].,-J(R3U=22#G7U&5O">QF!E8.IP7XR`@V8\T at JT\#'
M)A1";`$[&DOK.NU1^=%T^4A6-B<F"!/W_'>2(5UZM(W\GY*>&,\#TI."E4E/
M0LQC2<^?/_2N07AV$N%))>//,X at PE`L!K+&Y<XMN4*_4[MY`../YB1C44]D@
M9XIL:&JD"2$L"N.W/)R#)9.;<KKJB8-4D(J,6`$SGZ7R at _R<#$/.4[N$G"WX
M:96<CY)S:RHY]PQ>P-\L%??:JL9+ID%("4)2GR7(E7'7!RMN%IU\/VU;R!&=
M[B*=9#1]89)UD;V3.8&<5S1Y>9?ZF,#;D>?#CNAC("*4AUW!9SW>(&%-P$9(
M`X$[+6V'6UL%MLEWZ+S!3CZIK=$PB?0BR#/&/ARPY^"LL3[^_N2!'*:V,R&_
M4[W/.FLV**&,;E&H^A=7U^;EQ8?KWOE;5G,M$1GD3L)D(NG0DTN$&SEZ6.BY
M1:$U'(+#1%9-@'3:8^4$J`;<],L]Z86N\9T5V6,3 at OI:YC52XLVF]30:*G'+
MM%8;8JXHAH%(TY2!+%MWO*^!@O$U,"2 at MNY/AIY1\`:-K"^8DG,#L at 4T=98X
MBLHET3VHV!6A$$)Z($,G%+H?HD>20>A'ONV[:)5[_?[EQ?6%"5V5J$I%8N>G
MYO')2;=_G9'7<S\`Z?#\B,:2Z)<YHS"@=$8-W1LM.J.:GR4%)O6T*!>4\?JU
M:4D?%=Q%G(N,F=LMX]MF at HC3F=#>'0H*$O0Q42I#[O);"SWQ-/Q3GK#<GI7_
MJYS<@G?L#;)*J'NSSS3ME6*6QA(WF47ZR"G>'CJ>(\8<X^9H3-1*2G'3P at P:
M1IQ(7B1`/>9JI,*2H'JC?O=#W^:#Q"$GA>(8`JE(#W,4&0W%`=?>6R/'7M.#
ML8^HU7'<%F];=5RAZ(Y;$^G-3VZ4<--XR&E8&0O``W>.2&XL*0$('/*_SR@<
M&*FXC#)LKJO%KY)BP!OY+H]#;$73<HOA>`\8C*7VXJO,Q==9B\<R%BJY`R3`
M!RVNM`.30DM=`X!5L/QWF-@,YM(]A35W/,D')3"9Y<%=$+=)#<DUG\*6:(%;
MVNO#\KD#+I,ELO6-C_C!S?#RRK"5400<&$;%O14<WC\N4XNB!--K-6!/2',(
M]H(D5DK!'[_!$&*W94,1'EA<S=PEBVXDY at Z]WA)CE\300*=#CKW,%Y;1"4 at W
MCRCG#-1I646(865;-`^HC=*'W9-W%Y)J:"\F%%]IV4<CYS/E)GBOF9J+B07!
M_%09`\P:D562V1:I_1X78 at T-Z1VR>N@,!J"#:)*4$90*K_(Y*%X@`'+F:?ZK
M3EI=+AIUA4?<60%V4:$[=I!I`FF%%28N<SV4RP5E2U\K++"R'A at E(5!BR920
MO0HM3TP50V)[CZ</I"&QQ5$H4KN3F!P'[$Q_S.\:C=BBDYYH/B8*@4A=S/S;
M'.2@%'*@(*4.DB2AN`63R"11.`1GZ>27[K4)[MC;"W#(%`PP`UPL*9K\5LI-
M+`.V!3O=\67_W>4;L]_O'V1W2,E.-+T>YP,!FUPF5+B!S6[2*>`YN[CHOP9*
M#@HMW>MWW<N#:N4W"A(3TT(F*C+'=SBQ7P'(/#[KGG_N$!3]0'_'OY5[,JE7
M`)20Z??EQWBW`&(GS+7FF-6$G5@:HXI:J3CCE1EXW0!AB1>YD\!2?'C(D)AW
M0$R';6W%:71HG5IV(Y8QU4D%D/$TZJRFX=T\&ILHK4:=Q=/+=EL`G'\MA=_`
M?8-])::X"TLITSJJ]56<K,C`"E[K_ACN#GE]);7YD(0+]#516^"'GW75U/:3
M23>J#V1+-Q:=49!TJ9DI)'7V(HGBZF1366D^,-L+AMC1`YS67MHQ%ZL5.K:;
M:4"4]E1+=*V\[SNR%11,"QGY#!R5*50')#+['?EQ#O04OF*@9X<^6*A!:CQ`
M2TUP>F;<_`)&2<JEQA",L556%'RK!>YE&N2GX;3$7HC1DW=:T(?JF0;NA=,4
MEC]$2>*`9:&]C$KCF#3)D,$N%#K@(PP@!DV7-MV;KJXO4`/14Z4LN.=$ICP-
MK4G/*,:(+8[E.O^0SJ(_$0G&^!2C at 3^(RX>)"Y@%"(;*`H(Y/35[Y]WK7'OH
M^*$3S7$MH!WVV<N>>=J[O+HN#@2N)-/@NG&DG$PR.2 at IT@7N82<+LHBR%.`!
MTC*#96G3POB$.&]HAGSD"*HJ at 1ZU%_'DY)J6M,<C%-C8)/;),Q*L^)D%Y2R\
MF?,<YV"4F;=\G%*0#*D+-+U,[$LL24%C[O_M:HD6U?^H.JM'&>.!^K_]O1VL
M_]G!\M_]O=V=)\W6[MY>:U7_\R,>V!J0U2#)#9M._+'>A\TBQT5[$%'9W:+8
M"SW=I_'Y9^K=8A`^\,'/QR2(JA1JL+]P<)PG[FPP at L``T\X<WML11/D-PO$>
M5!."1]C'G2#`$SQTHZ?SV&W'N*'G#?V^,R)XZO.Q6+.#90MX*`^.:[O9W(:M
MZVGUJ5:9(>9B"_U8T1@?9=Y'`\<OOG.=F]S+F0?68E"`!#,TRKWD8>@54,+P
MPE<U(7H#%K_DA[+"P-IR/`6++4-5Y?+AJFN^OGH#KUA2O*+>90)U"*@PZT31
M2>@'B7,K:%UD`0PBT<G``65!3XX^U6"J^AVD2,_O4DQ,2TO(RZL,9!0XF^)!
M,8.=!(4)O,(<U#H&?48'1T"XJ>5XU`&\`KNN$A/P^?;7S\;3ZF]/R=AF at X(!
M%K/]VM[=^ZSG'/K at G(-4HRQ9K(^99W34J.X-22ZB";E]B[1(3!(B*9AAZTZ`
MRZD']/#2H+%SP.BK4FHEWX$:C!KU*7JS at 6%D$:'LH"\-W@<3H;VXD4*]+`V>
M27'@=&XN;@1%O\TT`TI8-#/"T:C!1!(Q?Y2A5!LZIIA<+IB>B6WDQZ7OL5CB
MD+6T-^M!%)KR]0OXI5I4.^RLR'KVBFU#_/FT6AG2MH^C at S"'>#PNK!$_8'@N
M$?*I'W&(.:;S7I].)TA4FI^1L`K_`HYQ*Z;Q'D5,"<=;C/J95,Y$'. at H49^%
M;*\I/ZK.KBY.?C$OCS_6LWD@`VAM+J#UQ)^Y`TSZ at D)Z++3N%-)GZC0%^E1@
M60'8#VMD1(P'2!?RK)>]>W,)9)V<`699K>FI,L3<G&K0`5\"6#HY;09]_&P"
M-D1&]("C)?E3CQD/G_59VJXO>(J,"%X\<R18H5>T:I-7^LJ^:@U2*4'Q:P at 0
MY*$U=5ST98^5LYMKEQ_,.'V"J57\7",Q:7TV,K*GM&4A?#L/+T^-29_C(V,P
M'C&0:H[M2 at E$PM131]9_J at A:U0W%QS.*E61/-H\PV^.B5[[;*33<DN._4VR(
M?*&K9=H at RX=^+ND"7G at F#T+FV@@A9JP916BL`"H=((I<5/_VS\6F@*C5=:D(
M`_M'.5ZP4="0-7!:\T!@;)F1 at _RB4YBL+SSF@I.2+851F7'`B>*N4FA),K53
M"D7YV$.JZUP`,)'32G;(G78]O](JZX&]2L3%LUP7?"LZ$]'RD3'9FC;B"0);
MN\+M'P)>=5#2:#0H=DH-'V**?-U"*+'^>0<$M\[RQA_(>T&)HE2)RW>)I>;Q
MDW=J.7A2+++T/60>DX`Q9Q\+\_YH.70J)`_F`G=>G#GJ)SJ@^MQ3G05M73A_
M$$%I,%]D-LSR"4MZU'3!T8[D=/$013+OH3F76=V"B:SHBI!W5N)9Y82RQ$6)
M(9=Y*2H#]B)Q(V"1]'P=H=Y@/QMU"5])1"0=3BJFD17PHO!B_MPC'M*%!!@0
MO-OGXH!V?#+27N1;M822!&$!59R-/,#6["Y4FG14DVBU%Z.,4U3?B++]<V[:
M)?Q-&S6!OU?.^(DR'ILC[O%0'G`,E8-.E146Q%96J"J+T0-^R;'`U1[#B at EY
M%DV1E11""/\01LB38L>[M5S8`6H.;S`^#:*Y(>UC/*HRE*!.A.,48T3N^;/1
MF/78"'28ZC`>(S2(O?ZDT?5!<Z"S7"#Z`:)1BW>*&!T[TKYL;AIR(;$CYNP)
M]\9&1QI4$!KF\2\1D]E;RE1#.!32;9CD'$%95_HA1Z-!CXXPW8NE"_CM!=C]
M4W at 4_]1P&J!&MN+J?\G)W'_O^#^?_XGO2`Q-)WBL2\#?=/^[A?=_=[>W]U;W
M?W_$LY3_CW0)^,'[O\WM^/Y?N[V#^;_])HC!*O_W`Y[O<?\WD:%'N@'\W>[Z
M_M^_WOO at LU3_4T;^4P<!#^7_=_?W$_O?VB?]WVVN\O\_Y%E^_Q=_ at Q,X$[SL
M;B\VR7=TJV!=BSH%YN0AP$C*:.J(@#J1WS2T;%5Q$[^.2ZK`1ZQ2":1V:[C.
M3L%Z^'="71\NW"!FJRO$CW.%.%T9&A$+XESAL[?],P at 4GGWK!>/D6.0;KA[3
MR4.E at AD-//+I:^4XY?>+$U at L+%X,O/A*\[_1O>,'+PF_[N+9_/FUV>O7**K%
M&[NU?&$296&,7YN?Z\N:6\N;V\N;MS]K5/6NKUI[YF7W+^`@=!5AM1I]8*]>
M0;S/?F?R&\0[$.2J*\2J`H;D/"GKQ>*X,;@(9'6HO%.>SU,581S,:M6Y)?=(
MO>'8EQ=8>6-T4+0\;(U'XR;F"1(+Q=9:[?U&$_ZTUF1!3%SBF;\_/`C]`+9$
M\&HD%CF0=B?W+CYU\QG")A81;0JF)F)Z-F_F$0<+!`);>^V,-E$L+,\H*SS6
M1\::F+5/7_:'G[XTF^I?"ZE0"P&&$Z_44!=,:_RZW<:*N#PN[LVIVJ93O<_V
M%6(LIY28=C1HQ35<4!^MA/UF#FNTN$:ZLK!`NE)6^.S<:M71I1#9TNB*7A?-
MR at NC*V7UUT,\.\,D/RQ!A0Y_``KO75'+YI&Z("A%P$AO7U4J<2$*MN'YM<J^
M at J@^%S+!F/8#=E32,J4WEQ=]+*&+KR7I;7%Y;>6^JN[39+BBR>_#[`CHU&()
M2]2ARP*V4#[J0=8L@,JSAW*-7\VBDLKU2I)\E%7KP9 at X%HPS%;'X+F:,R[T1
M9K$$>SZ02<)@3`76R`Y9X#U.JFH/\<JGOE+KAN)>4&1WRFUR&[`>[_F@$?_%
ML:K)J7RLQ'6\Y:CT>8.U#!U$:V@;6<!MXW\F/"6R0W=^BD8.)RMOD,#VJO[_
MBN5W'99==?B:FPY?=='AZX4EN?:$OD"YH%1R2<69-(1+Y:TH6I4HERE/;B\Q
MO9R_%LM9<AN^(DLSY,T?VBHL;RYO@*JTHHC].S#6#OT'!HDC1+6>)*_/@(IR
M?E,K#%O>3.._7G8'+!Z!*%]TZ6N)I9*6,Y*G6U21G-.H^#0+%DJ,&\E>9$BD
M,U4%FG,K='R=1`65WB2:B!?*-&T$9>SU#S1U9'U`?O!\QHZ#P,7_I0&\B0.9
MHH]54'>R4J,`FDB$U7&';,375$M4L5JR*O=57;^H$C4)KJF2L5):BXHB0%6-
MSV198[6B#I!?``WQX7%\&B/&9!QPPPKFM9C(.EN#CVO4HJ^U]"+0>VCMK2FA
MP-SV]";Y?YVHM!3#0!%P6X8D*!GH7<FRT\."I>AHS1ZEPTMJ6"5(,-3J47%T
M](_3,#`=:EEU*H+DBTFQ$ZU5IH0T\[]MJ$K2!2S(U9)6RLI$U2"`6(8_)O*M
MEF4LM*M&.I,KC`KM_^KP?_6LGM6S>E;/ZED]JV?UK)[5LWI6S^I9/:MG]:R>
AU;-Z5L]_MP<')`````""_K]N1Z`"``#,!=C(SDX`>```
`
end


More information about the Kernelnewbies mailing list