Receive all multicast ICMPv6 packets on Linux - linux

I would like to receive all multicast IPv6 packets arriving on a certain interface, without resorting to operate on layer 2, if that is possible.
I open a socket for raw ICMPv6 packets, and receiving unicast packets dedicated for my machine works just fine. However many ICMPv6 packets are link-local multicast (e.g. neighbor solicitations). What's the right way to listen for all multicast traffic, including solicited-node multicast? Currently I try to add a multicast group with IPV6_ADD_MEMBERSHIP, but this does not seem to work. Here's my code:
/* open RAW socket to receive on */
if ((sockfd = socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6)) < 0) {
perror("socket");
}
/* get device index */
memset(&if_idx, 0, sizeof(struct ifreq));
strncpy(if_idx.ifr_name, DEVNAME, IFNAMSIZ-1);
if (ioctl(sockfd, SIOCGIFINDEX, &if_idx) < 0) {
perror("SIOCGIFINDEX");
}
/* configure to receive all multicast packets on this interface */
memset(&mreq, 0, sizeof(struct ipv6_mreq));
inet_pton(AF_INET6, "ff02::", &mreq.ipv6mr_multiaddr);
mreq.ipv6mr_interface = if_idx.ifr_ifindex;
if (setsockopt(sockfd, SOL_SOCKET, IPV6_ADD_MEMBERSHIP, &mreq,
sizeof(struct ipv6_mreq)) < 0) {
perror("setsockopt");
}
What am I doing wrong? What I want must be possible somehow. I tried ff02:: and ff02::1:ff00:0 as groups, and the latter even made setsockopt fail. What's going on? Unfortunately there's very little documentation on IPv6 multicast programming.

Use SOL_IPV6 instead of SOL_SOCKET.
Test subscribing to ff80::1 and generating traffic with ping6 -I eth0 ff08::1.

This appears to be impossible after all. I am now using Linux's AF_PACKET socket type with "cooked" mode (SOCKET_DGRAM) to access the raw IPv6 packets with link-layer header and a BPF to filter out ICMPv6 ND packets - at least I won't need to deal with parsing the ethernet header this way, and I can possibly support other link-layer types more easily.

Related

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.

Linux raw datalink layer socket only returns partial packet (96 bytes)

In my application, I am receiving packets at the data link layer using a raw socket (type PF_PACKET, SOCK_RAW). What I am finding is that I only get the first 96 bytes of any packet. I'm assuming there is some option somewhere that is preventing me from receiving the entire packet, but what?
Here is a snipped from my code:
int sock = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_IP));
int nBytesRead = read(sock, (char *) buf, 1500);
int nFlags = fcntl(m_sock, F_GETFL, 0); // make it non-blocking
fcntl(sock, F_SETFL, nFlags | O_NONBLOCK);
nBytesRead is never more than 96, even though my network sniffer shows longer packets. This is uClinux if that makes a difference.
I found someone else with the same problem at http://www.network-builders.com/raw-socket-captures-only-first-96-bytes-packet-t57283.html but no answers there.
Solved it! What I failed to mention in my original post was that I was attaching a filter to the raw socket so it would only receive traffic on certain TCP/IP ports. This filter code was created with TCPDUMP, which apprently limits capture to 96 bytes by default. I had to add the -s0 option to my TCPDUMP command line to tell it to capture everything:
tcpdump -dd -s0 "ip and tcp and dst port 60001".
With that change, it now gives me the full packet. Thanks to this blog post for the clue.
Hope this helps someone else in the future.

How to set linux kernel not to send RST_ACK, so that I can give SYN_ACK within raw socket

I want to ask a classic question about raw socket programming and linux kernel TCP handling. I've done the research to some same threads like linux raw socket programming question, How to reproduce TCP protocol 3-way handshake with raw sockets correctly?, and TCP ACK spoofing, but still can't get the solution.
I try to make a server which don't listen to any port, but sniff SYN packets from remote hosts. After the server do some calculation, it will send back a SYN_ACK packet to corresponding SYN packet, so that I can create TCP Connection manually, without including kernel's operation. I've create raw socket and send the SYN_ACK over it, but the packet cannot get through to the remote host. When I tcpdump on the server (Ubuntu Server 10.04) and wireshark on client (windows 7), the server returns RST_ACK instead of my SYN_ACK packet. After doing some research, I got information that we cannot preempt kernel's TCP handling.
Is there still any other ways to hack or set the kernel not to responds RST_ACK to those packets?
I've added a firewall to local ip of server to tell the kernel that maybe there's something behind the firewall which is waiting for the packet, but still no luck
Did you try to drop RST using iptables?
iptables -A OUTPUT -p tcp --tcp-flags RST RST -j DROP
should do the job for you.
I recommend using ip tables, but since you ask about hacking the kernel as well, here is an explanation of how you could do that (I'm using kernel 4.1.20 as reference):
When a packet is received (a sk_buff), the IP protocol handler will send it to the networking protocol registered:
static int ip_local_deliver_finish(struct sock *sk, struct sk_buff *skb)
{
...
ipprot = rcu_dereference(inet_protos[protocol]);
if (ipprot) {
...
ret = ipprot->handler(skb);
Assuming the protocol is TCP, the handler is tcp_v4_rcv:
static const struct net_protocol tcp_protocol = {
.early_demux = tcp_v4_early_demux,
.handler = tcp_v4_rcv,
.err_handler = tcp_v4_err,
.no_policy = 1,
.netns_ok = 1,
.icmp_strict_tag_validation = 1,
};
So tcp_v4_cv is called. It will try to find the socket for the skb received, and if it doesn't, it will send reset:
int tcp_v4_rcv(struct sk_buff *skb)
{
sk = __inet_lookup_skb(&tcp_hashinfo, skb, th->source, th->dest);
if (!sk)
goto no_tcp_socket;
no_tcp_socket:
if (!xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb))
goto discard_it;
tcp_v4_send_reset(NULL, skb);
...
There are many different ways you can hack this. You could go to the xfrm4_policy_check function and hack/change the policy for AF_INET. Or you can just simply comment out the line that calls xfrm4_policy_check, so that the code will always go to discard_it, or you can just comment out the line that calls tcp_v4_send_reset (which will have more consequences, though).
Hope this helps.

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.

linux raw socket programming question

I am trying to create a raw socket which send and receive message with ip/tcp header under linux.
I can successfully binds to a port and receive tcp message(ie:syn)
However, the message seems to be handled by the os, but not mine. I am just a reader of it(like wireshark).
My raw socket binds to port 8888, and then i try to telnet to that port .
In wireshark, it shows that the port 8888 reply a "rst ack" when it receive the "syn" request. In my program, it shows that it receive a new message and it doesnot reply with any message.
Any way to actually binds to that port?(prevent os handle it)
Here is part of my code, i try to cut those error checking for easy reading
sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_TCP);
int tmp = 1;
const int *val = &tmp;
setsockopt (sockfd, IPPROTO_IP, IP_HDRINCL, val, sizeof (tmp));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(8888);
bind(sockfd, (struct sockaddr*)&servaddr, sizeof(servaddr));
//call recv in loop
When your kernel receives a SYN/ACK from the remote host, it finds no record of it having sent a SYN to that IP:PORT combination (which was sent from your raw socket) which is why it assumes that there has been an error and sends a RST to the remote host. This problem can be solved by setting up an IP filter that blocks all TCP traffic on that port (Check the iptables manpage for this). That way you don't have to program in kernel space nor will there be any affect on already existing kernel TCP modules.
man 7 raw says:
Raw sockets may tap all IP protocols in Linux, even protocols like ICMP or TCP which have a protocol module in the kernel. In this case the packets are passed to both the kernel module and the raw socket(s).
I take this to mean that you can't "do TCP" on a raw socket without interference from the kernel unless your kernel lacks TCP support -- which, of course, isn't something you want. What raw sockets are good for is implementing other IP protocols that the kernel doesn't handle, or for special applications like sending crafted ICMP packets.
To access raw headers you dont bind a raw socket to a port. Thats not done.
Simply write a sniffer , to "PICK UP" all incoming packets and find out "YOUR" ones. That will also give you access to all of the packets content etc.
This is how you do it :
int sock_raw = socket( AF_PACKET , SOCK_RAW , htons(ETH_P_ALL)) ;
while(true)
{
saddr_size = sizeof saddr;
//Receive a packet
data_size = recvfrom(sock_raw , buffer , 65536 , 0 , &saddr , (socklen_t*)&saddr_size);
if(data_size <0 )
{
printf("Recvfrom error , failed to get packets\n");
return 1;
}
//Now process the packet
ProcessPacket(buffer , data_size);
}
In the ProcessPacket function analyse the packet and see if they belong to your application.
Edit:
In case you intend to program raw sockets, check this.
It has a few examples of how to send and receive raw packets.
In case you want to use SOCK_STREAM and SOCK_SEQPACKET connection-oriented type sockets:
You need to tell it to listen after binding to a given address:port.
int connectionQueue = 10;
if ( -1 == listen(sockfd, connectionQueue) )
{
// Error occurred
}
Afterwards, you will need to verify the descriptor for incoming connections using select, and accept an incoming connection on either the server socket (which will lead to not accepting new connections), or a dedicated client socket.

Resources