#include <arpa/inet.h>

#include <assert.h>

#include <errno.h>

#include <netdb.h>

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <sys/socket.h>

#include <sys/types.h>

#include <unistd.h>

#include <iostream>



void Usage(char *progname);

void PrintOut(int fd, struct sockaddr *addr, size_t addrlen);

void PrintReverseDNS(struct sockaddr *addr, size_t addrlen);

void PrintServerSide(int client_fd, int sock_family);

int  Listen(char *portnum, int *sock_family);

void HandleClient(int c_fd, struct sockaddr *addr, size_t addrlen,

                  int sock_family);



int main(int argc, char **argv) {

  // Expect the port number as a command line argument.

  if (argc != 2) {

    Usage(argv[0]);

  }



  int sock_family;

  int listen_fd = Listen(argv[1], &sock_family);

  if (listen_fd <= 0) {

    // We failed to bind/listen to a socket.  Quit with failure.

    std::cerr << "Couldn't bind to any addresses." << std::endl;

    return EXIT_FAILURE;

  }



  // Loop forever, accepting a connection from a client and doing

  // an echo trick to it.

  while (1) {

    struct sockaddr_storage caddr;

    socklen_t caddr_len = sizeof(caddr);

    int client_fd = accept(listen_fd,

                           reinterpret_cast<struct sockaddr *>(&caddr),

                           &caddr_len);

    if (client_fd < 0) {

      if ((errno == EINTR) || (errno == EAGAIN) || (errno == EWOULDBLOCK))

        continue;

      std::cerr << "Failure on accept: " << strerror(errno) << std::endl;

      break;

    }



    HandleClient(client_fd,

                 reinterpret_cast<struct sockaddr *>(&caddr),

                 caddr_len,

                 sock_family);

  }



  // Close up shop.

  close(listen_fd);

  return EXIT_SUCCESS;

}



void Usage(char *progname) {

  std::cerr << "usage: " << progname << " port" << std::endl;

  exit(EXIT_FAILURE);

}



void PrintOut(int fd, struct sockaddr *addr, size_t addrlen) {

  std::cout << "Socket [" << fd << "] is bound to:" << std::endl;

  if (addr->sa_family == AF_INET) {

    // Print out the IPV4 address and port



    char astring[INET_ADDRSTRLEN];

    struct sockaddr_in *in4 = reinterpret_cast<struct sockaddr_in *>(addr);

    inet_ntop(AF_INET, &(in4->sin_addr), astring, INET_ADDRSTRLEN);

    std::cout << " IPv4 address " << astring;

    std::cout << " and port " << ntohs(in4->sin_port) << std::endl;



  } else if (addr->sa_family == AF_INET6) {

    // Print out the IPV6 address and port



    char astring[INET6_ADDRSTRLEN];

    struct sockaddr_in6 *in6 = reinterpret_cast<struct sockaddr_in6 *>(addr);

    inet_ntop(AF_INET6, &(in6->sin6_addr), astring, INET6_ADDRSTRLEN);

    std::cout << " IPv6 address " << astring;

    std::cout << " and port " << ntohs(in6->sin6_port) << std::endl;



  } else {

    std::cout << " ???? address and port ????" << std::endl;

  }

}



void PrintReverseDNS(struct sockaddr *addr, size_t addrlen) {

  char hostname[1024];  // ought to be big enough.

  if (getnameinfo(addr, addrlen, hostname, 1024, nullptr, 0, 0) != 0) {

    sprintf(hostname, "[reverse DNS failed]");

  }

  std::cout << " DNS name: " << hostname << std::endl;

}



void PrintServerSide(int client_fd, int sock_family) {

  char hname[1024];

  hname[0] = '\0';



  std::cout << "Server side interface is ";

  if (sock_family == AF_INET) {

    // The server is using an IPv4 address.

    struct sockaddr_in srvr;

    socklen_t srvrlen = sizeof(srvr);

    char addrbuf[INET_ADDRSTRLEN];

    getsockname(client_fd, (struct sockaddr *) &srvr, &srvrlen);

    inet_ntop(AF_INET, &srvr.sin_addr, addrbuf, INET_ADDRSTRLEN);

    std::cout << addrbuf;

    // Get the server's dns name, or return it's IP address as

    // a substitute if the dns lookup fails.

    getnameinfo((const struct sockaddr *) &srvr,

                srvrlen, hname, 1024, nullptr, 0, 0);

    std::cout << " [" << hname << "]" << std::endl;

  } else {

    // The server is using an IPv6 address.

    struct sockaddr_in6 srvr;

    socklen_t srvrlen = sizeof(srvr);

    char addrbuf[INET6_ADDRSTRLEN];

    getsockname(client_fd, (struct sockaddr *) &srvr, &srvrlen);

    inet_ntop(AF_INET6, &srvr.sin6_addr, addrbuf, INET6_ADDRSTRLEN);

    std::cout << addrbuf;

    // Get the server's dns name, or return it's IP address as

    // a substitute if the dns lookup fails.

    getnameinfo((const struct sockaddr *) &srvr,

                srvrlen, hname, 1024, nullptr, 0, 0);

    std::cout << " [" << hname << "]" << std::endl;

  }

}



int Listen(char *portnum, int *sock_family) {

  // Populate the "hints" addrinfo structure for getaddrinfo().

  // ("man addrinfo")

  struct addrinfo hints;

  memset(&hints, 0, sizeof(struct addrinfo));

  hints.ai_family = AF_INET6;       // IPv6 (also handles IPv4 clients)

  hints.ai_socktype = SOCK_STREAM;  // stream

  hints.ai_flags = AI_PASSIVE;      // use wildcard "in6addr_any" address

  hints.ai_flags |= AI_V4MAPPED;    // use v4-mapped v6 if no v6 found

  hints.ai_protocol = IPPROTO_TCP;  // tcp protocol

  hints.ai_canonname = nullptr;

  hints.ai_addr = nullptr;

  hints.ai_next = nullptr;



  // Use argv[1] as the string representation of our portnumber to

  // pass in to getaddrinfo().  getaddrinfo() returns a list of

  // address structures via the output parameter "result".

  struct addrinfo *result;

  int res = getaddrinfo(nullptr, portnum, &hints, &result);



  // Did addrinfo() fail?

  if (res != 0) {

    std::cerr << "getaddrinfo() failed: ";

    std::cerr << gai_strerror(res) << std::endl;

    return -1;

  }



  // Loop through the returned address structures until we are able

  // to create a socket and bind to one.  The address structures are

  // linked in a list through the "ai_next" field of result.

  int listen_fd = -1;

  for (struct addrinfo *rp = result; rp != nullptr; rp = rp->ai_next) {

    listen_fd = socket(rp->ai_family,

                       rp->ai_socktype,

                       rp->ai_protocol);

    if (listen_fd == -1) {

      // Creating this socket failed.  So, loop to the next returned

      // result and try again.

      std::cerr << "socket() failed: " << strerror(errno) << std::endl;

      listen_fd = -1;

      continue;

    }



    // Configure the socket; we're setting a socket "option."  In

    // particular, we set "SO_REUSEADDR", which tells the TCP stack

    // so make the port we bind to available again as soon as we

    // exit, rather than waiting for a few tens of seconds to recycle it.

    int optval = 1;

    setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR,

               &optval, sizeof(optval));



    // Try binding the socket to the address and port number returned

    // by getaddrinfo().

    if (bind(listen_fd, rp->ai_addr, rp->ai_addrlen) == 0) {

      // Bind worked!  Print out the information about what

      // we bound to.

      PrintOut(listen_fd, rp->ai_addr, rp->ai_addrlen);



      // Return to the caller the address family.

      *sock_family = rp->ai_family;

      break;

    }



    // The bind failed.  Close the socket, then loop back around and

    // try the next address/port returned by getaddrinfo().

    close(listen_fd);

    listen_fd = -1;

  }



  // Free the structure returned by getaddrinfo().

  freeaddrinfo(result);



  // If we failed to bind, return failure.

  if (listen_fd == -1)

    return listen_fd;



  // Success. Tell the OS that we want this to be a listening socket.

  if (listen(listen_fd, SOMAXCONN) != 0) {

    std::cerr << "Failed to mark socket as listening: ";

    std::cerr << strerror(errno) << std::endl;

    close(listen_fd);

    return -1;

  }



  // Return to the client the listening file descriptor.

  return listen_fd;

}



void HandleClient(int c_fd, struct sockaddr *addr, size_t addrlen,

                  int sock_family) {

  // Print out information about the client.

  std::cout << std::endl;

  std::cout << "New client connection" << std::endl;

  PrintOut(c_fd, addr, addrlen);

  PrintReverseDNS(addr, addrlen);

  PrintServerSide(c_fd, sock_family);



  // Loop, reading data and echo'ing it back, until the client

  // closes the connection.

  while (1) {

    char clientbuf[1024];

    ssize_t res = read(c_fd, clientbuf, 1023);

    if (res == 0) {

      std::cout << " [The client disconnected.]" << std::endl;

      break;

    }



    if (res == -1) {

      if ((errno == EAGAIN) || (errno == EINTR))

        continue;



      std::cout << " [Error on client socket: ";

      std::cout << strerror(errno) << "]" << std::endl;

      break;

    }

    clientbuf[res] = '\0';

    std::cout << " the client sent: " << clientbuf;



    // Really should do this in a loop in case of EAGAIN, EINTR,

    // or short write, but I'm lazy.  Don't be like me. ;)

    write(c_fd, "You typed: ", strlen("You typed: "));

    write(c_fd, clientbuf, strlen(clientbuf));

  }



  close(c_fd);

}