ethermess/ethermess-backend.c

1196 lines
29 KiB
C

#define _GNU_SOURCE
#include <arpa/inet.h>
#include <assert.h>
#include <err.h>
#include <fcntl.h>
#include <inttypes.h>
#include <limits.h>
#include <linux/if_packet.h>
#include <net/ethernet.h>
#include <net/if.h>
#include <net/if_arp.h>
#include <poll.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <time.h>
#include <unistd.h>
#define EM_PROTOCOL_VERSION 1
#define EM_MESSAGE_MAX_LENGTH (1500 - 2 - 2 - 2)
#define EM_STATUS_BROADCAST_TIME (60 * 1000 + 1000 * random_byte() / 64)
#define EM_RETRANSMIT_TIME (1000 + random_byte() * 2)
#define EM_MAX_RETRANSMIT 5
// Version 0
#define EMT_SPEAK_VERSION 0
#define EMT_STATUS_REQUEST 1
#define EMT_STATUS 2
#define EMT_MSGID_REQUEST 3
#define EMT_MSGID 4
#define EMT_MESSAGE 5
#define EMT_ACK 6
// Version 1
#define EMT_FILE_OFFER 7
#define EMS_AVAILABLE 0
#define EMS_UNAVAILABLE 1
#define EMS_OFFLINE 2
bool running = true;
int packet_socket;
int urandom;
unsigned char own_mac[6];
unsigned char broadcast_mac[6] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
unsigned char own_status = EMS_AVAILABLE;
unsigned char own_nick[256];
unsigned char own_nick_length = 0;
struct timespec next_status_broadcast;
enum message_send_states {IDLE, QUEUED, WAITING_MSGID, SENDING, WAITING_ACK};
enum message_send_states own_message_send_state = IDLE;
unsigned char own_message_destination_mac[6];
unsigned char own_message[EM_MESSAGE_MAX_LENGTH];
size_t own_message_length = 0;
uint16_t own_message_msgid = 0;
unsigned char retransmission_count = 0;
struct timespec next_retransmission;
struct msgid_cache_entry {
unsigned char other_mac[6];
bool know_send;
bool know_receive;
uint16_t next_send;
uint16_t next_receive;
};
struct msgid_cache_entry msgid_cache[256];
ssize_t msgid_cache_fill = 0;
unsigned char next_slot = 0;
ssize_t msgid_cache_lookup(const unsigned char mac[6]) {
for (ssize_t i = 0; i < msgid_cache_fill; i++) {
if (memcmp(msgid_cache[i].other_mac, mac, 6) == 0) {
// Found it
return i;
}
}
// Did not find it
return -1;
}
ssize_t msgid_cache_add(const unsigned char mac[6]) {
ssize_t index = next_slot++;
// If we are adding into a new slot (instead of overwriting one),
// expand the fill pointer
//
// + 1 because msgid_cache_fill of N means that cache slots [0, N-1]
// are in use
if (msgid_cache_fill < index + 1) {
msgid_cache_fill = index + 1;
}
memcpy(msgid_cache[index].other_mac, mac, sizeof(msgid_cache[index].other_mac));
msgid_cache[index].know_send = false;
msgid_cache[index].know_receive = false;
return index;
}
unsigned char random_byte(void) {
unsigned char randomness;
if (read(urandom, &randomness, 1) != 1) {
err(1, "read");
}
return randomness;
}
struct timespec ms_in_future(intmax_t ms) {
struct timespec ts;
if (clock_gettime(CLOCK_MONOTONIC, &ts) == -1) {
err(1, "clock_gettime");
}
// We can't really do anything to check for time_t overflow, because
// there are no macros to test for its size, so let's just hope it
// won't overflow
ts.tv_sec += ms / 1000;
#if LONG_MAX < 1999999999L
#error This code does time arithmetic, and requires a long big enough to hold almost 2 seconds worth of nanoseconds
#endif
ts.tv_nsec += (ms % 1000) * 1000 * 1000;
if (ts.tv_nsec >= 1000 * 1000 * 1000) {
ts.tv_sec += 1;
ts.tv_nsec -= 1000 * 1000 * 1000;
}
return ts;
}
intmax_t saturating_add(intmax_t a, intmax_t b) {
if (a < 0 && b < 0) {
// a: [min, -1]
// b: [min, -1]
// min + min -> underflow
// min + -1 -> underflow
// -1 + -1 -> ok
// a + b < INTMAX_MIN || - a
// b < INTMAX_MIN - a
if (b < INTMAX_MIN - a) {
// Underflow
return INTMAX_MIN;
}
} else if (a < 0 && b >= 0) {
// a: [min, -1]
// b: [0, max]
// min + 0 -> ok
// min + max -> ok
// -1 + 0 -> ok
// -1 + max -> ok
} else if (a >= 0 && b < 0) {
// See above but swap a and b
} else if (a >= 0 && b >= 0) {
// a: [0, max]
// b: [0, max]
// 0 + 0 -> ok
// 0 + max -> ok
// max + max -> overflow
// a + b > INTMAX_MAX || -a
// b > INTMAX_MAX - a
if (b > INTMAX_MAX - a) {
// Overflow
return INTMAX_MAX;
}
}
return a + b;
}
intmax_t saturating_sub(intmax_t a, intmax_t b) {
// a - b = a + (-b)
// Only case where -b is not safe is when b < -INTMAX_MAX (because
// INTMAX_MIN can be smaller than -INTMAX_MAX, but INTMAX_MAX can't be
// larger than -INTMAX_MIN)
if (b < -INTMAX_MAX) {
// a - b || + c - c
// a - b + c - c
// a - (b - c) - c
// a + (c - b) - c
intmax_t c = saturating_sub(b, -INTMAX_MAX);
return saturating_sub(saturating_add(a, c - b), c);
} else {
return saturating_add(a, -b);
}
}
intmax_t saturating_mul(intmax_t a, intmax_t b) {
// Doesn't give 100% right results when one parameter is INTMAX_MIN,
// but at least it won't ever overflow
if (a == 0 || b == 0) {
return 0;
}
if (a < 0) {
return saturating_sub(0, saturating_mul(saturating_sub(0, a), b));
}
if (b < 0) {
return saturating_sub(0, saturating_mul(a, saturating_sub(0, b)));
}
if (INTMAX_MAX / a < b) {
// Overflow
return INTMAX_MAX;
}
return a * b;
}
int wait_ms_until(struct timespec then) {
// This function basically returns the difference in ms between the
// current time and the given time, clamped to [0, INT_MAX]
//
// That format works for poll(2), which will immediately exit after
// checking status if timeout is 0, and otherwise wait the given
// number of ms
struct timespec now;
if (clock_gettime(CLOCK_MONOTONIC, &now) == -1) {
err(1, "clock_gettime");
}
// Uses saturating arithmetic. I guess this might be wrong in some cases
// but can't be bothered to deal with it any other way, especially as a
// clamping the values to even something like [0, 1] would result in
// mostly correct functioning
intmax_t sec_diff = saturating_sub(then.tv_sec, now.tv_sec);
intmax_t ns_diff = saturating_sub(then.tv_nsec, now.tv_nsec);
intmax_t ms = saturating_add(saturating_mul(sec_diff, 1000), ns_diff / 1000 / 1000);
// Clamp
if (ms > INT_MAX) {
ms = INT_MAX;
} else if (ms < 0) {
ms = 0;
}
return (int)ms;
}
void drop_privileges(void) {
uid_t uid = getuid();
gid_t gid = getgid();
if (setresgid(gid, gid, gid) == -1) {
err(1, "setresgid");
}
if (setresuid(uid, uid, uid) == -1) {
err(1, "setresuid");
}
}
void send_frame(const unsigned char *frame, size_t frame_length) {
// 64B is the minimum frame side
// We check for 60B, since kernel handles the FCS (4B)
if (frame_length < 60) {
unsigned char padded_frame[60];
memset(padded_frame, 0, sizeof(padded_frame));
memcpy(padded_frame, frame, frame_length);
if (write(packet_socket, padded_frame, sizeof(padded_frame)) == -1) {
err(1, "write");
}
} else {
if (write(packet_socket, frame, frame_length) == -1) {
err(1, "write");
}
}
}
void write_headers(unsigned char frame[14], const unsigned char destination_mac[6], unsigned char version, unsigned char packet_type) {
// Destination MAC
memcpy(&frame[0], destination_mac, 6);
// Source MAC
memcpy(&frame[6], own_mac, 6);
// EtherType
frame[12] = 0xda;
frame[13] = 0x7a;
// Ethermess version
frame[14] = version;
// Ethermess packet type
frame[15] = packet_type;
}
void send_speak_version(const unsigned char destination[6]) {
unsigned char frame[14 + 2 + 1];
write_headers(frame, destination, 0, EMT_SPEAK_VERSION);
// Version to speak
frame[16] = EM_PROTOCOL_VERSION;
send_frame(frame, sizeof(frame));
}
void send_status_request(const unsigned char destination[6]) {
unsigned char frame[14 + 2];
write_headers(frame, destination, 0, EMT_STATUS_REQUEST);
send_frame(frame, sizeof(frame));
}
void send_status(const unsigned char destination[6]) {
unsigned char frame[14 + 2 + 1 + 1 + own_nick_length];
write_headers(frame, destination, 0, EMT_STATUS);
// Status
frame[16] = own_status;
// Length of nick
frame[17] = own_nick_length;
// Nick
memcpy(&frame[18], own_nick, own_nick_length);
send_frame(frame, sizeof(frame));
}
void send_msgid_request(const unsigned char destination[6]) {
unsigned char frame[14 + 2];
write_headers(frame, destination, 0, EMT_MSGID_REQUEST);
send_frame(frame, sizeof(frame));
}
void send_msgid(const unsigned char destination[6]) {
unsigned char frame[14 + 2 + 2];
write_headers(frame, destination, 0, EMT_MSGID);
// Look up destination in the ID cache
ssize_t cache_index = msgid_cache_lookup(destination);
if (cache_index == -1) {
// Not in the cache
// Create a new entry
cache_index = msgid_cache_add(destination);
}
if (!msgid_cache[cache_index].know_receive) {
// We don't have receive ID stored in the cache
// In that case, start from a random index
msgid_cache[cache_index].next_receive = (random_byte() << 8) | random_byte();
msgid_cache[cache_index].know_receive = true;
}
// Message ID of next message we're waiting to receive
uint16_t msgid = msgid_cache[cache_index].next_receive;
frame[16] = msgid >> 8;
frame[17] = msgid & 0xff;
send_frame(frame, sizeof(frame));
}
void send_message(void) {
unsigned char frame[14 + 2 + 2 + 2 + own_message_length];
write_headers(frame, own_message_destination_mac, 0, EMT_MESSAGE);
// Message ID
frame[16] = own_message_msgid >> 8;
frame[17] = own_message_msgid & 0xff;
// Message length
frame[18] = own_message_length >> 8;
frame[19] = own_message_length & 0xff;
// Message
memcpy(&frame[20], own_message, own_message_length);
send_frame(frame, sizeof(frame));
}
void send_ack(const unsigned char destination[6], uint16_t msgid) {
unsigned char frame[14 + 2 + 2];
write_headers(frame, destination, 0, EMT_ACK);
// Message ID
frame[16] = msgid >> 8;
frame[17] = msgid & 0xff;
send_frame(frame, sizeof(frame));
}
void send_file_offer(const unsigned char destination[6]) {
unsigned char frame[14 + 2];
write_headers(frame, destination, 1, EMT_FILE_OFFER);
send_frame(frame, sizeof(frame));
}
void readallx(int fd, unsigned char *buf, size_t length) {
size_t completed = 0;
while (completed < length) {
ssize_t res = read(fd, &buf[completed], length - completed);
if (res == -1) {
err(1, "read");
} else if (res == 0) {
errx(1, "Unexpected EOF");
}
completed += res;
}
}
void writeallx(int fd, const void *buf, size_t length) {
const unsigned char *cbuf = buf;
size_t completed = 0;
while (completed < length) {
ssize_t res = write(fd, &cbuf[completed], length - completed);
if (res == -1) {
err(1, "write");
}
completed += res;
}
}
void writeallx_u16(int fd, uint16_t value) {
unsigned char buf[2];
buf[0] = value >> 8;
buf[1] = value & 0xff;
writeallx(fd, buf, sizeof(buf));
}
bool check_utf8(const unsigned char *data, size_t data_length, bool newline_tab_allowed);
void read_status(void) {
unsigned char status;
readallx(0, &status, 1);
if (status != EMS_AVAILABLE && status != EMS_UNAVAILABLE && status != EMS_OFFLINE) {
errx(1, "Frontend sent a status %u that we don't understand", status);
}
unsigned char length;
readallx(0, &length, 1);
unsigned char nick[length];
readallx(0, nick, length);
if (!check_utf8(nick, length, false)) {
errx(1, "Frontend sent a nick with malformed utf-8 or control characters");
}
own_status = status;
memcpy(own_nick, nick, length);
own_nick_length = length;
}
void read_message(void) {
if (own_message_send_state != IDLE) {
errx(1, "Frontend sent a new message while we are still processing the old one");
}
unsigned char mac[6];
readallx(0, mac, sizeof(mac));
unsigned char length_raw[2];
readallx(0, length_raw, 2);
size_t length = (length_raw[0] << 8) | length_raw[1];
if (length > EM_MESSAGE_MAX_LENGTH) {
errx(1, "Frontend sent a message that is too long (%zuB, max is %uB)", length, EM_MESSAGE_MAX_LENGTH);
}
unsigned char message[length];
readallx(0, message, length);
if (!check_utf8(message, length, true)) {
errx(1, "Frontend sent a nick with malformed utf-8 or control characters other than newline");
}
memcpy(own_message_destination_mac, mac, sizeof(own_message_destination_mac));
memcpy(own_message, message, length);
own_message_length = length;
own_message_send_state = QUEUED;
}
void read_command(void) {
unsigned char cmd;
readallx(0, &cmd, 1);
if (cmd == 'q') {
running = false;
} else if (cmd == 'r') {
unsigned char mac[6];
readallx(0, mac, sizeof(mac));
send_status_request(mac);
} else if (cmd == 's') {
read_status();
send_status(broadcast_mac);
} else if (cmd == 'm') {
read_message();
} else if (cmd == 'f') {
unsigned char mac[6];
readallx(0, mac, sizeof(mac));
send_file_offer(mac);
} else {
errx(1, "Frontend sent an unknown command %c", cmd);
}
}
bool check_padding(const unsigned char *data, size_t index, size_t data_length) {
// Valid padding is all zero bytes
assert(index <= data_length);
for (size_t i = index; i < data_length; i++) {
if (data[i] != 0) {
// Check failed
return false;
}
}
// Check succeeded
return true;
}
bool check_utf8(const unsigned char *data, size_t data_length, bool newline_tab_allowed) {
size_t remaining = 0;
size_t length = 0;
uint32_t codepoint = 0;
for (size_t i = 0; i < data_length; i++) {
unsigned char byte = data[i];
if (byte <= 0x7f) {
// 0xxxxxxx - single byte
if (remaining != 0) {
// Can't appear in the middle of a multibyte sequence
return false;
}
remaining = 0;
length = 1;
codepoint = byte;
} else if (byte <= 0xbf) {
// 10xxxxxx - continuation byte
if (remaining == 0) {
// Can only appear in the middle of a multibyte sequence
return false;
}
remaining--;
length++;
codepoint <<= 6;
codepoint |= byte & 0x3f;
} else if (byte <= 0xdf) {
// 110xxxxx - first byte of double byte sequence
if (remaining != 0) {
// Can't appear in the middle of a multibyte sequence
return false;
}
remaining = 1;
length = 1;
codepoint = byte & 0x1f;
} else if (byte <= 0xef) {
// 1110xxxx - first byte of triple byte sequence
if (remaining != 0) {
// Can't appear in the middle of a multibyte sequence
return false;
}
remaining = 2;
length = 1;
codepoint = byte & 0x0f;
} else if (byte <= 0xf7) {
// 11110xxx - first byte of quadruple byte sequence
if (remaining != 0) {
// Can't appear in the middle of a multibyte sequence
return false;
}
remaining = 3;
length = 1;
codepoint = byte & 0x07;
}
if (remaining == 0) {
// Full codepoint constructed
// Reject overlong encodings
if (codepoint <= 0x007f && length > 1) {
return false;
} else if (codepoint <= 0x07ff && length > 2) {
return false;
} else if (codepoint <= 0xffff && length > 3) {
return false;
} else if (codepoint <= 0x10ffff && length > 4) {
return false;
}
// Reject code points over U+10FFFF
if (codepoint > 0x10ffff) {
return false;
}
// Reject surrogate pairs
if (0xd800 <= codepoint && codepoint <= 0xdfff) {
return false;
}
// Reject non-characters
if ((codepoint & 0xffff) == 0xfffe || (codepoint & 0xffff) == 0xffff) {
// Plane end non-characters
return false;
} else if (0xfdd0 <= codepoint && codepoint <= 0xfdef) {
// BMP non-character block
return false;
}
// Reject control characters
if (codepoint <= 0x1f) {
// C0 control character
if (!newline_tab_allowed || (codepoint != 0x0a && codepoint != 0x09)) {
return false;
}
} else if (0x80 <= codepoint && codepoint <= 0x9f) {
// C1 control character
return false;
} else if (codepoint == 0x2028) {
// U+2028 LINE SEPARATOR
return false;
} else if (codepoint == 0x2029) {
// U+2029 PARAGRAPH SEPARATOR
return false;
}
}
}
if (remaining != 0) {
// Can't end at the middle of a multibyte sequence
return false;
}
return true;
}
void handle_speak_version(const unsigned char source_mac[6], const unsigned char *data, size_t data_length) {
if (data_length < 1) {
// Too short
return;
}
unsigned char version = data[0];
if (!check_padding(data, 1, data_length)) {
// Malformed padding
return;
}
// Type of event: Speak version
writeallx(1, "v", 1);
// MAC
writeallx(1, source_mac, 6);
// Version
writeallx(1, &version, 1);
}
void handle_status(const unsigned char source_mac[6], const unsigned char *data, size_t data_length) {
if (data_length < 2) {
// Too short
return;
}
unsigned char status = data[0];
if (status != EMS_AVAILABLE && status != EMS_UNAVAILABLE && status != EMS_OFFLINE) {
// Unknown status, throw away
return;
}
unsigned char nick_length = data[1];
if (nick_length > data_length - 2) {
// Malformed length field
return;
}
unsigned char nick[nick_length];
memcpy(nick, &data[2], nick_length);
if (!check_padding(data, 2 + nick_length, data_length)) {
// Malformed padding
return;
}
if (!check_utf8(nick, nick_length, false)) {
// Malformed utf-8, or has control chars
return;
}
// Type of event: Status
writeallx(1, "s", 1);
// MAC
writeallx(1, source_mac, 6);
// Status
writeallx(1, &status, 1);
// Nick
writeallx(1, &nick_length, 1);
writeallx(1, nick, nick_length);
}
void handle_msgid(const unsigned char source_mac[6], const unsigned char *data, size_t data_length) {
if (data_length < 2) {
// Too short
return;
}
uint16_t msgid = (data[0] << 8) | data[1];
if (!check_padding(data, 2, data_length)) {
// Malformed padding
return;
}
ssize_t cache_index = msgid_cache_lookup(source_mac);
if (cache_index == -1) {
// Not in the cache, so add it there
cache_index = msgid_cache_add(source_mac);
}
if (msgid_cache[cache_index].know_send) {
// There is sth in the cache, test whether we should update it
uint16_t diff = msgid - msgid_cache[cache_index].next_send;
if (diff < 0x8000) {
// See the description in handle_message for what is going on
msgid_cache[cache_index].next_send = msgid;
}
} else {
// Nothing in the cache
msgid_cache[cache_index].next_send = msgid;
msgid_cache[cache_index].know_send = true;
}
}
void handle_message(const unsigned char source_mac[6], const unsigned char *data, size_t data_length) {
if (data_length < 4) {
// Too short
return;
}
uint16_t msgid = (data[0] << 8) | data[1];
uint16_t message_length = (data[2] << 8) | data[3];
if (message_length > data_length - 4 || message_length > EM_MESSAGE_MAX_LENGTH) {
// Malformed length field
return;
}
unsigned char message[message_length];
memcpy(message, &data[4], message_length);
if (!check_padding(data, 4 + message_length, data_length)) {
// Malformed padding
return;
}
if (!check_utf8(message, message_length, true)) {
// Malformed utf-8, or has control chars other than newline
return;
}
// See whether we've already received this message and update the next msgid if so
ssize_t cache_index = msgid_cache_lookup(source_mac);
if (cache_index == -1) {
// No cache entry -> add an empty one
cache_index = msgid_cache_add(source_mac);
}
if (!msgid_cache[cache_index].know_receive) {
// We have a cache entry, but no idea what next message to receive
// Assume this is not a repeat transmission, and accept the message
// The easiest way to do this is to set this message's msgid as the
// one we're looking to receive
msgid_cache[cache_index].next_receive = msgid;
msgid_cache[cache_index].know_receive = true;
}
uint16_t diff = msgid - msgid_cache[cache_index].next_receive;
if (diff < 0x8000) {
// The msgid counter can wrap around, so a simple larger than
// comparison will not work. Instead, we look at the distance
// between the message's msgid and the one we're expecting to
// receive. If it is from 0 to 0x8000, we consider it to be
// in the forwards direction, and if it is more, we consider it
// to be in the backwards one. This leaves equally sized (2^15)
// ranges for values in both cases.
// In this case the msgid is considered to be of a message we
// haven't yet seen. Therefore we'll continue processing, and
// update the next msgid we're expecting to receive to this one
// plus 1
msgid_cache[cache_index].next_receive = msgid + 1;
} else {
// In this case we consider the message already received
// We will send an ack, but not process it any further
send_ack(source_mac, msgid);
return;
}
// Type of event: Received a message
writeallx(1, "m", 1);
// MAC
writeallx(1, source_mac, 6);
// Message length
writeallx_u16(1, message_length);
// Message
writeallx(1, message, message_length);
send_ack(source_mac, msgid);
}
void handle_ack(const unsigned char source_mac[6], const unsigned char *data, size_t data_length) {
if (data_length < 2) {
// Too short
return;
}
uint16_t msgid = (data[0] << 8) | data[1];
if (!check_padding(data, 2, data_length)) {
// Malformed padding
return;
}
// Type of event: ACK received
writeallx(1, "a", 1);
// MAC
writeallx(1, source_mac, 6);
// Msgid
writeallx_u16(1, msgid);
if (own_message_send_state == WAITING_ACK && msgid == own_message_msgid) {
own_message_send_state = IDLE;
}
}
void process_frame(void) {
unsigned char frame[1518]; // Largest a 802.3 frame can be without FCS
ssize_t res = recv(packet_socket, frame, sizeof(frame), 0);
if (res == -1) {
err(1, "recv");
} else if (res < 16) {
// Frame too short to contain enough information
return;
}
size_t packet_length = (size_t)res;
// Check that the packet is Ethermess (EtherType DA7A)
if (frame[12] != 0xda || frame[13] != 0x7a) {
return;
}
bool broadcast;
if (memcmp(frame, own_mac, 6) == 0) {
// Targetted at us
broadcast = false;
} else if (memcmp(frame, broadcast_mac, 6) == 0) {
// Broadcast
broadcast = true;
} else {
// Does not concern us
return;
}
// Extract source MAC
unsigned char source_mac[6];
memcpy(source_mac, &frame[6], sizeof(source_mac));
// Extract version
unsigned char version = frame[14];
// If they speak a version we don't understand, tell them to speak ours
// Exception: If it was a broadcast, just ingore it
if (version > EM_PROTOCOL_VERSION) {
if (!broadcast) {
send_speak_version(source_mac);
}
return;
}
// Extract Ethermess packet type
unsigned char packet_type = frame[15];
// Process the packet based on the version and packet type
if (version == 0) {
switch (packet_type) {
case EMT_SPEAK_VERSION:
handle_speak_version(source_mac, &frame[16], packet_length - 16);
break;
case EMT_STATUS_REQUEST:
if (check_padding(&frame[16], 0, packet_length - 16)) {
send_status(source_mac);
}
break;
case EMT_STATUS:
handle_status(source_mac, &frame[16], packet_length - 16);
break;
case EMT_MSGID_REQUEST:
if (check_padding(&frame[16], 0, packet_length - 16)) {
send_msgid(source_mac);
}
break;
case EMT_MSGID:
handle_msgid(source_mac, &frame[16], packet_length - 16);
break;
case EMT_MESSAGE:
handle_message(source_mac, &frame[16], packet_length - 16);
break;
case EMT_ACK:
handle_ack(source_mac, &frame[16], packet_length - 16);
break;
}
} else if (version == 1) {
switch (packet_type) {
case EMT_FILE_OFFER:
fprintf(stderr, "File offered\n"); //debg
break;
}
}
}
void eventloop(void) {
// Listen on both stdin for commands and network interface for packets
struct pollfd pollfds[2];
// stdin
pollfds[0].fd = 0;
pollfds[0].events = POLLIN;
// Network interface
pollfds[1].fd = packet_socket;
pollfds[1].events = POLLIN;
while (running) {
int retransmit_wait_ms = INT_MAX;
// (Attempt) to process a message send
if (own_message_send_state == QUEUED) {
// We need to have the correct msgid to be able to send
ssize_t cache_index = msgid_cache_lookup(own_message_destination_mac);
if (cache_index == -1 || !msgid_cache[cache_index].know_send) {
// We don't know what the msgid should be
// -> ask the other side
send_msgid_request(own_message_destination_mac);
// Wait around 1 to 1.5s before asking again
next_retransmission = ms_in_future(EM_RETRANSMIT_TIME);
retransmission_count = 0;
own_message_send_state = WAITING_MSGID;
} else {
// It is in the cache
own_message_msgid = msgid_cache[cache_index].next_send++;
own_message_send_state = SENDING;
}
}
if (own_message_send_state == WAITING_MSGID) {
ssize_t cache_index = msgid_cache_lookup(own_message_destination_mac);
if (cache_index == -1 || !msgid_cache[cache_index].know_send) {
// Still no msgid
retransmit_wait_ms = wait_ms_until(next_retransmission);
if (retransmit_wait_ms == 0 && retransmission_count < EM_MAX_RETRANSMIT) {
// Time to resend
send_msgid_request(own_message_destination_mac);
// Wait around 1 to 1.5s before asking again
next_retransmission = ms_in_future(EM_RETRANSMIT_TIME);
retransmission_count++;
} else if (retransmit_wait_ms == 0 && retransmission_count >= EM_MAX_RETRANSMIT) {
// Time to give up
own_message_send_state = IDLE;
// Type of event: Msgid failed
writeallx(1, "I", 1);
}
} else {
// Found msgid
own_message_msgid = msgid_cache[cache_index].next_send++;
own_message_send_state = SENDING;
}
}
if (own_message_send_state == SENDING) {
// Type of event: msgid for a message
writeallx(1, "i", 1);
// Msgid
writeallx_u16(1, own_message_msgid);
// Send message
send_message();
// Wait around 1 to 1.5 before sending again
next_retransmission = ms_in_future(EM_RETRANSMIT_TIME);
retransmission_count = 0;
own_message_send_state = WAITING_ACK;
}
if (own_message_send_state == WAITING_ACK) {
retransmit_wait_ms = wait_ms_until(next_retransmission);
if (retransmit_wait_ms == 0 && retransmission_count < EM_MAX_RETRANSMIT) {
// Time to resend
send_message();
// Wait around 1 to 1.5 before sending again
next_retransmission = ms_in_future(EM_RETRANSMIT_TIME);
retransmission_count++;
} else if (retransmit_wait_ms == 0 && retransmission_count >= EM_MAX_RETRANSMIT) {
// Time to give up
own_message_send_state = IDLE;
// Type of event: ACK not received
writeallx(1, "A", 1);
}
}
// Process status broadcasting
int status_broadcast_wait_ms = wait_ms_until(next_status_broadcast);
if (status_broadcast_wait_ms == 0) {
// The time has come to send the status broadcast
send_status(broadcast_mac);
// Do next one in about a minute
next_status_broadcast = ms_in_future(EM_STATUS_BROADCAST_TIME);
}
// Figure out how many ms to wait
int wait_ms;
if (retransmit_wait_ms < status_broadcast_wait_ms) {
wait_ms = retransmit_wait_ms;
} else {
wait_ms = status_broadcast_wait_ms;
}
int ready = poll(pollfds, sizeof(pollfds) / sizeof(*pollfds), wait_ms);
if (ready == -1) {
err(1, "poll");
}
// stdin
if (ready > 0 && pollfds[0].revents != 0) {
ready--;
if (pollfds[0].revents & POLLIN) {
// Read a command
read_command();
} else if (pollfds[0].revents & POLLHUP) {
// Quit on frontend exiting
exit(1);
} else {
errx(1, "Got poll event %hd on stdin\n", pollfds[0].revents);
}
}
// packet_socket
if (ready > 0 && pollfds[1].revents != 0) {
ready--;
if (pollfds[1].revents & POLLIN) {
// Process a frame
process_frame();
} else if (pollfds[1].revents & POLLERR) {
// Lost connection
errx(1, "Network went down");
} else {
errx(1, "Got poll event %hd on packet socket\n", pollfds[1].revents);
}
}
if (ready > 0) {
errx(1, "poll(1) says we have ready fds, but neither was ready");
}
}
}
int main(int argc, char **argv) {
if (argc != 2) {
fprintf(stderr, "Usage: %s interface\n", argv[0]);
exit(1);
}
const char *interface_name = argv[1];
// Open /dev/urandom for getting randomness
urandom = open("/dev/urandom", O_RDONLY);
// Create a packet socket
packet_socket = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
if (packet_socket == -1) {
err(1, "socket");
}
// Only creating the socket requires root privs
drop_privileges();
// Find the index of the network interface
struct ifreq ifr;
strncpy(ifr.ifr_name, interface_name, IFNAMSIZ);
if (ioctl(packet_socket, SIOCGIFINDEX, &ifr) == -1) {
err(1, "ioctl");
}
// Bind to the network interface
struct sockaddr_ll sll;
sll.sll_family = AF_PACKET;
sll.sll_protocol = htons(ETH_P_ALL);
sll.sll_ifindex = ifr.ifr_ifindex;
if (bind(packet_socket, (const struct sockaddr*)&sll, sizeof(sll)) == -1) {
err(1, "bind");
}
// Get our own MAC
strncpy(ifr.ifr_name, interface_name, IFNAMSIZ);
if (ioctl(packet_socket, SIOCGIFHWADDR, &ifr) == -1) {
err(1, "ioctl");
}
if (ifr.ifr_hwaddr.sa_family != ARPHRD_ETHER) {
errx(1, "Not an Ethernet interface");
}
memcpy(own_mac, ifr.ifr_hwaddr.sa_data, sizeof(own_mac));
// Print it out
if (write(1, own_mac, 6) != 6) {
err(1, "write");
}
// Initialize the message id cache
memset(msgid_cache, 0, sizeof(msgid_cache));
// Set our status and nick
read_status();
// Broadcast our status to the network to let them know we're here
send_status(broadcast_mac);
// Schedule next broadcast of our status about 1 min in the future
next_status_broadcast = ms_in_future(EM_STATUS_BROADCAST_TIME);
// Request status from everyone, so that we can get an idea of who is on the network
send_status_request(broadcast_mac);
// Start the event loop
eventloop();
// Set our status to going offline and broadcast that
own_status = EMS_OFFLINE;
send_status(broadcast_mac);
// Close the socket (tho I'm not 100% sure it's needed)
if (close(packet_socket) == -1) {
err(1, "close");
}
// Flush stdout
if (fflush(stdout) == EOF) {
err(1, "fflush");
}
// Close urandom
if (close(urandom) == -1) {
err(1, "close");
}
return 0;
}