diff --git a/tools/Makefile.am b/tools/Makefile.am index 1186967cfb..d99b997327 100644 --- a/tools/Makefile.am +++ b/tools/Makefile.am @@ -70,6 +70,9 @@ findif_SOURCES = findif.c if BUILD_TICKLE halib_PROGRAMS += tickle_tcp tickle_tcp_SOURCES = tickle_tcp.c + +# Enable C99 for tickle_tcp and turn off a C89-style check that conflicts with C99 code +tickle_tcp.o: CFLAGS+=-std=c99 -Wno-declaration-after-statement endif .PHONY: install-exec-hook diff --git a/tools/tickle_tcp.c b/tools/tickle_tcp.c index 7c5a537130..4622408d20 100644 --- a/tools/tickle_tcp.c +++ b/tools/tickle_tcp.c @@ -1,379 +1,1188 @@ -/* - Tickle TCP connections tool - - Author: Jiaju Zhang - Based on the code in CTDB http://ctdb.samba.org/ written by - Andrew Tridgell and Ronnie Sahlberg - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, see . -*/ +/** + * "Tickle-ACK" TCP connection failover support utility + * + * Author: Robert Altnoeder + * Derived from prior work authored by Jiaju Zhang, Andrew Tridgell, Ronnie Sahlberg + * and the Samba project. + * + * This file is part of tickle_tcp. + * + * tickle_tcp is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * tickle_tcp is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with tickle_tcp. If not, see + */ +#include "tickle_tcp.h" -#include -#include #include +#include +#include #include -#include #include -#include -#include -#include -#include -#include -#include #include +#include + +const char *executable_name = "tickle_tcp"; + +// Invokes the arguments parser, then the application's main I/O loop +int main(int argc, char *argv[]) +{ + int rc = ERR_GENERIC; + if (argc >= 1) + { + executable_name = argv[0]; + } + + uint16_t packet_count = DEFAULT_PACKET_COUNT; + if (parse_arguments(argc, argv, &packet_count)) + { + rc = run(packet_count); + } + return rc; +} + +// Reads request line from stdin and hands them over to request line processing +int run(const uint16_t packet_count) +{ + char *const line_buffer = malloc(BUFFER_SIZE); + + struct address_text_buffers buffers; + buffers.src_ip = malloc(BUFFER_SIZE); + buffers.src_port = malloc(BUFFER_SIZE); + buffers.src_netif = malloc(BUFFER_SIZE); + buffers.dst_ip = malloc(BUFFER_SIZE); + buffers.dst_port = malloc(BUFFER_SIZE); + + struct endpoints endpoint_data; + endpoint_data.ipv4_src_address = malloc(sizeof (*(endpoint_data.ipv4_src_address))); + endpoint_data.ipv4_dst_address = malloc(sizeof (*(endpoint_data.ipv4_dst_address))); + endpoint_data.ipv6_src_address = malloc(sizeof (*(endpoint_data.ipv6_src_address))); + endpoint_data.ipv6_dst_address = malloc(sizeof (*(endpoint_data.ipv6_dst_address))); + + ipv4_packet *const ipv4_packet_data = malloc(sizeof (*ipv4_packet_data)); + ipv6_packet *const ipv6_packet_data = malloc(sizeof (*ipv6_packet_data)); + + bool have_allocations = line_buffer != NULL; + have_allocations &= buffers.src_ip != NULL && buffers.src_port != NULL; + have_allocations &= buffers.dst_ip != NULL && buffers.dst_port != NULL; + have_allocations &= buffers.src_netif != NULL; + have_allocations &= endpoint_data.ipv4_src_address != NULL && endpoint_data.ipv4_dst_address != NULL; + have_allocations &= endpoint_data.ipv6_src_address != NULL && endpoint_data.ipv6_dst_address != NULL; + have_allocations &= ipv4_packet_data != NULL && ipv6_packet_data != NULL; + + error_info error = {0, NULL}; + + int rc = EXIT_SUCCESS; + if (have_allocations) + { + char *fgets_rc = NULL; + do + { + fgets_rc = fgets(line_buffer, BUFFER_SIZE, stdin); + if (fgets_rc != NULL) + { + if (strlen(line_buffer) + 1 >= BUFFER_SIZE) + { + error.error_msg = "Error: I/O error while reading input from stdin, cannot continue"; + error.error_nr = EIO; + rc = ERR_IO; + break; + } + + process_line(line_buffer, &buffers, &endpoint_data, packet_count, &error); + } + else + if (feof(stdin) == 0) + { + error.error_msg = "Error: I/O error while reading input from stdin, cannot continue"; + error.error_nr = EIO; + break; + } + } + while (fgets_rc != NULL); + } + else + { + error.error_msg = ERRMSG_OUT_OF_MEMORY; + error.error_nr = ENOMEM; + } + + if (error.error_msg != NULL) + { + display_error(&error); + switch (error.error_nr) + { + case ENOMEM: + { + rc = ERR_OUT_OF_MEMORY; + break; + } + case EIO: + { + rc = ERR_IO; + break; + } + default: + { + rc = ERR_GENERIC; + break; + } + } + } + + free(buffers.src_ip); + free(buffers.src_port); + free(buffers.src_netif); + free(buffers.dst_ip); + free(buffers.dst_port); + + free(endpoint_data.ipv4_src_address); + free(endpoint_data.ipv4_dst_address); + free(endpoint_data.ipv6_src_address); + free(endpoint_data.ipv6_dst_address); + + free(ipv4_packet_data); + free(ipv6_packet_data); + + free(line_buffer); + + return rc; +} + +// Processes an input line +void process_line( + char *const line_buffer, + struct address_text_buffers *const buffers, + struct endpoints *const endpoint_data, + const uint16_t packet_count, + error_info *const error +) +{ + // Trim trailing newline character + trim_newline(line_buffer, strlen(line_buffer)); + + // Trim leading and trailing space or tab characters + trim_lead(line_buffer, strlen(line_buffer)); + trim_trail(line_buffer, strlen(line_buffer)); + + // WARNING: line_buffer_length may no longer be valid after the parse_endpoints function returns + const size_t line_buffer_length = strlen(line_buffer); + if (line_buffer_length >= 1) + { + bool is_ipv6 = false; + // The parse_endpoints function modifies the line_buffer + const bool have_endpoints = parse_endpoints( + line_buffer, line_buffer_length, buffers, endpoint_data, &is_ipv6, error + ); + if (have_endpoints) + { + bool send_rc = false; + if (is_ipv6) + { + send_rc = send_ipv6_packet( + endpoint_data->ipv6_src_address, + endpoint_data->ipv6_dst_address, + packet_count, + error + ); + } + else + { + send_rc = send_ipv4_packet( + endpoint_data->ipv4_src_address, + endpoint_data->ipv4_dst_address, + packet_count, + error + ); + } + + if (!send_rc) + { + display_error(error); + clear_error(error); + } + } + else + { + display_error(error); + clear_error(error); + } + } +} + +// Displays information about the application's command line arguments +void display_help(void) +{ + fputs("Syntax: tickle_tcp [ -n error_msg != NULL) + { + fprintf(stderr, "%s: Error: %s\n", executable_name, error->error_msg); + } + display_error_nr_msg(error->error_nr); +} + +// Displays the system's description for an error code received from system functions +void display_error_nr_msg(const int error_nr) +{ + if (error_nr != 0) + { + const char* const errno_msg = strerror(error_nr); + if (errno_msg != NULL) + { + fprintf(stderr, " Error description: %s\n", errno_msg); + fprintf(stderr, " Error code: %d\n", error_nr); + } + } +} + +// Clears error information in the error_info data structure +void clear_error(error_info *const error) +{ + error->error_nr = 0; + error->error_msg = NULL; +} + +// Parses the command line arguments for this application +// +// @returns true if successful, otherwise false +bool parse_arguments(const int argc, char *argv[], uint16_t *const packet_count) +{ + bool rc = true; + if (argv != NULL) + { + const char *crt_key = NULL; + + for (int idx = 1; idx < argc; ++idx) + { + if (crt_key == NULL) + { + if (strcmp(OPT_PACKET_COUNT, argv[idx]) == 0) + { + crt_key = OPT_PACKET_COUNT; + } + else + if (strcmp(OPT_HELP, argv[idx]) == 0 || strcmp(LONG_OPT_HELP, argv[idx]) == 0) + { + display_help(); + rc = false; + break; + } + else + { + fprintf(stderr, "Invalid command line argument '%s'\n", argv[idx]); + display_help(); + rc = false; + break; + } + } + else + { + if (crt_key == OPT_PACKET_COUNT) + { + error_info error; + // bool parse_uint16(const char *input, size_t input_length, uint16_t *value, error_info *error); + if (!parse_uint16(argv[idx], strlen(argv[idx]), packet_count, &error)) + { + rc = false; + display_error(&error); + break; + } + } + crt_key = NULL; + } + } + } + return rc; +} + +// Parses source/destination endpoints (IP address & port) information +// +// @returns true if successful, otherwise false +bool parse_endpoints( + char *const line_buffer, + const size_t line_length, + const struct address_text_buffers *const buffers, + struct endpoints *const endpoint_data, + bool *const is_ipv6, + error_info *const error +) +{ + bool have_endpoints = false; + const size_t split_idx = find_whitespace(line_buffer, line_length); + if (split_idx < line_length) + { + // Split source address & port + const bool have_src_str = split_address_and_port( + line_buffer, + split_idx, + buffers->src_ip, + buffers->src_port, + error + ); + + if (have_src_str) + { + *is_ipv6 = strchr(buffers->src_ip, ':') != NULL; -#define discard_const(ptr) ((void *)((intptr_t)(ptr))) - -typedef union { - struct sockaddr sa; - struct sockaddr_in ip; - struct sockaddr_in6 ip6; -} sock_addr; - -uint32_t uint16_checksum(uint16_t *data, size_t n); -void set_nonblocking(int fd); -void set_close_on_exec(int fd); -static int parse_ipv4(const char *s, unsigned port, struct sockaddr_in *sin); -static int parse_ipv6(const char *s, const char *iface, unsigned port, sock_addr *saddr); -int parse_ip(const char *addr, const char *iface, unsigned port, sock_addr *saddr); -int parse_ip_port(const char *addr, sock_addr *saddr); -int send_tickle_ack(const sock_addr *dst, - const sock_addr *src, - uint32_t seq, uint32_t ack, int rst); -static void usage(void); - -uint32_t uint16_checksum(uint16_t *data, size_t n) + // Split destination address & port + const size_t dst_str_offset = split_idx + 1; + // Trim leading space & tabs in the destination address + { + const size_t dst_str_length = line_length - dst_str_offset; + trim_lead(&line_buffer[dst_str_offset], dst_str_length); + } + const bool have_dst_str = split_address_and_port( + &line_buffer[dst_str_offset], + strlen(&line_buffer[dst_str_offset]), + buffers->dst_ip, + buffers->dst_port, + error + ); + + if (have_dst_str) + { + if (*is_ipv6) + { + const bool have_netif = split_address_and_netif( + buffers->src_ip, + strlen(buffers->src_ip), + buffers->src_netif, + error + ); + if (have_netif) + { + const bool have_src_address = parse_ipv6( + buffers->src_ip, + strlen(buffers->src_ip), + buffers->src_netif, + strlen(buffers->src_netif), + buffers->src_port, + strlen(buffers->src_port), + endpoint_data->ipv6_src_address, + error + ); + + if (have_src_address) + { + have_endpoints = parse_ipv6( + buffers->dst_ip, + strlen(buffers->dst_ip), + NULL, + 0, + buffers->dst_port, + strlen(buffers->dst_port), + endpoint_data->ipv6_dst_address, + error + ); + } + } + } + else + { + const bool have_src_address = parse_ipv4( + buffers->src_ip, + strlen(buffers->src_ip), + buffers->src_port, + strlen(buffers->src_port), + endpoint_data->ipv4_src_address, + error + ); + + if (have_src_address) + { + have_endpoints = parse_ipv4( + buffers->dst_ip, + strlen(buffers->dst_ip), + buffers->dst_port, + strlen(buffers->dst_port), + endpoint_data->ipv4_dst_address, + error + ); + } + } + } + } + } + else + { + fprintf(stderr, "Warning: Ignored invalid input line: \"%s\"\n", line_buffer); + } + return have_endpoints; +} + +// Splits the referenced address_and_port string into a separate address string and port string +// +// The port is expected to be the part of the string following the last occurrence of the separator character ":". +// +// @returns true if successful, otherwise false +bool split_address_and_port( + const char *const address_and_port, + const size_t address_and_port_length, + char *const address, + char *const port, + error_info *const error +) +{ + bool have_split = false; + + const size_t split_idx = find_last_char(address_and_port, address_and_port_length, ':'); + if (split_idx < address_and_port_length) + { + const size_t address_length = split_idx; + const size_t port_offset = split_idx + 1; + const size_t port_length = address_and_port_length - port_offset; + + strncpy(address, address_and_port, address_length); + address[address_length] = '\0'; + + strncpy(port, &address_and_port[port_offset], port_length); + port[port_length] = '\0'; + + have_split = true; + } + else + { + error->error_msg = ERRMSG_UNPARSABLE_ENDPOINT; + error->error_nr = 0; + } + + return have_split; +} + +// Splits the referenced character array into an address and network interface part +// +// This applies to the string representation of link-local IPv6 addresses. +// The format is: address%interface. +// Input is expected in the address character array. If a network interface suffix is present, the separator character +// "%" is replaced by a null character in the address character array, and the interface name is copied into +// the netif character array. +// +// If a network interface suffix is present and the argument netif is a NULL pointer, the address is treated as +// invalid and the function returns with an error. +// +// @returns true if successful, otherwise false +bool split_address_and_netif( + char *const address, + const size_t address_length, + char *const netif, + error_info *const error +) { - uint32_t sum=0; - while (n >= 2) { - sum += (uint32_t)ntohs(*data); - data++; - n -= 2; - } - if (n == 1) { - sum += (uint32_t)ntohs(*(uint8_t *)data); - } - return sum; -} - -static uint16_t tcp_checksum(uint16_t *data, size_t n, struct iphdr *ip) + bool have_split = false; + size_t split_idx = find_last_char(address, strlen(address), '%'); + if (split_idx < address_length) + { + if (netif != NULL) + { + const size_t netif_offset = split_idx + 1; + const size_t netif_length = address_length - netif_offset; + + strncpy(netif, &address[netif_offset], netif_length); + netif[netif_length] = '\0'; + address[split_idx] = '\0'; + have_split = true; + } + else + { + // Link-local scope id specified, but not valid for this address + // (e.g., a destination address) + error->error_msg = ERRMSG_UNPARSABLE_ENDPOINT; + error->error_nr = 0; + } + } + else + { + if (netif != NULL) + { + netif[0] = '\0'; + } + // Nothing to split, indicate success + have_split = true; + } + + return have_split; +} + +// Removes leading space and tab characters from the referenced character array +void trim_lead(char *const buffer, const size_t length) { - uint32_t sum = uint16_checksum(data, n); - uint16_t sum2; - sum += uint16_checksum((uint16_t *)(void *)&ip->saddr, - sizeof(ip->saddr)); - sum += uint16_checksum((uint16_t *)(void *)&ip->daddr, - sizeof(ip->daddr)); - sum += ip->protocol + n; - sum = (sum & 0xFFFF) + (sum >> 16); - sum = (sum & 0xFFFF) + (sum >> 16); - sum2 = htons(sum); - sum2 = ~sum2; - if (sum2 == 0) { - return 0xFFFF; - } - return sum2; + if (length >= 1) + { + size_t src_idx = 0; + while (src_idx < length && (buffer[src_idx] == ' ' || buffer[src_idx] == '\t')) + { + ++src_idx; + } + if (src_idx > 0) + { + size_t dst_idx = 0; + while (src_idx < length) + { + buffer[dst_idx] = buffer[src_idx]; + ++src_idx; + ++dst_idx; + } + buffer[dst_idx] = '\0'; + } + } } -static uint16_t tcp_checksum6(uint16_t *data, size_t n, struct ip6_hdr *ip6) +// Truncates a trailing newline character in the referenced character array +void trim_newline(char *const buffer, const size_t length) { - uint32_t phdr[2]; - uint32_t sum = 0; - uint16_t sum2; - - sum += uint16_checksum((uint16_t *)(void *)&ip6->ip6_src, 16); - sum += uint16_checksum((uint16_t *)(void *)&ip6->ip6_dst, 16); - - phdr[0] = htonl(n); - phdr[1] = htonl(ip6->ip6_nxt); - sum += uint16_checksum((uint16_t *)phdr, 8); - - sum += uint16_checksum(data, n); - - sum = (sum & 0xFFFF) + (sum >> 16); - sum = (sum & 0xFFFF) + (sum >> 16); - sum2 = htons(sum); - sum2 = ~sum2; - if (sum2 == 0) { - return 0xFFFF; - } - return sum2; + if (length >= 1) + { + const size_t last_idx = length - 1; + if (buffer[last_idx] == '\n') + { + buffer[last_idx] = '\0'; + } + } } -void set_nonblocking(int fd) +// Truncates trailing space and tab characters in the referenced character array +void trim_trail(char *const buffer, const size_t length) { - unsigned v; - v = fcntl(fd, F_GETFL, 0); - fcntl(fd, F_SETFL, v | O_NONBLOCK); + if (length >= 1) + { + size_t idx = length; + while (idx > 0) + { + if (buffer[idx - 1] != ' ' && buffer[idx - 1] != '\t') + { + break; + } + --idx; + } + buffer[idx] = '\0'; + } } -void set_close_on_exec(int fd) -{ - unsigned v; - v = fcntl(fd, F_GETFD, 0); - fcntl(fd, F_SETFD, v | FD_CLOEXEC); +// Finds the index of the first whitespace (space or tab) character in the specified character array +size_t find_whitespace(const char *const buffer, const size_t length) +{ + size_t idx = 0; + while (idx < length && buffer[idx] != ' ' && buffer[idx] != '\t') + { + ++idx; + } + return idx < length ? idx : ~((size_t) 0); +} + +// Finds the index of the last occurrence of the specified character in the specified character array +size_t find_last_char(const char *const buffer, const size_t length, const char letter) +{ + size_t split_idx = ~((size_t) 0); + if (length >= 1) + { + size_t idx = length; + while (idx > 0) + { + --idx; + if (buffer[idx] == letter) + { + split_idx = idx; + break; + } + } + } + return split_idx; } -static int parse_ipv4(const char *s, unsigned port, struct sockaddr_in *sin) +// Parses the string representation of a port number +// +// @returns true if successful, otherwise false +bool parse_uint16(const char *const input, const size_t input_length, uint16_t *const value, error_info *const error) { - sin->sin_family = AF_INET; - sin->sin_port = htons(port); + bool have_result = false; - if (inet_pton(AF_INET, s, &sin->sin_addr) != 1) { - fprintf(stderr, "Failed to translate %s into sin_addr\n", s); - return -1; - } + // Not using strtol/strtoll/etc. due to the various shortcomings of those functions, + // such as allowing whitespace or allowing to parse negative numbers + // as unsigned values. + // + // Derived from the integerparse module of the + // C++ DSA library at https://github.com/raltnoeder/cppdsaext + uint16_t result = 0; + if (input_length >= 1) + { + const uint16_t max_value_base = UINT16_MAX / 10; + size_t index = 0; + while (index < input_length) + { + if (result > max_value_base) + { + break; + } + result *= 10; - return 0; + const unsigned char digit_char = (const unsigned char) input[index]; + if (!(digit_char >= '0' && digit_char <= '9')) + { + break; + } + const uint16_t digit_value = digit_char - (const unsigned char) '0'; + if (digit_value > UINT16_MAX - result) + { + break; + } + result += digit_value; + ++index; + } + have_result = index == input_length; + } + + if (have_result) + { + *value = result; + } + else + { + error->error_msg = ERRMSG_INVALID_NR; + } + + return have_result; } -static int parse_ipv6(const char *s, const char *iface, unsigned port, sock_addr *saddr) +// Parses the string representation of unsigned decimal integer numbers with a width of at most 16 bits +// +// @returns true if successful, otherwise false +bool parse_port_number( + const char *const input, + const size_t input_length, + uint16_t *const port_number, + error_info *const error +) { - saddr->ip6.sin6_family = AF_INET6; - saddr->ip6.sin6_port = htons(port); - saddr->ip6.sin6_flowinfo = 0; - saddr->ip6.sin6_scope_id = 0; + bool have_port_number = parse_uint16(input, input_length, port_number, error); + if (!have_port_number) + { + error->error_msg = ERRMSG_INVALID_PORT_NR; + } + return have_port_number; +} - if (inet_pton(AF_INET6, s, &saddr->ip6.sin6_addr) != 1) { - fprintf(stderr, "Failed to translate %s into sin6_addr\n", s); - return -1; - } +// Parses the string representatio of an IPv4 address and port combination and applies the result +// to a sockaddr_in data structure +// +// @returns true if successful, otherwise false +bool parse_ipv4( + const char *const address_input, + const size_t address_input_length, + const char *const port_input, + const size_t port_input_length, + struct sockaddr_in *const address, + error_info *const error +) +{ + uint16_t port_number = 0; + bool have_address = parse_port_number(port_input, port_input_length, &port_number, error); + if (have_address) + { + errno = 0; + int rc = inet_pton(AF_INET, address_input, &address->sin_addr); + have_address = check_inet_pton_rc(rc, errno, error); + if (have_address) + { + address->sin_family = AF_INET; + address->sin_port = htons(port_number); + } + } + return have_address; +} - if (iface && IN6_IS_ADDR_LINKLOCAL(&saddr->ip6.sin6_addr)) { - saddr->ip6.sin6_scope_id = if_nametoindex(iface); - } +// Parses the string representation of an IPv6 address and port combination and applies the result +// to a sockaddr_in6 data structure +// +// @returns true if successful, otherwise false +bool parse_ipv6( + const char *const address_input, + const size_t address_input_length, + const char *const netif_input, + const size_t netif_input_length, + const char *const port_input, + const size_t port_input_length, + struct sockaddr_in6 *const address, + error_info *const error +) +{ + uint16_t port_number = 0; + bool have_address = false; + if (parse_port_number(port_input, port_input_length, &port_number, error)) + { + int rc = inet_pton(AF_INET6, address_input, &address->sin6_addr); + if (check_inet_pton_rc(rc, errno, error)) + { + address->sin6_family = AF_INET6; + address->sin6_port = htons(port_number); + address->sin6_flowinfo = 0; + address->sin6_scope_id = 0; - return 0; + if (IN6_IS_ADDR_LINKLOCAL(&(address->sin6_addr)) != 0) + { + if (netif_input != NULL) + { + if (netif_input_length >= 1) + { + const unsigned int netif_index = if_nametoindex(netif_input); + if (netif_index != 0) + { + address->sin6_scope_id = (uint32_t) netif_index; + have_address = true; + } + else + { + error->error_msg = ERRMSG_LINKLOCAL_NO_NETIF; + error->error_nr = errno; + } + } + else + { + error->error_msg = ERRMSG_UNUSABLE_LINKLOCAL; + error->error_nr = 0; + } + } + else + { + // no IPv6 zone ID is required (e.g. it's a destination address) + have_address = true; + } + } + else + { + have_address = true; + } + } + } + return have_address; } -int parse_ip(const char *addr, const char *iface, unsigned port, sock_addr *saddr) +// Creates an IPv4 packet and sends it to the specified destination by calling send_packet +// +// @returns true if successful, otherwise false +bool send_ipv4_packet( + const struct sockaddr_in *const src_address, + const struct sockaddr_in *const dst_address, + const uint16_t packet_count, + error_info *const error +) { - char *p; - int ret; + bool rc = false; + + ipv4_packet *const packet = malloc(sizeof (*packet)); + if (packet != NULL) + { + memset((void *) packet, 0, sizeof (*packet)); - p = index(addr, ':'); - if (!p) - ret = parse_ipv4(addr, port, &saddr->ip); - else - ret = parse_ipv6(addr, iface, port, saddr); + ipv4_header_no_opt *const ip_header = &packet->ip_header; + ip_header->vsn_length = sizeof (ipv4_header_no_opt) / sizeof (uint32_t); + ip_header->vsn_length |= 0x40; + ip_header->length = htons(sizeof (ipv4_header_no_opt) + sizeof (custom_tcp_header)); + ip_header->ttl = 255; + ip_header->protocol = IPPROTO_TCP; + memcpy(ip_header->src_address, &src_address->sin_addr, sizeof (ip_header->src_address)); + memcpy(ip_header->dst_address, &dst_address->sin_addr, sizeof (ip_header->dst_address)); + ip_header->chksum = 0; - return ret; + custom_tcp_header *const tcp_header = &packet->tcp_header; + tcp_header->src_port = src_address->sin_port; + tcp_header->dst_port = dst_address->sin_port; + tcp_header->tcp_flags = TCP_ACK; + tcp_header->cmb_data_off_ns = (sizeof (*tcp_header) / 4) << 4; + tcp_header->window_size = htons(1234); + tcp_header->checksum = ipv4_tcp_checksum( + (const unsigned char *) tcp_header, + sizeof (*tcp_header), + &packet->ip_header + ); + + rc = send_packet( + AF_INET, + (const struct sockaddr *) dst_address, + sizeof (*dst_address), + ntohs((uint16_t) dst_address->sin_port), + (void *) packet, + sizeof (*packet), + packet_count, + error + ); + free(packet); + } + else + { + error->error_nr = ENOMEM; + error->error_msg = ERRMSG_OUT_OF_MEMORY; + } + return rc; } -int parse_ip_port(const char *addr, sock_addr *saddr) +// Creates an IPv6 packet and sends it to the specified destination by calling send_packet +// +// @returns true if successful, otherwise false +bool send_ipv6_packet( + const struct sockaddr_in6 *const src_address, + const struct sockaddr_in6 *const dst_address, + const uint16_t packet_count, + error_info *const error +) { - char *s, *p; - unsigned port; - char *endp = NULL; - int ret; - - s = strdup(addr); - if (!s) { - fprintf(stderr, "Failed strdup()\n"); - return -1; - } - - p = rindex(s, ':'); - if (!p) { - fprintf(stderr, "This addr: %s does not contain a port number\n", s); - free(s); - return -1; - } - - port = strtoul(p+1, &endp, 10); - if (!endp || *endp != 0) { - fprintf(stderr, "Trailing garbage after the port in %s\n", s); - free(s); - return -1; - } - *p = 0; - - ret = parse_ip(s, NULL, port, saddr); - free(s); - return ret; + bool rc = false; + + ipv6_packet *const packet = malloc(sizeof (*packet)); + if (packet != NULL) + { + memset((void *) packet, 0, sizeof (*packet)); + + ipv6_header *const ip_header = &packet->ip_header; + ip_header->vsn_cls_flowlbl = htons(0x6000); + ip_header->hop_limit = 64; + ip_header->next_header = IPPROTO_TCP; + ip_header->length = htons(20); + memcpy(ip_header->src_address, &src_address->sin6_addr, sizeof (ip_header->src_address)); + memcpy(ip_header->dst_address, &dst_address->sin6_addr, sizeof (ip_header->dst_address)); + + custom_tcp_header *const tcp_header = &packet->tcp_header; + tcp_header->src_port = src_address->sin6_port; + tcp_header->dst_port = dst_address->sin6_port; + tcp_header->tcp_flags = TCP_ACK; + tcp_header->cmb_data_off_ns = (sizeof (*tcp_header) / 4) << 4; + tcp_header->window_size = htons(1234); + tcp_header->checksum = ipv6_tcp_checksum( + (const unsigned char *) tcp_header, + sizeof (*tcp_header), + &packet->ip_header + ); + + // Required for sending an IPv6 packet without generating an EINVAL error. + // This behavior seems to be undocumented. + struct sockaddr_in6 *const send_dst_address = malloc(sizeof (*send_dst_address)); + if (send_dst_address != NULL) + { + *send_dst_address = *dst_address; + send_dst_address->sin6_port = 0; + + rc = send_packet( + AF_INET6, + (const struct sockaddr *) send_dst_address, + sizeof (*send_dst_address), + ntohs((uint16_t) dst_address->sin6_port), + (void *) packet, + sizeof (*packet), + packet_count, + error + ); + free(send_dst_address); + } + else + { + error->error_nr = ENOMEM; + error->error_msg = ERRMSG_OUT_OF_MEMORY; + } + free(packet); + } + else + { + error->error_nr = ENOMEM; + error->error_msg = ERRMSG_OUT_OF_MEMORY; + } + return rc; } -int send_tickle_ack(const sock_addr *dst, - const sock_addr *src, - uint32_t seq, uint32_t ack, int rst) +// Sends a packet to the specified destination address +// +// @returns true if successful, otherwise false +bool send_packet( + const int socket_domain, + const struct sockaddr* const dst_address, + const size_t dst_address_size, + const uint16_t dst_port, + const void* const packet_data, + const size_t packet_data_size, + const uint16_t packet_count, + error_info* const error +) { - int s; - int ret; - uint32_t one = 1; - uint16_t tmpport; - sock_addr *tmpdest; - struct { - struct iphdr ip; - struct tcphdr tcp; - } ip4pkt; - struct { - struct ip6_hdr ip6; - struct tcphdr tcp; - } ip6pkt; - - switch (src->ip.sin_family) { - case AF_INET: - memset(&ip4pkt, 0, sizeof(ip4pkt)); - ip4pkt.ip.version = 4; - ip4pkt.ip.ihl = sizeof(ip4pkt.ip)/4; - ip4pkt.ip.tot_len = htons(sizeof(ip4pkt)); - ip4pkt.ip.ttl = 255; - ip4pkt.ip.protocol = IPPROTO_TCP; - ip4pkt.ip.saddr = src->ip.sin_addr.s_addr; - ip4pkt.ip.daddr = dst->ip.sin_addr.s_addr; - ip4pkt.ip.check = 0; - - ip4pkt.tcp.source = src->ip.sin_port; - ip4pkt.tcp.dest = dst->ip.sin_port; - ip4pkt.tcp.seq = seq; - ip4pkt.tcp.ack_seq = ack; - ip4pkt.tcp.ack = 1; - if (rst) - ip4pkt.tcp.rst = 1; - ip4pkt.tcp.doff = sizeof(ip4pkt.tcp)/4; - ip4pkt.tcp.window = htons(1234); - ip4pkt.tcp.check = tcp_checksum((uint16_t *)&ip4pkt.tcp, sizeof(ip4pkt.tcp), &ip4pkt.ip); - - s = socket(AF_INET, SOCK_RAW, IPPROTO_RAW); - if (s == -1) { - fprintf(stderr, "Failed to open raw socket (%s)\n", strerror(errno)); - return -1; - } - - ret = setsockopt(s, SOL_IP, IP_HDRINCL, &one, sizeof(one)); - if (ret != 0) { - fprintf(stderr, "Failed to setup IP headers (%s)\n", strerror(errno)); - close(s); - return -1; - } - - set_nonblocking(s); - set_close_on_exec(s); - - ret = sendto(s, &ip4pkt, sizeof(ip4pkt), 0, - (const struct sockaddr *)&dst->ip, sizeof(dst->ip)); - close(s); - if (ret != sizeof(ip4pkt)) { - fprintf(stderr, "Failed sendto (%s)\n", strerror(errno)); - return -1; - } - break; - - case AF_INET6: - memset(&ip6pkt, 0, sizeof(ip6pkt)); - ip6pkt.ip6.ip6_vfc = 0x60; - ip6pkt.ip6.ip6_plen = htons(20); - ip6pkt.ip6.ip6_nxt = IPPROTO_TCP; - ip6pkt.ip6.ip6_hlim = 64; - ip6pkt.ip6.ip6_src = src->ip6.sin6_addr; - ip6pkt.ip6.ip6_dst = dst->ip6.sin6_addr; - - ip6pkt.tcp.source = src->ip6.sin6_port; - ip6pkt.tcp.dest = dst->ip6.sin6_port; - ip6pkt.tcp.seq = seq; - ip6pkt.tcp.ack_seq = ack; - ip6pkt.tcp.ack = 1; - if (rst) - ip6pkt.tcp.rst = 1; - ip6pkt.tcp.doff = sizeof(ip6pkt.tcp)/4; - ip6pkt.tcp.window = htons(1234); - ip6pkt.tcp.check = tcp_checksum6((uint16_t *)&ip6pkt.tcp, sizeof(ip6pkt.tcp), &ip6pkt.ip6); - - s = socket(PF_INET6, SOCK_RAW, IPPROTO_RAW); - if (s == -1) { - fprintf(stderr, "Failed to open sending socket\n"); - return -1; + bool rc = false; + + bool have_connection = false; + int socket_dsc = -1; + for (uint16_t counter = 0; counter < packet_count; ++counter) + { + if (!have_connection) + { + // Create a raw IP socket + socket_dsc = socket(socket_domain, SOCK_RAW, IPPROTO_RAW); + if (socket_dsc != -1) + { + // Make the socket non-blocking + if (set_fcntl_flags(socket_dsc, O_NONBLOCK, error)) + { + have_connection = socket_domain == AF_INET6; + if (!have_connection) + { + // Change socket options for inclusion of the custom IP header + const uint32_t sockopt_value = 1; + have_connection = setsockopt( + socket_dsc, + SOL_IP, + IP_HDRINCL, + &sockopt_value, + sizeof (sockopt_value) + ) == 0; + } } + if (!have_connection) + { + close_file_dsc(&socket_dsc); - tmpdest = discard_const(dst); - tmpport = tmpdest->ip6.sin6_port; + error->error_nr = errno; + error->error_msg = "I/O error: Adjusting socket options failed"; + } + } + } - tmpdest->ip6.sin6_port = 0; - ret = sendto(s, &ip6pkt, sizeof(ip6pkt), 0, (const struct sockaddr *)&dst->ip6, sizeof(dst->ip6)); - tmpdest->ip6.sin6_port = tmpport; - close(s); + if (have_connection) + { + // Send the packet. This is supposed to either send the entire packet or send nothing and fail, + // returning -1. I/O errors generate a warning but are otherwise ignored, so the program can + // continue sending packets to other destinations. + const ssize_t write_count = sendto( + socket_dsc, + packet_data, + packet_data_size, + 0, + dst_address, + dst_address_size + ); + if (write_count == -1) + { + // Write failed, reset socket + close_file_dsc(&socket_dsc); + have_connection = false; - if (ret != sizeof(ip6pkt)) { - fprintf(stderr, "Failed sendto (%s)\n", strerror(errno)); - return -1; - } - break; + const int send_error_code = errno; + char *const dst_address_str = malloc(BUFFER_SIZE); + if (dst_address_str != NULL) + { + const void *dst_ip = NULL; + if (socket_domain == AF_INET6) + { + const struct sockaddr_in6 *const ipv6_dst_address = + (const struct sockaddr_in6 *const) dst_address; + dst_ip = (const void *) &ipv6_dst_address->sin6_addr; + } + else + { + const struct sockaddr_in *const ipv4_dst_address = + (const struct sockaddr_in *const) dst_address; + dst_ip = (const void *) &ipv4_dst_address->sin_addr; + } - default: - fprintf(stderr, "Not an ipv4/v6 address\n"); - return -1; - } + const char *const ntop_rc = inet_ntop( + socket_domain, + dst_ip, + dst_address_str, + BUFFER_SIZE + ); + if (ntop_rc != NULL) + { + fprintf( + stderr, + "Warning: Sending a packet to destination %s:%u failed\n", + dst_address_str, dst_port + ); + display_error_nr_msg(send_error_code); + } + else + { + const int ntop_error_code = errno; + fputs("Warning: Sending a packet failed\n", stderr); + display_error_nr_msg(send_error_code); + fputs( + "Warning: Failed to generate a string representation " + "of the destination address\n", + stderr + ); + display_error_nr_msg(ntop_error_code); + } + free(dst_address_str); + } + else + { + error->error_msg = ERRMSG_OUT_OF_MEMORY; + error->error_nr = ENOMEM; + break; + } + } + else + { + // Sending at least one packet succeeded + rc = true; + } + } + else + { + error->error_nr = errno; + error->error_msg = "I/O error: Creation of a raw IP protocol socket failed"; + } + } + if (have_connection) + { + close_file_dsc(&socket_dsc); + } - return 0; + return rc; } -static void usage(void) +// Checks the result of an inet_pton call and sets error_info information describing the problem +// if the inet_pton call was not successful +// +// @returns true if successful, otherwise false +bool check_inet_pton_rc(const int rc, const int error_nr, error_info *const error) { - printf("Usage: /usr/lib/heartbeat/tickle_tcp [ -n num ]\n"); - printf("Please note that this program need to read the list of\n"); - printf("{local_ip:port remote_ip:port} from stdin.\n"); - exit(1); + bool success_flag = false; + switch (rc) + { + case 0: + { + // Invalid address string + error->error_nr = error_nr; + error->error_msg = "Invalid IP address"; + break; + } + case 1: + { + // Successful sockaddr initialization + success_flag = true; + break; + } + case -1: + { + // Unsupported address family + error->error_nr = error_nr; + error->error_msg = "Unsupported address family"; + break; + } + default: + { + // Undocumented return code + error->error_nr = error_nr; + error->error_msg = "Library function inet_pton(...) returned an unexpected return code"; + break; + } + } + return success_flag; } -#define OPTION_STRING "n:h" +// Sets flags on a file descriptor +// +// @returns true if successful, otherwise false +bool set_fcntl_flags(const int file_dsc, const int flags, error_info *const error) +{ + bool rc = false; + const int current_flags = fcntl(file_dsc, F_GETFL, 0); + if (current_flags != -1) + { + if (fcntl(file_dsc, F_SETFL, current_flags | flags) == 0) + { + rc = true; + } + else + { + error->error_nr = errno; + error->error_msg = ERRMSG_FCNTL_FAIL; + } + } + else + { + error->error_nr = errno; + error->error_msg = ERRMSG_FCNTL_FAIL; + } + return rc; +} -int main(int argc, char *argv[]) +// Closes a file descriptor +void close_file_dsc(int* const file_dsc) +{ + int rc; + do + { + rc = close(*file_dsc); + } + while (rc != 0 && errno == EINTR); + *file_dsc = -1; +} + +// Calculates an IPv4 packet's TCP checksum +uint16_t ipv4_tcp_checksum( + const unsigned char *const data, + const size_t length, + const ipv4_header_no_opt *const header +) +{ + uint32_t native_sum = checksum(data, length); + native_sum += checksum((const unsigned char *) &header->src_address, sizeof (header->src_address)); + native_sum += checksum((const unsigned char *) &header->dst_address, sizeof (header->dst_address)); + native_sum += header->protocol + (uint32_t) length; + // Rotate / add twice + native_sum = (native_sum & 0xFFFF) + (native_sum >> 16); + native_sum = (native_sum & 0xFFFF) + (native_sum >> 16); + + uint16_t network_sum = htons((uint16_t) native_sum); + // Invert checksum, unless it's 0xFFFF + network_sum = network_sum != 0xFFFF ? ~network_sum : 0xFFFF; + return network_sum; +} + +// Calculates an IPv6 packet's TCP checksum +uint16_t ipv6_tcp_checksum( + const unsigned char *const data, + const size_t length, + const ipv6_header *const header +) +{ + uint32_t native_sum = 0; + native_sum += checksum((const unsigned char *) &header->src_address, 16); + native_sum += checksum((const unsigned char *) &header->dst_address, 16); + + { + uint32_t proto_header[2]; + proto_header[0] = htonl(length); + proto_header[1] = htonl(header->next_header); + native_sum += checksum((const unsigned char *) &proto_header[0], sizeof (proto_header)); + } + + native_sum += checksum(data, length); + + // Rotate / add twice + native_sum = (native_sum & 0xFFFF) + (native_sum >> 16); + native_sum = (native_sum & 0xFFFF) + (native_sum >> 16); + + uint16_t network_sum = htons(native_sum); + // Invert checksum, unless it's 0xFFFF + network_sum = network_sum != 0xFFFF ? ~network_sum : 0xFFFF; + return network_sum; +} + +uint32_t checksum(const unsigned char *const data, const size_t length) { - int optchar, i, num = 1, cont = 1; - sock_addr src, dst; - char addrline[128], addr1[64], addr2[64]; - - while(cont) { - optchar = getopt(argc, argv, OPTION_STRING); - switch(optchar) { - case 'n': - num = atoi(optarg); - break; - case 'h': - usage(); - exit(EXIT_SUCCESS); - break; - case EOF: - cont = 0; - break; - default: - fprintf(stderr, "unknown option, please use '-h' for usage.\n"); - exit(EXIT_FAILURE); - break; - }; - } - - while(fgets(addrline, sizeof(addrline), stdin)) { - sscanf(addrline, "%s %s", addr1, addr2); - - if (parse_ip_port(addr1, &src)) { - fprintf(stderr, "Bad IP:port '%s'\n", addr1); - return -1; - } - if (parse_ip_port(addr2, &dst)) { - fprintf(stderr, "Bad IP:port '%s'\n", addr2); - return -1; - } - - for (i = 1; i <= num; i++) { - if (send_tickle_ack(&dst, &src, 0, 0, 0)) { - fprintf(stderr, "Error while sending tickle ack from '%s' to '%s'\n", - addr1, addr2); - return -1; - } - } - - } - return 0; + uint32_t result = 0; + for (size_t idx = 0; idx < length; ++idx) + { + result += (idx & 0x1) == 0 ? ((uint16_t) data[idx]) << 8 : (uint16_t) data[idx]; + } + return result; } diff --git a/tools/tickle_tcp.h b/tools/tickle_tcp.h new file mode 100644 index 0000000000..017578b3e4 --- /dev/null +++ b/tools/tickle_tcp.h @@ -0,0 +1,263 @@ +/** + * "Tickle-ACK" TCP connection failover support utility + * + * Author: Robert Altnoeder + * Derived from prior work authored by Jiaju Zhang, Andrew Tridgell, Ronnie Sahlberg + * and the Samba project. + * + * This file is part of tickle_tcp. + * + * tickle_tcp is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * tickle_tcp is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with tickle_tcp. If not, see + */ +#ifndef TICKLE_TCP_H +#define TICKLE_TCP_H + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +typedef struct +{ + int error_nr; + const char *error_msg; +} +error_info; + +typedef struct +{ + // Version, Length: + // Version: 4 bits (mask 0xF0) + // Length: 4 bits (mask 0x0F) + uint8_t vsn_length; + // Type of service: DSCP, ECN + // DSCP: 6 bits (mask 0xFC) + // ECN: 2 bits (mask 0x03) + uint8_t tos; + uint16_t length; + uint16_t id; + // Flags, fragment offset: + // Flags: 3 bits (mask 0xE000) + // Frg offset: 13 bits (mask 0x1FFF) + uint16_t flags_frg_off; + uint8_t ttl; + uint8_t protocol; + uint16_t chksum; + uint8_t src_address[4]; + uint8_t dst_address[4]; +} +ipv4_header_no_opt; + +typedef struct +{ + // Version, Traffic class, Flow label: + // Version: 4 bits (mask 0xF0000000) + // Traffic class: 8 bits (mask 0x0FF00000) + // Flow label: 20 bits (mask 0x000FFFFF) + uint32_t vsn_cls_flowlbl; + uint16_t length; + uint8_t next_header; + uint8_t hop_limit; + uint8_t src_address[16]; + uint8_t dst_address[16]; +} +ipv6_header; + +typedef struct +{ + uint16_t src_port; + uint16_t dst_port; + uint32_t seq_nr; + uint32_t ack_nr; + // Combined (from high order bits to low order bits): + // data offset (4 bits), reserved (3 bits), ns (1 bit) + uint8_t cmb_data_off_ns; + uint8_t tcp_flags; + uint16_t window_size; + uint16_t checksum; + uint16_t urgent_ptr; +} +custom_tcp_header; + +typedef struct +{ + ipv4_header_no_opt ip_header; + custom_tcp_header tcp_header; +} +ipv4_packet; + +typedef struct +{ + ipv6_header ip_header; + custom_tcp_header tcp_header; +} +ipv6_packet; + +struct address_text_buffers +{ + char *src_ip; + char *src_netif; + char *src_port; + char *dst_ip; + char *dst_port; +}; + +struct endpoints +{ + struct sockaddr_in *ipv4_src_address; + struct sockaddr_in *ipv4_dst_address; + struct sockaddr_in6 *ipv6_src_address; + struct sockaddr_in6 *ipv6_dst_address; +}; + +const int ERR_GENERIC = 1; +const int ERR_OUT_OF_MEMORY = 2; +const int ERR_IO = 3; + +const size_t BUFFER_SIZE = 140; + +const uint16_t DEFAULT_PACKET_COUNT = 1; + +const uint8_t TCP_FIN = 0x01; +const uint8_t TCP_SYN = 0x02; +const uint8_t TCP_RST = 0x04; +const uint8_t TCP_PSH = 0x08; +const uint8_t TCP_ACK = 0x10; +const uint8_t TCP_URG = 0x20; +const uint8_t TCP_ECE = 0x40; +const uint8_t TCP_CWR = 0x80; + +const char *const ERRMSG_OUT_OF_MEMORY = "Out of memory"; +const char *const ERRMSG_INVALID_NR = "Unparsable number"; +const char *const ERRMSG_INVALID_PORT_NR = "Invalid port number"; +const char *const ERRMSG_UNPARSABLE_ENDPOINT = "Unparsable IP address:port string"; +const char *const ERRMSG_FCNTL_FAIL = "I/O error: Changing the mode of a file descriptor failed"; +const char *const ERRMSG_UNUSABLE_LINKLOCAL = + "Unusable IPv6 link-local address: Missing network interface specifier (%netif suffix)"; +const char *const ERRMSG_LINKLOCAL_NO_NETIF = + "Nonexistent IPv6 link-local network interface"; + +const char *const OPT_PACKET_COUNT = "-n"; +const char *const OPT_HELP = "-h"; +const char *const LONG_OPT_HELP = "--help"; + +int run(uint16_t packet_count); +void process_line( + char *line_buffer, + struct address_text_buffers *buffers, + struct endpoints *endpoint_data, + uint16_t packet_count, + error_info *error +); +void display_help(void); +void display_error(error_info *error); +void display_error_nr_msg(int error_nr); +void clear_error(error_info *error); +bool parse_arguments(int argc, char *argv[], uint16_t *packet_count); +bool parse_endpoints( + char *line_buffer, + size_t line_length, + const struct address_text_buffers *buffers, + struct endpoints *endpoint_data, + bool *is_ipv6, + error_info *error +); +bool split_address_and_port( + const char *address_and_port, + size_t address_and_port_length, + char *address, + char *port, + error_info *error +); +bool split_address_and_netif( + char *address, + size_t address_length, + char *netif, + error_info *error +); +void trim_newline(char *buffer, size_t length); +void trim_lead(char *buffer, size_t length); +void trim_trail(char *buffer, size_t length); +size_t find_whitespace(const char *buffer, size_t length); +size_t find_last_char(const char *buffer, size_t length, char value); +bool parse_uint16(const char *input, size_t input_length, uint16_t *value, error_info *error); +bool parse_port_number( + const char *const input, + const size_t input_length, + uint16_t *const port_number, + error_info *const error +); +bool parse_ipv4( + const char *address_input, + size_t address_input_length, + const char *port_input, + size_t port_input_length, + struct sockaddr_in *address, + error_info *error +); +bool parse_ipv6( + const char *address_input, + size_t address_input_length, + const char *netif_input, + size_t netif_input_length, + const char *port_input, + size_t port_input_length, + struct sockaddr_in6 *address, + error_info *error +); +bool send_ipv4_packet( + const struct sockaddr_in *src_address, + const struct sockaddr_in *dst_address, + uint16_t packet_count, + error_info *error +); +bool send_ipv6_packet( + const struct sockaddr_in6 *src_address, + const struct sockaddr_in6 *dst_address, + uint16_t packet_count, + error_info *error +); +bool send_packet( + int socket_domain, + const struct sockaddr *dst_address, + size_t dst_address_size, + uint16_t dst_port, + const void *packet_data, + size_t packet_data_size, + uint16_t packet_count, + error_info *error +); +bool check_inet_pton_rc(int rc, int error_nr, error_info *error); +bool set_fcntl_flags(int file_dsc, int flags, error_info *error); +void close_file_dsc(int *file_dsc); +uint16_t ipv4_tcp_checksum( + const unsigned char *data, + size_t length, + const ipv4_header_no_opt *header +); +uint16_t ipv6_tcp_checksum( + const unsigned char *data, + size_t length, + const ipv6_header *header +); +uint32_t checksum(const unsigned char *data, size_t length); + +#endif /* TICKLE_TCP_H */