0

Basically, I need to make a kind of chat. I have set up the JSch library and using their examples I'm able to successfully open an SSH (-R) tunnel between the client(Win10-local) and server(Rpi-remote). I can send commands to shell in rpi, pass files, create new files, write to them etc... But I need to be able to "catch" and read the data on server side right from the socket, let's say some string sent from the client side. Then I have to change this string on the server side and resend it back to the client, where it will be printed out.

I tried to create a socket(server) and bind to the port on which the SSH communication goes, but I get an error that socket can't be bound to this port because it is already used. If I first bind the socket, then the SSH tunnel won't pass.

I also tried the daemon example in jsch examples: http://www.jcraft.com/jsch/examples/Daemon.java.html but without any success.

Right now I'm trying to use this code on the server side:

public class ServerSide {
    private final static Random random = new Random();
    private final static String[] ADVICE_LIST = {
        "Take smaller bites", 
        "Go for the tight jeans. No they do NOT make you look fat.", 
        "One word: inappropriate", 
    };

    private void go() throws IOException {
        ServerSocket serverSocket = new ServerSocket();
        serverSocket.bind(new InetSocketAddress("localhost",12005));

        while(!serverSocket.isClosed()) {
            Socket socket = serverSocket.accept();

            PrintWriter writer = new PrintWriter(socket.getOutputStream());
            System.out.println(socket.getOutputStream());
            String advice = getAdvice();
            System.out.println("Sending advice: " + advice);
            writer.write(advice);
            writer.close();
            System.out.println("Advice sent!");
            socket.close();
        }
    }

    private static String getAdvice() {
        return ADVICE_LIST[random.nextInt() % ADVICE_LIST.length];
    }

    public static void main(String[] args) throws IOException {

        ServerSide server = new ServerSide();
        server.go();
    }
  }
}

The server listens on port 12005 and if it detects any client it just sends a random message to him. When I tried it yesterday, with server listening on, the tunnel could not be established, but when I tried it yet, the tunnel is established but the server doesn't detect anything at all. The code on client is:

public class PortForwardingR{
  public static void main(String[] arg){

    int rport;
    String lhost;
    int lport;

    try{
      JSch jsch=new JSch();

      String host=null;
      if(arg.length>0){
        host=arg[0];
      }
      else{
        host=JOptionPane.showInputDialog("Enter username@hostname",
                                         System.getProperty("user.name")+
                                         "@localhost"); 
      }
      String user=host.substring(0, host.indexOf('@'));
      host=host.substring(host.indexOf('@')+1);

      Session session=jsch.getSession(user, host, 22);

      String foo=JOptionPane.showInputDialog("Enter -R port:host:hostport", 
                         "port:host:hostport");
      rport=Integer.parseInt(foo.substring(0, foo.indexOf(':')));
      foo=foo.substring(foo.indexOf(':')+1);
      lhost=foo.substring(0, foo.indexOf(':'));
      lport=Integer.parseInt(foo.substring(foo.indexOf(':')+1));

      // username and password will be given via UserInfo interface.
      UserInfo ui=new MyUserInfo();
      session.setUserInfo(ui);

      session.connect();

      // Channel channel=session.openChannel("shell");
      // channel.connect();

      session.setPortForwardingR(rport, lhost, lport);

      System.out.println(host+":"+rport+" -> "+lhost+":"+lport);
    }
    catch(Exception e){
      System.out.println(e);
    }
  }

  public static class MyUserInfo implements UserInfo, UIKeyboardInteractive{
    public String getPassword(){ return passwd; }
    public boolean promptYesNo(String str){
      Object[] options={ "yes", "no" };
      int foo=JOptionPane.showOptionDialog(null, 
             str,
             "Warning", 
             JOptionPane.DEFAULT_OPTION, 
             JOptionPane.WARNING_MESSAGE,
             null, options, options[0]);
       return foo==0;
    }

    String passwd;
    JTextField passwordField=(JTextField)new JPasswordField(20);

    public String getPassphrase(){ return null; }
    public boolean promptPassphrase(String message){ return true; }
    public boolean promptPassword(String message){
      Object[] ob={passwordField}; 
      int result=
      JOptionPane.showConfirmDialog(null, ob, message,
                    JOptionPane.OK_CANCEL_OPTION);
      if(result==JOptionPane.OK_OPTION){
    passwd=passwordField.getText();
    return true;
      }
      else{ return false; }
    }
    public void showMessage(String message){
      JOptionPane.showMessageDialog(null, message);
    }
    final GridBagConstraints gbc = 
      new GridBagConstraints(0,0,1,1,1,1,
                             GridBagConstraints.NORTHWEST,
                             GridBagConstraints.NONE,
                             new Insets(0,0,0,0),0,0);
    private Container panel;
    public String[] promptKeyboardInteractive(String destination,
                                              String name,
                                              String instruction,
                                              String[] prompt,
                                              boolean[] echo){
      panel = new JPanel();
      panel.setLayout(new GridBagLayout());

      gbc.weightx = 1.0;
      gbc.gridwidth = GridBagConstraints.REMAINDER;
      gbc.gridx = 0;
      panel.add(new JLabel(instruction), gbc);
      gbc.gridy++;

      gbc.gridwidth = GridBagConstraints.RELATIVE;

      JTextField[] texts=new JTextField[prompt.length];
      for(int i=0; i<prompt.length; i++){
        gbc.fill = GridBagConstraints.NONE;
        gbc.gridx = 0;
        gbc.weightx = 1;
        panel.add(new JLabel(prompt[i]),gbc);

        gbc.gridx = 1;
        gbc.fill = GridBagConstraints.HORIZONTAL;
        gbc.weighty = 1;
        if(echo[i]){
          texts[i]=new JTextField(20);
        }
        else{
          texts[i]=new JPasswordField(20);
        }
        panel.add(texts[i], gbc);
        gbc.gridy++;
      }

      if(JOptionPane.showConfirmDialog(null, panel, 
                                       destination+": "+name,
                                       JOptionPane.OK_CANCEL_OPTION,
                                       JOptionPane.QUESTION_MESSAGE)
         ==JOptionPane.OK_OPTION){
        String[] response=new String[prompt.length];
        for(int i=0; i<prompt.length; i++){
          response[i]=texts[i].getText();
        }
    return response;
      }
      else{
        return null;  // cancel
      }
    }
  }
}

When I'm prompt to "Enter username@hostname" I enter RPI's_username@It's_publicIP then in "Enter -R port:host:hostport" I'm entering: 12005:localhost:12005. Am I doing it right? The connection successfully establishes so I believe it's ok.

Also, I found this code here (tried it and it worked), which is very close to what I want, but it works with shell, I want to communicate directly with my code on the server side.

public class TestShell {

    public static void main(String[] arg) {

        try {

            JSch jsch = new JSch();
            String host = null;

            final Session session = jsch.getSession("user", "remotecomputer", 22);
            session.setPassword("fluffybunnyslippers");

            session.setConfig("StrictHostKeyChecking", "no");
            session.connect(30000);   // making a connection with timeout.

            final Channel channel = session.openChannel("shell");

            PipedInputStream pis = new PipedInputStream();
            final PipedOutputStream pos = new PipedOutputStream(pis);

            channel.setInputStream(pis);
            channel.setOutputStream(new OutputStream() {

                private int cmdIndx = 0;
                private String[] cmds = {
                    "ls\n",
                    "cd ..\n",
                    "ls\n",
                    "exit\n"
                };

                private String line = "";

                @Override
                public void write(int b) throws IOException {
                    char c = (char) b;
                    if (c == '\n') {
                        logout(line);
                        System.out.print(line);
                        line = "";
                    } else {
                        line += c;
                        logout(line);
                        if (line.endsWith("$ ")) {
                            String cmd = cmds[cmdIndx];
                            cmdIndx++;
                            pos.write(cmd.getBytes());
                        }
                    }
                }

                public void logout(String line) {
                    if (line.startsWith("logout")) {
                        System.out.println("...logout...");
                        channel.disconnect();
                        session.disconnect();
                        System.exit(0);
                    }
                }
            });

            channel.connect(3 * 1000);

        } catch (Exception e) {
            System.out.println(e);
        }
    }
}

The Rpi(server) is on a router, which has a public IP. The router has set port forwarding on port 12005, which sends the data to the local IP (and to port 12005) where my RPI is. The router also has port 12005 exposed as public, probably not necessary and I will close it if I achieve what I'm trying to. The client is simply on wifi from a mobile hotspot.

Can someone give me a bit of advice on how to use an existing, working, SSH tunnel to read/write communication directly from sockets? I'm a very beginner in communication through the internet.

  • You just connect to it, same way you would connect to a server. No server socket required. – user207421 Mar 27 '19 at 23:57
  • Why `ssh -R`? How exactly are you using the socket/port-forwarding in your client-server communication? – Martin Prikryl Mar 28 '19 at 08:24
  • I wasn't sure if ssh -L enables two-way communication and I wanted to make it sure that the client, who is behind the firewall will be able to get messages from the server. I just want the ssh tunnel to connect into my application where I can read the output stream, but I don't know which channel type in Jsch I have to use and how to go from that point. – FrantišekV Mar 28 '19 at 14:48
  • I'm lost. Are you trying to implement the port forwarding in Java/JSch? Or do you plan to use a port forwarding created by `ssh`? – Martin Prikryl Mar 28 '19 at 15:13
  • I'm doing port forwardingR with this example: http://www.jcraft.com/jsch/examples/PortForwardingR.java.html which returns: :12005 -> localhost:12005 , the connection is estabilished I can send commands to shell if I want to, but what I'm trying to achieve is that the server will read the incoming message from the client in my java application and then resend it back to the client, who will print it out. As first step it would suffice if I could read the incoming message from client. – FrantišekV Mar 28 '19 at 16:20
  • Sorry, but we need [mcve]. We do not know anything about your server. It is a separate application? Why do you even use port forwarding? --- I also have no idea how does "port forwardingR" help you with executing shell commands. --- The information you are providing are sooo confusing --- It looks like you miss something important, but it's difficult to say what that is. – Martin Prikryl Mar 28 '19 at 16:44
  • I extended my question with some information and with the code. I appreciate your interest in the problem. – FrantišekV Mar 28 '19 at 18:37
  • You still didn't answer why are you trying to use a port forwarding. The server is listening on port 12005. Your router forwards that post. So just connect directly (with socket) to port 12005 in your client. No SSH/JSch/port-forwarding code is needed. – Martin Prikryl Mar 29 '19 at 05:46
  • I first did that, but the problem was that while the message from the client was successfully sent to the server, the server's response couldn't make it back to the client. The client is behind ISP routers and I can't set port forwarding on this router. The response from the server is sent to a random port from the ISP. I hoped I can bypass this with SSH. So when I tried it, the client just waited for a response until timeout. – FrantišekV Mar 29 '19 at 07:43
  • If the clients open a TCP connection to the server, the server can send response using that connection without any additional setup. That's how TCP works. If the response didn't make it back, you have a problem in your socket code --- No point trying to workaround it using an SSH and port forwarding ---- If your webbrowser opens a connection to an HTTP server and sends a request there, the server can send the response back using the same connection without any port forwarding either - It's the same. – Martin Prikryl Mar 29 '19 at 08:42
  • In the end, I solved the problem with closing the port 12005 being public (my router has such a setting). Don't know why it was a problem. Now everything works as it should without SSH. – FrantišekV Apr 03 '19 at 07:54

0 Answers0