Network traffic forwarding in Linux Kernel

When developing virtual network device drivers for Linux, sometimes there is a need of redirecting packets between the real device and a virtual one. Fortunately, the Linux kernel (version 4.14 in our case) has the right tools to do so, without any extra headache. Let’s look at redirecting incoming packets from a real network device. To do so, you don’t even have to change the sources of a real network driver itself. All you need to do is just use the mechanism of rx_handlers. To add your own handler, you need to use the following API call from your virtual driver:   int netdev_rx_handler_register (

struct net_device * dev,

 rx_handler_func_t * rx_handler,

 void * rx_handler_data);

The signature of a rx_handler is following: typedef rx_handler_result_t rx_handler_func_t(struct sk_buff **pskb);   Here is the list of possible return codes:   enum rx_handler_result { RX_HANDLER_CONSUMED, RX_HANDLER_ANOTHER, RX_HANDLER_EXACT, RX_HANDLER_PASS, };   This enum has a good amount of description in Linux sources, so we will stop in what we need – RX_HANDLER_ANOTHER. So, let’s summarize what we should do to redirect incoming packets to a different device:  
  1. Register rx_handler
  2. In rx_handler change dev (pointer to net_device struct) to the device we want to redirect packets to
  3. Return RX_HANDLER_ANOTHER to inform the kernel that we actually changed the destination device
Here is a simplified code of the handler:   static rx_handler_result_t handle_frame(struct sk_buff **pskb) {     struct sk_buff *skb = *pskb;      // virtnet_dev is a pointer to net_device struct // you can obtain the pointer with this call  // dev_get_by_name(&init_net,device_name_cstr); skb->dev = virtnet_dev; return RX_HANDLER_ANOTHER;     }   That’s it, we have a working solution for the problem. However, this type of solution comes with its own limitations. We cannot add a network device with a registered rx_handler to a virtual bridge. This is a known issue and it happens because bridge code uses the same API and tries to register rx_handler (but devices can have only one rx_handler registered!).   In our case, it wasn’t a problem, because in our topology only virtual devices were enslaved by a bridge. In the other case, it can be achieved by using protocol handlers and dev_forward_skb call, but this is a different subject to describe.   Another use case is redirecting all outgoing packets from the virtual device and sending them with a real one.    All network devices have their ndo_start_xmit callback. There we already have fully-formed packets, which are ready to be sent. Here is a simplified example of how to send all packets through a different network interface:   static netdev_tx_t virtnet_xmit(struct sk_buff *skb, struct net_device *dev) { // eth_dev is a pointer to net_device struct skb->dev = eth_dev; dev_queue_xmit(skb); return NETDEV_TX_OK; }   As you can see, it’s enough to change skb->dev and call dev_queue_xmit(skb)! You shouldn’t even call free() for skb, it’s already implemented in dev_queue_xmit logic.

Need help with Linux development?

Share:
Share on facebook
Share on twitter
Share on telegram
Share on whatsapp
Share on email
Share on linkedin

Get proposal

We will be happy to answer any question.
Please fill out the following fields