
/*
 * wnix, a Unix-like operating system for WebAssembly applications.
 * Copyright (C) 2025  Xavier Del Campo Romero
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero 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 Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/ip.h>
#include <net/if.h>
#include <linux/if.h>
#include <linux/if_tun.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <fcntl.h>
#include <termios.h>
#include <unistd.h>
#include <errno.h>
#include <limits.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

static int getport(const char *const s, unsigned short *const port)
{
    char *end;
    unsigned long v;

    errno = 0;
    v = strtoull(s, &end, 10);

    if (errno || *end || v > USHRT_MAX)
        return -1;

    *port = v;
    return 0;
}

int main(int argc, char *argv[])
{
    static const char path[] = "/dev/net/tun";
    char *s = NULL;
    int ret = EXIT_FAILURE, cfd = -1, tfd = -1;
    unsigned short port;

    if (argc != 2)
    {
        fprintf(stderr, "%s <port>\n", *argv);
        goto end;
    }
    else if (getport(argv[1], &port))
    {
        fprintf(stderr, "invalid port: %s\n", argv[1]);
        goto end;
    }
    else if ((cfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
    {
        perror("socket(2)");
        goto end;
    }

    const struct sockaddr_in addr =
    {
        .sin_family = AF_INET,
        .sin_addr.s_addr = inet_addr("127.0.0.1"),
        .sin_port = htons(port)
    };

    const struct ifreq ifr = {.ifr_flags = IFF_TUN |  IFF_NO_PI};

    if (connect(cfd, (const struct sockaddr *)&addr, sizeof addr))
    {
        perror("connect(2)");
        goto end;
    }
    else if ((tfd = open(path, O_RDWR)) < 0)
    {
        fprintf(stderr, "open(2) %s: %s\n", path, strerror(errno));
        goto end;
    }
    else if (ioctl(tfd, TUNSETIFF, &ifr) < 0)
    {
        perror("ioctl(2)");
        goto end;
    }
    else if (!(s = strdup(ifr.ifr_name)))
    {
        perror("strdup(3)");
        goto end;
    }

    fprintf(stderr, "set up TUN device %s\n", s);
    unsigned long long total = 0;

    for (;;)
    {
        char b[1500];
        ssize_t n = read(tfd, &b, sizeof b);

        if (n < 0)
        {
            perror("read(2)");
            goto end;
        }

        fprintf(stderr, "read %zu bytes\n", n);

        size_t rem = n;

        while (rem)
        {
            const void *p = b;

            fprintf(stderr, "sending %zu bytes\n", rem);

            if ((n = write(cfd, p, rem)) < 0)
            {
                perror("write(2)");
                goto end;
            }

            fprintf(stderr, "sent %zu bytes, total: %llu\n", n, total += n);

            p = (const char *)p + n;
            rem -= n;
        }
    }

    ret = EXIT_SUCCESS;

end:

    if (cfd >= 0 && close(cfd))
    {
        ret = EXIT_FAILURE;
        perror("close(2)");
    }

    if (tfd >= 0 && close(tfd))
    {
        ret = EXIT_FAILURE;
        fprintf(stderr, "close(2) %s: %s\n", path, strerror(errno));
    }

    free(s);
    return ret;
}
