I'm trying to send some manually crafted ARP packets over the network,more specifically an ARP request to get the MAC address of a host.
I can't get the final packet right, on wireshark it stills shows some inconsistency.
Let me walk you through :
Here are the struct & typedef I use all over the program ,
I've defined
a IP struct ( => in_addr )
a MAC struct ( => ether_addr )
a Host struct composed of a MAC & IP
Custom struct to represent a Ethernet frame & an ARP frame.
The code:
#define ETH_ADDR_SIZE 6
#define IP_ADDR_SIZE 4
typedef u_char Packet;
typedef struct in_addr IP;
typedef struct ether_addr MAC;
struct Host {
IP ip;
MAC mac;
};
typedef struct pkt_eth {
MAC dest;
MAC src;
u_short type;
} pkt_eth;
typedef struct pkt_arp {
u_short htype;/* hardware type => ethernet , etc */
u_short ptype; /*protocol type => ipv4 or ipv6 */
u_char hard_addr_len; /* usually 6 bytes for ethernet */
u_char proto_addr_len; /*usually 8 bytes for ipv4 */
u_short opcode; /* type of arp */
MAC hard_addr_send;
IP proto_addr_send;
MAC hard_addr_dest;
IP proto_addr_dest;
} pkt_arp;
/* Designate our own MAC / IP addresses of the interface */
extern MAC mac;
extern IP ip;
extern char * interface;
/* Just some vars used to compare with the struct we use */
const MAC broadcast_mac = { 0xff,0xff,0xff,0xff,0xff,0xff };
const MAC null_mac = { 0x00,0x00,0x00,0x00,0x00,0x00 };
const IP broadcast_ip = { 0xffffffff };
const IP null_ip = { 0x00000000 };
const struct Host null_host = {{ 0x00000000 },
{ 0x00,0x00,0x00,0x00,0x00,0x00 }};
/* Empty mac address which can be used as a temp variable */
MAC tmp_mac = { 0x00,0x00,0x00,0x00,0x00,0x00 };
IP tmp_ip = { 0x00000000 };
Here is the relevant function :
int
arp_resolve_mac ( struct Host * host )
{
struct pkt_arp * arp;
struct pkt_eth * eth;
/*Create the request packet */
Packet * request = arp_packet(REQUEST);
eth = (struct pkt_eth *) (request);
arp = (struct pkt_arp *) (request + ETH_SIZE);
/* ethernet frame */
copy_mac(ð->dest,&broadcast_mac);
copy_mac(ð->src,&mac);
/* arp request => mac dest address set to null */
copy_mac(&arp->hard_addr_send,&mac);
copy_mac(&arp->hard_addr_dest,&null_mac);
/* arp request => target ip ! */
copy_ip(&arp->proto_addr_send,&ip);
copy_ip(&arp->proto_addr_dest,&host->ip);
/* Set up sniffing. Better to do it before so less
* prepare time and if any error occurs, no need to send
* the packet. less intrusive */
pcap_init(interface,"arp");
pcap_set_arp_analyzer(arp_analyzer_resolv);
/* Sets the tmp ip variable so we will know if it the right
* response we get or a response coming from another source */
tmp_ip = host->ip;
/* sends the packet */
if(pcap_send_packet(request,ARP_PACKET_SIZE) == -1) {
fprintf(stderr,"Error while sending ARP request packet.\n");
return -1;
}
....
}
Packet *
arp_packet ( int opcode )
{
struct pkt_arp * arp;
struct pkt_eth * eth;
Packet * bytes = (Packet *) malloc(ARP_PACKET_SIZE);
if(bytes == NULL) {
fprintf(stderr,"Could not alloc ARP packet.\n");
return NULL;
}
eth = (struct pkt_eth *) (bytes);
eth->type = htons(ETHERTYPE_ARP);
/* length about hard / proto ... */
arp = (struct pkt_arp *) (bytes + ETH_SIZE);
arp->htype = htons(1);
arp->ptype = htons(0x0800);
arp->hard_addr_len = ETH_ADDR_SIZE;
arp->proto_addr_len = IP_ADDR_SIZE;
/* reply or request */
arp->opcode = opcode == REQUEST ? htons(ARPOP_REQUEST) : htons(ARPOP_REPLY);
return bytes;
} /* ----- end of function arp_empty ----- */
void copy_mac(MAC * m1,const MAC * m2) {
memcpy(m1,m2,ETH_ADDR_SIZE);
}
void copy_ip(IP * i1,const IP * i2) {
memcpy(i1,i2,IP_ADDR_SIZE);
}
void copy_host(struct Host * h1,const struct Host * h2) {
copy_mac(&h1->mac,&h2->mac);
copy_ip(&h1->ip,&h2->ip);
}
Problem:
The created packet is not quite right. Everything is fine up to the hard_addr_send. After this field, there is 2 bytes 0x00,0x00, (seen in GDB) and then the IP address. But due to this offset, it's impossible to correctly parse this packet. For example,in wireshark, instead of getting "10.0.0.1", I've got "0.0.10.0" for IP.
Here is the transcript of GDB :
/** 14 to pass ethernet frame & 4 + 2 + 2 to go to the addresses section*/
(gdb) x/6xb request+14+4+2+2
/** My MAC address , field hard_addr_send. it's GOOD. */
0x606b16: 0x34 0x67 0x20 0x01 0x9a 0x67
(gdb) x/6xb request+14+4+2+2+6
/** 6bytes later, supposedly my IP address.
* It should be 10.0.0.7 but you can see the 0x0a shifted by 2 bytes */
0x606b1c: 0x00 0x00 0x0a 0x00 0x00 0x07
In the method "arp_resolv_mac", everything info is right, i.e. struct Host contains the good information etc; I've checked everything.
I just don't get this offset by 2 bytes ... In a older versions, not using all theses new structs (only char *), I've already succeed at creating a right ARP packet, so I'm kind of wondering if this is not due to the struct, but my knowledge of C does not extend to the memory alignement subject ...!
Thank you.
The problem is that your structs are not packed. One solution would be to use packed structs, i.e.
typedef struct __attribute__ ((__packed__)) pkt_arp {
u_short htype;/* hardware type => ethernet , etc */
u_short ptype; /*protocol type => ipv4 or ipv6 */
u_char hard_addr_len; /* usually 6 bytes for ethernet */
u_char proto_addr_len; /*usually 8 bytes for ipv4 */
u_short opcode; /* type of arp */
MAC hard_addr_send;
IP proto_addr_send;
MAC hard_addr_dest;
IP proto_addr_dest;
} pkt_arp;
However, that is a gcc-specific extension other compilers may not support.
In my opinion, the best solution is accessing the elements of the byte array directly instead of using structs. Yes, it adds a few lines of code, but it's guaranteed to work for compilers that don't implement packed structs too.
Related
The pcap callback function returns the IP header and data as follows:
void packet_handler(u_char* param, const struct pcap_pkthdr* header, const u_char* pkt_data);
My understanding is the first 4 bits of the pkt_data is the IP version from which I can determine it is is IPv4 or IPv6. However, I've tried a few different ways to read the first 4 bits and I'm getting data that does not make sense.
For example, I defined the following structure:
struct ipdata {
u_char version : 4;
u_char dontcare : 4;
};
And then I tried to get the ip version using this code:
ipdata* pipdata;
pipdata = (ipdata*) pkt_data;
ip_ver = pipdata->version;
printf(" %d ", ip_ver);
The above method prints values of 3, 6, 9, 8 and 12. If I watch the traffic at the same time in Wireshark I see that most of the packets are IPv6.
Could someone who has done this clarify how would I go about reading the IP version?
Figure out the answer. Npcap returns the entire ethernet packet, so the first 14 bytes are the Ethernet header:
/* Length of the Ethernet Header (Data Link Layer) */
#define ETHERNET_HEADER_LEN 14
/* Ethernet addresses are 6 bytes */
#define ETHER_ADDR_LEN 6
/* Ethernet header */
struct sniff_ethernet {
u_char ether_dhost[ETHER_ADDR_LEN]; /* Destination host address (i.e. Destination MAC Address) */
u_char ether_shost[ETHER_ADDR_LEN]; /* Source host address (i.e. Source MAC Address) */
u_short ether_type; /* IP? ARP? RARP? etc */
};
You can figure out whether it is an IPv4 or IPv6 packet by looking at the ether_type in the above structure rather than the version in the IP header, such as:
/* Common ethernet types in Hex*/
#define ETHERNET_TYPE_IPv4 0x0800
#define ETHERNET_TYPE_IPv6 0x86DD
u_short eth_type;
ethernet = (struct sniff_ethernet*)(pkt_data);
eth_type = ntohs(ethernet->ether_type);
if (eth_type == ETHERNET_TYPE_IPv4) {
ipv4_handler(pkt_data);
}
else if (eth_type == ETHERNET_TYPE_IPv6)
{
ipv6_handler(pkt_data);
}
The IP header starts right after the ethernet header, so you can get it with code such as the following example for an IPv6 packet:
/* IPv6 header */
typedef struct ipv6_header
{
unsigned int
version : 4,
traffic_class : 8,
flow_label : 20;
uint16_t length;
uint8_t next_header;
uint8_t hop_limit;
struct in6_addr saddr;
struct in6_addr daddr;
} ipv6_header;
const ipv6_header* iph;
iph = (ipv6_header*)(pkt_data + ETHERNET_HEADER_LEN);
From there you can access the version and other information about the IP header. See this post for more information: Getting Npcap IPv6 source and destination addresses
I am currently decomposing a data packet into several headers.
Here's my current code :
void analyse(struct pcap_pkthdr *header, const unsigned char *packet, int verbose) {
// Define headers and payload
const struct ether_header *ethernet = NULL;
const struct ether_arp *arp = NULL;
const struct ip *ip = NULL;
const struct tcphdr *tcp = NULL;
const char *payload = NULL;
/* Ethernet header is the first data block of packet **/
ethernet = ( struct ether_header* ) packet;
// ARP packet following
if( ntohs( ethernet->ether_type ) == ETHERTYPE_ARP ) {
arp = ( struct ether_arp* ) ( packet + ETH_HLEN );
// If the operation performed by the sender is a reply, we increment the ARP Response Counter
if( ntohs(arp->ea_hdr.ar_op ) == 2 ) {
arpResponsesCounter++;
}
} else { // IP packet following
ip = ( struct ip* ) ( packet + ETH_HLEN );
}
// ARP header and IP header don't have the same size
if( arp == NULL ) {
u_int shift_size = (ip->ip_hl)*4;
} else {
}
}
According to http://unix.superglobalmegacorp.com/BSD4.4/newsrc/netinet/ip.h.html and http://unix.superglobalmegacorp.com/Net2/newsrc/netinet/if_ether.h.html , the size of an IP header is given by (ip->ip_hl)*4; but I can't figure out how to get the size of an ARP header.
I need it to define properly the TCP header pointer.
Thanks
I think you are confused. The ARP packet is the ARP header. ARP is a protocol unto itself, and it doesn't contain other protocols as payloads in its packets the way IP does. It is properly a Link-Layer protocol in the way ICMP is a Network-Layer protocol. Both are top-layer protocols, and neither carries other protocols.
You can determine the size of an ARP packet on a network if you know the size of the layer-2 and layer-3 addresses for the network (48 bits for ethernet and 32 bits for IPv4).
Hardware Type is two octets
Protocol Type is two octets
Hardware Address Length is one octet
Protocol Address Length is one octet
Operation is two octets
Sender Hardware Address is Hardware Address Length octets
Sender Protocol Address is Protocol Address Length octets
Destination Hardware Address is Hardware Address Length octets
Destination Protocol Address is Protocol Address Length octets
Basically, you have eight octets, plus two times the Hardware Address Length, plus two times the Protocol Address Length.
For IPv4 on ethernet, this means ( 8 + ( 2 * 6 ) + ( 2 * 4 ) ) = 28.
this is my first time posting on stack overflow so be gentle. I am writing a networking program in c to run on linux machines. The goal of my program is to be able to capture packets sent to it, change the source ip and hw address, rebuild the packet with the new info and send it back out onto the wire. My question relates to the rebuilding process. I have some structs that I am using to hold information about various headers in my programs. Detailed here
struct my_ip
{
u_int8_t ip_vhl; /* header length, version */
#define IP_V(ip) (((ip)->ip_vhl & 0xf0) >> 4)
#define IP_HL(ip) ((ip)->ip_vhl & 0x0f)
u_int8_t ip_tos; /* type of service */
u_int16_t ip_len; /* total length */
u_int16_t ip_id; /* identification */
u_int16_t ip_off; /* fragment offset field */
#define IP_DF 0x4000 /* dont fragment flag */
#define IP_MF 0x2000 /* more fragments flag */
#define IP_OFFMASK 0x1fff /* mask for fragmenting bits */
u_int8_t ip_ttl; /* time to live */
u_int8_t ip_p; /* protocol */
u_int16_t ip_sum; /* checksum */
struct in_addr ip_src,ip_dst; /* source and dest address */
};
/* UDP header */
struct sniff_udp
{
u_short uh_sport; /* source port */
u_short uh_dport; /* destination port */
u_short uh_ulen; /* udp length */
u_short uh_sum; /* udp checksum */
};
#define SIZE_UDP 8 /* length of UDP header */
#define SIZE_ETHERNET 14
As well as a few other structs from the pcap library(like ether_header). I cast the u_char* to these structs like so
struct my_ip* ip = (struct my_ip*)(packet + sizeof(struct ether_header));
struct ether_header* eptr = (struct ether_header *) packet;
Where packet is a u_char holding the entirety of the packet
My question is, once I have modified data within these structures how do I cast all of my stucts back into a single u_char string? I am trying to cast each struct to fill a different segment of string in the same way a packet is structured
This is the code I have so far.
void buildPacket(sniff_udp *udp, ether_header *ethh, my_ip *ip, u_char *payload, u_char *buffer)
{
memset(buffer,0, (sizeof(udp)+sizeof(ethh)+sizeof(ip)+sizeof(payload)));
buffer=(u_char *)(ethh); // adds layer 2 header
(buffer+SIZE_ETHERNET)= (u_char *)ip; // adds layer 3 header
(buffer+SIZE_ETHERNET+sizeof(ip))=(u_char *) udp; // adds protocol header
(buffer+SIZE_ETHERNET+sizeof(ip)+SIZE_UDP)=(u_char *)payload; // adds payload
}
This isn't the correct way to do it from what I've gathered. How can I cast multiple structs to the same string?
Something like
(buffer+SIZE_ETHERNET)= (u_char *)ip; // adds layer 3 header
isn't valid because the lefthand operator of = won't be a (modifable) lvalue.
You can use memcpy() to copy contents of memory. The correct code should be like this:
void buildPacket(sniff_udp *udp, ether_header *ethh, my_ip *ip, u_char *payload, u_char *buffer)
{
memset(buffer,0, (sizeof(udp)+sizeof(ethh)+sizeof(ip)+sizeof(payload)));
memcpy(buffer, ethh, SIZE_ETHERNET); // adds layer 2 header
memcpy(buffer+SIZE_ETHERNET, ip, sizeof(ip)); // adds layer 3 header
memcpy(buffer+SIZE_ETHERNET+sizeof(ip), udp, SIZE_UDP); // adds protocol header
memcpy(buffer+SIZE_ETHERNET+sizeof(ip)+SIZE_UDP, payload, sizeof(payload)); // adds payload
}
This code doesn't seem correct because sizeof(udp), sizeof(ethh), sizeof(ip) and sizeof(payload) will return the size of pointers, not what is pointed, and I don't think it is what you want. Use correct size instead of them.
I am reading a buffer from a socket (AF_PACKET, SOCK_DGRAM, htons(ETH_P_ARP)) with the following code. I am using an arp_frame struct to access the component parts of the contained ARP reply. The inet_ntoa() returns the correct first octet of the IP but the other octets are 0 producing 172.0.0.0.
Question 1 is why might this happen?
Question 2 is how can I print r bytes of the msg buffer as hex in host byte order to debug the packet?
unsigned char msg[65535];
struct ether_arp *arp_frame = (struct ether_arp *)msg;
while ((r = recv(sock, msg, sizeof(msg), 0))) {
// skip it's not an ARP REPLY
if (ntohs(arp_frame->arp_op) != ARPOP_REPLY)
continue;
for (i = 0; i < SONOS_PREFIX_NUM; i++) {
if (!memcmp(sonos_prefixes[i], arp_frame->arp_sha, 3)) {
struct in_addr addr;
addr.s_addr = *arp_frame->arp_spa;
printf("Blah: %lu\n", ntohl(*arp_frame->arp_spa));
printf("Sonos found at %s\n", inet_ntoa(addr));
}
}
}
struct ether_arp looks like this:
struct ether_arp {
struct arphdr ea_hdr; /* fixed-size header */
u_int8_t arp_sha[ETH_ALEN]; /* sender hardware address */
u_int8_t arp_spa[4]; /* sender protocol address */
u_int8_t arp_tha[ETH_ALEN]; /* target hardware address */
u_int8_t arp_tpa[4]; /* target protocol address */
};
With that in mind, I think that your addr.s_addr = *arp_frame->arp_spa; looks a little fishy. arp_frame->arp_spa yields a u_int8_t[4], which you then dereference as a pointer. I think memcpy() might be more appropriate there.
If you think the API is broken, print out the bytes.
"Methinks, Brutus, the fault is not in the stars."
I'm working with the pcap library and I'm trying to capture the source and destination IP addresses but it seems to be giving me totally random results:
Here is my struct:
struct sniff_ip {
u_char ip_vhl;
u_char ip_tos;
u_short ip_len;
u_short ip_id;
u_short ip_off;
u_char ip_ttl;
u_char ip_p;
u_short ip_sum;
struct in_addr ip_src, ip_dst;
};
Here is the relevant code that uses the struct:
void print_payload(u_char *arg, const struct pcap_pkthdr *pkthdr, const u_char *packet)
{
const struct sniff_ip *ip;
int i=0;
static int count=0;
ip = (struct sniff_ip*)(packet + SIZE_ETHERNET);
printf("Source [%s] - Destination [%s]\n", inet_ntoa(ip->ip_src), inet_ntoa(ip->ip_dst));
printf("Payload:\n");
for(i=0; i<pkthdr->len; i++)
{
if(isprint(packet[i]))
printf("%c", packet[i]);
else
printf(".", packet[i]);
if((i%16 == 0 && i != 0) || ( i== pkthdr->len-1))
printf("\n");
}
}
The output is:
Source [207.117.127.0] - Destination [207.117.127.0]
Payload:
................E
..<m4#.#..u.....
.......Q#1......
....0....#......
1\.........
Source [60.190.127.0] - Destination [60.190.127.0]
Payload:
................E
..(..#.#.<......
...........Q#1.P
...wN..
The IP addresses seem to be totally random and aren't mine. The expected output would be to show my own IP for both source and destination since I'm testing it by connecting to myself. I'm running it on port 23 to avoid anybody other data from interfering.
Edit: I got it working, for some reason I had to telnet to "eth0" instead of "localhost" for it to work. However once I changed the port to something more useful, like port 80 it worked fine. I'm not sure why port 23 was different, but oh well.
inet_ntoa returns pointer to its internal buffer, so it's not likely that you can use two inet_ntoas in one call to printf and hope for different results.