0

Premise:

I'm building on newly-learned networking fundamentals learned from these two questions: one, two.

I'll call the the code at the bottom of this post my "http responder," and not a "http server," since I recently got an educational/appreciated slap on the wrist for calling it the latter.

The program functions as follows:

  1. it listens at INADDR_ANY port 9018 (a naively/randomly-chosen number)
  2. it dumps (to stdout) the content received at the accepted socket until there's no more content to read
  3. it sends a minimal HTTP response with status OK.

(in case @Remy Lebeau visits this question, item 2, specifically, is why this program is not a http server: it does not parse the incoming HTTP request, it just dumbly dumps it and responds -- even in the case of a closed TCP connection -- but I believe this is not relevant to the question asked here).

From my second link, above, I learned about why a web server would want to listen to a specific port on all interfaces.
My understanding is that the way this is done in C-family languages is by binding to INADDR_ANY (as opposed to a specific IP address, like "127.0.0.13").


Question:

When I run this program, I observe the expected result if I try to connect from a web browser that is running on the same PC as where the executable is run: my browser shows a minimal webpage with content "I'm the content" if I connect to 127.0.0.1:9018, 127.0.0.2:9018, 127.0.0.13.9018, 127.0.0.97:9018, etc.

Most relevant to this question, I also get the same minimal webpage by pointing my browser to 10.0.0.17:9018, which is the IP address assigned to my "wlpls0" interface:

$ ifconfig
...
wlp1s0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 10.0.0.17  netmask 255.255.255.0  broadcast 10.0.0.255
        inet6 fe80::5f8c:c301:a6a3:6e35  prefixlen 64  scopeid 0x20<link>
        ether f8:59:71:01:89:cf  txqueuelen 1000  (Ethernet)
        RX packets 1272659  bytes 1760801882 (1.7 GB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 543118  bytes 74285210 (74.2 MB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

However, I only observe this desired webpage if the browser that I point to 10.0.0.7:9018 is running on the same PC as where the a.out is running.
From another PC on the same network, if I point its browser to 10.0.0.17:9018, the browser spins without connecting, and eventually says "Hmm...can't reach this page" and "10.0.0.17 took too long to respond".

So my question is: what are reasons why only a browser running on the same PC as the running a.out can connect to the "http responder"? Why do browsers on a different PC in the same network seem unable to connect?


What I have tried:

On the other PC, I am able to ping 10.0.0.17 -- and that just about exhausts my knowledge of how to debug networking issues.

I considered whether the issue at root is more likely to be "networking stuff", which might make this question better asked at Super User, but then I thought to start my inquiry with Stack Overflow, in case the issues is in the C++ code.


The code:

// main.cpp
#include <arpa/inet.h>                                                                                   
#include <cerrno>                                                                                        
#include <cstdio>                                   
#include <cstring>        
#include <fcntl.h>                                  
#include <iostream>                                                                                      
#include <netinet/in.h>                                                                                  
#include <pthread.h>                                                                                     
#include <semaphore.h>                              
#include <stdexcept>      
#include <sstream>                                                                                       
#include <sys/types.h>                              
#include <sys/socket.h>                             
#include <unistd.h>       
                                                                                                         
#define IP "0.0.0.0"                                
#define PORT (9018)                                                                                      
                                                                                                         
/**                                                 
 * A primitive, POC-level HTTP server that accepts its first incoming connection                         
 * and sends back a minimal HTTP OK response.       
 */                                                                                                      
class Server {                                                                                           
private:                                                                                                 
  static const std::string ip_;
  static const std::uint16_t port_{PORT};                                                                
  int listen_sock_;                                 
  pthread_t tid_;                                                                                        
                                                    
public:                                                                                                  
  Server() { ///< create + bind listen_sock_; start thread for startRoutine().
    using namespace std;                                                                                 
    int result;                                                                                          
                                                    
    if (! createSocket()) { throw runtime_error("failed creating socket"); }
    if (! bindSocket()) { throw runtime_error("failed binding socket"); }
                                                    
    if ((result = pthread_create(&tid_, NULL, startRoutine, this))) {
      std::stringstream ss;
                                                                                                         
      ss << "pthread_create() error " << errno << "(" << result << ")";
      std::cerr << ss.str() << std::endl;           
      throw runtime_error("failed spawning Server thread");                                              
    }                                                                                                    
  }                                                                                                      
                                                                                                         
  ~Server() { ///< wait for the spawned thread and destroy listen_sock_.                                 
    pthread_join( tid_, NULL );      
    destroySocket();
  }                                                                                                      
                                                    
private:                                                                                                 
  bool createSocket() { ///< Creates listen_sock_ as a stream socket.
    listen_sock_ = socket(PF_INET, SOCK_STREAM, 0);
                          
    if (listen_sock_ < 0) {                         
      std::stringstream ss;                                                                              
                                                                                                         
      ss << "socket() error " << errno << "(" << strerror(errno) << ")";
      std::cerr << ss.str() << std::endl;
    }                                               
                                                                                                         
    return (listen_sock_ >= 0);          
  }                                                                                                      
                                                    
  void destroySocket() { ///< shut down and closes listen_sock_.
    if (listen_sock_ >= 0) {
      shutdown(listen_sock_, SHUT_RDWR);                                                                 
      close(listen_sock_);                                                                               
    }                                               
  }                                                                                                      
                                                    
  bool bindSocket() { ///< binds listen_sock_ to ip_ and port_.
    int ret;                                                                                             
    sockaddr_in me;                                                                                      
    me.sin_family = PF_INET;                        
    me.sin_port = htons(port_);                                                                          
    me.sin_addr.s_addr = INADDR_ANY;     
    int optval = 1;                                                                                      
                                                    
    setsockopt(listen_sock_, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof optval);
                          
    if ((ret = bind(listen_sock_, (sockaddr*)&me, sizeof me))) {
      std::stringstream ss;                                                                              
                                                    
      ss << "bind() error " << errno << "(" << strerror(errno) << ")";
      std::cerr << ss.str() << std::endl;
    }                                               
                          
    return (! ret);       
  }                                                                                                      
                                                    
  /**                                               
   * Accept a connection from listen_sock_.                                                              
   * Caller guarantees listen_sock_ has been listen()ed to already.
   * @param tv [in, out] How long to wait to accept a connection.
   * @return accepted socket; -1 on any error.                                                           
   */                                                                                                    
  int acceptConnection(timeval& tv) {
    int sock = -1;                                  
    int ret;                                                                                             
    fd_set readfds;                                 
    sockaddr_in peer;                                                                                    
    socklen_t addrlen = sizeof peer;     
                                                    
    FD_ZERO(&readfds);                              
    FD_SET(listen_sock_, &readfds);                 
    ret = select(listen_sock_ + 1, &readfds, NULL, NULL, &tv);
                                                                                                         
    if (ret < 0) {                                  
      std::stringstream ss;
                                                    
      ss << "select() error " << errno << "(" << strerror(errno) << ")";
      std::cerr << ss.str() << std::endl;
                                                                                                         
      return sock;                                  
    }                                               
    else if (! ret) {                               
      std::cout << "no connections within " << tv.tv_sec << " seconds"
        << std::endl;                                                                                    
                                                    
      return sock;                                                                                       
    }                                               
                                                    
    if ((sock = accept(listen_sock_, (sockaddr*)&peer, &addrlen)) < 0) {
      std::stringstream ss;                                                                              
                                                    
      ss << "accept() error " << errno << "(" << strerror(errno) << ")";
      std::cerr << ss.str() << std::endl;           
    }                                                                                                    
    else {                                          
      std::stringstream ss;                         
                                                    
      ss << "socket " << sock << " accepted connection from "
        << inet_ntoa( peer.sin_addr ) << ":" << ntohs(peer.sin_port);
      std::cout << ss.str() << std::endl;
    }                                               
                                                    
    return sock;                                    
  }                                                 
                                                    
  static void dumpReceivedContent(const int& sock) { ///< read & dump from sock.
    fd_set readfds;                                 
    struct timeval tv = {30, 0}; 
    int ret;                                                                                             
                                                    
    FD_ZERO(&readfds);                              
    FD_SET(sock, &readfds);                                                                              
    ret = select(sock + 1, &readfds, NULL, NULL, &tv);

    if (ret < 0) {                                  
      std::stringstream ss;                                                                              
                                                    
      ss << "select() error " << errno << "(" << strerror(errno) << ")";
      std::cerr << ss.str() << std::endl;
                                                    
      return;                                       
    }                                               
    else if (! ret) {                               
      std::cout << "no content received within " << tv.tv_sec << "seconds"
        << std::endl;                               

      return;                                       
    }                                               

    if (FD_ISSET(sock, &readfds)) {                                                                      
      ssize_t bytes_read;                           
      char buf[80] = {0};                           
                                                    
      fcntl(sock, F_SETFL, fcntl(sock, F_GETFL, 0) | O_NONBLOCK);
      std::cout << "received content:" << std::endl; 
      std::cout << "----" << std::endl;            
      while ((bytes_read = read(sock, buf, (sizeof buf) - 1)) >= 0) {
        buf[bytes_read] = '\0';
        std::cout << buf;                           
      }                                             
      std::cout << std::endl << "----" << std::endl; 
    }                                               
  }                                                 
                                                    
  static void sendMinHttpResponse(const int& sock) { ///< min HTTP OK + content.
    static const std::string html = 
      "<!doctype html>"                             
      "<html lang=en>"                              
      "<head>"                                      
      "<meta charset=utf-8>"    
      "<title>blah</title>"     
      "</head>"                                     
      "<body>"                                      
      "<p>I'm the content</p>"                      
      "</body>"                                     
      "</html>";                                    
    std::stringstream resp;                         
                                                    
    resp << "HTTP/1.1 200 OK\r\n"
      << "Content-Length: " << html.length() << "\r\n"
      << "Content-Type: text/html\r\n\r\n"
      << html;                                      
    write(sock, resp.str().c_str(), resp.str().length());
  }                                                 

  /**
   * Thread start routine: listen for, then accept connections; dump received
   * content; send a minimal response.
   */
  static void* startRoutine(void* arg) {
    Server* s;

    if (! (s = (Server*)arg)) {
      std::cout << "Bad arg" << std::endl;
      return NULL;
    }

    if (listen(s->listen_sock_, 3)) {
      std::stringstream ss;

      ss << "listen() error " << errno << "(" << strerror(errno) << ")";
      std::cerr << ss.str() << std::endl;

      return NULL;
    }

    std::cout << "Server accepting connections at "
      << s->ip_ << ":" << s->port_ << std::endl;

    {
      timeval tv = { 30, 0 };

      int sock = s->acceptConnection(tv);

      if (sock < 0) {
        std::cout << "no connections accepted" << std::endl;

        return NULL;
      }

      dumpReceivedContent(sock);
      sendMinHttpResponse(sock);
      shutdown(sock, SHUT_RDWR);
      close(sock);
    }

    return NULL;
  }
};

const std::string Server::ip_{IP};

int main( int argc, char* argv[] ) {
  Server s;
  return 0;
}

Compilation/execution:

This is a "working" case when the http responder receives a connection from a web browser on the same PC connecting to 10.0.0.17:9018:

$ g++ -g ./main.cpp -lpthread && ./a.out 
Server accepting connections at 0.0.0.0:9018
socket 4 accepted connection from 10.0.0.17:56000
received content:
----
GET / HTTP/1.1
Host: 10.0.0.17:9018
Connection: keep-alive
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9


----

This is the problem/question case when the http responder receives nothing from a web browser on a different PC in the same network connecting to 10.0.0.17:9018:

$ ./a.out 
Server accepting connections at 0.0.0.0:9018
no connections within 0 seconds
no connections accepted

** The "no connections within 0 seconds" message is because select() updated the struct timeval.tv_sec field -- the program has actually waited 30 seconds.

StoneThrow
  • 5,314
  • 4
  • 44
  • 86
  • Your OS is probably running a firewall, most do these days. If it is you will need to configure a hole through the firewall to allow incoming connections for your application's port. You might also need to configure `SELinux` (if installed/running) on your computer to allow the firewall changes. – Richard Critten Aug 20 '22 at 18:58
  • @RichardCritten - holy moly, that was it! I ran `sudo ufw allow 9018` then suddenly the problem scenario worked! Don't feel obliged, since you've already "answered" my question, but I wonder if you might be willing to expand your comment into an answer? Google led me to that `ufw` command, but I don't really understand what I did, or why it changed nonworking -> working. My understanding of "networking stuff" is still very basic, and I'm still very early in trying to cobble together piecemeal learning into a more comprehensive understanding. – StoneThrow Aug 20 '22 at 19:12

0 Answers0