-
Notifications
You must be signed in to change notification settings - Fork 645
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
Add RawSocketBootstrap
#2320
Conversation
There was a problem hiding this 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) |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
0498fed
to
5b2dfd6
Compare
…ctually create a socket and skipping if we fail because of `EPERM`
Tests/NIOPosixTests/IPv4Header.swift
Outdated
let headerChecksum: UInt16 = buffer.readInteger(), | ||
let sourceIpAddress: UInt32 = buffer.readInteger(), | ||
let destinationIpAddress: UInt32 = buffer.readInteger() | ||
else { return nil } |
There was a problem hiding this comment.
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:
- check
PCAP.swift
on this PR: more PCAP swift-nio-extras#158 : https://github.com/apple/swift-nio-extras/pull/158/files#diff-8b0f17fa27d694721788c7adab20151f44ff3672becc16dd728bd530c0de44ba - also here (this code is changed by the above PR): https://github.com/apple/swift-nio-extras/blob/main/Tests/NIOExtrasTests/WritePCAPHandlerTest.swift#L746
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
- 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
- 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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added them in fabaf9b
There was a problem hiding this 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.
|
||
extension ByteBuffer { | ||
@discardableResult | ||
mutating func readIPv4Header() -> IPv4Header? { |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added in 1ac14d2
// like e.g. we do now too. | ||
return totalLength + 20 | ||
#elseif os(Linux) | ||
return totalLength |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This pull broke building the tests for Android. I just confirmed that adding Android here fixes it again, finagolfin/swift-android-sdk#83.
@dnadoba, mind adding these Android checks in a subsequent commit?
There was a problem hiding this comment.
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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done in #2328
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:
RawSocketBoostrap
which configures aDatagramChannel
with a raw socket.Result:
NIO can send and receive IP packets.
Alternatives
RawSocketBootstrap
is inspired by the socket mode name (SOCK_RAW
). Instead we could also call itIPBootstrap
to reflect that the resulting channel will operate on IP packets.