TCP::collect and large TLS v1.3 client hello packets
Is anyone using iRules successfully to parse SNI names from the new TLS 1.3 hybridized Kyber client hello packets? The problem is the these packets are larger than MTU(?) size, around ~1800 bytes. Normal hello packets are ~500 bytes.
I'm using TCP profile for which iRule parses SNI name to pass the connection as is to correct destination pool.
How to reproduce:
when CLIENT_ACCEPTED {
TCP::collect
}
when CLIENT_DATA {
# [TCP::payload length] shows only 1352, rest of packet missing, CLIENT_DATA is never called again
}
It only ever gets the first ~1352 bytes from packet, CLIENT_DATA is only called once, seems there is no way to get rest of the packet.
If I add argument to collect length >1500 with "TCP::collect 1600", then it will read the whole ~1800 byte packet in first CLIENT_DATA. But this will break all connections that send normal small ~500 byte hello packets, as it just keeps on waiting for data indefinitely. Is there any workaround? BIG-IP version 16.1.4.1.
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 }