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

Concatenate header and first upstream payload #73

Merged
merged 3 commits into from
Jul 28, 2020
Merged

Conversation

bemasc
Copy link

@bemasc bemasc commented Jul 21, 2020

When possible, this change concatenates the Shadowsocks header and the
first upstream payload into a single TCP segment.

This change imposes additional memory (extra buffer) and time (lock
acquisition) costs, but only during the first read.

When possible, this change concatenates the Shadowsocks header and the
first upstream payload into a single TCP segment.

This change imposes additional memory (extra buffer) and time (lock
acquisition) costs, but only during the first read.
@bemasc bemasc requested review from fortuna and alalamav July 21, 2020 15:20
// We therefore use a short delay, longer than any reasonable IPC but similar to
// typical network latency. (In an Android emulator, the 90th percentile delay
// was ~1 ms.) If no client payload is received by this time, we connect without it.
const helloWait = 20 * time.Millisecond
Copy link

Choose a reason for hiding this comment

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

How did you decide no 20 ms?
This will only delay connections where the client doesn't immediately send data, but I have no idea what those are, so maybe reduce the wait time?

Copy link
Author

Choose a reason for hiding this comment

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

OK, reduced to 10ms

t.Fatalf("ShadowsocksClient.DialTCP failed: %v", err)
}

// Wait for more than 20 milliseconds to ensure that the target
Copy link

Choose a reason for hiding this comment

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

Should we check that no data was sent in the first 20ms?

Copy link
Author

Choose a reason for hiding this comment

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

OK, I added a test for this.

LazyWrite(p []byte) (int, error)
// Flush sends the pending data, if any. This method is
// thread-safe, but must not be the first method called.
Flush() error
Copy link

Choose a reason for hiding this comment

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

I don't get the "MUST". Why can't it be called first? Wouldn't it just return and do nothing? I don't see what would break.

Copy link
Author

Choose a reason for hiding this comment

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

Flush() relies on init() having run, but it can't run init() because init() isn't thread-safe.

Copy link

Choose a reason for hiding this comment

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

When there was no write, you only depend on needFlush and the mutex, both of which are initialized before init(). A sequence Flush, LazyWrite, Write seems safe.

Copy link
Author

Choose a reason for hiding this comment

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

You're right. Done.

// modified on the flush thread.
// Any size is acceptable here, but this is the largest value
// for which a single call to enqueue() is sufficient.
readBuf := make([]byte, len(payloadBuf)-pending)
Copy link

Choose a reason for hiding this comment

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

You don't need this allocation.
You can use sw.buf[saltSize + 2 + sw.aead,Overhead() + sw.pending + sw.aead.Overhead():]

The enqueue call later will shift things to the right position.

Copy link
Author

Choose a reason for hiding this comment

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

Beautiful. Done.

sw.mu.Lock()

sw.enqueue(readBuf[:plaintextSize])
readBuf = nil // Release memory before blocking I/O.
Copy link

Choose a reason for hiding this comment

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

This can be deleted if you remove the make call

Copy link
Author

Choose a reason for hiding this comment

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

Done

LazyWrite(p []byte) (int, error)
// Flush sends the pending data, if any. This method is
// thread-safe, but must not be the first method called.
Flush() error
Copy link

Choose a reason for hiding this comment

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

When there was no write, you only depend on needFlush and the mutex, both of which are initialized before init(). A sequence Flush, LazyWrite, Write seems safe.

if err != nil {
t.Errorf("Write failed: %v", err)
}
if buf.Len() == len1 {
Copy link

Choose a reason for hiding this comment

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

Also add a check that buf.Len() == 8 to show the first write got flushed

Copy link

Choose a reason for hiding this comment

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

I also suggest you make the second write be 3 or 5 bytes, so it's not confused with the first write.

Copy link
Author

Choose a reason for hiding this comment

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

OK, done

@fortuna
Copy link

fortuna commented Jul 27, 2020

Thanks for this change. I'm hopeful it will help disguise server access. Hopefully we'll see a decrease in probes.

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.

2 participants