#define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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; }