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

refactor: share Shadowsocks key search between TCP and UDP #189

Draft
wants to merge 4 commits into
base: sbruens/modularize-udp
Choose a base branch
from

Conversation

sbruens
Copy link

@sbruens sbruens commented Jun 26, 2024

No description provided.

@sbruens sbruens force-pushed the sbruens/modularize-udp branch 2 times, most recently from d74d1fd to bcf7e53 Compare June 26, 2024 22:06
@sbruens sbruens marked this pull request as ready for review June 27, 2024 14:58
@sbruens sbruens requested a review from a team as a code owner June 27, 2024 14:58
@sbruens sbruens requested review from fortuna and jyyi1 and removed request for a team, fortuna and jyyi1 June 27, 2024 14:59
@sbruens sbruens marked this pull request as draft June 28, 2024 00:13
@sbruens sbruens changed the title refactor: share access key search between TCP and UDP refactor: share Shadowsocks key search between TCP and UDP Jun 28, 2024
@sbruens sbruens marked this pull request as ready for review June 28, 2024 20:00
@sbruens sbruens requested review from fortuna, jyyi1 and a team June 28, 2024 20:00
Copy link

@fortuna fortuna left a comment

Choose a reason for hiding this comment

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

I'm not convinced of the value and correctness of this change. The API is more complex, and I don't think we can merge the logic. The formats are different. For UDP we need to unpack the whole package, while for TCP we need to unpack based on the cipher tag and IV size.

metrics.AddTCPCipherSearch(false, 0)
return "", clientConn, onet.NewConnectionError("ERR_CIPHER", fmt.Sprintf("Reading header failed after %d bytes", n), err)
}

// Find the cipher and acess key id.
Copy link

Choose a reason for hiding this comment

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

Suggested change
// Find the cipher and acess key id.
// Find the cipher and access key id.


unpackStart := time.Now()
// To hold the decrypted chunk length.
chunkLenBuf := make([]byte, bufferSize)
Copy link

Choose a reason for hiding this comment

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

This is a very critical piece of code. It had to be heavily optimized to work well in production.

Could you please run the benchmarks to compare the performance and allocations?

@@ -134,7 +112,6 @@ func (h *packetHandler) Handle(clientConn net.PacketConn) {

func (h *packetHandler) authenticate(clientConn net.PacketConn) (net.Addr, *CipherEntry, []byte, int, *onet.ConnectionError) {
cipherBuf := make([]byte, serverUDPBufferSize)
textBuf := make([]byte, serverUDPBufferSize)
Copy link

Choose a reason for hiding this comment

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

I believe there's an optimization we can do here. Instead of serverUDPBufferSize, I believe we can use clientProxyBytes, since the decrypted packet is always clientProxyBytes - key tag size (but we don't know the key yet).


unpackStart := time.Now()
// To hold the decrypted chunk length.
chunkLenBuf := make([]byte, bufferSize)
Copy link

Choose a reason for hiding this comment

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

Let's pass the buffer instead of the size, so the caller can own the allocation.

// NewShadowsocksStreamAuthenticator creates a stream authenticator that uses Shadowsocks.
// TODO(fortuna): Offer alternative transports.
func NewShadowsocksStreamAuthenticator(ciphers CipherList, replayCache *ReplayCache, metrics ShadowsocksTCPMetrics) StreamAuthenticateFunc {
return func(clientConn transport.StreamConn) (string, transport.StreamConn, *onet.ConnectionError) {
firstBytes := make([]byte, bytesForKeyFinding)
if n, err := io.ReadFull(clientConn, firstBytes); err != nil {
metrics.AddTCPCipherSearch(false, 0)
Copy link

Choose a reason for hiding this comment

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

This is a behavior change and will bias the metric. The point is to understand how slow the search is, there's no pointing in including the failures when read the data.

chunkLenBuf := make([]byte, bufferSize)
for ci, elt := range ciphers {
entry := elt.Value.(*CipherEntry)
buf, err := shadowsocks.Unpack(chunkLenBuf, src, entry.CryptoKey)
Copy link

Choose a reason for hiding this comment

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

I believe this is broken. src should have the exact size firstBytes[:cryptoKey.SaltSize()+2+cryptoKey.TagSize() for TCP and be the full packet for UDP.

We should have a test to catch that. Try different ciphers.

I remember trying to unify TCP and UDP, but they are different. I believe we are better off keeping the logic separate. It makes the signature of each method simpler, rather than a more complex API you have now. It's easier to use it wrong, vs the current code.

@sbruens sbruens marked this pull request as draft August 27, 2024 20:56
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