/* Sends a UDP IPv6 packet with ECN. See RFC 10055 for explanations.
*/

#include <stdbool.h>
#include <stdlib.h>
#include <getopt.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/udp.h>

#define MAXHOSTNAMELEN 256
#define MSGSIZE 64
#define PORTSIZE 32
#define DEFAULT_PORT 4242
/* 3 = two bits sets (CE, see RFC 3168): */
#define DEFAULT_DSCP 3
#define MESSAGE "abcdefghijk"

static int      verbose_flag = 0;
static int      port = 0;
static int      disable_flag = 0;
static int      ecn = 0;

static struct option long_options[] = {
    {"verbose", no_argument, &verbose_flag, 1},
    {"port", required_argument, NULL, 0},
    {"ecn", required_argument, NULL, 0},
    {"disable", no_argument, &disable_flag, 0},
    {0, 0, 0, 0}
};

static char     progname[MAXHOSTNAMELEN + 1];

void
usage()
{
    printf("Usage: %s [-p port] [-e ecn_bits] hostname\n", progname);
}

char           *
text_of(struct sockaddr *address)
{
    char           *text = malloc(INET6_ADDRSTRLEN);
    char           *port_text = malloc(PORTSIZE);
    struct sockaddr_in6 *address_v6;
    if (address->sa_family != AF_INET6) {
        exit(1);
    }
    address_v6 = (struct sockaddr_in6 *) address;
    inet_ntop(AF_INET6, &address_v6->sin6_addr, text, INET6_ADDRSTRLEN);
    if (address_v6->sin6_port != 0) {
        strcat(text, " port ");
        snprintf(port_text, PORTSIZE, "%i", ntohs(address_v6->sin6_port));
        strcat(text, port_text);
    }
    return text;
}

int
main(int argc, char **argv)
{
    char            c;
    char            hostname[MAXHOSTNAMELEN + 1];
    char            message[MSGSIZE];
    char            port_str[PORTSIZE];
    int             status;
    struct addrinfo hints_numeric, hints;
    struct addrinfo *result;
    size_t          messagesize;
    strncpy(progname, argv[0], MAXHOSTNAMELEN);
    progname[MAXHOSTNAMELEN] = 0;
    int             sd;         /* Socket Descriptors */
    ssize_t         num;
    int             dscp_value = 1 + 2; /* From RFC 3168 : two bits set */
    strcpy(message, MESSAGE);
    while (true) {
        /* getopt_long stores the option index here.  */
        int             option_index = 0;

        c = getopt_long(argc, argv, "vdp:e:", long_options, &option_index);
        /* Detect the end of the options.  */
        if (c == -1)
            break;

        switch (c) {
        case 0:
            /* If this option set a flag, do nothing else now.  */
            if (long_options[option_index].flag != 0)
                break;
            else if (strcmp(long_options[option_index].name, "port") == 0) {
                port = strtol(optarg, NULL, 10);        /* Assume the port number is 
                                                         * in base 10 */
                if (port == 0) {
                    fprintf(stderr, "Invalid port number %s\n", optarg);
                    exit(1);
                }
            } else if (strcmp(long_options[option_index].name, "ecn") == 0) {
                ecn = strtol(optarg, NULL, 10);
                if (ecn == 0) {
                    fprintf(stderr, "Invalid DSCP value \"%s\"\n", optarg);
                    exit(1);
                }
            } else {
                fprintf(stderr, "Internal error, option %s not known\n",
                        long_options[option_index].name);
            }
            break;

        case 'v':
            verbose_flag = 1;
            break;

        case 'd':
            disable_flag = 1;
            break;

        case 'p':
            port = strtol(optarg, NULL, 10);    /* Assume the port number is in base 
                                                 * 10 */
            if (port == 0) {
                fprintf(stderr, "Invalid port number %s\n", optarg);
                exit(1);
            }
            break;

        case 'e':
            ecn = strtol(optarg, NULL, 10);
            if (ecn == 0) {
                fprintf(stderr, "Invalid DSCP value \"%s\"\n", optarg);
                exit(1);
            }
            break;

        case '?':
            usage();
            exit(0);

        default:
            fprintf(stderr, "Internal error parsing options %i\n", c);
            exit(1);
        }
    }

    /* Print any remaining command line arguments (not options).  */
    if (argc - optind != 1) {
        usage();
        exit(1);
    }
    strncpy(hostname, argv[optind], MAXHOSTNAMELEN);
    hostname[MAXHOSTNAMELEN] = 0;
    if (port == 0) {
        port = DEFAULT_PORT;
    }
    if (ecn == 0) {
        ecn = DEFAULT_DSCP;
    }
    if (ecn > 3) {
        fprintf(stderr, "Invalid DSCP value %i (must be a number < 4)\n", ecn);
        exit(1);
    }
    dscp_value = ecn;

    strcpy(message, MESSAGE);
    messagesize = strlen(message);

    memset(&hints_numeric, 0, sizeof(hints_numeric));
    hints_numeric.ai_flags = AI_NUMERICHOST;
    hints_numeric.ai_socktype = SOCK_DGRAM;
    snprintf(port_str, PORTSIZE, "%i", port);

    memset(&hints, 0, sizeof(hints));
    hints.ai_family = AF_INET6;
    hints.ai_socktype = SOCK_DGRAM;
    result = malloc(sizeof(struct addrinfo));
    status = getaddrinfo(hostname, port_str, &hints_numeric, &result);
    if (status && status == EAI_NONAME) {       /* It may be a name, but it is
                                                 * certainly not an address */
        status = getaddrinfo(hostname, port_str, &hints, &result);
        if (status) {
            fprintf(stderr, "Nothing found about host name %s\n", hostname);
            exit(1);
        }
    } else if (status) {
        fprintf(stderr, "Internal error, cannot resolve %s (error %i)\n", hostname,
                status);
        exit(1);
    }
    if (result->ai_family != AF_INET6) {
        fprintf(stderr, "Only IPv6: family %i unsupported\n", result->ai_family);
        exit(1);
    }
    if (verbose_flag) {
        fprintf(stdout, "Connecting to %s...\n", text_of(result->ai_addr));
    }
    sd = socket(result->ai_family, SOCK_DGRAM, SOL_UDP);
    if (sd < 0) {
        perror("socket: Cannot create socket");
        exit(1);
    }
    /* DSCP is 0 by default. */
    if (!disable_flag) {
        if (setsockopt
            (sd, IPPROTO_IPV6, IPV6_TCLASS, &dscp_value, sizeof(dscp_value)) == -1) {
            perror("setsockopt IPV6_TCLASS");
            exit(1);
        }
    }
    if (verbose_flag) {
        fprintf(stdout, "Sending %i bytes with DSCP %i...\n", (int) messagesize,
                (int) dscp_value);
    }
    num =
        sendto(sd, message, (size_t) messagesize, 0, result->ai_addr,
               result->ai_addrlen);
    if (num < messagesize) {
        perror("Cannot send message\n");
        exit(1);
    }
    return (0);
}
