Determining if a packet is ipv4 or ipv6 from sk_buff's network header - linux

I have the SKB of type 'struct sk_buff' and I was trying to determine if the packet is of type IPv4 or IPv6. Basically I need the source IP address, but dont know, for sure how to check the 'version' field inside iph or ipv6h, or dont know if it is a reliable approach to check 'version' value.
Network header inside the sk_buff implementation on my machine is:
union {
struct iphdr *iph;
struct ipv6hdr *ipv6h;
struct arphdr *arph;
unsigned char *raw;
} nh;
And the iphdr and ipv6hdr have usual definitions.
How to determine the IP version from the IP network header in sk_buff?

The IP version number is encoded in the first 4 bits of the packet whether it is ipv4 or ipv6 for just this reason. Use the ipv4 pointer (ipv) and examine the version field.

nf_ct_l3num(skb->nfct) can tell you whether it's IPv4 or IPv6, after conntrack for this skb is initialized.

Related

Error - getHostByAddr ENOTFOUND ::ffff:18.234.32.226

When I use this utility with nodejs: https://nodejs.org/api/dns.html#dns_dns_reverse_ip_callback
like so:
const {remoteAddress, remotePort} = req.connection;
dns.reverse(remoteAddress, (err, hostnames) => {
if(err){
console.error(err.message);
}
});
I get that error -
getHostByAddr ENOTFOUND ::ffff:18.234.32.226
what is the ffff stuff at the beginning of the address/ip? I assume I should get rid of that before passing to the dns.reverse lookup call?
::ffff:18.234.32.226 is an IPv4 address (18.234.32.226) mapped as an IPv6 one, which you detect because of the use of :.
This is a common case that happens on systems configured to prefer IPv6 over IPv4 (something you can configure in Unix systems with the file /etc/gai.conf).
It is explained in https://www.rfc-editor.org/rfc/rfc3493 section 3.7:
The API also provides a different type of compatibility: the ability
for IPv6 applications to interoperate with IPv4 applications. This
feature uses the IPv4-mapped IPv6 address format defined in the IPv6
addressing architecture specification [2]. This address format
allows the IPv4 address of an IPv4 node to be represented as an IPv6
address. The IPv4 address is encoded into the low-order 32 bits of
the IPv6 address, and the high-order 96 bits hold the fixed prefix
0:0:0:0:0:FFFF. IPv4-mapped addresses are written as follows:
::FFFF:<IPv4-address>
You need either to configure your system not to map IPv4 addresses as IPv6 ones, or use a library that knows how to handle those IP addresses (which are completely legit). Or in the worse case, indeed remove yourself the ::ffff: at beginning.

How to create a kernel module that can intercept all packets coming to/from a network interface

I have 2 port NIC on my system - eth0 and eth1 as seen by Linux.
I want to intercept all packets coming in/to eth0, send them out through eth1 to an external device connected to the same switch as eth1 is. So I need to slap on an additional header to make it reach the correct external device.
I know that there is a concept of network taps that both the transmit and receive code in the kernel send to, but how do I create one? Also I want to capture not just IP, but all ethernet packets, I know NETFILTER_HOOK would have helped me get me IPv4 packets.
The can be readily implemented with a rx_handler:
static rx_handler_result_t handle_frame(struct sk_buff **pskb)
{
struct sk_buff *skb = *pskb;
struct net_device *whereto_dev;
skb = skb_share_check(skb, GFP_ATOMIC);
if (unlikely(!skb))
return RX_HANDLER_CONSUMED;
*pskb = skb;
whereto_dev = rcu_dereference(skb->dev->rx_handler_data);
skb->dev = whereto_dev;
return RX_HANDLER_ANOTHER; /* Do another round in receive path */
}
They are registered via netdev_rx_handler_register(slave_dev, handle_frame, whereto). See the bonding or my uman driver for example usage.
dev_add_pack would work too, but it seems, apart from af_packet.c, all all-packet-catching users of dev_add_pack have been migrated to use rx_handlers, e.g. https://patchwork.ozlabs.org/patch/367236/. The patch's discussion suggests this might be more effecient.

How to printk with IP address or MAC address in Linux Kernel Source Code

I have to change TCP Congestion Control algorithm a little bit by modifying Linux Kernel Source Code. But to check if the result is correct, I need to log info of MAC or IP address.
I used PRINTK function to print messages for kernel. But I feel hard to print out MAC/IP address of hosts.
printk("%pM \n", mac)
But what is mac refer to?
In TCP source code, I often work with skbuff or sock struct.
Thank you.
UPDATE:
struct iphdr *iph = ip_hdr(skb);
printk(KERN_DEBUG "%pI4", iph->saddr);
Linux documents the printk format specifier extensions in the file Documentation/printk-formats.txt available as part of the kernel's source code. For your example,
IPv4 addresses:
%pI4 1.2.3.4
%pi4 001.002.003.004
%p[Ii][hnbl]
For printing IPv4 dot-separated decimal addresses. The 'I4' and 'i4'
specifiers result in a printed address with ('i4') or without ('I4')
leading zeros.
The parameter passed would be a pointer to the IP address to print (skbuff, socket structure, etc).
Are you looking for something like this?
from arch/xtensa/platforms/iss/network.c:
printk("(%pM) ", lp->mac);
i found it using this command
grep -R "mac)" * | grep printk
It might be easier to help if you could tell us which file/line you are currently working on.

Is there a kernel module that returns exactly what a simple 'ifconfig' does?

I'm writing a kernel module that needs information about the local machine's interfaces just like the ones retuned by a simple 'ifconfig' command, I've searched a lot for it, but couldn't find anything
You can get all of that information through the struct net_device one way or another.
As Albert Veli said, you can get this struct net_device pointer using __dev_get_by_name().
If you tell us what information you need specifically we might even be able to point you to the correct fields.
Finding the MAC address is fairly simple:
struct net_device *dev = __dev_get_by_name("eth0");
dev->dev_addr; // is the MAC address
dev->stats.rx_dropped; // RX dropped packets. (stats has more statistics)
Finding the IP address is rather harder, but not impossible:
struct in_device *in_dev = rcu_dereference(dev->ip_ptr);
// in_dev has a list of IP addresses (because an interface can have multiple)
struct in_ifaddr *ifap;
for (ifap = in_dev->ifa_list; ifap != NULL;
ifap = ifap->ifa_next) {
ifap->ifa_address; // is the IPv4 address
}
(None of this was compile tested, so typos are possible.)
See for example the in6_dump_addrs function in net/ipv6/addrconf.c for how to get at addresses. For link properties like link layer address, see core/rtnetlink.c instead. ifconfig and its ioctls are obsolete (on Linux), so better don't think in terms of that now-bug-ridden program.

Specify source IP address for TCP socket when using Linux network device aliases

For some specific networking tests, I've created a VLAN device, eth1.900, and a couple of aliases, eth1.900:1 and eth1.900.2.
eth1.900 Link encap:Ethernet HWaddr 00:18:E7:17:2F:13
inet addr:1.0.1.120 Bcast:1.0.1.255 Mask:255.255.255.0
eth1.900:1 Link encap:Ethernet HWaddr 00:18:E7:17:2F:13
inet addr:1.0.1.200 Bcast:1.0.1.255 Mask:255.255.255.0
eth1.900:2 Link encap:Ethernet HWaddr 00:18:E7:17:2F:13
inet addr:1.0.1.201 Bcast:1.0.1.255 Mask:255.255.255.0
When connecting to a server, is there a way to specify which of these aliases will be used? I can ping using the -I <ip> address option to select which alias to use, but I can't see how to do it with a TCP socket in code without using raw sockets, since I would also like to run without extra socket privileges, i.e. not running as root, if possible.
Unfortunately, even with root, SO_BINDTODEVICE doesn't work because the alias device name is not recognized:
printf("Bind to %s\n", devname);
if (setsockopt(s, SOL_SOCKET, SO_BINDTODEVICE, (char*)devname, sizeof(devname)) != 0)
{
perror("SO_BINDTODEVICE");
return 1;
}
Output:
Bind to eth1.900:1
SO_BINDTODEVICE: No such device
Use getifaddrs() to enumerate all the interfaces and find the IP address for the interface you want to bind to. Then use bind() to bind to that IP address, before you call connect().
Since a packet can't be send out on an aliased interface anyway, it would make no sense to use SO_BINDTODEVICE on one. SO_BINDTODEVICE controls which device a packet is sent out from if routing cannot be used for this purpose (for example, if it's a raw Ethernet frame).
You don't show the definition of devname, but if it's a string pointer, e.g.:
char *devname = "eth1.900:1";
Then perhaps it's failing since you specify the argument size using sizeof devname, which would in this case be the same as sizeof (char *), i.e. typically 4 on a 32-bit system.
If setsockopt() expects to see the actual size of the argument, i.e. the length of the string, this could explain the issue since it's then perhaps just inspecting the first four characters and failing since the result is an invalid interface name.

Resources