views:

587

answers:

1

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, using PuTTY 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 do this part? Can an example be developed of this? Also, for the opposite, where a remote port is brought to a local port, do I use libssh2_channel_direct_tcpip_ex? What about an example of this? I really need to do this exact thing on a project right now. How hard would it be to develop a couple of samples of this? I'm willing to put up a bounty if need be to get a couple of working examples of this.

+1  A: 

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.

flxkid