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

Allow for a connection to check if it's sending bytes #324

Open
wants to merge 3 commits into
base: main
Choose a base branch
from

Conversation

PeterZhizhin
Copy link
Contributor

This will be helpful for some strategies:

  • Split: check for bytes to get sent before sending next packet
  • Disorder, Fake: wait for bytes to get sent before changing TTL back

Comment on lines 17 to 21
// 1 == TCP_ESTABLISHED, but for some reason not available in the package
if tcpInfo.State != 1 {
// If the connection is not established, the socket is not sending bytes
return false, nil
}
Copy link

Choose a reason for hiding this comment

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

could you confirm this does what we supposed the bye-dpi code does? I'm starting having some doubts looking at the list of states

Copy link
Contributor Author

Choose a reason for hiding this comment

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

x/sockopt/is_sending_bytes_linux.go Outdated Show resolved Hide resolved
return isConnectionSendingBytesImplemented()
}

func (o *tcpOptions) WaitUntilBytesAreSent() error {
Copy link
Contributor

Choose a reason for hiding this comment

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

This functionality is too high-level to live here.

I'd say we just need the bytes not sent and state options.

Then you can implement the logic to wait completely separate.

@fortuna fortuna self-requested a review November 12, 2024 01:53
}

// 1 == TCP_ESTABLISHED, but for some reason not available in the package
if tcpInfo.State != 1 {
Copy link
Contributor

Choose a reason for hiding this comment

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

Why is this relevant? Won't tcpInfo.Notsent_bytes be zero if the connection is not established?

Also, we won't have the conenction object if it's not established.

Copy link

Choose a reason for hiding this comment

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

Why is this relevant? Won't tcpInfo.Notsent_bytes be zero if the connection is not established?

I don't get this comment. This check is not about connection establishment

Also, we won't have the conenction object if it's not established.

This function should not be called when the connection is not yet established.

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm saying we can remove the State test. It's not doing anything. tcpInfo.Notsent_bytes will be zero if it's not established.

Copy link

Choose a reason for hiding this comment

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

Got it, thanks

Copy link

Choose a reason for hiding this comment

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

Hmm, what about the cases when TCP connection is closing? This way we can abort waiting loop earlier. Maybe that's why it was there in the original code.

@@ -50,15 +60,51 @@ var _ HasHopLimit = (*hopLimitOption)(nil)

// TCPOptions represents options for TCP connections.
type TCPOptions interface {
HasWaitUntilBytesAreSent
Copy link
Contributor

Choose a reason for hiding this comment

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

We can't have such high-level functionality here. As mentioned below, we should perhaps expose lower level functionality instead.

Copy link
Contributor

Choose a reason for hiding this comment

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

I worry about having TCPOptions have options that are not cross-platform. I'm still trying to figure out what a good design for this stuff is, and we'll only know better when we try different things.

In favor of simpler APIs, for now, let's go with a simple standalone function BytesToSend(Rawconn) that lives in the fake code, since that's the only strategy that really needs it. It shouldn't be defined in platforms that don't support it.

Copy link

Choose a reason for hiding this comment

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

that lives in the fake code, since that's the only strategy that really needs it. It shouldn't be defined in platforms that don't support it.

at least on linux, more strategies need it (split, esp. when it performs multiple small splits, disorder)

@PeterZhizhin
Copy link
Contributor Author

PeterZhizhin commented Nov 12, 2024

Fixed some comments. For more fixes, I'd like more input.

Waiting for the socket to send all bytes is useful for pretty much all strategies.

  • split, tlsfrag: makes it more robust to kernel reassembling packets in it's memory buffer. I have seen split:1000*1 will write in chunks of 100-300 bytes instead of always 1 byte. Combining it with a strategy that always waits in between writes will help.
  • oob and disorder: guarantees that the data will be sent out of order. byeDPI currently does it on Linux, but not Windows. It's going to be "best effort" here. The strategies will work on all platforms, but will be more robust on Linux and Darwin.
  • fake: same as oob and disorder, but seems to be more important there, since the kernel will do more stuff (like reading a temporary file)

We'll use waiting when possible. And when it's not available on the platform we either wait for a defined duration, or don't wait at all.

@PeterZhizhin
Copy link
Contributor Author

I've pushed a new strategy into this PR: waitstream.

This new strategy will call Write, and call the new WaitUntilBytesAreSent after it. Skipping the error if it's not implemented.

Baseline:

go run *.go -timeout 1000 -transport "override:host=104.16.208.90|split:10000*1" -method HEAD -v https://meduza.io/

WireShark screenshot:
изображение

Data got packaged together into 5 packets of 1, 2, 118, 110, 35 bytes

Using the strategy:

go run *.go -timeout 1000 -transport "override:host=104.16.208.90|waitstream|split:10000*1" -method HEAD -v https://meduza.io/

WireShark screenshot:
изображение
Wireshark shows that everything gets send 1 byte at a time, as expected

@PeterZhizhin
Copy link
Contributor Author

Did a bit of debugging of waitstream strategy on the command I've shared above:

  • The code sleeps for about 1.1ms in between sends
  • tcpInfo.State seems to be always equal to 1, possibly hinting that our assumption that we wouldn't get the socket if the connection wasn't established.
  • Removing ReadFrom optimization from split does not change the situation: data still gets sent in chunks of up to 100 bytes

Copy link
Contributor

@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 like the waitstream approach! It's a nice way to compose. But let's decouple from the TCPOptions. That's intended for low-level portable options.

x/sockopt/is_sending_bytes_not_implemented.go Outdated Show resolved Hide resolved
}

// 1 == TCP_ESTABLISHED, but for some reason not available in the package
if tcpInfo.State != 1 {
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm saying we can remove the State test. It's not doing anything. tcpInfo.Notsent_bytes will be zero if it's not established.

x/wait_stream/writer.go Outdated Show resolved Hide resolved
written, err = w.conn.Write(data)

// This may not be implemented, so it's best effort really.
waitUntilBytesAreSentErr := w.tcpOptions.WaitUntilBytesAreSent()
Copy link
Contributor

Choose a reason for hiding this comment

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

I think this should be before the Write is done.

Also, this may significantly slow down communication. What's the performance impact? We will need to understand that for practical use.

We may want to restrict the wait for only when it's needed (on splits).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Used a fetch-speed tool:

go run *.go -timeout 100500s -transport "waitstream"  http://speedtest.belwue.net/100M
Downloaded 100.00 MiB in 9.01s
Downloaded Speed: 11.10 MiB/s

Baseline:

go run *.go -timeout 100500s -transport ""  http://speedtest.belwue.net/100M                                                                                          
Downloaded 100.00 MiB in 9.75s
Downloaded Speed: 10.26 MiB/s

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.

3 participants