Skip to content

Instantly share code, notes, and snippets.

@s1037989
Last active November 9, 2023 11:30
Show Gist options
  • Save s1037989/fe4f3d1bcb54ee3a86eb83f227fa45cd to your computer and use it in GitHub Desktop.
Save s1037989/fe4f3d1bcb54ee3a86eb83f227fa45cd to your computer and use it in GitHub Desktop.
minimal raw ethernet socket send/recv C program
all:
gcc -c -o send_recv.o send_recv.c -Wno-psabi
gcc -c -o recv.o recv.c -lcrypto
gcc -o recv send_recv.o recv.o -lm -lcrypto -Wno-psabi
gcc -c -o send.o send.c -Wno-psabi
gcc -o send send_recv.o send.o -lm -lcrypto -Wno-psabi
#include "send_recv.h"
char *names[]={
"<", /* incoming */
"B", /* broadcast */
"M", /* multicast */
"P", /* promisc */
">", /* outgoing */
};
int fd=-1;
char *map;
struct tpacket_req req = {0};
struct iovec *ring;
void sigproc(int sig)
{
struct tpacket_stats st;
int len=sizeof(st);
if (!getsockopt(fd,SOL_PACKET,PACKET_STATISTICS,(char *)&st,&len)) {
fprintf(stderr, "recieved %u packets, dropped %u\n",
st.tp_packets, st.tp_drops);
}
if ( map ) munmap(map, req.tp_block_size * req.tp_block_nr);
if ( fd>=0 ) close(fd);
if ( ring ) free(ring);
exit(0);
}
int main ( int argc, char **argv )
{
struct pollfd pfd;
struct sockaddr_ll addr;
int i;
signal(SIGINT, sigproc);
/* Open the packet socket */
if ( (fd=socket(PF_PACKET, ETH_P_ALL, 0))<0 ) {
perror("socket()");
return 1;
}
/* Setup the fd for mmap() ring buffer */
req.tp_block_size=8192;
req.tp_frame_size=2048;
req.tp_block_nr=512;
req.tp_frame_nr=4*512;
if ( (setsockopt(fd,
SOL_PACKET,
PACKET_RX_RING,
(char *)&req,
sizeof(req))) != 0 ) {
perror("setsockopt()");
close(fd);
return 1;
};
/* mmap() the sucker */
map=mmap(NULL,
req.tp_block_size * req.tp_block_nr,
PROT_READ|PROT_WRITE|PROT_EXEC, MAP_SHARED, fd, 0);
if ( map==MAP_FAILED ) {
perror("mmap()");
close(fd);
return 1;
}
/* Setup our ringbuffer */
ring=malloc(req.tp_frame_nr * sizeof(struct iovec));
for(i=0; i<req.tp_frame_nr; i++) {
ring[i].iov_base=(void *)((long)map)+(i*req.tp_frame_size);
ring[i].iov_len=req.tp_frame_size;
}
/* bind the packet socket */
memset(&addr, 0, sizeof(addr));
addr.sll_family=AF_PACKET;
addr.sll_protocol=htons(ETH_P_ALL);
addr.sll_ifindex=2;
addr.sll_hatype=0;
addr.sll_pkttype=0;
addr.sll_halen=0;
if ( bind(fd, (struct sockaddr *)&addr, sizeof(addr)) ) {
munmap(map, req.tp_block_size * req.tp_block_nr);
perror("bind()");
close(fd);
return 1;
}
struct ifreq ifopts;
char ifname[] = "enp36s0"; // TODO: get from -i
strncpy(ifopts.ifr_name, ifname, IFNAMSIZ);
if (ioctl(fd, SIOCGIFFLAGS, &ifopts) == -1) perror("Getting socket flags failed when entering promiscuous mode ");
ifopts.ifr_flags |= IFF_PROMISC;
if (ioctl(fd, SIOCSIFFLAGS, &ifopts) == -1) perror("Setting socket flags failed when entering promiscuous mode ");
long long int c = 0;
FILE *file;
// Initialize sha256 digest
EVP_MD_CTX *mdctx;
struct tpacket_stats st;
int len = sizeof(st);
// Initialize the counter
if (!getsockopt(fd, SOL_PACKET, PACKET_STATISTICS, &st, &len)) {
unsigned int total_drops = st.tp_drops;
}
for(i=0;;) {
while(*(unsigned long*)ring[i].iov_base) {
struct tpacket_hdr *h=ring[i].iov_base;
struct sockaddr_ll *sll=(void *)h + TPACKET_ALIGN(sizeof(*h));
unsigned char *bp = (unsigned char *)h + h->tp_mac;
struct ethhdr *eh = (struct ethhdr *)bp;
// TODO: sometimes packets arrive and are not dropped, but they are not being handled here
// this is clear from the exit report of received / dropped packets
// TODO: syslog dropped packets
// if (!getsockopt(fd, SOL_PACKET, PACKET_RX_RING, &st, &len)) {
// printf("recieved %u packets, dropped %u\n", st.tp_packets, st.tp_drops);
// if (st.tp_drops > 0) sigproc(SIGINT);
// }
// printf("%lld %u.%.6u: if%u %s %u bytes | ",
// ++c,
// h->tp_sec, h->tp_usec,
// sll->sll_ifindex,
// names[sll->sll_pkttype],
// h->tp_len);
// TODO: handle protocols via libraries
// if compiled with, e.g., ethabac, then handle it with ethabac
if (ntohs(eh->h_proto) == 0xabac) { // TODO: get from -e
struct frame frame = parse_frame(bp);
// if (frame.packet_nr % 10 == 0) { printf("."); fflush(stdout); } // TODO: use a spinner
// frame 0 is the metadata
// TODO: sometimes the first packet is dropped, so we need to check for that
if (frame.packet_nr == 0) { // TODO: time how long it takes to recv all the packets to last_packet_nr
print_frame(&frame); // TODO: if -v
// TODO: syslog every recv
if (frame.data[0] == 0x00) { // command
// TODO: get command handler dir from -C
// TODO: verify that the command handler is owned by the same owner as this program or exit
// TOOD: run the command handler
if (frame.data[1] == 0xcc && frame.data[2] == 0x01) { // exit
sigproc(SIGINT);
}
}
else { // file
// open the file to recv
file = fopen("filename", "wb"); // TODO: get from frame.data
if (!file) {
perror("fopen");
}
mdctx = EVP_MD_CTX_new();
if (!mdctx) {
perror("EVP_MD_CTX_new");
return 1;
}
if (!EVP_DigestInit_ex(mdctx, EVP_sha256(), NULL)) {
perror("EVP_DigestInit_ex");
return 1;
}
}
}
// frames 1-n are the file data
else if (frame.packet_nr > 0 && frame.packet_nr <= frame.last_packet_nr) {
print_frame(&frame); // TODO: if -vv
fwrite(frame.data, 1, frame.data_len, file);
// add the buffer to the sha256 digest
if (!EVP_DigestUpdate(mdctx, frame.data, frame.data_len)) {
perror("EVP_DigestUpdate");
}
}
// frame n+1 is the hash (all commands have the same hash)
// TODO: sometimes the last packet is dropped, so we need to handle for that
else if (frame.packet_nr > frame.last_packet_nr) {
print_frame(&frame); // TODO: if -v
fclose(file);
// finalize the sha256 digest
unsigned char hash[EVP_MAX_MD_SIZE];
unsigned int hash_len;
if (!EVP_DigestFinal_ex(mdctx, hash, &hash_len)) {
perror("EVP_DigestFinal_ex");
}
// printf("hash exp: %s\n", hex(frame.data, frame.data_len));
// printf("hash act: %s\n", hex(hash, hash_len));
if (memcmp(hash, frame.data, hash_len) == 0) {
printf("The hashes match.\n\n");
// TODO: verify that the callback script is owned by the same owner as this program or exit
// TODO: execute the success callback script
} else {
perror("The hashes do not match.\n\n");
// TODO: verify that the callback script is owned by the same owner as this program or exit
// TODO: execute the error callback script
}
}
}
else if (ntohs(eh->h_proto) == 0x1234) { // TODO: handle other protocols
printf("TODO: handle other protocols\n");
}
else if (ntohs(eh->h_proto) == 0x5678) { // TODO: handle other protocols
printf("TODO: handle other protocols\n");
}
else {
// printf("TODO: handle other protocols\n");
}
// tell the kernel this packet is done with
h->tp_status=TP_STATUS_KERNEL;
__sync_synchronize(); // mb(); // memory barrier
i=(i==req.tp_frame_nr-1) ? 0 : i+1;
}
/* sleep when nothing is happening */
pfd.fd=fd;
pfd.events=POLLIN|POLLERR;
pfd.revents=0;
poll(&pfd, 1, -1);
}
return 0;
}
#include "send_recv.h"
int main() {
struct timespec ts;
ts.tv_sec = 0;
ts.tv_nsec = 1000; // TODO: get from -d
// Initialize sha256 digest
EVP_MD_CTX *mdctx = EVP_MD_CTX_new();
if (!mdctx) {
perror("EVP_MD_CTX_new");
return 1;
}
if (!EVP_DigestInit_ex(mdctx, EVP_sha256(), NULL)) {
perror("EVP_DigestInit_ex");
return 1;
}
int rawSocket = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
if (rawSocket == -1) {
perror("Socket creation error");
exit(1);
}
// TODO: setuid this program to root and allow non-root to run
// TODO: drop privileges ... is this the right place to do that?
struct sockaddr_ll socket_address;
socket_address.sll_ifindex = 2; // ifindex, TODO: get from -i
socket_address.sll_halen = ETH_ALEN;
socket_address.sll_addr[0] = 0x00;
socket_address.sll_addr[1] = 0x00;
socket_address.sll_addr[2] = 0x00;
socket_address.sll_addr[3] = 0x00;
socket_address.sll_addr[4] = 0x00;
socket_address.sll_addr[5] = 0x00;
// initialize the frame
struct frame frame;
for (int i=0; i<sizeof(frame.dst); i++)
frame.dst[i] = socket_address.sll_addr[i]; // link dst to sll
for (int i=0; i<sizeof(frame.src); i++)
frame.src[i] = 0; // TODO: get from ifindex
for (int i=0; i<sizeof(frame.eth); i++)
frame.eth[i] = 0xab+i; // eth, TODO: get from -e
// determine what to send
// TODO: syslog every send
if (0) { // TODO if -c
printf("command");
/* PROCESS TODO: send a file that contains a TSV of:
xxxx (file number)
ssss (file name)
xxxx (file hash)
RECV handler will:
- verify each entry in the TSV
- if recv handler has no errors, it will finalize the batch and exit
- report any missing / failed files by number and exit
- sender will accept stdin for failed numbers and resend, using the same batch id
- sender will accept stdin 0 to finalize the batch and exit
*/
// frame 0 is the metadata
frame.packet_nr = 0;
frame.last_packet_nr = 0;
frame.unique_id = unique_id(frame.last_packet_nr); // TODO: get from -u
// frame.data_len = 1;
// memcpy(&frame.data, &filename, sizeof(filename));
print_frame(&frame); // print the frame, TODO: if -v
send_frame(rawSocket, socket_address, &frame);
}
else { // TODO: if -f
char filename[] = "filename"; // TODO: get from -f
// TODO: run the file against the callback script and exit if it returns false
// TODO: verify that the callback script is owned by the same owner as this program or exit
FILE *file = fopen(filename, "rb");
if (!file) {
perror("fopen");
return 1;
}
// frame 0 is the metadata
frame.packet_nr = 0;
frame.last_packet_nr = (int)ceil((double)file_size(file) / PAYLOAD_DATA_LEN);
frame.unique_id = unique_id(frame.last_packet_nr); // TODO: get from -u
frame.data_len = sizeof(filename) - 1;
memcpy(&frame.data, &filename, sizeof(filename));
print_frame(&frame); // print the frame, TODO: if -v
send_frame(rawSocket, socket_address, &frame);
// frames 1-n are the file data
unsigned char buffer[PAYLOAD_DATA_LEN];
int bytesRead = 0;
while ((bytesRead = fread(buffer, 1, sizeof(buffer), file))) {
// TODO: slow down transmission based on -d ns
//nanosleep(&ts, &ts); // sleep in between packets to not overwhelm the kernel or link
frame.packet_nr++;
frame.data_len = bytesRead;
memcpy(&frame.data, &buffer, bytesRead);
// add the buffer to the sha256 digest
if (!EVP_DigestUpdate(mdctx, buffer, bytesRead)) {
perror("EVP_DigestUpdate");
break;
}
// send the frame
print_frame(&frame); // print the frame, TODO: if -vv
send_frame(rawSocket, socket_address, &frame);
}
fclose(file);
}
// finalize the sha256 digest
unsigned char hash[EVP_MAX_MD_SIZE];
unsigned int hash_len;
if (!EVP_DigestFinal_ex(mdctx, hash, &hash_len)) {
perror("EVP_DigestFinal_ex");
return 1;
}
// frame n+1 is the hash of the 1-n file data frames
frame.packet_nr++;
frame.data_len = hash_len;
memcpy(&frame.data, &hash, frame.data_len);
print_frame(&frame); // print the frame, TODO: if -v
send_frame(rawSocket, socket_address, &frame);
close(rawSocket);
return 0;
}
#include "send_recv.h"
uint64_t htonll(uint64_t value) {
// Check the endianness of the system
const int num = 1;
if(*(char *)&num == 1) {
// If the system is little endian
const uint32_t high_part = htonl((uint32_t)(value >> 32));
const uint32_t low_part = htonl((uint32_t)(value & 0xFFFFFFFFLL));
return ((uint64_t)low_part << 32) | high_part;
} else {
// If the system is big endian
return value;
}
}
uint64_t ntohll(uint64_t value) {
// The operations are the same for both ntohll and htonll
return htonll(value);
}
char* hex(unsigned char *buffer, int length) {
char* hexString = malloc(2*length + 1); // Each byte is 2 hex chars, and +1 for null terminator
if (!hexString) {
perror("malloc");
return NULL;
}
for (int i = 0; i < length; i++) {
sprintf(hexString + i*2, "%02x", buffer[i]);
}
hexString[2*length] = '\0'; // Null-terminate the string
return hexString;
}
unsigned long long file_size (FILE *file) {
fseek(file, 0, SEEK_END);
unsigned long long size = ftell(file);
fseek(file, 0, SEEK_SET);
return size;
}
unsigned long long unique_id(unsigned long long last_packet_nr) {
struct timeval tv;
gettimeofday(&tv, NULL);
long long timestamp = tv.tv_sec * 1000000LL + tv.tv_usec; // Get current timestamp in microseconds
srand(time(NULL));
unsigned int rand_num = rand(); // Generate a random number
return (timestamp << 32) | rand_num | last_packet_nr; // Combine timestamp and random number to create a unique 8-byte ID
}
void print_frame(struct frame *frame) {
int frame_len = FRAME_SIZE(frame->data_len);
printf("% 5d | %12s %12s %4s %016llx %016llx %016llx %04x ", frame_len, hex(frame->dst, 6), hex(frame->src, 6), hex(frame->eth, 2), frame->unique_id, frame->packet_nr, frame->last_packet_nr, frame->data_len);
for (int i = 0; i < frame->data_len; i++) {
if (frame->packet_nr > 0 && frame->packet_nr <= frame->last_packet_nr) {
if (i < 10 || (i > frame->data_len - 10 && i < frame->data_len)) printf("%02x", frame->data[i]);
if (i == 9 && i < frame->data_len - 10) printf("...");
}
else
printf("%02x", frame->data[i]);
}
printf("\n");
}
// void print_frame(struct frame frame, int frame_len) {
// printf("% 5d | %12s %12s %4s %016llx %016llx %016llx %04x ", frame_len, hex(frame.dst, 6), hex(frame.src, 6), hex(frame.eth, 2), frame.unique_id, frame.packet_nr, frame.last_packet_nr, frame.data_len);
// for (int i = 0; i < frame.data_len; i++) {
// if (frame.packet_nr > 0 && frame.packet_nr <= frame.last_packet_nr) {
// if (i < 10 || (i > frame.data_len - 10 && i < frame.data_len)) printf("%02x", frame.data[i]);
// if (i == 9 && i < frame.data_len - 10) printf("...");
// }
// else
// printf("%02x", frame.data[i]);
// }
// printf("\n");
// }
struct frame parse_frame(unsigned char *bp) {
struct frame frame;
memcpy(frame.dst, bp, 6);
memcpy(frame.src, bp + 6, 6);
memcpy(frame.eth, bp + 12, 2);
memcpy(&frame.unique_id, bp + 14, 8);
memcpy(&frame.packet_nr, bp + 14 + 8, 8);
memcpy(&frame.last_packet_nr, bp + 14 + 8 + 8, 8);
memcpy(&frame.data_len, bp + 14 + 8 + 8 + 8, 2);
frame.unique_id = ntohll(frame.unique_id);
frame.packet_nr = ntohll(frame.packet_nr);
frame.last_packet_nr = ntohll(frame.last_packet_nr);
frame.data_len = ntohs(frame.data_len);
memcpy(frame.data, bp + 14 + 8 + 8 + 8 + 2, frame.data_len);
return frame;
}
boolean send_frame(int rawSocket, struct sockaddr_ll socket_address, struct frame *frame) {
unsigned char buffer[sizeof(struct frame)];
int offset = 0;
memcpy(buffer + offset, frame->dst, sizeof(frame->dst));
offset += sizeof(frame->dst);
memcpy(buffer + offset, frame->src, sizeof(frame->src));
offset += sizeof(frame->src);
memcpy(buffer + offset, &frame->eth, sizeof(frame->eth));
offset += sizeof(frame->eth);
uint64_t unique_id = htonll(frame->unique_id);
memcpy(buffer + offset, &unique_id, sizeof(unique_id));
offset += sizeof(unique_id);
uint64_t packet_nr = htonll(frame->packet_nr);
memcpy(buffer + offset, &packet_nr, sizeof(packet_nr));
offset += sizeof(packet_nr);
uint64_t last_packet_nr = htonll(frame->last_packet_nr);
memcpy(buffer + offset, &last_packet_nr, sizeof(last_packet_nr));
offset += sizeof(last_packet_nr);
uint16_t data_len = htons(frame->data_len);
memcpy(buffer + offset, &data_len, sizeof(data_len));
offset += sizeof(data_len);
memcpy(buffer + offset, frame->data, frame->data_len);
offset += frame->data_len;
if (sendto(rawSocket, buffer, offset, 0, (struct sockaddr*)&socket_address, sizeof(struct sockaddr_ll)) == -1) {
perror("Send error");
close(rawSocket);
exit(1);
}
return true;
}
#ifndef SEND_RECV_H
#define SEND_RECV_H
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <stdint.h>
#include <sys/socket.h>
#include <linux/if_packet.h>
#include <netinet/if_ether.h>
#include <net/ethernet.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <netinet/ether.h>
#include <sys/time.h>
#include <time.h>
#include <errno.h>
#include <openssl/evp.h>
#include <math.h>
#ifndef __linux__
#error "Are you loco? This is Linux only!"
#endif
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#define __USE_XOPEN
#include <sys/poll.h>
#include <sys/socket.h>
#include <sys/mman.h>
#include <features.h> /* for the glibc version number */
#if __GLIBC__ >= 2 && __GLIBC_MINOR >= 1
#include <netpacket/packet.h>
#include <net/ethernet.h> /* the L2 protocols */
#else
#include <asm/types.h>
#include <linux/if_packet.h>
#include <linux/if_ether.h> /* The L2 protocols */
#endif
#include <string.h>
#include <netinet/in.h>
//#include <asm/system.h>
#include <signal.h>
//void mb() __attribute__((alias("__sync_synchronize")));
#include <net/if.h>
#include <sys/ioctl.h>
#include <openssl/evp.h>
#define PAYLOAD_DATA_LEN (ETH_FRAME_LEN - ETH_HLEN - 8 - 8 - 8 - 2)
#define FRAME_SIZE(frame_data_len) (ETH_FRAME_LEN - PAYLOAD_DATA_LEN + frame_data_len)
typedef enum { false, true } boolean;
struct frame {
unsigned char dst[6];
unsigned char src[6];
unsigned char eth[2]; // TODO: make this a short int
unsigned long long unique_id; // TODO: split unique_id into a batch id and transaction id
unsigned long long packet_nr;
unsigned long long last_packet_nr;
unsigned short int data_len;
unsigned char data[PAYLOAD_DATA_LEN];
};
void sigproc(int sig);
uint64_t htonll(uint64_t value);
uint64_t ntohll(uint64_t value);
char* hex(unsigned char *buffer, int length);
unsigned long long file_size (FILE *file);
unsigned long long unique_id(unsigned long long last_packet_nr);
void print_frame(struct frame *frame);
struct frame parse_frame(unsigned char *bp);
boolean send_frame(int rawSocket, struct sockaddr_ll socket_address, struct frame *frame);
#endif
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment