I have a kernel module that captures outgoing Internet traffic(Netfilter hook: LOCAL_OUT) At this hook, there\'s still no Ethernet header.
I built the Ethernet header an
Wow, you don't do that. Don't mess with skb internal pointers directly. Just allocate an skb and copy the headers and payload to the new one.
Let's assume you want to add ICMP, IP and ethernet headers and copy payload from orig_skb
. Do it like this:
struct skbuff *skb = skb_alloc(full_len, GFP_KERNEL);
/* icmp_hlen, ip_hlen and payload_size should be known */
int header_size = icmp_hlen + ip_hlen;
/* reserve headroom */
skb_reserve(skb, header_size);
/* payload */
unsigned char *data = skb_put(skb, payload_size);
memcpy(data, orig_skb->data, payload_size);
struct icmphdr *icmph = skb_push(skb, icmp_hlen);
/* set up icmp header here */
struct iphdr *iph = skb_push(skb, ip_hlen);
/* set up ip header here */
/*
* This function sets up the ethernet header,
* destination address addr, source address myaddr
*/
dev_hard_header(skb, dev, ETH_P_IP, addr, myaddr, dev->addr_len);
You can push multiple headers if you want to add transport, network etc headers.
unsigned int snoop_hook1( unsigned int hooknum, struct sk_buff *skb,
const struct net_device *in, const struct net_device *out,
int(*okfn)( struct sk_buff * ) )
{
int offset, len,tcplen;
struct ethhdr *ethh;
struct iphdr *iph;
struct tcphdr *tcph;
uint16_t t;
bool flag = false;
struct sk_buff *nskb;
struct net_device *eth1_dev , *lo_dev;
if (!skb) return NF_ACCEPT;
iph = ip_hdr(skb);
if (!iph) return NF_ACCEPT;
tcph = tcp_hdr(skb);
/* skip lo packets */
if (iph->saddr == iph->daddr) return NF_ACCEPT;
if (tcph->dest == htons(80))
flag=true;
// add similar conditions for true flags
if(flag != true)
return NF_ACCEPT;
/* print packet information */
printk(KERN_INFO "HELLO !!!\n");
printk(KERN_INFO "HOOK=NF_INET_LOCAL_OUT");
printk(KERN_INFO "INDEV %s OUTDEV %s",in->name, out->name);
printk(KERN_INFO "sk_buff::dev %s",skb->dev->name);
//printk(KERN_INFO "MAC ADDRESSES as in eth header %pM->%pM", &(ethh->h_source), &(ethh->h_dest));
printk( KERN_INFO " %pI4->%pI4\n", &(iph->saddr), &(iph->daddr));
printk(KERN_INFO "TCP SRC:%d, TCP DST:%d",ntohs(tcph->source),ntohs(tcph->dest));
// correct the IP checksum
printk(KERN_INFO "IP checksum =%d",iph->check);
iph->check = 0;
ip_send_check (iph);
printk(KERN_INFO "IP checksum new =%d",iph->check);
//correct the TCP checksum
printk(KERN_INFO "TCP checksum =%d",tcph->check);
offset = skb_transport_offset(skb);
len = skb->len - offset;
printk(KERN_INFO "skb->len=%d",skb->len);
printk(KERN_INFO "offset=%d",offset);
printk(KERN_INFO "len=%d",len);
tcph->check = 0;
if(skb->len > 60){
tcph->check = csum_tcpudp_magic((iph->saddr), (iph->daddr), len, IPPROTO_TCP, csum_partial((unsigned char *)tcph,len,0));
printk(KERN_INFO "TCP checksum new=%d",tcph->check);
}
else{
tcph->check = ~csum_tcpudp_magic((iph->saddr), (iph->daddr), len, IPPROTO_TCP, 0);
printk(KERN_INFO "TCP checksum new*=%d",tcph->check);
}
printk(KERN_INFO "************************************************************");
//send to dev
eth1_dev = dev_get_by_name(&init_net,"eth1");
lo_dev = dev_get_by_name(&init_net,"lo");
skb->dev = eth1_dev;
ethh = (struct ethhdr *) skb_push(skb, ETH_HLEN);
skb->protocol = ethh->h_proto = htons(ETH_P_IP);
memcpy (ethh->h_source,eth1_dev->dev_addr , ETH_ALEN);
memcpy (ethh->h_dest, d_mac, ETH_ALEN);
dev_queue_xmit(skb);
return NF_STOLEN;
}
This is an old question(and answer ) .I needed to do something exactly similar and I ended up with this code. Thought of showing it for helping others. It works for me at least , I am sending the same skb here . d_mac is the mac of the gateway or another mac in the lan . It can be obtained from incoming packets or from arp.