I am using raw sockets to send an IP Packet of Type UDP.
My goal is to send a DHCP request. The RFC defines, that the Source IP Address of Clients that not yet have an IP, must be set to 0. So, what I am doing is I create a buffer which holds enough space for the IP Header, the UDP Header and the Payload. I then modify the IP Headers and set the source IP Address to 0, like this:
struct iphdr ip;
ip.version = 4; // Ipv4
ip.frag_off = 0; // IP Fragmentation
ip.tos = 0; // Type of Service - We don't need this
ip.ttl = 255; // Maxmimum value
ip.protocol = IPPROTO_UDP; // DHCP uses UDP
ip.check = 0; // The checksum needs to be 0 before being calculated
// We don't yet have an IP Address
if((ip.saddr = inet_addr("0.0.0.0")) == -1) {
perror("Failed to convert IP address");
exit(1);
}
ip.daddr = htonl(INADDR_BROADCAST); // we have to do a broadcast
ip.id = 489; // The packets ID
ip.ihl = 5; // Header length - 5 means no additional options are being sent in the IP header
ip.tot_len = sizeof(struct packet); // The total length of the Packet
// Secondly, create the udp header and fill out the information
struct udphdr udp;
udp.source = htons(DHCP_CLIENT_PORT); // We are a client sending a request
udp.dest = htons(DHCP_SERVER_PORT); // We want to send the packet to a DHCP server
udp.check = 0; // zero the checksum until it's calculated
// The total length of the UDP packet is the size of the UDP header combined with the DHCP message struct
udp.len = htons(sizeof(struct udphdr) + sizeof(struct dhcp_message));
I then do my DHCP stuff and copy all of the structs into one buffer, like this:
//Now, let's copy the structs inside our packet struct we want to return
struct packet *packet = (struct packet *)malloc(sizeof(struct packet));
memcpy(&packet->iph, &ip, sizeof(struct iphdr));
memcpy(&packet->udph, &udp, sizeof(struct udphdr));
memcpy(&packet->dhcpm, dhcp, sizeof(struct dhcp_message));
Then the last part of my code sends the packet I created:
struct packet *pckt = minidhc_make_packet(DHCP_DISCOVER, "enp7s0");
// Create socket
int sockd;
if((sockd = socket(AF_INET, SOCK_RAW, IPPROTO_RAW)) == -1) {
perror("Failed to create socket");
exit(1);
}
// Enable broadcasting
int broadcast = 1;
if(setsockopt(sockd, SOL_SOCKET, SO_BROADCAST, &broadcast, sizeof(broadcast))) {
perror("Failed to enable broadcasting on socket");
exit(1);
}
int hdrincl=1;
if (setsockopt(sockd,IPPROTO_IP,IP_HDRINCL,&hdrincl,sizeof(hdrincl))==-1) {
perror("Failed to set IP_HDRINCL");
exit(1);
}
union sockunion {
struct sockaddr sa;
struct sockaddr_in sin;
};
union sockunion su;
su.sin.sin_family = AF_INET;
su.sin.sin_port = DHCP_SERVER_PORT;
su.sin.sin_addr.s_addr = htonl(INADDR_BROADCAST);
// Send to
int bytes;
if((bytes = sendto(sockd, (void *)pckt, sizeof(struct packet), 0, &su.sa, sizeof(struct sockaddr_in))) == -1) {
perror("Failed to send DHCP request");
exit(1);
}
printf("Bytes sent: %d\n", bytes);
printf("Sent raw packet\n");
free(pckt);
My problem: When sniffing the network traffic with Wireshark, I can capture the broadcast I sent. It is a valid DHCP request. However, the source IP address is my real IP and not 0.0.0.0.
I have tried setting the source IP Address to some arbitrary one ( such as eg 192.168.192.1 ), and it works perfectly fine. So my assumption is, that the Network stack checks for the IP headers and sees that the value of the source IP address is 0 and then adds my real IP to it. That is just an assumption though, I have no idea if that is the case.
I have also tried setting the destination IP address in the IP header to a different one than the destination IP address that I give the sendto as parameter, and it takes the desstination IP of the IP header.
How can I prevent the Network stack from changing my code? Am I following a wrong approach? How is this doable?
So my assumption is, that the Network stack checks for the IP headers and sees that the value of the source IP address is 0 and then adds my real IP to it.
Your assumption is right. See raw(7):
The source address gets filled in automatically by the kernel if it equals 0.0.0.0 (which is the same as INADDR_ANY) and the socket option IP_HDRINCL is enabled.
I think there is no way to change this behaviour, unless you want to modify and recompile your kernel. But you should be able to solve this problem by creating your own ethernet frame, or at least adding a valid ethernet header.
Before you try to do that, you should read the corresponding packet(7) man page. The synopsis section shows what you need:
#include <sys/socket.h>
#include <linux/if_packet.h>
#include <net/ethernet.h> /* the L2 protocols */
packet_socket = socket(AF_PACKET, int socket_type, int protocol);
In consideration of your use case, you would most likely insert htons(ETH_P_ALL) for protocol. That should be enough info to get started. I'm sure you will find plenty of code examples on the internet on how to construct and send the frames properly. Source code dealing with ARP is probably also a good reference since it's happening at the same level.
Hope this helps.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With