Example code of libssh2 being used for port forwarding

flxkid picture flxkid · Oct 17, 2009 · Viewed 8.3k times · Source

I'm looking for an example of how to use libssh2 to setup ssh port forwarding. I've looked at the API, but there is very little in the way of documentation in the area of port forwarding.

For instance, when using PuTTY's plink there is the remote port to listen on, but also the local port that traffic should be sent to. Is it the developers responsibility to set this up? Can someone give an example of how to do this?

Also, an example where remote port is brought to a local port would be useful. Do I use libssh2_channel_direct_tcpip_ex()?

I'm willing to put up a bounty if need be to get a couple of working examples of this.

Answer

flxkid picture flxkid · Nov 3, 2009

The key to making libssh2 port forwarding work was discovering that it basically just gives you the data that came in to that port. You have to actually send the data onto a local port that you open:

(Note, this code is not yet complete, there is no error checking, and the thread yielding isn't correct, but it gives a general outline of how to accomplish this.)

void reverse_port_forward(CMainDlg* dlg, addrinfo * hubaddr, std::string username, std::string password, int port)
{
  int iretval;
  unsigned long mode = 1;
  int last_socket_err = 0;
  int other_port = 0;
  fd_set read_set, write_set;

  SOCKET sshsock = socket(AF_INET, SOCK_STREAM, 0);
  iretval = connect(sshsock, hubaddr->ai_addr, hubaddr->ai_addrlen);
  if (iretval != 0)
    ::PostQuitMessage(0);

  LIBSSH2_SESSION * session = NULL;
  session = libssh2_session_init();

  iretval = libssh2_session_startup(session, sshsock);
  if (iretval)
    ::PostQuitMessage(0);

  iretval = libssh2_userauth_password(session, username.c_str(), password.c_str());

  dlg->m_track_status(dlg, 1, 0, "Authorized");

  LIBSSH2_LISTENER* listener = NULL;
  listener = libssh2_channel_forward_listen_ex(session, "127.0.0.1", port, &other_port, 1);
  if (!listener)
    ::PostQuitMessage(0);

  LIBSSH2_CHANNEL* channel = NULL;

  ioctlsocket(sshsock, FIONBIO, &mode);
  libssh2_session_set_blocking(session, 0); // non-blocking
  int err = LIBSSH2_ERROR_EAGAIN;
  while (err == LIBSSH2_ERROR_EAGAIN)
  {
    channel = libssh2_channel_forward_accept(listener);
    if (channel) break;
    err = libssh2_session_last_errno(session);
    boost::this_thread::yield();
  }

  if (channel)
  {
    char buf[MAX_BUF_LEN];
    char* chunk;
    long bytes_read = 0;
    long bytes_written = 0;
    int total_set = 0;
    timeval wait;
    wait.tv_sec = 0;
    wait.tv_usec = 2000;

    sockaddr_in localhost;
    localhost.sin_family = AF_INET;
    localhost.sin_addr.s_addr = inet_addr("127.0.0.1");
    localhost.sin_port = htons(5900);
    SOCKET local_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    ioctlsocket(local_sock, FIONBIO, &mode);
    iretval = connect(local_sock, (sockaddr*) &localhost, sizeof(localhost) );
    if (iretval == SOCKET_ERROR)
      iretval = WSAGetLastError();

    while (1)
    {
      bytes_read = libssh2_channel_read(channel, buf, MAX_BUF_LEN);
      if (bytes_read >= 0){
        FD_ZERO(&read_set);
        FD_ZERO(&write_set);
        FD_SET(local_sock, &write_set);

        // wait until the socket can be written to
        while (select(0, &read_set, &write_set, NULL, &wait) < 1)
          boost::this_thread::yield();

        if (FD_ISSET(local_sock, &write_set))
        {
          FD_CLR(local_sock, &write_set);
          chunk = buf;

          // everything may not get written in this call because we're non blocking.  So
          // keep writing more data until we've emptied the buffer pointer.
          while ((bytes_written = send(local_sock, chunk, bytes_read, 0)) < bytes_read)
          {
            // if it couldn't write anything because the buffer is full, bytes_written
            // will be negative which won't help our pointer math much
            if (bytes_written > 0)
            {
              chunk = buf + bytes_written;
              bytes_read -= bytes_written;
              if (bytes_read == 0)
                break;
            }
            FD_ZERO(&read_set);
            FD_ZERO(&write_set);
            FD_SET(local_sock, &write_set);

            // wait until the socket can be written to
            while (select(0, &read_set, &write_set, NULL, &wait) < 1)
              boost::this_thread::yield();
          }

        }
      }

      FD_ZERO(&read_set);
      FD_ZERO(&write_set);
      FD_SET(local_sock, &read_set);
      select(0, &read_set, &write_set, NULL, &wait);
      if (FD_ISSET(local_sock, &read_set))
      {
        FD_CLR(local_sock, &read_set);
        bytes_read = recv(local_sock, buf, MAX_BUF_LEN, 0);
        if (bytes_read >= 0)
        {
          while ((bytes_written = libssh2_channel_write_ex(channel, 0, buf, bytes_read)) == LIBSSH2_ERROR_EAGAIN)
            boost::this_thread::yield();
        }
      }
      boost::this_thread::yield();
    } // while
  } // if channel
}

P.S. To make this work requires the latest SVN builds of libssh2. There were bugs in prior versions that kept port forwarding from being usable.