I am trying to send and receive packets of type SOCK_RAW over PF_SOCKETs using my own custom protocol ID on the same machine. Here is my sender and receiver sample code-
sender.c
#include<sys/socket.h>
#include<linux/if_packet.h>
#include<linux/if_ether.h>
#include<linux/if_arp.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#define CUSTOM_PROTO 0xB588
int main ()
{
int sockfd = -1;
struct sockaddr_ll dest_addr = {0}, src_addr={0};
char *buffer = NULL;
struct ethhdr *eh;
sockfd = socket(PF_PACKET, SOCK_RAW, htons(CUSTOM_PROTO) );
if ( sockfd == -1 )
{
perror("socket");
return -1;
}
buffer = malloc(1518);
eh = (struct ethhdr *)buffer;
dest_addr.sll_ifindex = if_nametoindex("eth0");
dest_addr.sll_addr[0] = 0x0;
dest_addr.sll_addr[1] = 0xc;
dest_addr.sll_addr[2] = 0x29;
dest_addr.sll_addr[3] = 0x49;
dest_addr.sll_addr[4] = 0x3f;
dest_addr.sll_addr[5] = 0x5b;
dest_addr.sll_addr[6] = 0x0;
dest_addr.sll_addr[7] = 0x0;
//other host MAC address
unsigned char dest_mac[6] = {0x0, 0xc, 0x29, 0x49, 0x3f, 0x5b};
/*set the frame header*/
memcpy((void*)buffer, (void*)dest_mac, ETH_ALEN);
memcpy((void*)(buffer+ETH_ALEN), (void*)dest_mac, ETH_ALEN);
eh->h_proto = htons(PAVAN_PROTO);
memcpy((void*)(buffer+ETH_ALEN+ETH_ALEN + 2), "Pavan", 6 );
int send = sendto(sockfd, buffer, 1514, 0, (struct sockaddr*)&dest_addr,
sizeof(dest_addr) );
if ( send == -1 )
{
perror("sendto");
return -1;
}
return 0;
}
receiver.c
#include<sys/socket.h>
#include<linux/if_packet.h>
#include<linux/if_ether.h>
#include<linux/if_arp.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#define CUSTOM_PROTO 0xB588
int main ()
{
int sockfd = -1;
struct sockaddr_ll dest_addr = {0}, src_addr={0};
char *recvbuf = malloc(1514);
sockfd = socket(PF_PACKET, SOCK_RAW, htons(CUSTOM_PROTO) );
if ( sockfd == -1 )
{
perror("socket");
return -1;
}
int len = recvfrom(sockfd, recvbuf, 1514, 0, NULL, NULL);
printf("I received: \n");
return 0;
}
Both sender and receiver are running on Ubuntu Virtualbox. The problem is the receiver hangs in recvfrom. But in receiver.c, if I change htons(CUSTOM_PROTO) to htons(ETH_P_ALL), the receiver works just fine.
Why is the kernel not delivering the packet with my custom protocol ID to my custom protocol ID socket?
I verified in GDB that the ethernet header is formed correctly when I receive packet with htons(ETH_P_ALL)
Update: Instead of interface eth0 and its corresponding MAC, if I choose local loopback lo and a MAC address of 00:00:00:00:00:00, CUSTOM_PROTO works just fine!
Update 2 CUSTOM_PROTO works fine if the sender and receiver are on different machines. This finding and prev update made me suspect that packets being sent out on eth0 are not being received by the same machine. But the fact that ETH_P_ALL works on the same machine, refutes my suspicion.
ETH_P_ALL vs any other protocol
The protocol ETH_P_ALL has the special role of capturing outgoing packets.
Receiver socket with any protocol that is not equal to ETH_P_ALL receives packets of that protocol that come from the device driver.
Socket with protocol ETH_P_ALL receives all packets before sending outgoing packets to the device driver and all incoming packets that are received from the device driver.
Loopback device vs Ethernet device
Packets sent to the loopback device go out from that device and then the same packets are received from the device as incoming.
So, when CUSTOM_PROTO is used with loopback the socket captures packets with custom protocol as incoming.
Note that if ETH_P_ALL is used with the loopback device each packet is received twice. Once it is captured as outgoing and the second time as incoming.
In case of eth0 the packet is transmitted from the device. So, such packets go to the device driver and then they can be seen on the other side of the physical Ethernet port. For example, with VirtualBox "Host-only" network adapter those packets can be captured by some sniffer in the host system.
However, packets transmitted to the physical port (or its emulation) are not redirected back to that port. So, they are not received as incoming from the device. That is why such packets can be captured only by ETH_P_ALL in outgoing direction and they cannot be seen by CUSTOM_PROTO in incoming direction.
Technically it should possible to prepare special setup to do external packet loopback (packets from the device port should be sent back to that port). In that case the behavior should be similar to the loopback device.
Kernel implementation
See the kernel file net/core/dev.c. There are two different lists:
struct list_head ptype_base[PTYPE_HASH_SIZE] __read_mostly;
struct list_head ptype_all __read_mostly; /* Taps */
The list ptype_all is for socket handlers with protocol ETH_P_ALL. The list ptype_base is for handlers with normal protocols.
There is a hook for outgoing packets in xmit_one() called from dev_hard_start_xmit():
if (!list_empty(&ptype_all))
dev_queue_xmit_nit(skb, dev);
For outgoing packets the function dev_queue_xmit_nit() is called for ETH_P_ALL processing each item of ptype_all. Finally the sockets of type AF_SOCKET with protocol ETH_P_ALL capture that outgoing packet.
So, the observed behavior is not related to any custom protocol. The same behavior can be observed with ETH_P_IP. In that case the receiver is able to capture all incoming IP packets, however it cannot capture IP packets from sender.c that sends from "eth0" to MAC address of "eth0" device.
It can be also seen by tcpdump. The packets sent by the sender are not captured if tcpdump is called with an option to capture only incoming packets (different versions of tcpdump use different command line argument to enable such filtering).
The initial task where on the same machines it is needed to distinguish packets by protocol IDs can be solved using ETH_P_ALL. The receiver should capture all packets and check the protocol, for example:
while (1) {
int len = recvfrom(sockfd, recvbuf, 1514, 0, NULL, NULL);
if (ntohs(*(uint16_t*)(recvbuf + ETH_ALEN + ETH_ALEN)) == CUSTOM_PROTO) {
printf("I received: \n");
break;
}
}
Useful reference "kernel_flow" with a nice diagram http://www.linuxfoundation.org/images/1/1c/Network_data_flow_through_kernel.png
It is based on the 2.6.20 kernel, however in the modern kernels ETH_P_ALL is treated in the same way.
When packets with same source nad destination MAC address are transmitted from real network device ethX and physically looped back.
If protocol ETH_P_ALL is specified, packet is captured twice:
first packet with socket_address.sll_pkttype is PACKET_OUTGOING
and second packet with socket_address.sll_pkttype is PACKET_HOST
If specific protocol is specified CUSTOM_PROTO, packet is captured once:
in the case of normal packet: socket_address.sll_pkttypeis
PACKET_HOST.
in the case of VLAN packet:
socket_address.sll_pkttypeis PACKET_OTHERHOST.
Related
I am trying to send raw bits over an Ethernet cable without using any protocol, even without an Ethernet frame. I realize that this data won't really go anywhere as it will not have a receiving MAC address, but this is purely educational.
I know I can create a socket but it always encapsulates my data in an Ethernet frame. Does this mean I would have to write raw data somehow to the port itself?
This is a pseudo example of how I send data by creating a socket.
int main()
{
char *request = "GET / HTTP/1.1";
socket = socket(AF_INET, SOCK_STREAM, 0);
bind(server_fd, (struct sockaddr *)&address, sizeof(address));
write(new_socket , request , strlen(request));
}
I have a raw socket set up bound to a device that is already in promiscuous mode:
int sock = socket (PF_INET, SOCK_RAW, IPPROTO_TCP);
if(sock == -1)
{
return -1;
}
struct ifreq ifr;
memset(&ifr, 0, sizeof(ifr));
strncpy(ifr.ifr_ifrn.ifrn_name, "eth0", IFNAMSIZ);
if (setsockopt(sock, SOL_SOCKET, SO_BINDTODEVICE, &ifr, sizeof(ifr)) < 0)
{
close(sock);
return -2;
}
while(1) {
packet_size = recvfrom(sock , buffer , 65536 , 0 , NULL, NULL);
// packet processing...
}
And my issue is that I am only receiving packets on my socket with IP destination matching the IP of the device (eth0) I am bound to. How can I receive all the TCP packets that the device is receiving? I can see all the TCP packets on the device in Wireshark, but the only packets that I see in my raw socket are those addressed to the device IP.
The reason of receiving packets directed only to IP of your device is that you are using PF_INET raw socket. When PF_INET raw socket is used - skb faces different sanity checks when goes across the stack (see below).
F.e. :
int ip_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt, struct net_device *orig_dev)
{
/*...*/
/* When the interface is in promisc. mode, drop all the crap
* that it receives, do not try to analyse it.
*/
if (skb->pkt_type == PACKET_OTHERHOST)
goto drop;
So the call trace is something like: __netif_receive_skb_core()->ip_rcv()->...->ip_local_deliver()->...->raw_local_deliver()->raw_rcv()->...->tcp_rcv() (you can check trace through the trace-cmd).
But tcpdump/Wireshark obtains packets around __netif_receive_skb_core(), i.e. before some sanity checks. Hence is discrepancy that confused you.
Therefore if you want skb's to bypass a large part of Linux kernel network stack - you should use PF_PACKET raw sockets.
Useful link
I have a forking HTTP proxy implemented on my Ubuntu 14.04 x86_64 with the following scheme (I'm reporting the essential code and pseudocode just to show the concept):
socketClient = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
bind(socketClient,(struct sockaddr*)&addr, sizeof(addr));
listen(socketClient, 50);
newSocket = accept(socketClient, (struct sockaddr*)&cliAddr, sizeof(cliAddr));
get request from client, parse it to resolve the requested hostname in an IP address;
fork(), open connection to remote server and deal the request;
child process: if it is a GET request, send original request to server and while server is sending data, send data from server to client;
child process: else if it is a CONNECT request, send string 200 ok to client and poll both client socket descriptor and server socket descriptor with select(); if I read data from server socket, send this data to client; else if I read data from client socket, send this data to server.
The good thing is that this proxy works, the bad thing is that now I must collect statistics; this is bad because I'm working on a level where I can't get the data I'm interested in. I don't care about the payload, I just need to check in IP and TCP headers the flags I care about.
For example, I'm interested in:
connection tracking;
number of packets sent and received.
As for the first, I would check in the TCP header the SYN flag, SYN/ACK and then a last ACK; as for the second, I would just do +1 to a counter of mine every time a char buffer[1500] is filled with data when I send() or recv() a full packet.
I realized that this is not correct: SOCK_STREAM doesn't have the concept of packet, it is just a continuous stream of bytes! The char buffer[1500] I use at point 7. and 8. has useful statistic, I may set its capacity to 4096 bytes and yet I couldn't keep track of the TCP packets sent or received, because TCP has segments, not packets.
I couldn't parse the char buffer[] looking for SYN flag in TCP header either, because IP and TCP headers are stripped from the header (because of the level I'm working on, specified with IPPROTO_TCP flag) and, if I understood well, the char buffer[] contains only the payload, useless to me.
So, if I'm working on a too high level, I should go lower: once I saw a simple raw socket sniffer where an unsigned char buffer[65535] was cast to struct ethhdr, iphdt, tcphdr and it could see all the flags of all the headers, all the stats I'm interested in!
After the joy, the disappointment: since raw sockets work on a low level they don't have some concepts vital to my proxy; raw sockets can't bind, listen and accept; my proxy is listening on a fixed port, but raw sockets don't know what a port is, it belongs to the TCP level and they bind to a specified interface with setsockopt.
So, if I'd socket(PF_INET, SOCK_RAW, ntohs(ETH_P_ALL)) I should be able to parse the buffer where I recv() and send() at .7 and .8, but I should use recvfrom() and sendto()...but all this sounds quite messy, and it envolves a nice refactoring of my code.
How can I keep intact the structure of my proxy (bind, listen, accept to a fixed port and interface) and increase my line of vision for IP and TCP headers?
My suggestion is to open a raw socket in, for example, another thread of your application. Sniff all traffic and filter out the relevant packets by addresses and port numbers. Basically you want to implement your own packet sniffer:
int sniff()
{
int sockfd;
int len;
int saddr_size;
struct sockaddr saddr;
unsigned char buffer[65536];
sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_TCP);
if (sockfd < 0) {
perror("socket");
return -1;
}
while (1) {
saddr_size = sizeof(saddr);
len = recvfrom(sockfd, buffer, sizeof(buffer), 0, &saddr, &saddr_size);
if (len < 0) {
perror("recvfrom");
close(sockfd);
return -1;
}
// ... do the things you want to do with the packet received here ...
}
close(sockfd);
return 0;
}
You can also bind that raw socket to a specific interface if you know which interface is going to be used for the proxy's traffic. For example, to bind to "eth0":
setsockopt(sockfd, SOL_SOCKET, SO_BINDTODEVICE, "eth0", 4);
Use getpeername() and getsockname() function calls to find the local and remote addresses and port numbers of your TCP connections. You'll want to filter the packets by those.
I have a UDP connection up and listening on a port (localhost) and I am trying to send a Scapy packet from localhost as well. For some reason, my C code never actually captures the packet, however I can see the packet show up in Wireshark just fine. It's been awhile since I've used sockets, but is there some special socket options I have to set or why would I be able to see the packet in Wireshark just fine but not by the C socket?
Note: I was able to successfully catch a packet when I wrote corresponding socket code to send out packets (from localhost) however I am still unable to get the listening code to catch the packet when sent from another computer.
I have found a similar question but when I tried their approach (using UDP instead of TCP), I still couldn't get netcat to catch the Scapy packet.
C Code (condensed for clarity sake)
int main() {
int sock, dataLen, inLen;
struct sockaddr_in inAddr;
short listen_port = 8080;
char buffer[2048];
if (sock = socket(AF_INET,SOCK_DGRAM,0) < 0) {
printf("ERROR: unable to establish socket\n");
return -1;
}
// zero out address structure
memset(&inAddr, 0, sizeof(inAddr));
inAddr.sin_family = AF_INET;
inAddr.sin_addr.s_addr = htonl(INADDR_ANY);
inAddr.sin_port = htons(listen_port);
if (bind(sock, (struct sockaddr*)&inAddr, sizeof(inAddr)) < 0) {
printf("ERROR: unable to bind\n");
return -1;
}
inLen = sizeof(inAddr);
printf("Now listening on port %d\n", listen_port);
while(1) {
dataLen = recvfrom(sock, buffer, 1500, 0, (struct sockaddr*)&inAddr, &inLen);
if (dataLen < 0)
printf("Error receiving datagram\n");
else
printf("Received packet of length %d\n", dataLen);
}
return 0;
}
Scapy Script
# set interface
conf.iface="lo0"
# create IP packet
ip_pkt = IP()/UDP()
ip_pkt.payload = "payload test message"
ip_pkt.dport = 8080
ip_pkt.dst = "127.0.0.1"
ip_pkt.src = "127.0.0.1"
# send out packet
send(ip_pkt)
Scapy needs to be configured slightly differently to work on the Loopback interface, see http://www.secdev.org/projects/scapy/doc/troubleshooting.html under the heading "I can’t ping 127.0.0.1. Scapy does not work with 127.0.0.1 or on the loopback interface"
I used the code given there and sent a scapy packet which was received by a C Socket, this was specifically:
from scapy.all import *
conf.L3socket=L3RawSocket
packet=IP()/UDP(dport=32000)/"HELLO WORLD"
send(packet)
This was then received on a UDP C Socket bound to lo on port 32000 (Scapy defaults to sending IP packets over the loopback interface).
I have the same problem, udp socket does not receive scapy packet.
I suppose there might be something related to this post: Raw Socket Help: Why UDP packets created by raw sockets are not being received by kernel UDP?
And what works for me is the socket.IP_HDRINCL option. Here is the working code for both and sender.
sender:
import socket
from scapy.all import *
rawudp=socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_UDP)
rawudp.bind(('0.0.0.0',56789))
rawudp.setsockopt(socket.SOL_IP, socket.IP_HDRINCL,1)
pkt = IP()/UDP(sport=56789, dport=7890)/'hello'
rawudp.sendto(pkt.build(), ('127.0.0.1',7890))
receiver:
import socket
so = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
so.bind(('0.0.0.0',7890))
while True:
print so.recv(1024)
Verified on Fedora 14, although doesn't work on my MBP...
I think the problem is in setting incompatible set of interface, src and dst address.
When destination is loopback (127.0.0.1), interface should be lo and addresses (assuming both client and server run on the same host):
ip_pkt.dst = "127.0.0.1"
ip_pkt.src = "127.0.0.1"
Another way is to send to the ethernet address (assuming 192.168.1.1 is configured on eth0 and both client and server run on the same host):
ip_pkt.dst = "192.168.1.1"
ip_pkt.src = "192.168.1.1"
If you try different hosts, then using 127.0.0.1 and lo is not possible. Set src to client machine's ip and dst to server machine's ip.
I have a client on PC. I have a server on PC. The client and server are connected via a router with firmware based on Linux OS.
The client sends a packet to the server and receive a response. The router must intercept the packets and modify it. Something like sniffing but it's not a sniffing because i need to modify the packets.
I must to write a program for this.
I tried to open a raw socket on the router, but reсvfrom on raw socket does not intercept the packet and just copy it. The packet is going on.
Could you suggest me any way to solve this problem?
P.S. Sorry for my bad English. :)
I'd use a mix of iptables and libnetfilter_queue (assuming your kernel is relatively recent)
Add to the iptables a rules that forward all the udp packets to the NFQUEUE 0 in order to get packets from kernel to user space.
iptables -A INPUT -p udp -m udp --dport xxxxx -j NFQUEUE --queue-num 0
Build a process who listen to the NFQUEUE number 0, modify payload and give the full packet back to the kernel space using libnetfilter_queue capabilities. Follow this link to know how to do it.
In a nutshell you have to open the queue 0 (nfq_create_queue), set the mode in order to get the content of the packet (nfq_set_mode), then loop in an infinite recv to get ever udp packet filtered by iptables
fd = nfq_fd(h);
while ((rv = recv(fd, buf, sizeof(buf), 0)) >= 0) {
printf("pkt received\n");
nfq_handle_packet(h, buf, rv);
}
Everytime you call nfq_handle_packet is called, the callback defined during the nfq_create_queue phase is called. In that callback you have to modify the payload, update the size and recalculate the checksum, then set as "valid" with nfq_set_verdict
I wrote the module for the kernel and some applications. Module uses netfilter and discards packets that I need to netfilter_queue. The application processes a queue and I decide what to do with each package.
uint hook_main(uint hooknum,
struct sk_buff *skb,
const struct net_device *in,
const struct net_device *out,
int (*okfn)(struct sk_buff *) )
{
struct iphdr *ip;
struct udphdr *udp;
if (skb->protocol == htons(ETH_P_IP)){
ip = (struct iphdr *)(skb->data);
if (ip->version == 4 && ip->protocol == IPPROTO_UDP){
udp = (struct udphdr *)(skb->data + sizeof(struct iphdr));
if(ntohs(udp->dest) == SOME_PORT){
return NF_QUEUE;
}
}
}
return NF_ACCEPT;
}
int init_module ()
{
printk("[udp-catch] start udp-catch\n");
catch_hook.hook = hook_main;
catch_hook.owner = THIS_MODULE;
catch_hook.pf = PF_INET;
catch_hook.hooknum = NF_INET_FORWARD;
catch_hook.priority = NF_IP_PRI_FIRST;
nf_register_hook(&catch_hook);
return 0;
}
And a redesigned sample from netfilter.org is the application.
Routers will automatically send out whatever they receive on their other ports.
e.g. For a 4 port router, what comes in on port 1 will be sent out on ports 2,3 & 4.
To do what you require, you need another PC with 2 network cards. Connect your client PC to one network card, and the server PC to the other.
Then your program will need to recvfrom on one network card, modify the packet and sendto on the other network card.