Postfix Randomizing Outgoing IP Using TCP_TABLE And Perl

This time i’ll show you how to randomize your smtp outbound’s IP addresses. This can be done via transport map. But, since ordinary Postfix lookup tables store information as (key, value) pairs. it will provide static value only. we need someting that can manipulate the value (right hand side) of a lookup table. In order to answer random transport value.

first come to mind was tcp_tables, tcp_tables lookup table gives some flexibility for us to execute our tiny perl script that will randomizing transport. that’s the basic idea.

Ok, here’s the first part, create perl script call random.pl, anyway this script only provide answer in “catch-all” manner. so it will randomized, all outgoing mail.

# cd /etc/postfix
# vi random.pl
#!/usr/bin/perl -w
# author: Hari Hendaryanto <hari.h -at- csmcom.com>

use strict;
use warnings;
use Sys::Syslog qw(:DEFAULT setlogsock);

#
# our transports array, we will define this in master.cf as transport services
#

our @array = (
'rotate1:',
'rotate2:',
'rotate3:',
'rotate4:',
'rotate5:'
);

#
# Initalize and open syslog.
#
openlog('postfix/randomizer','pid','mail');

#
# Autoflush standard output.
#
select STDOUT; $|++;

while (<>) {
        chomp;
        # randomizing transports array
        my $random_smtp = int(rand(scalar(@array)));
        if (/^get\s(.+)$/i) {
                print "200 $array[$random_smtp]\n";
                syslog("info","Using: %s Transport Service", $random_smtp);
                next;
        }

	print "200 smtp:";
}

Make it executable

# chmod 755 random.pl

master.cf parts

Run the scripts via postfix spawn daemon service.

127.0.0.1:2527 inet  n       n       n       -       0      spawn
          user=nobody argv=/etc/postfix/random.pl

add 5 smtp client services called rotate1, rotate2, rotate3, rotate4, rotate5, that bind to its own ip
address and has uniq syslog/helo name.

# random smtp
rotate1  unix -       -       n       -       -       smtp
          -o syslog_name=postfix-rotate1
          -o smtp_helo_name=smtp1.example.com
          -o smtp_bind_address=1.2.3.1

rotate2  unix -       -       n       -       -       smtp
          -o syslog_name=postfix-rotate2
          -o smtp_helo_name=smtp2.example.com
          -o smtp_bind_address=1.2.3.2

rotate3  unix -       -       n       -       -       smtp
          -o syslog_name=postfix-rotate3
          -o smtp_helo_name=smtp3.example.com
          -o smtp_bind_address=1.2.3.3

rotate4  unix -       -       n       -       -       smtp
          -o syslog_name=postfix-rotate4
          -o smtp_helo_name=smtp4.example.com
          -o smtp_bind_address=1.2.3.4

rotate5  unix -       -       n       -       -       smtp
          -o syslog_name=postfix-rotate5
          -o smtp_helo_name=smtp5.example.com
          -o smtp_bind_address=1.2.3.5

Before we actually implement our randomize transport, let’s make sure that the setting actually work.

Reload postfix

# postfix reload

Run this query fiew times, and you’ll see the perl script will return “random answer” transport

# postmap -q "whatever" tcp:127.0.0.1:2527
rotate1:
# postmap -q "whatever" tcp:127.0.0.1:2527
rotate5:

And so on..

Note on “whatever”, since the script acted in “catch-all” mode as i’ve mentioned earlier, what ever postfix transport_maps client asked. it will be answered with random values such as rotate1, rotate2, rotate3, rotate4, rotate5 in randomized fashion.

main.cf parts

Add these lines

transport_maps = tcp:[127.0.0.1]:2527
127.0.0.1:2527_time_limit = 3600s

Reload postfix
that’s it. example log would be like these and that’s indicate that randomizer is working.

Month date 12:26:53 host postfix-rotate1/smtp[4252]: A1CA68480A4: to=<xxx@example.com>, relay=mx.example.com.com[xx.xx.xxx.xx]:25], delay=3.6, delays=0.69/0.01/0.81/2, dsn=2.0.0, status=sent (250 ok dirdel)
--snip--
Month date 12:27:06 host postfix-rotate5/smtp[4253]: 41C2E8480A4: to=<xxx@example.net>, relay=mx.example.net[xx.xxx.xxx.xxx]:25], delay=6, delays=0.14/0.01/0.85/5, dsn=2.0.0, status=sent (250 ok dirdel)
--snip--
Month date 12:27:22 host postfix-rotate3/smtp[4277]: 4BA9F8480A4: to=<xxx@example.org>, relay=mx.example.org[xx.xxx.xx.xxx]:25], delay=7.9, delays=0.85/0.02/0.61/6.4, dsn=2.0.0, status=sent (250 ok dirdel)

disclaimer:
I’m not taking any responsible if the reader “misuse” this tutorial.the tutorial is provide as-is for experimental purposes.

use Sys::Syslog qw(:DEFAULT setlogsock);

96 Comments

  1. Peter

    I like your new tut, would probably perform better with more IPs than your IPtables tut… What do you think would be the max of IPs a setup like this could handle to rotate?

    • it depend on hardware resource, i think. as usual, this tutorial is just a prototype, but it work. 🙂

      • Peter

        I will try it out at the beginning of the year and let you know how it performs 😉

  2. Amaury

    Thank you, this is very neat.

    Do not forget to add best_mx_transport = local of you may face the dreaded “mail loops back to myself”.

    Also, I modified your script to add “weight” to the SMTP clients, so that randomness is not truly random. This is useful if you add a new IP address to the pool, so it does not get blacklisted as soon as it starts sending emails.
    my %pool = (
    “smtp1” => 20,
    “smtp2” => 20,
    “smtp3” => 20,
    “smtp4” => 40
    );

    Last but not least, how do you maintain specific transport for specific domains? Eg some SMTP servers require a “slow transport” such as what is described in http://www.pubbs.net/postfix/200909/98526/
    You would need to process the request in order to check the target domain, to see if you return a random SMTP or a specific transport.

    Regards,
    Amaury

    • that’s cool with weighted feature you’ve added. fyi, the reason i’ve wrote this script was not for doing spammy things. this just for fun in my spare time 🙂 i work in legal ISP
      if you want to filter specific domain/destination to certain transport. you have just to “hardcoded” in the script.

      create conditional filter before default action/randomize. ie

              if (/^get\s+(yahoo\.com) {
                      print "200 to_slow_yahoo:\n";
                      next;
              fi
              ....
              ....
              if (/^get\s+(.+)$/i) {
                      my $rcpt_domain = $1;
                      print "200 $random_smtp\n";
                      syslog("info","using transport: <%s> service for: <%s>", $random_smtp, $rcpt_domain);
                      next;
              }
      

      to_slow_yahoo: can be randomize too with slow transport specific settings and specific ip pools
      says, …to_slow_yahoo1: to_slow_yahoo2: to_slow_yahoo3: etc…

      this untested yet

      • Amaury

        Regarding specific transports I came up with a similar solution, although it is used the other way around:
        – normal mail go through a specific transport name “standard”
        – mail sent to mailservers using conservative SMTP rates (such as RIM SMTP relays or Orange MXs) go through the randomizer. This way only the emails which need to be randomized are randomized, and they can be sent without much delay to their recipient.
        This is not very efficient as you lose the “db” format which is favored by Postfix for this matter.

        Last but not least you should remember that this technique (known as “snowshoeing” in the email deliverability community) is frowned upon and that it should not be abused, even to perform legit operations…

        • about postfix “db”, it’s not quite true that you’re loosing them. you can still access them through perl
          little example, for “hash” lookup table

          #!/usr/bin/perl
          use Fcntl;
          use DB_File;
          
          my %tab;
          my $null=chr(0);
          
          tie %tab,'DB_File','testmap.db',O_RDONLY,0400,$DB_HASH;
          
          # Sample query
          my $key='foo';
          
          my $value=$tab{$key.$null};
          chop $value;  # chop null byte
          
          print $key." = ".$value."\n";
          

          hash file

          foo             bar:
          

          i know this technique similar to snowshoeing :). that’s why i put disclaimer at the bottom of my posting.
          however, you can tune your outgoing not too greedy with concurency recipients, messages, or connection, set your dns reverse.
          if your traffic are legit, it’s like you own 100 machine in single box if you have 100 ip 🙂

    • Lukas

      Hi,
      could you please post your modificated version with “weight” implemented? I try to implement it, but it is not working… I am not a very good programmer 🙁

      Thank you
      Lukas

  3. Cidou

    “Do not forget to add best_mx_transport = local of you may face the dreaded “mail loops back to myself””

    If you use postfix with virtuals users :
    best_mx_transport = virtual

    Regards,
    Cidou

    • since i used it just for smtp relay, in experiment, no local/virtual user, so it doesn’t really matter.
      but thanks for noticing anyway

  4. Peter

    I’m finally implementing this for some testing, but I have stumbled across something.

    I already have a transport_maps entry pointing:

    transport_maps = hash:/etc/postfix/transport

    In that file I have entries for specific domains that will use another relay server, e.g.

    ch***.com smtp:***.***.211.74

    Any ideas on how I can combine my existing transport_maps with yours?

    • try this

      transport_maps = hash:/etc/postfix/transport, tcp:[127.0.0.1]:port
      
      • Peter

        Works great 😉

        Do you have an idea on how to implement this based on sending domain?

        E.g. mails from domain1.com get a random in the perl from rotate1 – rotate3, domain2.com get random in the perl from rotate4-rotate9, domain3.net get random in the perol from rotate10-rotate12 – and the master contains the IP, helo_name etc

        Basically what I am looking for, is a way to tell the perl from which array it should take a random based on the senderdomain.

        • that cannot be done in transport_maps, maybe you can use sender_dependent_relayhost_maps. i’ve never try it, though.

  5. Juan

    Hello,

    Thanks for your great tutorials.

    With reference to:

    Amaury says: january 22, 2011 at 2:14 am

    my %pool = (
    “smtp1″ => 20,
    “smtp2″ => 20,
    “smtp3″ => 20,
    “smtp4″ => 40
    );

    I am a newbie and I would like to add that feature.

    Could you please add this feature to your tutorial?

    I do not know how or where.

    • i think it’s not as simple by adding %pool hash in original code. that’s only variable. but basically that refer to “weight value” you can access with:

      $pool{smtp1}
      $pool{smtp2}
      $pool{smtp3}
      $pool{smtp4}
      

      you should add “round robin weighted” algorithm rather than randomness to the script.
      http://kb.linuxvirtualserver.org/wiki/Weighted_Round-Robin_Scheduling.
      this example might not what you want, but it’s simple, you can play with this code.
      require List::Util::WeightedRoundRobin module

      #!/usr/bin/perl
      use strict;
      use warnings;
      use List::Util::WeightedRoundRobin;
      my $list = [
          {
              name    => 'smtp1:',
              weight  => 6,
          },
          {
              name    => 'smtp2:',
              weight  => 2,
          },
          {
              name    => 'smtp3:',
              weight  => 2,
          },
          {
              name    => 'smtp4:',
              weight  => 3,
          },
        ];
      my $WeightedList = List::Util::WeightedRoundRobin->new();
      my $weighted_list = $WeightedList->create_weighted_list( $list );
      my $random_index = rand @{$weighted_list};
      print "${$weighted_list}[$random_index]\n";
      

      This code still randomize but not as random as original script.

  6. Philipp

    hello,

    thx for the script.

    i have a small problem.

    i send an email and look to the header:
    Received: from smtp.example.1 (EHLO smtp.example.1) [xx.xx.xx.xx]

    Received: by MYHOSTNAME.COM (Postfix, from userid 48)

    ip and domain rotating works find – but why in header is written my hostname? how i can change the hostname that it will be the same like the smtp_helo_name.

    i tried
    rotate1 unix – – n – – smtp
    -o syslog_name=postfix-rotate1
    -o myhostname=smtp.example.1
    -o smtp_bind_address=XX.XX.XX.XX
    -o myhostname=smtp.example.1

    but it doesnt work.

    can you help me?

    • may be you want

      rotate1 unix – – n – – smtp
       -o syslog_name=postfix-rotate1
       -o myhostname=smtp.example.1
       -o smtp_bind_address=XX.XX.XX.XX
       -o smtp_helo_name=smtp.example.1
      

      note on -o smtp_helo_name=smtp.example.1
      same with rest of rotate2, rotate3…

  7. Peter

    Guys, a really quick dirtyfix for rotate-weightning in 10% steps is to duplicate the entries in the array, e.g.:

    our @array = (
    ‘rotate1:’,
    ‘rotate1:’,
    ‘rotate1:’,
    ‘rotate2:’,
    ‘rotate3:’,
    ‘rotate3:’,
    ‘rotate4:’,
    ‘rotate4:’,
    ‘rotate5:’,
    ‘rotate5:’
    );

  8. Korcan

    Hi;
    It not works in my postfix server. i am getting “postmap: fatal: unsupported dictionary type: tcp” error.Are you doing freelance jobs ? I need this immediatly.Thanks

    • your postfix installation does not support tcp_table, i suggest installing the latest distribution.
      i used to do freelance job when i have time.

  9. yongli.dang

    when using the tcp random transport table , the local mail which send to root will log an loopback error

    • tgis article intended works on smtp relay not local mail, but someone in this comment suggest to set best_mx_transport = local

      Cidou says:
      February 24, 2011 at 9:25 am (Edit)

      “Do not forget to add best_mx_transport = local of you may face the dreaded “mail loops back to myself””

      If you use postfix with virtuals users :
      best_mx_transport = virtual

      Regards,
      Cidou

  10. Andre

    I already have a transport_maps entry pointing:

    transport_maps = mysql:/etc/postfix/mysql_transport_maps

    error: postfix/smtpd[20302]: NOQUEUE: reject: RCPT from localhost[127.0.0.1]: 550 5.1.1 : Recipient address rejected: User unknown; from= to= proto=ESMTP helo=

    I try
    transport_maps = mysql:/etc/postfix/mysql_transport_maps, tcp:[127.0.0.1]:2527
    but cannot resolve

    • How did you test that?why there’s no “from” address , “to” address and helo?

  11. Andrei

    Just a feedback. The script is running ok but…but…after two days of sending I get the following error :”smtp postfix/qmgr[20602]: fatal: fcntl F_DUPFD 128: Too many open files”.
    Regards
    Andrei 🙂

    • that’s postfix errors, look likes you ran out of file descriptor, you should tuned up you box, increase maximum file that can be opened (man ulimit).
      i ran this setup years without any problem 🙂

      • Andrei

        What value do you suggest it will be enough? Thank you for you kind support.

  12. Hey,
    I’m using your code but when sending an e-mail to myself, I’m getting the following error message in the log file:

    to=, relay=none, delay=1.9, delays=0.21/0/1.7/0, dsn=5.4.6, status=bounced (mail for *** loops back to myself
    where *** is my domain.

    Any idea where it comes from?

  13. pedro

    postmap -q “whatever” tcp:127.0.0.1:2527

    postmap: warning: read TCP map reply from 127.0.0.1:2527: malformed reply: Can’t open -o: No such file or directory at /etc/postfix/random.pl line 23.
    postmap: fatal: tcp:127.0.0.1:2527: query error: Operation now in progress

    What i’m doing wrong?

    Thanks!

  14. pedro

    PS:
    line 23 on my script is:
    while () {

  15. benqing

    when i check

    postmap -q “whatever” tcp:127.0.0.1:2527

    then back message : “postmap: fatal: unsupported dictionary type: tcp”

    how do i fix it?

    • what version of postfix did you use? you have to upgrade to recent postfix in order for tcp table to work

  16. benqing

    hi,

    i have completed all step, but running no good. it cannot sending out email, connect always online.

    how do i fix it?

    • benqing

      look the netstat , its ” TIME WAIT”

    • benqing

      hello,

      need your help.

      i run the follow as :
      [root@mail ~]# postmap -q “whatever” tcp:127.0.0.1:2527

      and get back message
      rotate1:

      but it cannot sent out email.connect always online ,

      netstat -antp | grep 2527

      tcp 0 0 *.*.*.*:25 *.*.*.*:44980 ESTABLISHED 1903/spawn

    • benqing

      outlook show in the connecting.

    • benqing

      log in messages:

      Feb 21 19:46:20 mail syslogd 1.4.1: restart.
      Feb 21 19:46:20 mail iscsid: iSCSI logger with pid=1496 started!
      Feb 21 19:46:20 mail iscsid: Missing or Invalid version from /sys/module/scsi_transport_iscsi/version. Make sure a up to date scsi_transport_iscsi module is loaded and a up todate version of iscsid is running. Exiting…
      Feb 21 19:46:22 mail init: no more processes left in this runlevel

  17. benqing

    hi, do you have time help me for setting this perl ?

  18. DavdD

    I have the following config in master:

    rotate5 unix – – n – – smtp
    -o syslog_name=postfix-rotate5
    -o smtp_helo_name=smtp5.example.com
    -o smtp_bind_address=1.2.3.4
    -o myhostname=realdomain5.com

    But I can’t figure out how to randomize the hostname of the actual server.

    For instance:

    echo “test” | mail test@test.com

    The e-mail headers still show the source as the physical server:

    To: test@test.com
    Message-Id:
    Date: Fri, 18 May 2012 02:50:18 -0400 (EDT)
    From: root@freebsd1.myserver.com

    The same would happen using PHPMail.

    So the master config of mydomain or myhostname does not work… or does not work as I would hope 🙂

    • DavdD

      The message ID did not post properly.

      I was trying to show that the real name of the server appears in the message ID.

      I have the following config in master:

      rotate5 unix – – n – – smtp
      -o syslog_name=postfix-rotate5
      -o smtp_helo_name=smtp5.example.com
      -o smtp_bind_address=1.2.3.4
      -o myhostname=realdomain5.com

      But I can’t figure out how to randomize the hostname of the actual server.

      For instance:

      echo “test” | mail test@test.com

      The e-mail headers still show the source as the physical server:

      Message-Id: 201205189090980980998.D11090090991721C@freebsd1.myserver.com

    • well, when you’re using “mail” command for testing in server, apparantly it will call “sendmail” command, which is not goes through tansport_maps. so ramdomize script will never work. and we cannot override “myhostname” in master.cf, AFAIK.

  19. Hello.

    I’m using your post for implementing random rotation with 10 IPs and all is working great.
    Do you have an idea how to implement random rotation based on sender domain ?

    – @example.com, use random IPs from 1…5
    – @example.net, use random IPs from 6…8
    – @example.org, use random IPs from 9…10

    Please contact me if you need more info.

    Regards,
    /Sorin

    • i wrote article about sender based outgoing mail, just look for related article and combine those article with this randomize smtp

      cheers

      • Hello.

        I did see this article [http://www.kutukupret.com/2011/05/28/postfix-bind-sender-outgoing-ip-based-on-geoip-location], but I’m not so good in Perl.

        Based on this, if I’ll use the code below, I will get Sender domain or Destination domain ?


        while () {
        chomp;
        if (/^get\s(.+)$/i) {
        # extract domain from email
        my @email = split(/@/, $1);
        my $domain = $email[1];

        On the other hand, if I can’t make it work, are you available for a quick freelance job, to sort this out ?

        Regards,
        /Sorin

        • yes, you can modified perl script into something like that, extracting every recipient domain, and create array for each domain.

          our @array_domain1 = (
          'domain1_rotate1:',
          'domain1_rotate2:',
          'domain1_rotate3:',
          'domain1_rotate4:',
          'domain1_rotate5:'
          );
          
          our @array_domain2 = (
          'domain2_rotate1:',
          'domain2_rotate2:',
          );
          
          if ( $domain == "domain1")
               print "200 $array_domain1[$random_smtp]\n";
          if ( $domain == "domain2")
               print "200 $array_domain2[$random_smtp]\n";
          ...
          ...
          

          and so on..something like that..

          right now, i’m bussy with my main job, i still don’t have spare time to make any experiment.. i don’t even have any chance updating my blog 😀

          • I was thinking of something like that, the only problem is by using that code I’ll get “To: email address (recipient)”. I will need “From: email address (sender)”.

            Any hint how can I get that in ?

            Do I have to pass sender in main.cf ? If yes, how can I process it from Perl ?
            I’ve tried several scenarios and all wrong 🙁

            Thanks in advance.

          • instead of using transport_maps, you can try using sender_dependent_default_transport_maps, however this feature exist prior to postfix-2.7-20091209 and newer.

            main.cf

            sender_dependent_default_transport_maps = tcp:[127.0.0.1]:2527
            

            i’ve never tested that, but that would be work, i guess 🙂

  20. Oscar Bass

    This script works fine and dandy for outgoing mail but if you try to send mail into the server you get loopback errors and the mail fails. Any idea on how to correct this so mail sent out can be replied to and the mail coming into the server lands in the users mail box instead of being bounced?

  21. Gsus Rafael

    I’ve deployed this configuration, but it has a strange behaviour…


    Delivered-To: jesusrafael@gmail.com
    Received: by 10.194.16.5 with SMTP id b5csp211917wjd;
    Sun, 11 Nov 2012 13:18:54 -0800 (PST)
    Received: by 10.69.1.1 with SMTP id bc1mr52225163pbd.102.1352668734095;
    Sun, 11 Nov 2012 13:18:54 -0800 (PST)
    Return-Path:
    Received: from mail2.quedondeycuanto.com (mail3.quedondeycuanto.com. [184.95.62.36])
    by mx.google.com with ESMTP id ux7si6960278pbc.12.2012.11.11.13.18.53;
    Sun, 11 Nov 2012 13:18:53 -0800 (PST)
    Received-SPF: pass (google.com: domain of info@quedondeycuanto.com designates 184.95.62.36 as permitted sender) client-ip=184.95.62.36;
    Authentication-Results: mx.google.com; spf=pass (google.com: domain of info@quedondeycuanto.com designates 184.95.62.36 as permitted sender) smtp.mail=info@quedondeycuanto.com
    Received: by ns1.quedondeycuanto.com (Postfix, from userid 514)
    id 79FA76EE006A; Sun, 11 Nov 2012 17:18:52 -0400 (AST)
    From: "Correo Destinatario"
    Subject: prueba
    To: jesusrafael@gmail.com
    Message-Id:
    Cc: gsusrafael@hotmail.com
    X-Originating-IP: 190.167.170.29
    X-Mailer: Usermin 1.520
    Date: Sun, 11 Nov 2012 17:18:52 -0400 (AST)
    Content-Type: text/plain

    could you please explain this to me?

    • can you point me what exactly “strange behaviour” specifically? i’ve just seen different helo name bound to different ip/hostname there.
      that might be typo in your master.cf.

      • Gsus Rafael

        It isn’t a typo, anyways i’ll check it again…


        from mail2.quedondeycuanto.com (mail3.quedondeycuanto.com. [184.95.62.36])

        but the rotator must only change the ip address and its respective hostname, unlike is shown above

        • i’ve never seen that failure before, if you sure not doing any typo, could it be you dns setup not properly propagate(gmail resolver cache old data).
          well, everything is possible.

          • Gsus Rafael

            i can send you the main.cf and the master.cf to your mail if you don’t mind… to ensure there is no mistake in those configs

          • sure, you can post here master.cf rotate related part.

  22. Gsus Rafael

    Right now there is other problem…


    This is the mail system at host ns1.quedondeycuanto.com.

    I'm sorry to have to inform you that your message could not
    be delivered to one or more recipients. It's attached below.

    For further assistance, please send mail to postmaster.

    If you do so, please include this problem report. You can
    delete your own text from the attached returned message.

    The mail system

    (expanded from
    ): unknown user:
    "jjescobar.quedondeycuanto@ns1.quedondeycuanto.com"

    Final-Recipient: rfc822; jjescobar.quedondeycuanto@ns1.quedondeycuanto.com
    Original-Recipient: rfc822;jjescobar@quedondeycuanto.com
    Action: failed
    Status: 5.1.1
    Diagnostic-Code: X-Postfix; unknown user:
    "jjescobar.quedondeycuanto@ns1.quedondeycuanto.com"

    • Gsus Rafael

      This is the log part i got…


      Nov 18 23:11:05 ns1 postfix/virtual[12699]: 2AE0D6EE0082: to=jjescobar.quedondeycuanto@ns1.quedondeycuanto.com>, orig_to=, relay=virtual, delay=1.4, delays=1.3/0.01/0/0.03, dsn=5.1.1, status=bounced (unknown user: "jjescobar.quedondeycuanto@ns1.quedondeycuanto.com")

      • when i first wrote the article, it was intended to be used for outgoing email only, no local/virtual user in the system.
        script works in catchall ways. but you can always make exception for local/virtual user in the script.

        example:

        while (<>) {
                chomp;
                # randomizing transports array
                my $random_smtp = int(rand(scalar(@array)));
                if (/^get\s(.+)$/i) {
                        my $local_domain = $1;
                        $local_domain =~ s/.*\@//i;
                        if ($local_domain eq "ns1.quedondeycuanto.com") {
                           print "200 local:\n";
                        } else {
                           print "200 $array[$random_smtp]\n";
                        }
                        next;
                }
         
            print "200 smtp:";
        }
        

        cheers

        • Gsus Rafael

          Excellent, now is working like a charm!!!!!!

          Thanks for all!!!!!!

  23. bart

    Hi I would like to have postfix listen to as many as 20-30 different IP/Domain combinations and when sending email use the specific IP/Domain info in the header as was used by the client.
    For example:

    User 1 uses the domain mail.mymail1.com to send email with IP 1.1.1.1

    User 2 uses the domain mail.mymail2.com to send email with IP 2.2.2.2

    I need the header of each email to use that specific domain/ip per user.

    Please Help,
    Thanks

    • that would be involving header_checks on each domain to replace/append custom header. i’m not figuring that out yet.

  24. Wilson A. Galafassi Jr.

    Using your solution how we can implement Throttling per domain? It’s possible? Because Throttling involve to manipule the transport too.

    Thanks,
    Wilson

    • you can make exception for some domain in perl script, can i see how did you throttle mails?

  25. aFriend

    Hi…. I have seen this tutorial. Right now i am following this tutorial. Every thing is fine. But the mail log report is not showing like below.

    Month date 12:26:53 host postfix-rotate1/smtp[4252]: A1CA68480A4: to=, relay=mx.example.com.com[xx.xx.xxx.xx]:25], delay=3.6, delays=0.69/0.01/0.81/2, dsn=2.0.0, status=sent (250 ok dirdel)

    my mail server mail log is given below.

    Aug 26 17:55:02 ms14 postfix/local[18140]: 26F3D2C1D7D: to=, orig_to=, relay=local, delay=0.81, delays=0.68/0.06/0/0.08, dsn=2.0.0, status=sent (delivered to maildir)

    but i am not seeing mail log report like below

    postfix-rotate1/smtp[4252]

    where did i mistake i am not getting ? Please solve the issue

    Adv…… Thanks

  26. aFriend

    hi to all ………..

    first thanks for this tutorial.

    I followed this tutorial as it is.

    i am facing one problem with mail log. I am not getting like what you have given.

    Month date 12:26:53 host postfix-rotate1/smtp[4252]: A1CA68480A4: to=, relay=mx.example.com.com[xx.xx.xxx.xx]:25], delay=3.6, delays=0.69/0.01/0.81/2, dsn=2.0.0, status=sent (250 ok dirdel)

    please my maillog is given below

    Aug 27 17:39:57 ms14 postfix/smtp[23233]: B2E942C1D7D: to=, relay=mta5.am0.yahoodns.net[98.138.112.33]:25, delay=4.6, delays=0.23/0/2/2.4, dsn=2.0.0, status=sent (250 ok dirdel)

    please check it once and tell me where did i mistake ?

    advanced thanks

    ………..

  27. aFriend

    Dear leenoux,

    thanks for your reply. please check it my master.cf

    #
    2 # Postfix master process configuration file. For details on the format
    3 # of the file, see the master(5) manual page (command: “man 5 master”).
    4 #
    5 # Do not forget to execute “postfix reload” after editing this file.
    6 #
    7 # ==========================================================================
    8 # service type private unpriv chroot wakeup maxproc command + args
    9 # (yes) (yes) (yes) (never) (100)
    10 # ==========================================================================
    11 smtp inet n – n – – smtpd
    12 submission inet n – n – – smtpd
    13
    14 smtps inet n – n – – smtpd
    15 -o smtpd_sasl_auth_enable=yes
    16 -o smtpd_reject_unlisted_sender=ye
    17 -o smtpd_sasl_security_options=noanonymous
    18 -o smtpd_sasl_local_domain=hire-now.com
    19 -o header_checks=
    20 -o body_checks=
    21 -o smtpd_client_restrictions=permit_sasl_authenticated,reject_unauth_destination
    22 -o smtpd_recipient_restrictions=permit_sasl_authenticated,reject_unauth_destination
    23 -o smtpd_sasl_security_options=noanonymous,noplaintext
    24 -o smtpd_sasl_tls_security_options=noanonymous
    25 -o broken_sasl_auth_clients=yes
    26
    27 #submission inet n – n – – smtpd
    28 # -o smtpd_tls_security_level=encrypt
    29 # -o smtpd_sasl_auth_enable=yes
    30 # -o smtpd_client_restrictions=permit_sasl_authenticated,reject
    31 # -o milter_macro_daemon_name=ORIGINATING
    32 #smtps inet n – n – – smtpd
    33 # -o smtpd_tls_wrappermode=yes
    34 # -o smtpd_sasl_auth_enable=yes
    35 # -o smtpd_client_restrictions=permit_sasl_authenticated,reject
    36 # -o milter_macro_daemon_name=ORIGINATING
    37 #628 inet n – n – – qmqpd
    38 pickup fifo n – n 60 1 pickup
    39 cleanup unix n – n – 0 cleanup
    40 qmgr fifo n – n 300 1 qmgr
    41 #qmgr fifo n – n 300 1 oqmgr
    42 tlsmgr unix – – n 1000? 1 tlsmgr
    43 rewrite unix – – n – – trivial-rewrite
    44 bounce unix – – n – 0 bounce
    45 defer unix – – n – 0 bounce
    46 trace unix – – n – 0 bounce
    47 verify unix – – n – 1 verify
    48 flush unix n – n 1000? 0 flush
    49 proxymap unix – – n – – proxymap
    50 proxywrite unix – – n – 1 proxymap
    51 smtp unix – – n – – smtp
    52 # When relaying mail as backup MX, disable fallback_relay to avoid MX loops
    53 relay unix – – n – – smtp
    54 -o smtp_fallback_relay=
    55 # -o smtp_helo_timeout=5 -o smtp_connect_timeout=5
    56 showq unix n – n – – showq
    57 error unix – – n – – error
    58 retry unix – – n – – error
    59 discard unix – – n – – discard
    60 local unix – n n – – local
    61 virtual unix – n n – – virtual
    62 lmtp unix – – n – – lmtp
    63 anvil unix – – n – 1 anvil
    64 scache unix – – n – 1 scache
    65 #
    66 # ====================================================================
    67 # Interfaces to non-Postfix software. Be sure to examine the manual
    68 # pages of the non-Postfix software to find out what options it wants.
    69 #
    70 # Many of the following services use the Postfix pipe(8) delivery
    71 # agent. See the pipe(8) man page for information about ${recipient}
    72 # and other message envelope options.
    73 # ====================================================================
    74 #
    75 # maildrop. See the Postfix MAILDROP_README file for details.
    76 # Also specify in main.cf: maildrop_destination_recipient_limit=1
    77 #
    78 #maildrop unix – n n – – pipe
    79 # flags=DRhu user=vmail argv=/usr/local/bin/maildrop -d ${recipient}
    80 #
    81 # ====================================================================
    82 #
    83 # The Cyrus deliver program has changed incompatibly, multiple times.
    84 #
    85 #old-cyrus unix – n n – – pipe
    86 # flags=R user=cyrus argv=/usr/lib/cyrus-imapd/deliver -e -m ${extension} ${user}
    87 #
    88 # ====================================================================
    89 #
    90 # Cyrus 2.1.5 (Amos Gouaux)
    91 # Also specify in main.cf: cyrus_destination_recipient_limit=1
    92 #
    93 #cyrus unix – n n – – pipe
    94 # user=cyrus argv=/usr/lib/cyrus-imapd/deliver -e -r ${sender} -m ${extension} ${user}
    95 #
    96 # ====================================================================
    97 #
    98 # See the Postfix UUCP_README file for configuration details.
    99 #
    100 #uucp unix – n n – – pipe
    101 # flags=Fqhu user=uucp argv=uux -r -n -z -a$sender – $nexthop!rmail ($recipient)
    102 #
    103 # ====================================================================
    104 #
    105 # Other external delivery methods.
    106 #
    107 #ifmail unix – n n – – pipe
    108 # flags=F user=ftn argv=/usr/lib/ifmail/ifmail -r $nexthop ($recipient)
    109 #
    110 #bsmtp unix – n n – – pipe
    111 # flags=Fq. user=bsmtp argv=/usr/local/sbin/bsmtp -f $sender $nexthop $recipient
    112 #
    113 #scalemail-backend unix – n n – 2 pipe
    114 # flags=R user=scalemail argv=/usr/lib/scalemail/bin/scalemail-store
    115 # ${nexthop} ${user} ${extension}
    116 #
    117 #mailman unix – n n – – pipe
    118 # flags=FR user=list argv=/usr/lib/mailman/bin/postfix-to-mailman.py
    119 # ${nexthop} ${user}
    120 policy unix – n n – 0 spawn user=nobody argv=/usr/bin/perl /usr/lib/postfix/postfix-policyd-spf-perl
    121
    122 ## Round-robin outgoing smtp
    123
    124 127.0.0.1:2527 inet n n n – 0 spawn user=nobody argv=/etc/postfix/random.pl
    125 #127.0.0.1:23000 inet n n n – 0 spawn user=nobody argv=/etc/postfix/random.pl
    126
    127 ## random smtp
    128
    129 #rotate1 unix – – n – – smtp
    130 # -o syslog_name=postfix-rotate1
    131 # -o smtp_helo_name=ms14.mydomain.com
    132 # -o smtp_bind_address=X.X.X.1
    133
    134
    135 rotate1 unix – – n – – smtp
    136 -o syslog_name=postfix-rotate1
    137 -o smtp_helo_name=ms0.mydomain.com
    138 -o smtp_bind_address=X.X.X.2
    139
    140 rotate2 unix – – n – – smtp
    141 -o syslog_name=postfix-rotate2
    142 -o smtp_helo_name=ms1.mydomain.com
    143 -o smtp_bind_address=X.X.X.3

    ……………..

    • which postfix version are you using? is it suporting for tcp_table? when i first create this randomizing methode it’s for outgoing gateway/relay only. not mixing beetween virtual/local maildir and relaying. (there’s a hack in perl script just search in related comments below)

      and don’t forget to create bunch of ip aliasing otherwise postfix would fail to bind an ip address.

  28. aFriend

    Dear leenoux,

    postfix version is: postfix-2.9.1-1.rhel5.x86_64 and

    [root@xxx ~]# postconf -m
    btree
    cidr
    environ
    fail
    hash
    internal
    ldap
    memcache
    nis
    proxy
    regexp
    static
    tcp
    texthash
    unix

    I have created IP aliases. like eth0:1, eth0:2, etc.,

    still i am not getting maillog status like

    postfix-rotate1/smtp[4252]

    postfix-rotate2/smtp[4252]

    how can i get the maillog status like that ?

    …………………

    • what is in transport_maps? seems like your transport_maps doesn’t read from tcp table and fall to default smtp.

  29. aFriend

    Dear Loonoux,

    transport_map details are given below

    transport_maps = tcp:/etc/postfix/transport, tcp:127.0.0.1:2527

    will I do any changes on transport_maps ?

    • what is in tcp:/etc/postfix/transport ? when you need to exclude mail for local/virtual delivery, you should do it in perl script. there’s examples at comments section.

  30. aFriend

    Dear Loonoux,

    I am having 3 Public IPS

    1. one is given STATIC IP
    2. installed postfix with dovecot
    3. created two IPs as aliases of ethernet, ie., eth0:1, eth0:2
    4. created perl script
    5. added few lines to main.cf and master.cf from your tutorial for IP rotation.

    I am not getting any errors. But i am not seeing the result in maillog as

    postfix-rotate1/smtp[4252]

    postfix-rotate2/smtp[4252]

    is there required any modifications to get the result ?

  31. Incoming mail rejecting…. Log is appended here

    Dec 24 07:30:51 host postfix/smtpd[6219]: 989408406FB: client=mail-ve0-f174.google.com[209.85.128.174]
    Dec 24 07:30:51 host postfix/randomizer[6225]: Using: smtp16: Transport Service
    Dec 24 07:30:51 host postfix/randomizer[6225]: Using: smtp17: Transport Service
    Dec 24 07:30:51 host postfix/smtpd[6219]: 989408406FB: reject: RCPT from mail-ve0-f174.google.com[209.85.128.174]: 550 5.1.1 : Recipient address rejected: User unknown in local recipient table; from= to= proto=ESMTP helo=
    Dec 24 07:30:51 host postfix/cleanup[6226]: 989408406FB: message-id=
    Dec 24 07:30:51 host opendkim[2281]: 989408406FB: mail-ve0-f174.google.com [209.85.128.174] not internal
    Dec 24 07:30:51 host opendkim[2281]: 989408406FB: not authenticated
    Dec 24 07:30:51 host opendkim[2281]: 989408406FB: DKIM verification successful
    Dec 24 07:30:51 host opendkim[2281]: 989408406FB: s=20120113 d=gmail.com SSL
    Dec 24 07:30:51 host postfix/qmgr[6216]: 989408406FB: from=, size=2010, nrcpt=2 (queue active)
    Dec 24 07:30:51 host postfix/randomizer[6225]: Using: smtp18: Transport Service
    Dec 24 07:30:51 host postfix/randomizer[6225]: Using: smtp1: Transport Service
    Dec 24 07:30:51 host postfix/smtpd[6219]: disconnect from mail-ve0-f174.google.com[209.85.128.174]
    Dec 24 07:30:51 host postfix-rotate1/smtp[6227]: 989408406FB: to=, relay=none, delay=0.34, delays=0.18/0.01/0.14/0, dsn=5.4.6, status=bounced (mail for amritanews.com loops back to myself)
    Dec 24 07:30:51 host postfix-rotate1/smtp[6228]: 989408406FB: to=, relay=none, delay=0.34, delays=0.18/0.01/0.14/0, dsn=5.4.6, status=bounced (mail for amritanews.com loops back to myself)
    Dec 24 07:30:51 host postfix/cleanup[6226]: E8CF38406FD: message-id=
    Dec 24 07:30:51 host postfix/qmgr[6216]: E8CF38406FD: from=, size=4273, nrcpt=1 (queue active)
    Dec 24 07:30:51 host postfix/randomizer[6225]: Using: smtp2: Transport Service
    Dec 24 07:30:51 host postfix/bounce[6230]: 989408406FB: sender non-delivery notification: E8CF38406FD
    Dec 24 07:30:51 host postfix/qmgr[6216]: 989408406FB: removed
    Dec 24 07:30:52 host postfix-rotate1/smtp[6231]: connect to gmail-smtp-in.l.google.com[2607:f8b0:400d:c02::1a]:25: Network is unreachable
    Dec 24 07:30:52 host postfix-rotate1/smtp[6231]: E8CF38406FD: to=, relay=gmail-smtp-in.l.google.com[173.194.68.26]:25, delay=0.99, delays=0.01/0.01/0.59/0.38, dsn=2.0.0, status=sent (250 2.0.0 OK 1387888253 j7si17727126qab.167 – gsmtp)
    Dec 24 07:30:52 host postfix/qmgr[6216]: E8CF38406FD: removed

  32. Lukas

    Hi Leenoux, thank you for a wonderfull script.

    I am using it for over 2 years without issues but I have just noticed a strange behaviour/bug?

    It always uses the first MX record. When the recepient server replay with 4xx code than postfix should try a different MX server, but it does not. It just queues the message and tries the same MX server again.

    Without this script it works fine.

    Do you know where the issue could be? What should I fix? Thank you

  33. Matt

    I have a question about headers. The rotation may or may not be working. Part of my header says server3, but the ip addresses being shown is actually server1. No matter which hostname is shown, it always shows the same xx.xxx.xxx.9 ip address. Server3 would be the xx.xxx.xxx.11 address. Any thoughts? Here is the header.

    Authentication-Results: hotmail.com; spf=pass (sender IP is XX.XXX.XXX.9) smtp.mailfrom=admin@mydomain.com; dkim=pass header.d=mydomain.com; x-hmca=pass header.id=marketing@mydomain.com
    X-SID-PRA: marketing@mydomain.com
    X-AUTH-Result: PASS
    X-SID-Result: PASS
    Received: from server3.mydomain.com ([XX.XXX.XXX.9]) by SNT0-MC1-F43.Snt0.hotmail.com with Microsoft SMTPSVC(6.0.3790.4900);
    Tue, 15 Apr 2014 15:12:07 -0700

    • show me your master.cf configuration for ip XX.XXX.XXX.9 and xx.xxx.xxx.11 (smtp_bind_address), don’t forget to check dns part, are server1 and server3 point to the right ip address (both A record and PTR record)?

  34. Matt

    DNS is fine okay

    .9 is pointing to server1
    .11 is pointing to server3
    .12 is pointing to server4

    PTR records are all set up correctly too.

    Here is my master.cf minus some stuff

    smtp inet n – n – – smtpd
    #smtp inet n – – – 1 postscreen
    #smtpd pass – – – – – smtpd
    #dnsblog unix – – – – 0 dnsblog
    #tlsproxy unix – – – – 0 tlsproxy
    submission inet n – n – – smtpd
    -o syslog_name=postfix/submission
    -o smtpd_tls_security_level=encrypt
    -o smtpd_sasl_auth_enable=yes
    -o smtpd_client_restrictions=permit_sasl_authenticated,reject
    # -o milter_macro_daemon_name=ORIGINATING
    smtps inet n – n – – smtpd
    -o syslog_name=postfix/smtps
    -o smtpd_tls_wrappermode=yes
    -o smtpd_sasl_auth_enable=yes
    -o smtpd_client_restrictions=permit_sasl_authenticated,reject

    127.0.0.1:10025 inet n – – – – smtpd
    -o content_filter=
    -o local_recipient_maps=
    -o relay_recipient_maps=
    -o smtpd_restriction_classes=
    -o smtpd_client_restrictions=
    -o smtpd_helo_restrictions=
    -o smtpd_sender_restrictions=
    -o smtpd_recipient_restrictions=permit_mynetworks,reject
    -o mynetworks=127.0.0.0/8
    -o strict_rfc821_envelopes=yes
    -o receive_override_options=no_unknown_recipient_checks,no_header_body_checks,no_milters
    yahoo unix – – n – – smtp
    -o syslog_name=postfix-yahoo
    #random
    127.0.0.1:2527 inet n n n – 0 spawn
    user=nobody argv=/etc/postfix/random.pl
    # random smtp
    rotate1 unix – – n – – smtp
    -o syslog_name=postfix-rotate1
    -o myhostname=server1.mydomain.com
    -o smtp_helo_name=server1.mydomain.com
    -o smtp_bind_address=xxx.xxx.xxx.9

    rotate2 unix – – n – – smtp
    -o syslog_name=postfix-rotate2
    -o myhostname=server3.mydomain.com
    -o smtp_helo_name=server3.mydomain.com
    -o smtp_bind_address=xxx.xxx.xxx.11

    rotate3 unix – – n – – smtp
    -o syslog_name=postfix-rotate3
    -o myhostname=server4.mydomain.com
    -o smtp_helo_name=server4.mydomain.com
    -o smtp_bind_address=xxx.xxx.xxx.12

    • master.cf part is okay, still not clear your real configuration on DNS, you can check your A record an PTR record at least, let me know your real hostname so i can check it out from here.

  35. Matt

    server1.iaminsuranceagent.com
    server2…
    server3…
    server4…

    • looks fine from here
      # dig a server1.iaminsuranceagent.com +short
      50.245.192.9
      # dig -x 50.245.192.9 +short
      server1.iaminsuranceagent.com.
      # dig a server3.iaminsuranceagent.com +short
      # dig -x 50.245.192.11 +short
      server3.iaminsuranceagent.com.

      might be hotmail resolver, just give another try

  36. Rafael

    Thanks for a great script. I got it working. But there is one question. Please tell me, how do I make it so tough to bind to each (rigidly designate) IP-address separate address Mail-From. For example:
    rotate1 Mail-From – info@mail1.my-site.com
    rotate1 Mail-From – info@mail2.my-site.com
    rotate1 Mail-From – info@mail3.my-site.com
    It is necessary for the proper email provider is authenticated when checking SPF, DKIM-recording. For each IP/domen, its separate SPF, DKIM-record.

    • i’m not really sure, maybe you should check this postfix feature: sender_dependent_default_transport_maps

  37. hy Leenoux, thanks for your amazing tutorials and scripts.

    I try to applicate this on zpanel/centos 6 without success how can i update my postfix for use tcp tables???

    thanks in advance cool dude!

  38. Rafael

    Hi!
    My program sends letters and mailing at the same address all mail-FROM (Return-Path 🙂
    To in headings incoming letters correctly formed DKIM signing, I need to replace the address of mail-FROM (Return-Path 🙂 to the address from which you are sending.
    To this I added to each transport one line:

    rotate1 unix – n – smtp
    -o smtp_bind_address = hhh.hh.28.20
    -o smtp_helo_name = mail1.oni4you.com
    -o myhostname = mail1.oni4you.com
    -o syslog_name = postfix-rotate1
    -o sender_canonical_maps = hash :/ etc/postfix/canonical_maps1.cf

    rotate2 unix – n – smtp
    -o smtp_bind_address = hhh.hh.28.21
    -o smtp_helo_name = mail2.oni4you.com
    -o myhostname = mail2.oni4you.com
    -o syslog_name = postfix-rotate2
    -o sender_canonical_maps = hash :/ etc/postfix/canonical_maps2.cf

    rotate3 unix – n – smtp
    -o smtp_bind_address = hhh.hh.30.83
    -o smtp_helo_name = oni4you.com
    -o myhostname = mx.oni4you.com
    -o syslog_name = postfix-rotate3

    Then I created 2 files
    canonical_maps1.cf
    @ oni4you.com @ mail1.oni4you.com
    and
    canonical_maps2.cf
    @ oni4you.com @ mail2.oni4you.com
    performed
    # Postmap hash :/ etc / postfix / canonical_maps
    for each file,

    But it does not work! Could you tell me where is the error?

  39. Chris

    I’m not sure if this site is still alive in 2020, but I have a question about the “catch-all” behavior with this script. Is it possible to use it in conjunction with a regular transport_maps table in order to define specific destinations for discard or relay to other smtp servers while allowing all non-matching messages to be fed to the random.pl script?

    • well, it’s still alive. sure, go ahead, i’ll give my best shot to answer your question. i’m little bit rusty though 😀

  40. Chris

    Good to hear, Hari! I’ll rephrase my question with an example. If I’m using the script on this page with an array of IP addresses, and the following defined in /etc/postfix/main.cf:

    transport_maps = hash:/etc/postfix/transport, tcp:127.0.0.1:2527

    And, if I put the following in /etc/postfix/transport:

    user1@example.com smtp:[1.2.3.4] #relay this user to another SMTP server

    everything will work just fine. The email address above will get relayed elsewhere and all other non-matching email will get a random transport entry via your script. However, if I put the following in /etc/postfix/transport:

    example.com smtp:[1.2.3.4] #send ALL email for example.com to another SMTP server

    this does not work at all. This entry is ignored and email for example.com follows the same path as all other email. Any ideas why the pattern matching doesn’t work here, and any suggestions to fix?

    Thx!

    • can i see your main.cf, transport_maps part. AFAIK, i will put all catch-all and exclussion in tcp_tables perl script(need some modification).

Leave a Reply

Your email address will not be published. Required fields are marked *