Forum Discussion
TCP::collect and large TLS v1.3 client hello packets
- Sep 04, 2024
Here is working cleaned up example code for everyone. TLS parsing itself is based on some old forum code which I cleaned up, all credits there.
when CLIENT_ACCEPTED { set collect_count 0 set tls_servername "" TCP::collect } when CLIENT_DATA { # Collect max 2 extra packets if { $collect_count > 2 } { reject; event disable all; return } # Parse first packet? if { not $collect_count } { if { [binary scan [TCP::payload] cSSc tls_xacttype tls_version tls_recordlen tls_action] != 4 } { reject; event disable all; return } # Only allow TLSv1.2 = 771, TLSv1.3 = 769 if { $tls_xacttype ne "22" or ($tls_version ne "769" and $tls_version ne "771") or $tls_action ne "1" } { reject; event disable all; return } set record_offset 43 if { not [binary scan [TCP::payload] @${record_offset}c tls_sessidlen] } { reject; event disable all; return } incr record_offset [expr {1 + $tls_sessidlen}] if { not [binary scan [TCP::payload] @${record_offset}S tls_ciphlen] } { reject; event disable all; return } incr record_offset [expr {2 + $tls_ciphlen}] if { not [binary scan [TCP::payload] @${record_offset}c tls_complen] } { reject; event disable all; return } incr record_offset [expr {1 + $tls_complen}] if { not [binary scan [TCP::payload] @${record_offset}S tls_extenlen] } { reject; event disable all; return } incr record_offset 2 } # Collect more if TLS extensions data not fully received if { $tls_extenlen > [TCP::payload length] - $record_offset } { incr collect_count return } if { not [binary scan [TCP::payload] @${record_offset}a* tls_extensions] } { reject; event disable all; return } for { set x 0 } { $x < $tls_extenlen } { incr x 4 } { if { [binary scan $tls_extensions @${x}SS etype elen] != 2 } { reject; event disable all; return } if { $etype == "00" } { if { [binary scan $tls_extensions @[expr {$x + 9}]A[expr {$elen - 5}] tls_servername] } { break } } incr x $elen } # Choose pool, unknown will be rejected switch -- [string tolower $tls_servername] { "host1.example.com" { pool host1.example.com } "host2.example.com" { pool host2.example.com } default { reject; event disable all; return } } TCP::release }
It sounds like here that you're trying to read the whole CLIENT_HELLO message (at L4), but sometimes it's bigger than a single (L2) Ethernet packet. The example you mention will only ever grab the very first (L2) packet, so it won't work for this kind of dynamic situation. You'll have to switch to some kind of implementation that runs TCP::collect inside of CLIENT_DATA until you've captured the SNI data you need, then run TCP::release to let the connection proceed. This is a fairly common pattern while dealing with streaming data of unknown length.
Can you share the logic you're using to grab the SNI? We could take a shot at adjusting the rule.
- henrik_kSep 04, 2024Altostratus
Hi, I've already tried to call TCP::collect second time inside CLIENT_DATA with or without length argument, nothing ever happens. CLIENT_DATA is never called again and TCP::payload never changes. Can you provide simple protocode which you think should achieve reading both L2 packets? I believe I've already tried all variations I can think of.
- henrik_kSep 04, 2024Altostratus
Oh dear this was an user error. I didn't see I had some leftover other priority CLIENT_DATA code that died, this is why it never got called again...
I now managed to get it to collect more data when needed, I'll post a cleaned up code in a moment for others if interested.
Recent Discussions
Related Content
* Getting Started on DevCentral
* Community Guidelines
* Community Terms of Use / EULA
* Community Ranking Explained
* Community Resources
* Contact the DevCentral Team
* Update MFA on account.f5.com