Forum Discussion
iRule for SMTP: Passing Client IP Addr to backend mail servers
When SNATs are used for a virtual server, the backend SMTP servers cannot get the client IP address. This irule is intended to replace the string after "EHLO" or "HELO" in mail client initiation with the client's real IP address. For us, this could enable us to track down an offending mail originating device.
when CLIENT_ACCEPTED {
set c-addr [IP::client_addr]
log local0. "Client addr: $c-addr"
}
when CLIENT_DATA {
STREAM::expression {@^EHLO.*\r\n@@ @^HELO.*\r\n@@}
STREAM::enable
event STREAM_MATCHED enable
}
when STREAM_MATCHED {
set mstring [STREAM::match]
log local0. "STREAM_MATCHED: string: $mstring"
if {$mstring starts_with "EHLO"} {
set replacment "EHLO $c-addr\r\n"
log local0. "STREAM_MATCHED: replacement string: $replacement"
STREAM::replace "$mstring/$replacment"
}
if {$mstring starts_with "HELO"} {
set replacment "HELO $c-addr\r\n"
log local0. "STREAM_MATCHED: replacement string: $replacement"
STREAM::replace "$mstring/$replacment"
}
event STREAM_MATCHED disable
}
when SERVER_DATA {
STREAM::disable
}
This is just an idea at this moment, and I won't be able to test the code until I find a suitable test environment for it; but for now, any comment is welcome as to if this will work at all and if yes what can be improved. Thanks.
- John_Alam_45640Historic F5 Account
This is pretty nice.
CLIENT_DATA and SERVER_DATA are not going to trigger on their own. You have to initiate TCP::collect in the CLIENT_ACCEPTED or SERVER_CONNECTED. That said, you don't need these events.
Also, "event STREAM_MATCHED enable" is not needed. When there is a match, this event will fire.
Simplify the code under STREAM_MATCHED for easier troubleshooting:
when STREAM_MATCHED { set mstring [STREAM::match] log local0. "STREAM_MATCHED: string: $mstring" set replacment [string range $mstring 0 1] append replacment "LO $c-addr\r\n" log local0. "STREAM_MATCHED: replacement string: $replacement" STREAM::replace "$mstring/$replacment" }
Don't forget the generic (empty) STREAM profile to be added to the virtual. Here is a good link for more info: https://devcentral.f5.com/articles/ltm-stream-profile-multiple-replacements-regular-expressions.U3i7UygfSok
Finally, why not test this using a mocked up SMTP server. Setup up an echo server and use telnet for a client. See if the BigIP does the replacement.
- JGCumulonimbus
Thanks so much, John! Here's a new version of it based on your advice:
when CLIENT_ACCEPTED { set c-addr [IP::client_addr] log local0. "Client addr: $c-addr" STREAM::expression {@^EHLO.*\r\n@@ @^HELO.*\r\n@@} STREAM::enable } when STREAM_MATCHED { set mstring [STREAM::match] log local0. "STREAM_MATCHED: string: $mstring" set replacment [string range $mstring 0 1] append replacment "LO $c-addr\r\n" log local0. "STREAM_MATCHED: replacement string: $replacement" STREAM::replace "$mstring/$replacment" event STREAM_MATCHED disable } when SERVER_CONNECTED { STREAM::disable }
I can't test this (it compiled alright) yet because my devices are all firewall'ed off at the moment.
- John_Alam_45640Historic F5 Account
The SERVER_CONNECTED event is not needed.
One more thing. In the STREAM::replace command, only the replacement is needed.
STREAM::replace $replacment
Now test it by using a linux server behind a BigIP virtual and netcat such as in these examples.
http://stackoverflow.com/questions/8375860/echo-server-with-bash
You should be able to use telnet and type:
HELO 1.2.3.4
or EHLO 1.2.3.4and it echos back "xxLO [your IP address]"
- HamishCirrocumulus
Just a thought... But why don't you add a received: header? That way the sending IP is available for anything you want to put in the sequence (e.g. spamassassin etc).
H
- JGCumulonimbus
Just a short note for now:
I will need to modify this to accommodate the sending of multiple messages in the same TCP connection.
- John_Alam_45640Historic F5 Account
To accommodate multiple messages, check out this SMTP proxy iRule example. https://devcentral.f5.com/wiki/iRules.SMTPProxy.ashx
It uses [TCP::collect] and the CLIENT_DATA event as well as [TCP::payload replace] instead of [STREAM::replace]. It offers more flexibility and allows multiple messages search/replace operations.
- nitassEmployee
i think stream profile may be fine with multiple messages. anyway, i think we are having an issue about newline (\r\n) in stream expression.
- John_Alam_45640Historic F5 Account
Take out the \r\n from the expression. The (.*) should match that as well.
STREAM::expression {@^EHLO.*@@ @^HELO.*@@}
Here is an example:
(System32) 30 % regexp {EHLO.*} "EHLO 1.1.1.1\r\n" var1 1 (System32) 31 % puts $var1 EHLO 1.1.1.1 (System32) 32 %
- JGCumulonimbus
I have finally had a chance to actually test an SMTP irule using the stream profile, and here's my latest version, which seems to work, at least under v10.2.4:
when RULE_INIT { set static::smtp_debug 1 } when CLIENT_ACCEPTED { set caddr [IP::client_addr] if { ${static::smtp_debug} } { log local0. "Client addr: $caddr" } STREAM::expression {@[hH][eE][lL][oO] @@ @[eE][hH][lL][oO] @@ @354 End data with @354 End data with @ @250 2.0.0 Ok: queued as @250 2.0.0 Ok: queued as @} STREAM::enable set end_data_with_seen 0 } when STREAM_MATCHED { set mstring [STREAM::match] if { ${static::smtp_debug} } { log local0. "STREAM_MATCHED: string: \"$mstring\"" } if { $mstring contains "354 End data with " } { if { ${static::smtp_debug} } { log local0. "STREAM_MATCHED: end_data_with_seen incremented." } incr end_data_with_seen return } elseif { $mstring starts_with "250 2.0.0 Ok: queued as " } { if { ${static::smtp_debug} } { log local0. "STREAM_MATCHED: seen queued_as: closing conn." } TCP::close return } else { if { $end_data_with_seen < 1 } { if { ${static::smtp_debug} } { log local0. "STREAM_MATCHED: init string seen: end_data_with_seen is 0." } set replacement [string range $mstring 0 1] append replacement "LO \[$caddr\]" if { ${static::smtp_debug} } { log local0. "STREAM_MATCHED: replacement string: \"$replacement\"" } STREAM::replace $replacement } else { if { ${static::smtp_debug} } { log local0. "STREAM_MATCHED: end_data_with_seen: not 0: stream disabled." } STREAM::disable } } } when SERVER_CONNECTED { STREAM::expression {@[hH][eE][lL][oO] @@ @[eE][hH][lL][oO] @@ @354 End data with @354 End data with @ @250 2.0.0 Ok: queued as @250 2.0.0 Ok: queued as @} STREAM::enable }
Of course it is not perfect, and I don't think it can be perfect, as it seems we have run into the limitations of the BRE library the stream profile uses, or of my understanding of how to use the stream functionality.
As a result, I have to terminate the connection after a mail message is delivered to the backend SMTP server in this irule.
Some of the text strings in the regex expressions are specific to my mail server in order to minimize the chances of unexpected/incorrect/unwanted matching.
- JGCumulonimbus
I have finally decided to change to another approach: to validate client's ID (hostname or IP addr) via DNS lookup and make a deny/allow decision based on the result of the lookup. The irule is here:
Editors note: Updated this link to point to the new Codeshare @JG created.
An irule to validate client ID via DNS lookup using the stream profile. DevCentral (f5.com)
The link for the irule needs fixing as I don't find it even in the search in Devcentral.
- JGCumulonimbus
I am trying to get this back.
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