You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@mina.apache.org by "Feng Jiajie (Jira)" <ji...@apache.org> on 2020/09/02 08:43:00 UTC

[jira] [Commented] (SSHD-1055) Remote port forwarding mode does not handle EOF properly

    [ https://issues.apache.org/jira/browse/SSHD-1055?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=17189083#comment-17189083 ] 

Feng Jiajie commented on SSHD-1055:
-----------------------------------

Hi [~lgoldstein], [~roberto.deandrea]

I got some log using openssh server debug mode:

 
{code:java}

debug1: Connection to port 15678 forwarding to 0.0.0.0 port 0 requested.
debug2: fd 12 setting TCP_NODELAY
debug2: fd 12 setting O_NONBLOCK
debug3: fd 12 is O_NONBLOCK
debug1: channel 2: new [forwarded-tcpip]
debug3: send packet: type 90
debug3: receive packet: type 91
debug2: channel 2: open confirm rwindow 2097152 rmax 32768
debug3: receive packet: type 96
debug2: channel 2: rcvd eof      // ============================================= begin eof handle
debug2: channel 2: output open -> drain
debug2: channel 2: obuf empty
debug2: channel 2: chan_shutdown_write (i0 o1 sock 12 wfd 12 efd -1 [closed])
debug2: channel 2: output drain -> closed
debug2: channel 2: read<=0 rfd 12 len 0
debug2: channel 2: read failed
debug2: channel 2: chan_shutdown_read (i0 o3 sock 12 wfd 12 efd -1 [closed])
debug2: channel 2: input open -> drain
debug2: channel 2: ibuf empty
debug2: channel 2: send eof
debug3: send packet: type 96
debug2: channel 2: input drain -> closed
debug2: channel 2: send close
debug3: send packet: type 97
debug3: channel 2: will not send data after close
debug3: receive packet: type 97
debug2: channel 2: rcvd close
debug3: channel 2: will not send data after close
debug2: channel 2: is dead
debug2: channel 2: garbage collecting
debug1: channel 2: free: forwarded-tcpip: listening port 15678 for 0.0.0.0 port 0, connect from ::1 port 40480 to ::1 port 15678, nchannels 3
debug3: channel 2: status: The following connections are open:
  #2 forwarded-tcpip: listening port 15678 for 0.0.0.0 port 0, connect from ::1 port 40480 to ::1 port 15678 (t4 r0 i3/0 o3/0 e[closed]/0 fd 12/12/-1 sock 12 cc -1)

{code}
 

[https://github.com/openssh/openssh-portable/blob/V_7_9_P1/serverloop.c#L906]

 
{code:java}
ssh_dispatch_set(ssh, SSH2_MSG_CHANNEL_EOF, &channel_input_ieof);   // ============= call channel_input_ieof
{code}
[https://github.com/openssh/openssh-portable/blob/V_7_9_P1/channels.c#L3095]

 
{code:java}
int
channel_input_ieof(int type, u_int32_t seq, struct ssh *ssh)
{
 Channel *c = channel_from_packet_id(ssh, __func__, "ieof");

 ssh_packet_check_eom(ssh);

 if (channel_proxy_upstream(c, type, seq, ssh))
  return 0;
 chan_rcvd_ieof(ssh, c);               // =================================== call chan_rcvd_ieof

 /* XXX force input close */
 if (c->force_drain && c->istate == CHAN_INPUT_OPEN) {
  debug("channel %d: FORCE input drain", c->self);
  c->istate = CHAN_INPUT_WAIT_DRAIN;
  if (sshbuf_len(c->input) == 0)
   chan_ibuf_empty(ssh, c);
 }
 return 0;
}

{code}
[https://github.com/openssh/openssh-portable/blob/V_7_9_P1/nchan.c]

 
{code:java}
void
chan_rcvd_ieof(struct ssh *ssh, Channel *c)
{
 debug2("channel %d: rcvd eof", c->self);    // =============== log debug2: channel 2: rcvd eof
 c->flags |= CHAN_EOF_RCVD;
 if (c->ostate == CHAN_OUTPUT_OPEN)
  chan_set_ostate(c, CHAN_OUTPUT_WAIT_DRAIN);  // ================= log debug2: channel 2: output open -> drain
 if (c->ostate == CHAN_OUTPUT_WAIT_DRAIN &&
     sshbuf_len(c->output) == 0 &&
     !CHANNEL_EFD_OUTPUT_ACTIVE(c))
  chan_obuf_empty(ssh, c);         // ======================= call chan_obuf_empty
}{code}
 

 

[https://github.com/openssh/openssh-portable/blob/V_7_9_P1/nchan.c#L147]

 
{code:java}
void
chan_obuf_empty(struct ssh *ssh, Channel *c)
{
 debug2("channel %d: obuf empty", c->self);  // ===================== debug2: channel 2: obuf empty
 if (sshbuf_len(c->output)) {
  error("channel %d: chan_obuf_empty for non empty buffer",
      c->self);
  return;
 }
 switch (c->ostate) {
 case CHAN_OUTPUT_WAIT_DRAIN:
  chan_shutdown_write(ssh, c);     //  ====================== call chan_shutdown_write
  chan_set_ostate(c, CHAN_OUTPUT_CLOSED);  // ================ log debug2: channel 2: output drain -> closed
  break;
 default:
  error("channel %d: internal error: obuf_empty for ostate %d",
      c->self, c->ostate);
  break;
 }
}{code}
[https://github.com/openssh/openssh-portable/blob/V_7_9_P1/nchan.c#L373]

 
{code:java}
static void
chan_shutdown_write(struct ssh *ssh, Channel *c)
{
 sshbuf_reset(c->output);
 if (c->type == SSH_CHANNEL_LARVAL)
  return;
 /* shutdown failure is allowed if write failed already */
 debug2("channel %d: %s (i%d o%d sock %d wfd %d efd %d [%s])",   // ======== log debug2: channel 2: chan_shutdown_write (i0 o1 sock 12 wfd 12 efd -1 [closed])
     c->self, __func__, c->istate, c->ostate, c->sock, c->wfd, c->efd,
     channel_format_extended_usage(c));
 if (c->sock != -1) {
  if (shutdown(c->sock, SHUT_WR) < 0) {   // ================ call shutdown fd SHUT_WR
   debug2("channel %d: %s: shutdown() failed for "
       "fd %d [i%d o%d]: %.100s", c->self, __func__,
       c->sock, c->istate, c->ostate,
       strerror(errno));
  }
 } else {
  if (channel_close_fd(ssh, &c->wfd) < 0) {
   logit("channel %d: %s: close() failed for "
       "fd %d [i%d o%d]: %.100s",
       c->self, __func__, c->wfd, c->istate, c->ostate,
       strerror(errno));
  }
 }
}{code}
I find MINA SSHD serverSession.shutdownOutputStream(); do shutdown fd:
{code:java}

@Override
public final AsynchronousSocketChannel shutdownOutput() throws IOException {
    try {
        begin();
        if (remoteAddress == null)
            throw new NotYetConnectedException();
        synchronized (writeLock) {
            if (!writeShutdown) {
                Net.shutdown(fd, Net.SHUT_WR);    // ======================
                writeShutdown = true;
            }
        }
    } finally {
        end();
    }
    return this;
}

{code}
but [https://github.com/apache/mina-sshd/commit/385f63731e74d0c73b6624170bfd1ad385ebffc6] remove the call.

 

> Remote port forwarding mode does not handle EOF properly
> --------------------------------------------------------
>
>                 Key: SSHD-1055
>                 URL: https://issues.apache.org/jira/browse/SSHD-1055
>             Project: MINA SSHD
>          Issue Type: Bug
>    Affects Versions: 2.5.1
>            Reporter: Feng Jiajie
>            Priority: Major
>
> I want to call the remote server's gRPC service locally through an SSH tunnel. 
> MyApp -> MINA SSHD -> \{Internet} -> gRPC Server
> It works just fine with OpenSSH, but there is a small problem(no problems with core functions, only in unusual circumstances) with Mina SSHD.
> I think the problem is Mina SSHD's handling of EOF.
> Here is the example:
> Step 1. Start a gRPC server:
> Because we only need a gRPC server to reproduce the problem, so I write a simple version without any service: 
> {code:java}
> <dependency>
>     <groupId>io.grpc</groupId>
>     <artifactId>grpc-netty-shaded</artifactId>
>     <version>1.27.2</version>
> </dependency>
> <dependency>
>     <groupId>io.grpc</groupId>
>     <artifactId>grpc-protobuf</artifactId>
>     <version>1.27.2</version>
> </dependency>
> <dependency>
>     <groupId>io.grpc</groupId>
>     <artifactId>grpc-stub</artifactId>
>     <version>1.27.2</version>
> </dependency>
> {code}
> main: 
> {code:java}
> import io.grpc.Server;
> import io.grpc.ServerBuilder;
> public class EmptyGrpcServer {
>   public static void main(String[] args) throws Exception {
>     Server server = ServerBuilder.forPort(23645).build().start();
>     server.awaitTermination();
>   }
> }
> {code}
> Full example can be fond here: [https://github.com/grpc/grpc-java/blob/master/examples/src/main/java/io/grpc/examples/helloworld/HelloWorldServer.java] 
> Step 2. Start a MINA SSHD server: 
> {code:java}
> import org.apache.sshd.server.SshServer;
> import org.apache.sshd.server.forward.AcceptAllForwardingFilter;
> import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider;
> import java.nio.file.Paths;
> public class Example1 {
>   public static void main(String[] args) throws Exception {
>     SshServer sshd = SshServer.setUpDefaultServer();
>     sshd.setPort(12133);
>     sshd.setKeyPairProvider(new SimpleGeneratorHostKeyProvider(Paths.get("/tmp/a.ser")));
>     sshd.setPasswordAuthenticator((username, password, session) -> true);
>     sshd.setForwardingFilter(AcceptAllForwardingFilter.INSTANCE);
>     sshd.start();
>     Thread.sleep(10000000);
>   }
> }
> {code}
> Step 3. Create a channel using ssh client 
> {code:java}
> ssh -o 'ExitOnForwardFailure yes' -vvv -p 12133 -f -x -N -T -R 0.0.0.0:0:127.0.0.1:23645 test5@127.0.0.1
> {code}
> Step 4. Reproduce
> If I connect directly to the gRPC server using curl, cause gRPC using http/2, I would get error output like this: 
> {code:java}
> $ curl 127.0.0.1:23645
> ���+Unexpected HTTP/1.x request: GET /
> $
> {code}
> Then if I do step 3 with an OpenSSH server, I would get same error output:
> {code:java}
> $ ssh -o 'ExitOnForwardFailure yes' -f -x -N -T -R 0.0.0.0:0:127.0.0.1:23645 work@dev.kbyte.cn
> Allocated port 13525 for remote forward to 127.0.0.1:23645
> $
> $ curl dev.kbyte.cn:13525
> ���+Unexpected HTTP/1.x request: GET / 
> $
> {code}
> But when I do step 3 with MINA SSHD, curl would stuck without any output:
> {code:java}
> $ curl 127.0.0.1:55604
> {code}
> I found MINA SSHD had already got and wrote the package with the string "Unexpected.HTTP/1.x.request:.GET", and received SSH_MSG_CHANNEL_EOF.
> So I think handleEof should do more? like send SSH_MSG_CHANNEL_EOF to curl?
>  



--
This message was sent by Atlassian Jira
(v8.3.4#803005)

---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@mina.apache.org
For additional commands, e-mail: dev-help@mina.apache.org