I have been experiencing a behavior I do not understand relating to handling packets in the linux kernel. Below a minimal(ish) example which exposes the situation.
Please, kindly consider that I'm an experienced software engineer with a terrible blind spot for networking.
context
suppose I want to write a some form of a loopback interface as a linux module with a twist : unlike the "normal" lo which only insert packets as inbound, I which that packet reinsert could be forwarded normally after being re-injected.
For instance, here is a scenario:
- I do
ping google.com -I lato forcefully use loop-around to contact google.com - the packet is forced to
la la_xmitcaptures the packetla_xmitpublish a new packet through the stack usingnetif_rx- the packet goes into the network stack and is routed naturally
- the packet leaves through
eth0
What I tried
following, is what I thought to be a minimally viable module
#include <linux/module.h>
#include <linux/init.h>
#include <linux/skbuff.h>
#include <net/ip_tunnels.h>
MODULE_LICENSE("GPL");
MODULE_AUTHOR("me");
MODULE_DESCRIPTION("re-injector");
struct net_device *my_netdev;
static netdev_tx_t la_xmit(struct sk_buff *captured_skb, struct net_device *dev) {
int len = captured_skb->len;
// move to a new skb
struct sk_buff *skb = alloc_skb(len, GFP_KERNEL);
memcpy(skb_put(skb, len), captured_skb->data, len);
dev_kfree_skb(captured_skb);
// initialize new packet
skb_tx_timestamp(skb);
skb_clear_tstamp(skb);
skb_orphan(skb);
skb_dst_force(skb);
// inject into network stack
int skb_len = skb->len; // copy now, after netif, it's not ours anymore
int err = netif_rx(skb);
if (likely(err == NET_RX_SUCCESS)) dev_lstats_add(my_netdev, skb_len);
return NETDEV_TX_OK;
}
const struct net_device_ops ndo = {
.ndo_start_xmit = la_xmit
};
static void setup_netdev(struct net_device *dev) {
dev->netdev_ops = &ndo;
dev->header_ops = &ip_tunnel_header_ops;
dev->mtu = 1500;
dev->flags |= IFF_POINTOPOINT;
dev->flags |= IFF_NOARP;
dev->flags |= IFF_MULTICAST;
}
static int __init la_init(void) {
my_netdev = alloc_netdev(0, "la", NET_NAME_UNKNOWN, setup_netdev);
register_netdev(my_netdev);
return 0;
}
static void __exit la_cleanup(void) {
unregister_netdev(my_netdev);
free_netdev(my_netdev);
}
module_init(la_init);
module_exit(la_cleanup);
I capture incoming packet, copy it and follow the logic from net/loopback.c to inject the packet but using netif_rx() instead of __netif_rx() since I understand the latter to skips part of the routing process under the assumption of inbound traffic.
What I expected
I hoped to see ICMP traffic out of eth0
What happens instead
My VM hangs suddenly. sudo journalctl -k -b -1 to read logs from last session doesn't bring any error.