/*
* Copyright 2018 Steven Gribble, Justin Hsia
*
* This file is the solution to an exercise problem posed during
* one of the UW CSE 333 lectures (333exercises).
*
* 333exercises 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.
*
* 333exercises 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 333exercises. If not, see .
*/
// Lec 24 Exercise 1
//
// Write a program that creates a listening socket, accepts
// connections from clients, and:
// - reads a line of text from the client
// - parses the line of text as a DNS name
// - connects to that DNS name on port 80
// - writes a valid HTTP for "/"
// - reads the reply and returns it to the client
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define BUF_SIZE 1024
using std::string;
// This structure stores information about a client that has
// connected to us.
typedef struct {
int fd;
struct sockaddr_storage caddr;
socklen_t caddr_len;
} ClientInfo;
void Usage(char *progname);
void PrintOut(int fd, struct sockaddr *addr, size_t addrlen);
void PrintReverseDNS(struct sockaddr *addr, size_t addrlen);
void HTTPRequest(ClientInfo cinfo, string hostname);
int Listen(char *portnum);
bool LookupName(string name,
unsigned short port,
struct sockaddr_storage *ret_addr,
size_t *ret_addrlen);
bool Connect(const struct sockaddr_storage &addr,
const size_t &addrlen,
int *ret_fd);
// HandleClient is set up this way to later work with multithreading
void *HandleClient(void *c_info);
bool ReadLineFromSocket(int fd, string *retstr);
int WrappedRead(int fd, unsigned char *buf, int readlen);
int WrappedWrite(int fd, unsigned char *buf, int writelen);
int main(int argc, char **argv) {
// Expect the port number as a command line argument.
if (argc != 2) {
Usage(argv[0]);
}
// Parse the port number, or fail.
unsigned short port = 0;
if (sscanf(argv[1], "%hu", &port) != 1) {
Usage(argv[0]);
}
int listen_fd = Listen(argv[1]);
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 client connections and handling them.
while (1) {
// Allocate a "ClientInfo" structure to store information about
// the next client connection that we receive. We'll hand this
// structure off to a thread that we dispatch to handle the
// client.
ClientInfo *cinfo = new ClientInfo;
cinfo->caddr_len = sizeof(cinfo->caddr);
cinfo->fd = accept(listen_fd,
reinterpret_cast(&(cinfo->caddr)),
&(cinfo->caddr_len));
if (cinfo->fd < 0) {
if ((errno == EAGAIN) || (errno == EINTR)) {
delete cinfo;
continue;
}
std::cerr << "Failure on accept: " << strerror(errno) << std::endl;
delete cinfo;
break;
}
// Handle this file descriptor.
HandleClient(reinterpret_cast(cinfo));
}
// 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(addr);
inet_ntop(AF_INET, &(in4->sin_addr), astring, INET_ADDRSTRLEN);
std::cout << " IPv4 address " << astring;
std::cout << " and port " << htons(in4->sin_port) << std::endl;
} else if (addr->sa_family == AF_INET6) {
// Print out the IPV4 address and port
char astring[INET6_ADDRSTRLEN];
struct sockaddr_in6 *in6 = reinterpret_cast(addr);
inet_ntop(AF_INET, &(in6->sin6_addr), astring, INET6_ADDRSTRLEN);
std::cout << " IPv6 address " << astring;
std::cout << " and port " << htons(in6->sin6_port) << std::endl;
} else {
std::cout << " ???? address and port ????" << std::endl;
}
}
void PrintReverseDNS(struct sockaddr *addr, size_t addrlen) {
char hostname[BUF_SIZE]; // ought to be big enough.
if (getnameinfo(addr, addrlen, hostname, BUF_SIZE, nullptr, 0, 0) != 0) {
sprintf(hostname, "[reverse DNS failed]");
}
std::cout << " DNS name: " << hostname << std::endl;
}
int Listen(char *portnum) {
// 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 "INADDR_ANY"
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;
assert(setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR,
&optval, sizeof(optval)) == 0);
// 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);
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 <= 0)
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 listen_fd;
}
void *HandleClient(void *c_fd) {
ClientInfo cinfo = *reinterpret_cast(c_fd);
delete reinterpret_cast(c_fd);
// Print out information about the client.
std::cout << std::endl;
std::cout << "New client connection" << std::endl;
PrintOut(cinfo.fd,
reinterpret_cast(&(cinfo.caddr)), cinfo.caddr_len);
PrintReverseDNS(reinterpret_cast(&(cinfo.caddr)),
cinfo.caddr_len);
// Loop, reading data and doing a DNS lookup on the content.
while (1) {
string nextstr;
bool res = ReadLineFromSocket(cinfo.fd, &nextstr);
if (!res) {
std::cout << " [The client disconnected.]" << std::endl;
break;
}
std::cout << " the client sent: " << nextstr << std::endl;
HTTPRequest(cinfo, nextstr);
}
close(cinfo.fd);
return nullptr;
}
// Reads characters from the socket "fd" until it spots
// a newline or EOF. Returns the read characters (minus the
// newline) through "retstr". Returns true if something was
// read, false otherwise. If returns false, customer should
// close the socket.
bool ReadLineFromSocket(int fd, string *retstr) {
// We won't try to make this efficient; we'll keep appending
// single characters to a string until we hit EOF or newline.
// This means that C++ will be making a *ton* of string copies
// along the way.
string data;
int count = 0;
while (1) {
unsigned char nextC;
ssize_t res = read(fd, &nextC, 1);
if (res == 1) {
// Ignore '\r' characters if we see them.
if (nextC == '\r')
continue;
// Stop on '\n' characters.
if (nextC == '\n') {
// All done!
*retstr = data;
return true;
}
// Append the character.
data.append(1, nextC);
count++;
continue;
}
if (res == 0) {
if (count == 0)
return false;
*retstr = data;
return true;
}
if (res == -1) {
if ((errno == EINTR) || (errno == EAGAIN))
continue;
// Unrecoverable error.
return false;
}
}
// should never get here.
assert(0);
return false;
}
void HTTPRequest(ClientInfo cinfo, string hostname) {
// Send an HTTP request for "/" to the given hostname.
unsigned char buf[BUF_SIZE];
// Get an appropriate sockaddr structure.
struct sockaddr_storage addr;
size_t addrlen;
if (!LookupName(hostname, 80, &addr, &addrlen)) {
std::cerr << "LookupName failed for " << hostname << std::endl;
exit(EXIT_FAILURE);
}
// Connect to the remote host.
int socket_fd;
if (!Connect(addr, addrlen, &socket_fd)) {
std::cerr << "Failed to connect to " << hostname << std::endl;
exit(EXIT_FAILURE);
}
// build HTTP GET request
string request("GET / HTTP/1.1\r\nHost: ");
request += hostname;
request += "\r\nConnection: close\r\n\r\n";
std::cout << "sending: " << request;
request.copy(reinterpret_cast(buf), request.size(), 0);
WrappedWrite(socket_fd, buf, request.size());
// Loop, reading data from socket_fd and writing it to the client.
while (1) {
int res = WrappedRead(socket_fd, buf, BUF_SIZE);
if (res <= 0)
break;
WrappedWrite(cinfo.fd, buf, BUF_SIZE);
int wres = fwrite(buf, 1, res, stdout);
if (wres != res)
break;
}
close(socket_fd);
}
// Do a DNS lookup on name "name", and return through the output
// parameter "ret_addr" a fully-formed struct sockaddr. The length
// of that struct sockaddr is returned through "ret_addrlen". Also
// initialize the port to "port".
//
// On failure, returns false. On success, returns true.
bool LookupName(string name,
unsigned short port,
struct sockaddr_storage *ret_addr,
size_t *ret_addrlen) {
struct addrinfo hints, *results;
int retval;
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
// Do the lookup by invoking getaddrinfo().
if ((retval = getaddrinfo(name.c_str(), nullptr, &hints, &results)) != 0) {
std::cerr << "getaddrinfo failed: ";
std::cerr << gai_strerror(retval) << std::endl;
return false;
}
// Set the port in the first result.
if (results->ai_family == AF_INET) {
struct sockaddr_in *v4addr = (struct sockaddr_in *) results->ai_addr;
v4addr->sin_port = htons(port);
} else if (results->ai_family == AF_INET6) {
struct sockaddr_in6 *v6addr = (struct sockaddr_in6 *) results->ai_addr;
v6addr->sin6_port = htons(port);
} else {
std::cerr << "getaddrinfo failed to provide an IPv4 or IPv6 address";
std::cerr << std::endl;
return false;
}
// Return the first result.
assert(results != nullptr);
memcpy(ret_addr, results->ai_addr, results->ai_addrlen);
*ret_addrlen = results->ai_addrlen;
// Clean up.
freeaddrinfo(results);
return true;
}
// Connect to the remote host and port specified by addr/addrlen.
// Return a connected socket through the output parameter "ret_fd".
//
// On failure, returns false. On success, returns true.
bool Connect(const struct sockaddr_storage &addr,
const size_t &addrlen,
int *ret_fd) {
// Create the socket.
int socket_fd = socket(addr.ss_family, SOCK_STREAM, 0);
if (socket_fd == -1) {
std::cerr << "socket() failed: " << strerror(errno) << std::endl;
return false;
}
// Connect the socket to the remote host.
int res = connect(socket_fd,
reinterpret_cast(&addr),
addrlen);
if (res == -1) {
std::cerr << "connect() failed: " << strerror(errno) << std::endl;
return false;
}
*ret_fd = socket_fd;
return true;
}
// A wrapper around "write" that shields the caller from dealing
// with the ugly issues of partial writes, EINTR, EAGAIN, and so
// on.
//
// Write "writelen" bytes to the file descriptor fd from
// the buffer "buf". Blocks the caller until either writelen
// bytes have been written, or an error is encountered. Return
// the total number of bytes written; if this number is less
// than writelen, it's because some fatal error was encountered,
// like the connection being dropped.
int WrappedWrite(int fd, unsigned char *buf, int writelen) {
int res, written_so_far = 0;
while (written_so_far < writelen) {
res = write(fd, buf + written_so_far, writelen - written_so_far);
if (res == -1) {
if ((errno == EAGAIN) || (errno == EINTR))
continue;
break;
}
if (res == 0)
break;
written_so_far += res;
}
return written_so_far;
}
// A wrapper around "read" that shields the caller from dealing
// with the ugly issues of partial reads, EINTR, EAGAIN, and so
// on.
//
// Read at most "readlen" bytes from the file descriptor fd
// into the buffer "buf". Return the number of bytes actually
// read. On fatal error, return -1. If EOF is hit and no
// bytes have been read, return 0. Might read fewer bytes
// than requested.
int WrappedRead(int fd, unsigned char *buf, int readlen) {
int res;
while (1) {
res = read(fd, buf, readlen);
if (res == -1) {
if ((errno == EAGAIN) || (errno == EINTR))
continue;
}
break;
}
return res;
}