SMTP Proxy

Problem this snippet solves:

This iRule implements a simple SMTP proxy. This is just to show an idea to relay smtp message and to route mail traffic based on FROM or TO... this rule is tested and work with one default pool and basic smtp client.

Code :

when CLIENT_ACCEPTED {
    set chelo ""
    set cfrom ""
    set crcpt ""    
    TCP::respond "220\r\n"
    log local0. "client accepted"
    TCP::collect
}

when CLIENT_DATA {

    set cdata [TCP::payload]
    if { [ string length $cdata ] <= 0 } {
        return
    }
    if { not ( $cdata contains "\r\n" ) } {
        log local0. "get <$cdata> so far"
        return
    }
    if { $cdata starts_with "HELO" } {
        set chelo [TCP::payload]
        log local0. "get helo <$cdata>"
        TCP::respond "250 OK\r\n"
        TCP::payload replace 0 [string length $chelo] ""
        return
    }
    if { $cdata starts_with "MAIL FROM:" } {
        set cfrom [TCP::payload]
        log local0. "get from <$cfrom>"
        TCP::respond "250 OK\r\n"
        TCP::payload replace 0 [string length $cfrom] ""
        return
    }
    if { $cdata starts_with "RCPT TO:" } {
        set crcpt "$crcpt[TCP::payload]"
        log local0. "get rcpt <$crcpt>"
        TCP::respond "250 OK\r\n"
        TCP::payload replace 0 [string length [TCP::payload]] ""
        return
    }
    if { $cdata starts_with "DATA" } {
        log local0. "get data <$cdata>"
        TCP::payload replace 0 0 $chelo$cfrom$crcpt
    }
    log local0. "payload [TCP::payload]"
    TCP::release
    TCP::collect
}

when SERVER_CONNECTED {
    log "server connected"
    TCP::collect
}

when SERVER_DATA {
    set sdata [TCP::payload]

    if { $sdata starts_with "220" } {
        log local0. "get data <$sdata>"
        TCP::payload replace 0 [string length $sdata] ""
        return
    }
    if { $sdata contains "\r\n354 " } {
        log local0. "get data <$sdata>"
        TCP::payload replace 0 [string length $sdata] "354\r\n"
    }
    if { [ string length $sdata ] <= 0 } {
        return
    }
    log local0. "payload <[TCP::payload]>"
    TCP::release
    TCP::collect
}
when CLIENT_CLOSED {
    log local0. "client closed"
}
Published Mar 18, 2015
Version 1.0

Was this article helpful?

2 Comments

  • Hi,

    We are also constructing iRule which will direct traffic on a specific pool if an email was received from "gmail.com" or "google.com"

    here is our script:

    when CLIENT_ACCEPTED {
      log local0. "Client_ACCEPTED_HIT: IP address:[IP::client_addr]"
      TCP::respond "220\r\n" 
      TCP::collect
    } 
    
    when CLIENT_DATA { 
      log local0. "Client_DATA_HIT: IP address:[IP::client_addr] Data: [TCP::payload]"
      if { [TCP::payload] contains "gmail.com" } {
          pool testtesttest
          log local0. "cdata_FOUND: IP address:[IP::client_addr] Dest IP:[server_addr]:[server_port] Data: [TCP::payload]"
       }
      TCP::release
      TCP::collect
    }
    

    The issue we are having is that, the traffic is not being directed to the specified pool but to the default pool. however, the log "cdata_FOUND:.." can be seen. This mean that the traffic satisfied the "if condition".

    This is where we are lost.

  • HI F5_Jeff -

    So SMTP is a tricky protocol when it comes to intercepting it within the F5 iRules.

     

    In the example `mailx` output below, each exchange is a separate set of rules firing in the LTM irule. So the "connecting..." line will fire your CLIENT_ACCEPTED event. Then each >>> will fire your CLIENT_DATA event, which needs to respond with a response code and status. Basically being a middleman between your client and the smtp server. So if you want to do the load balancing up front, you need to intercept the commands up until the RCPT TO command, make the determination, and THEN route.

     

    There is an example here, https://devcentral.f5.com/s/question/0D51T00006j2svT/irule-based-on-smtp-receiver-domain where its "queuing" or saving up the HELO and FROM commands in variables and responding to the client, and then once a RCPT comes through and the destination domain is known, it "replays" the saved commands to the back end SMTP server (after picking a pool).

     

    I will admit, I have not been able to get it working perfectly (yet) but hopefully this helps explain the way SMTP communications are handled in the F5 LTM iRules, where its a series of events that you need to orchestrate (and its not trivial).

    echo "Testing" | mailx -v -r "testing-sender@example.com" -s "New test email through LTM" -S smtp="smtp-vip.example.com:25" testing-recipient@example.com
     
    Resolving host smtp-vip.example.com . . . done.
    Connecting to 10.1.2.3:25 . . . connected.
    220 EXGHOST03.example.com Microsoft ESMTP MAIL Service ready at Thu, 16 Jan 2020 08:59:52 -0500
    >>> HELO mydesktop.example.com
    250 EXGHOST03.example.com Hello [10.4.5.6]
    >>> MAIL FROM:<testing-sender@example.com>
    250 2.1.0 Sender OK
    >>> RCPT TO:<testing-recipient@example.com>
    250 2.1.5 Recipient OK
    >>> DATA
    354 Start mail input; end with <CRLF>.<CRLF>
    >>> .
    250 2.6.0 <5c216d54./ukd0CI0g7ZM5ydM%testing-sender@example.com> [InternalId=106902174639121, Hostname=EXGHOST03.example.com] Queued mail for delivery
    >>> QUIT
    221 2.0.0 Service closing transmission channel