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

Add RawSocketBootstrap #2320

Merged
merged 13 commits into from
Dec 1, 2022
Merged

Add RawSocketBootstrap #2320

merged 13 commits into from
Dec 1, 2022

Conversation

dnadoba
Copy link
Member

@dnadoba dnadoba commented Nov 22, 2022

Motivation:

We want to support custom IP protocols through POSIX Raw Socket API. To allow this, users will need to bootstrap a DatagramChannel with a socket in raw mode.

Modifications:

  • add RawSocketBoostrap which configures a DatagramChannel with a raw socket.
  • add end-to-end test

Result:

NIO can send and receive IP packets.

Alternatives

RawSocketBootstrap is inspired by the socket mode name (SOCK_RAW). Instead we could also call it IPBootstrap to reflect that the resulting channel will operate on IP packets.

@dnadoba dnadoba added the semver/minor Adds new public API. label Nov 22, 2022
@dnadoba dnadoba requested a review from Lukasa November 22, 2022 14:21
Copy link
Contributor

@Lukasa Lukasa left a comment

Choose a reason for hiding this comment

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

Thanks, this looks great! I think we need a few extra tests though, as we're currently not coming close to testing all the new code that was added here.


let elg = MultiThreadedEventLoopGroup(numberOfThreads: 1)
defer { XCTAssertNoThrow(try elg.syncShutdownGracefully()) }
let channel = try RawSocketBootstrap(group: elg)
Copy link
Contributor

Choose a reason for hiding this comment

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

We probably need a test of the ip_hdrincl path.

Copy link
Member Author

Choose a reason for hiding this comment

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

Tested now in RawSocketBootstrapTests.testIpHdrincl


let receivedMessages = Set(try channel.waitForDatagrams(count: 10).map(\.data).map { buffer in
String(
decoding: buffer.readableBytesView.dropFirst(4 * 5), // skip the IPv4 header
Copy link
Contributor

Choose a reason for hiding this comment

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

Ideally we'd actually validate that this is an IPv4 header.

Copy link
Member Author

Choose a reason for hiding this comment

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

Tested in now in all tests of RawSocketBootstrapTests

@dnadoba dnadoba requested a review from Lukasa November 24, 2022 10:32
let headerChecksum: UInt16 = buffer.readInteger(),
let sourceIpAddress: UInt32 = buffer.readInteger(),
let destinationIpAddress: UInt32 = buffer.readInteger()
else { return nil }
Copy link
Member

Choose a reason for hiding this comment

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

this is incorrect because it forgets to reset the buffer if you half-parsed something.

Also this doesn't follow the usual pattern of read*/write* methods. And lastly, there's an implementation of this already:

Copy link
Member Author

Choose a reason for hiding this comment

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

Done in 20ed380

// like we do.
XCTAssertEqual(Int(header.totalLength), data.readableBytes)
// On BSD the checksum is always zero
XCTAssertEqual(header.headerChecksum, 0)
Copy link
Member

Choose a reason for hiding this comment

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

I don't think we should make this platform specific. I think we should have a bsdHeaderChecksum and bsdTotalLength etc properties. And maybe maybe a headerChecksumForPlatformRawSockets or so properties that then (platform specifically) return the correct one.

Copy link
Member Author

Choose a reason for hiding this comment

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

I thought about this too but couldn't come up with good names. These properties should return the correct value (as specified in RFC-791). The problem though is not only naming but also that the workaround (i.e. adding 20 to totalLength) requires knowing if we have just parsed the IPv4 packet from a ByteBuffer we got from a raw socket or not. If we create a IPv4Header ourselves e.g. for sending through the Raw Socket API, the totalLength will be correct and adding 20 will result in a wrong value.

The options I see are:

  • store this information in the type and dynamically return the correct value
    • This works awful if we reuse the same value to e.g. echo a packet
  • use different types for receiving and sending.
    • stores this information statically and we will need to explicitly convert the type when we want to echo a packet where we can get the totalLength fixed up
  • use some rather long name which conveys that this is only for the receiving side e.g. platformIndependetTotalLengthForRecivedPacket
  • add some documentation and leave it to the call site to handle it

I kind of want to use the "rather long name" variant but don't yet have name I like much.

Copy link
Contributor

Choose a reason for hiding this comment

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

I also don't think we need to worry too much about this, as this is testing code. It's ok if the parsing is a bit janky.

Copy link
Member Author

Choose a reason for hiding this comment

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

I want to eventually make IPv4Header public API so we can still discuss this here. However, I will make this in a separate PR so it is not blocking RawSocketBootstrap to land.

Copy link
Member

Choose a reason for hiding this comment

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

I'd go for some long names to explain this. And +1 for the public API, this should be reconciled with the already-existing IPv4/6 parsing code in swift-nio-extras.

Copy link
Member Author

Choose a reason for hiding this comment

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

Added them in fabaf9b

Copy link
Contributor

@Lukasa Lukasa 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 happy enough with this change, but it'd be good to confirm that @weissi is happy too.

@dnadoba dnadoba requested a review from weissi November 29, 2022 07:48

extension ByteBuffer {
@discardableResult
mutating func readIPv4Header() -> IPv4Header? {
Copy link
Member

Choose a reason for hiding this comment

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

As discussed offline this should IMHO really not be platform specific.
I think we should have readIPv4Header which reads it correctly. And then we can have another one which is readIPv4HeaderWithDarwinRawSocketQuirks() or something which does the weird thing.
Finally we can then have a readIPv4HeaderFromOSRawSocket() or something which chooses (in a platform specific way) the right one for the platform

But any readFoo that can't read something written by a writeFoo even if on a different platform is a major red flag

Copy link
Member Author

@dnadoba dnadoba Nov 30, 2022

Choose a reason for hiding this comment

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

Is there a reason someone would want readIPv4HeaderWithDarwinRawSocketQuirks on non Darwin/BSD platforms?

Copy link
Member

Choose a reason for hiding this comment

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

Yes, unless impossible we should offer everything on every platform. There's no reason not to have it.

Copy link
Member

Choose a reason for hiding this comment

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

There's no reason we shouldn't unit test the darwin quirks on Linux too.

Copy link
Member Author

Choose a reason for hiding this comment

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

Added in 1ac14d2

@dnadoba dnadoba merged commit 810544e into apple:main Dec 1, 2022
@dnadoba dnadoba deleted the dn-raw-socket branch December 1, 2022 14:35
@dnadoba dnadoba mentioned this pull request Dec 1, 2022
// like e.g. we do now too.
return totalLength + 20
#elseif os(Linux)
return totalLength
Copy link
Contributor

Choose a reason for hiding this comment

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

Copy link
Member Author

Choose a reason for hiding this comment

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

Oh yeah sure, sorry about that. I think we can also just remove os(Linux) and just use an #else

Copy link
Member Author

Choose a reason for hiding this comment

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

Done in #2328

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
semver/minor Adds new public API.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants