Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[SSHD-85] Tests & Candidate fix #33

Closed
wants to merge 6 commits into from
Closed

Conversation

bkuker
Copy link
Contributor

@bkuker bkuker commented Jun 22, 2017

SSHD-85 is a bug that occurs on Windows where the last few bytes of data sent through a port forward can be lost when the server at the far end of the port forward sends the data and immediately closes the socket.

An example of this server behavior would be HTTP 1.0 with the Connection: close header. The server sends the data, and then closes the socket to indicate that it is done sending data. The server does not send a Content-Length header or similar. This is a stupid way to do it, all modern http servers do better, but there are still old embedded systems that work this way, and it does impact my use case.

The test cases include some sleeps on the client end. This seems to be a race condition that occurs possibly one time in a hundred or more, but these sleeps cause it to happen every single time.
The test cases include two controls: JSCH as the client

During testing I did verify that the test server did send all data to the ssh client by examining the buffers in the ssh client and seeing all of the data. I then verified that all of the data was transferred from the ssh client to the ssh server by examining the buffers in the sshd server. The sshd server does write all of that data to the socket as far as I can tell, but on Windows some still goes missing at the end! I also verified that on Local port forwards the error does occur with Apache SSHD as the client, and does NOT occur with JSCH as the client. I would like to try that the other way around with Apache SSH Client and another server, but the pickings for Java SSH servers are slim, and this one is the best!

I am currently trying to understand WHY the fix (basically one line, getSocket().shutdownOutput()) fixes the problem. I am submitting this PR now in hopes that someone with a better understanding will point out why it is the correct fix and accept it. All I can say at the moment is that it works.

Also includes a fix for a test in PortForwardingLoadTest which was failing for me before I started investigating. Frankly it looks like it never worked; I would guess that there was some copy-paste mistake in the past when it got updated to use try-with-resources.

I am ... very motivated to get this fixed so I do not have to maintain my own private branch of sshd, so please ask questions, make suggestions, if you think I am totally crazy help me prove that I am not!

Copy link
Contributor

@lgoldstein lgoldstein left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general, in order for me to actually test the code I need it to conform to the project style. Please make sure you run a mvn clean install before re-posting the changes. In general here is what seems to be missing:

  • Standard license header
  • Standard class javadoc format
  • We no longer use final modifier
  • All our test methods are called testXXX
  • All our test classes extend BaseTestSupport and call assertXXX directly instead of Assert.assertXXX

Please make the necessary changes and re-post once mvn clean install passed so I can start examining it.

@@ -215,6 +215,7 @@ public void run() {
try (InputStream sockIn = s.getInputStream();
ByteArrayOutputStream baos = new ByteArrayOutputStream()) {

//Reag a payload worth of data, save it to baos
while (baos.size() < payload.length()) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Typo error (I assume) Read...
  2. Please align the comment indentation

@@ -0,0 +1,227 @@
package org.apache.sshd.common.forward.sshd85;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please attach the standard license header

* close the TCP connection to indicate that all data has been sent.
*
* This test implements TWO methods to connect to and read data from that
* server.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please format this comment according to our standards

* @author bkuker
*
*/
public abstract class AbstractServerCloseTest {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All out tests extend BaseTestSupport class

private final String PAYLOAD;
private AsynchronousServerSocketChannel testServerSock;

public AbstractServerCloseTest() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

abstract class constructor should be protected

*/
@Before
public void startTestServer() throws Exception {
final InetSocketAddress sockAddr = new InetSocketAddress("localhost", TEST_SERVER_PORT);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no need to declare it final

@Override
public void completed(final AsynchronousSocketChannel sockChannel,
final AsynchronousServerSocketChannel serverSock) {
// a connection is accepted, start to accept next
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove final modifier

// a connection is accepted, start to accept next
// connection
serverSock.accept(serverSock, this);
final ByteBuffer buf = ByteBuffer.wrap(PAYLOAD.getBytes());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove final modifier

try {
final int read2 = s.getInputStream().read(b2);
log.info("Got {} bytes from the server: {}", read2, new String(b2, 0, read2));
Assert.assertEquals(PAYLOAD, new String(b1, 0, read1) + new String(b2, 0, read2));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We use direct assertXXX calls (inherited from BaseTestSupport)

@bkuker
Copy link
Contributor Author

bkuker commented Jun 22, 2017

Will do thanks! I had just copied these tests from my internal branch and had not worried about style yet...
Quick question "We no longer use final modifier" Like... Ever? I'll happily remove all instances from my code, I am just so used to modern style guidelines that say the exact opposite!

@lgoldstein
Copy link
Contributor

modern style guidelines that say the exact opposite

Our (current) opinion is that it makes the code cluttered with un-necessary final modifiers all over the place. Java 8 is very good in deciding which variables are effectively final, so no need to state this explicitly. As far as other uses of final that style guidelines refer to - I would be happy to discuss them, but not just right now. Basically, unless some special case (none of which currently comes to mind) I would rather not use final (it has some debugging ramifications as well...)

* specific language governing permissions and limitations
* under the License.
*/
package org.apache.sshd.common.forward.sshd85;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need for the sshd85 package - org.apache.sshd.common.forward is good enough

log.info("SSHD Running on port {}", server.getPort());
}

@AfterClass
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use some reasonable timeout for the await to avoid stuck servers,

@Override
protected int startLocalPF() throws Exception {
final SshdSocketAddress remote = new SshdSocketAddress("localhost", 0);
final SshdSocketAddress local = new SshdSocketAddress("localhost", TEST_SERVER_PORT);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't use localhost - use TEST_LOCALHOST from BaseTestSuppot

public class ApacheServerJSchClient extends AbstractServerCloseTest {
private static final Logger log = LoggerFactory.getLogger(ApacheServerJSchClient.class);

private static final int SSH_SERVER_PORT = 1123;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please don't use static ports - what if this port is used by some service on the user's host

return TEST_SERVER_PORT;
}

}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Superfluous empty line

*/
package org.apache.sshd.common.forward.sshd85;

public class NoServerNoClient extends AbstractServerCloseTest {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing explicit public no-args constructor


@Override
protected int startRemotePF() throws Exception {
port++;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use AtomicInteger.incrementAndGet()

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually I had to change that to use dynamically chosen free ports instead.

@Before
public void createClient() throws Exception {
log.info("Creating SSH Client...");
JSch client = new JSch();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please call JSchLogger.init() in the @BeforeClass method


@AfterClass
public static void stopServer() throws IOException {
server.close(true).await();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add a reasonable await timeout to avoid stuck servers

* @author bkuker
*
*/
public abstract class AbstractServerCloseTest {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

abstract classes from which other tests are derived are called XXXTestSupport

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class ApacheServerApacheClient extends AbstractServerCloseTest {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test classes are called XXXTest and have a @FixMethodOrder(MethodSorters.NAME_ASCENDING annotaton

import com.jcraft.jsch.JSch;
import com.jcraft.jsch.Session;

public class ApacheServerJSchClient extends AbstractServerCloseTest {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See comment regarding test class name and required annotation

*/
package org.apache.sshd.common.forward.sshd85;

public class NoServerNoClient extends AbstractServerCloseTest {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See comment regarding test class name and required annotation

@bkuker
Copy link
Contributor Author

bkuker commented Jun 22, 2017

@lgoldstein Before I kill you with 1000 more style violations, can you direct me to the current version of those guidelines? The web page https://mina.apache.org/sshd-project/sources.html is horribly out of date, and the "contributing" link just asks for money, not code!

@bkuker
Copy link
Contributor Author

bkuker commented Jun 22, 2017

Apologies for so many commits - I am not able to upgrade Maven on my Windows desktop where the bug exists, so I have to run checkstyle on the Linux desktop! I am getting dizzy spinning my chair from one to the other.

@bkuker bkuker changed the title Tests & Candidate fix for SSHD-85. [SSHD-85] Tests & Candidate fix Jun 22, 2017
@bkuker
Copy link
Contributor Author

bkuker commented Jun 22, 2017

Thanks so much for your feedback; I'd been so deep in debugging the past few days I was worried less about the style. Also I did not mean to imply anything negative about the "final" style, I have just been getting used to doing it more often.
Rat and checkstyle now pass, and I think I have addressed all of your comments so far. I have to admit I am still not sure where to find the style guidelines, but I'll be happy to keep working to fix things up.

(BTW at the moment fixing this falls under "my job" so I'll be very quick to fix anything you ask, please don't take that for impatience or demanding responses from you immediately)

@lgoldstein
Copy link
Contributor

Thanks for all your effort, my comments may seem short/terse but they are in no way a sign of impatience - I appreciate your willingness to adhere to the project standards. Many programmers discount style as being petty, but since this is an open-source project, it is helpful to have the source code have a standard look-and-feel so one can find one's way easily around it. I apologize for not having more time to discuss or write a document explaining the style choices and why we chose them - but as you may have surmised, my involvement with this project is purely on a voluntary basis - for which my day job does not leave too much time. All I can recommend at the moment is to look at other existing test classes and adopt the style in them.

@bkuker
Copy link
Contributor Author

bkuker commented Jun 22, 2017

No problem and no worries, and I totally agree (Well I may have a different opinion on tabs and final but it is more important to conform to contribute!)

I'll have another look through later on, I think the tests are a lot better then before your feedback.

I also want to take some time to better understand WHY the one line fix makes a difference, because I fee like the behavior should be the same with or without it.

@bkuker
Copy link
Contributor Author

bkuker commented Jun 23, 2017

Thanks for the updates, every one is an improvement to the tests. They still pass, which is good, and they still fail without the fix, which is great.

I am not sure from your branch if you intended to remove ApacheServerJSchClientTest and NoServerNoClientTest.

IMHO the JSCH one should still be included. I actually had to write it in order to narrow down the problem.

The NoServerNoClientTest... I see it as a test to ensure that the Test Server and Test Client work properly, and confirmation that yes, TCP works that way and you should be able to expect to receive all the data. I did have to run it and see it pass ten thousand times before I was certain enough to re-open the ticket :)

try (OutputStream sockOut = s.getOutputStream()) {
sockOut.write(baos.toByteArray(), 0, payload.length());
sockOut.flush();
sockOut.close();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why the change here ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I probably should not have mixed this in, it is unrelated to SSHD-85, sorry. This test did not pass on the unmodified code base , it seems logically wrong, so I fixed it.

@lgoldstein
Copy link
Contributor

lgoldstein commented Jun 24, 2017

I have added all the tests from this PR to my branch and surprisingly, the previously failing test now succeeds - I am not comfortable with this as it seems that the tests affect each other. I will run some more tests to make sure that is not the case...

@bkuker
Copy link
Contributor Author

bkuker commented Jun 24, 2017

I suspect that with so many minor formatting, renaming, and small cleanups and fixes something may have been broken.. I will go through everything thoroughly and make sure each test still fails as expected without the fix, and passes as expected with the fix.

I will also remove the unrelated fix to PortForwardingLoadTest from this PR. I will look further into what I believe is a bug in that test, make sure it really is a bug, and track it as a different ticket as I should have from the beginning!

@lgoldstein
Copy link
Contributor

I updated the branch - everything passes - I am still running some tests to be doubly sure - including running tests on Linux (Fedora 22 in this case). If they all pass I will merge the branch

@bkuker
Copy link
Contributor Author

bkuker commented Jun 25, 2017

FInal version was completed and merged by @lgoldstein
Thank you so much for your help!

@bkuker bkuker closed this Jun 25, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants