mardi 4 octobre 2022

Using popen within a server application holds port hostage

TLDR: I have a HTTP server application written in C++ which launches some scripts using popen(). The scripts start a few daemons: wpa_supplicant and udhcpd. Those daemons seem to hold onto my HTTP server port after my server stops. Why?

During initialization, my HTTP server application uses popen() to launch a script to start wpa_supplicant and udhcpd to make sure my interfaces are ready to go., then my application opens port 80 as you would expect.

The problem: When my application closes and goes through all the destructors, it correctly closes the socket with close(int_socket_val), yet trying to start my application a second time will fail because port 80 is not available.

Doing a netstat -tulpn shows that either wpa_supplicant or udhcpd is hanging onto my port 80. Interestingly, while my HTTP server is still running, netstat shows this same result - so my HTTP server is never listed as owning the port. Killing those applications with killall -9 wpa_supplicant udhcpd will free port 80 and allow me to start my HTTP server again. But why does this happen? This has proven a difficult problem to research.

For reference, here is the method I use to launch scripts and be able to read what was returned during those calls:

std::string ConnectionManager::exec(const std::string& command, bool strip)
{
   char buffer[EXEC_BUFFER_LEN];
   std::string result = "";

   // Open pipe to file
   FILE* pipe = popen(command.c_str(), "r");
   if (!pipe)
   {
       std::cout << "ERROR: ConnectionManager::exec() - failed to open command: " << command << std::endl;
      return result;
   }

   // read till end of process:
   while (!feof(pipe))
   {
      // use buffer to read and add to result
      if (fgets(buffer, EXEC_BUFFER_LEN, pipe) != NULL)
      {
        result += buffer;
      }
   }

   pclose(pipe);

   if ( strip )
   {
       removeLineEndings(result);
   }

   return result;
}

This is not a special case where port 80 is somehow magical. It works on any port I use for development - starting my HTTP server on port 50000 poses the same effect. Here is the netstat output for reference:

root@device:/usr/bin# netstat -tulpn
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name
.........
tcp        0      0 0.0.0.0:80              0.0.0.0:*               LISTEN      4267/udhcpd
.........
root@device:/usr/bin#

During a subsequent run, I might get wpa_supplicant hanging onto the port - that part seems random:

root@device:/usr/bin# netstat -tulpn
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name
.........
tcp        0      0 0.0.0.0:80              0.0.0.0:*               LISTEN      4393/wpa_supplicant
.........
root@device:/usr/bin#

For reference, here is a section of the script that calls these two daemons:

wpa_supplicant -B -i wlan0 -c /etc/wpa_supplicant.conf
udhcpc -i wlan0

Aucun commentaire:

Enregistrer un commentaire