Skip to content

Allow intercepting and injecting DTLS packets#766

Open
fippo wants to merge 11 commits intopion:mainfrom
fippo:dtls-intercept
Open

Allow intercepting and injecting DTLS packets#766
fippo wants to merge 11 commits intopion:mainfrom
fippo:dtls-intercept

Conversation

@fippo
Copy link
Copy Markdown

@fippo fippo commented Dec 29, 2025

which allows embedding them into STUN.

@fippo fippo force-pushed the dtls-intercept branch 2 times, most recently from 16f2df2 to 787aa43 Compare December 31, 2025 09:58
@codecov
Copy link
Copy Markdown

codecov bot commented Jan 7, 2026

Codecov Report

❌ Patch coverage is 93.26923% with 7 lines in your changes missing coverage. Please review.
✅ Project coverage is 82.62%. Comparing base (d0e736c) to head (d823e41).

Files with missing lines Patch % Lines
options.go 90.69% 4 Missing ⚠️
conn.go 95.00% 2 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master     #766      +/-   ##
==========================================
- Coverage   82.66%   82.62%   -0.05%     
==========================================
  Files         121      121              
  Lines        6853     6917      +64     
==========================================
+ Hits         5665     5715      +50     
- Misses        777      788      +11     
- Partials      411      414       +3     
Flag Coverage Δ
go 82.62% <93.26%> (-0.05%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@fippo fippo force-pushed the dtls-intercept branch 2 times, most recently from ad5d88c to 9a2f30a Compare January 7, 2026 13:27
@fippo fippo marked this pull request as ready for review January 7, 2026 13:45
if hasHandshake {
if c.inboundHandshakePacketNotifier != nil {
// Would it be useful to know this was injected?
// Should this work on a copy?
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

flagging

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I would perfer that we do not implement such logic here, thus the users of this functionality should handle it themselves. I think it's reasonable to assume that it's the same library that would both use inject and the notifier.

@theodorsm theodorsm self-requested a review January 7, 2026 16:55
@Sean-Der
Copy link
Copy Markdown
Member

Sean-Der commented Jan 7, 2026

Sorry if I am being dense @fippo, but how is the experience different then a user getting DTLS traffic via existing APIs?

Copy link
Copy Markdown
Member

@theodorsm theodorsm left a comment

Choose a reason for hiding this comment

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

Thanks for working on this @fippo! I think it's useful functionallity to have for multiple usecases and I think the re-factor makes sense. I have made some comments to your PR.

}
rawPackets = append(rawPackets, rawHandshakePackets...)
} else {
rawPacket, err := c.processPacket(pkt)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Non-handshake packets are still processed in the writeHandshakePackets. I assume it is only supposed to be done in the writePackets function after this refactor.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

if you mean change cipher spec which doesn't have a handshake content type then yes, it is handled here since they need to go into the same network packet as part of a handshake flight (terminology... this one isn't great)

config.go Outdated
// OutboundHandshakePacketInterceptor is an optional callback that can be set to
// intercept outgoing raw handshake packets. It is called with the raw packet bytes.
// The interceptor can decide to drop the packet by returning true.
OutboundHandshakePacketInterceptor func(packet []byte) bool
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I suggest we could make the interceptors more general and allow for modification of handshake messages, similar to the message hooks above (I already know about some usecases, for example anti-fingerprinting).

OutboundHandshakePacketInterceptor func(packet []byte) []byte and InboundHandshakePacketInterceptor func(packet []byte) []byte. Rather than a bool deciding if we drop a packet, we can drop a packet if the returned byte buffer is empty.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

wouldn't such a use-case implement its own flight generator which allows way more flexibility?

conn_test.go Outdated
server, err := Server(dtlsnet.PacketConnFromConn(cb), cb.RemoteAddr(), &Config{
Certificates: []tls.Certificate{serverCert},
OutboundHandshakePacketInterceptor: func(packet []byte) bool {
client.InjectPacket(packet, ca.RemoteAddr())
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Could you make a seprate test for injected packets? Now the interceptor and the injection functionallity are tested closely together, I would suggest seperating them. Would there be any way of testing the interceptors without using the injection functionallity?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Testing them in complete isolation is only going to result in weaker assertions like "number of handshake packets seen" whereas TestInboundNotifier asserts that what one connection is intercepting (or not) as part of the handshake is seen by the other connection.

@theodorsm
Copy link
Copy Markdown
Member

theodorsm commented Jan 7, 2026

Sorry if I am being dense @fippo, but how is the experience different then a user getting DTLS traffic via existing APIs

@Sean-Der the exisiting API only allows reading and writing application data, not handshake data like what is needed here. The message hooks offers some of the functionality (intercepting ClientHello, ServerHello and CertificateRequest messsages), but not all handshake messages. Correct me if I'm wrong @fippo. Unless there is some other way of getting traffic than the Conn API.

@Sean-Der
Copy link
Copy Markdown
Member

Sean-Der commented Jan 7, 2026

@theodorsm aye, thanks for explanation :)

You should have full access to the traffic if you pass your own net.PacketConn like Client/Server

Then if you care about specific messages you can parse things via pkg/protocol

I worry about offering APIs that don't match crypto/tls because honestly they are just way better designers than anything I have done :)

@theodorsm
Copy link
Copy Markdown
Member

@Sean-Der ahhh I overlooked this API, thanks, that's much cleaner! Does this solve your usecase @fippo (if so, we should close this PR)? You can take inspiration from pkg/net/net.go.

@fippo
Copy link
Copy Markdown
Author

fippo commented Jan 7, 2026

You should have full access to the traffic if you pass your own net.PacketConn like [Client/Server]

I was aware of that but that API operates on []byte which means parsing again and since it does not know the handshake is done (easily), will have to do so for every packet.

@theodorsm
Copy link
Copy Markdown
Member

theodorsm commented Jan 7, 2026

@fippo, with the approach in this PR we are still working with []byte beyond knowing it's a handshake message, further parsing must happen anyways. To filter out the handshake messages from the raw bytes from net.PacketConn you can use RecordLayer.Unmarshal(data []byte) error and check if RecordLayer.Header.ContentType == 22. The content is also parsed so you can check the header type and keep track of the handshake messages, if you see TypeFinished you know the handshake has successfully completed.

@fippo fippo force-pushed the dtls-intercept branch 2 times, most recently from 2c5c975 to da78a87 Compare January 8, 2026 09:55
@theodorsm
Copy link
Copy Markdown
Member

@fippo I agree with Sean about not wanting to offer the InjectPacket API on Conn. However, I think imlementing a custom net.PacketConn that exposes a InjectHandshakePacket would be cleaner, we could offer this in our net package. This custom PacketConn could also support interceptors and notifications. I see crypto/tls has a API for exposing the underlying net.Conn , we could also offer this that would allow for the flexibility needed.

@Sean-Der
Copy link
Copy Markdown
Member

Sean-Der commented Jan 9, 2026

@fippo another sticking point is that Justin is planning on using boringssl. So the more logic you can put into pion/webrtc instead of here will make things easier.

Sorry to burn your time :( I just think in the long run it will work better for everyone. I am hesitant to make this library complicated because non-WebRTC users and it gets so much scrutiny.

Doing funky stuff in pion/webrtc ends up being easier. Tell me if I am completely off base though! I purposefully exported all the Handshake parsing code to make stuff easy.

@fippo
Copy link
Copy Markdown
Author

fippo commented Jan 9, 2026

BoringSSL needs the same interface changes - a way to get notified about incoming packets and a way to inject a packet (which is a lot simpler there) but usually wouldn't concern itself with parsing of the bytes.

@fippo
Copy link
Copy Markdown
Author

fippo commented Jan 20, 2026

new requirement which makes it even less of a fit for net.Conn: the "subscriber" needs to know when a flight is starting or complete and needs to be "flushed" (which is required to invalidate a flight in case of timeout + resend) - OpenSSL does flush but I slightly prefer a signal for start-of-flight but for compat reasons ...

@fippo fippo force-pushed the dtls-intercept branch 3 times, most recently from b2eb45d to b50dd93 Compare January 21, 2026 08:08
@fippo
Copy link
Copy Markdown
Author

fippo commented Mar 26, 2026

(updated for options which wasn't too bad)

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