Dec 062010
 

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);
Share

  70 Responses to “Postfix Randomizing Outgoing IP Using TCP_TABLE And Perl”

Comments (69) Pingbacks (1)
  1. 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?

  2. 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

      • 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 :)

    • 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. “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

  4. 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
      
      • 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.

  5. 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.

 Leave a Reply

(required)

(required)


*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>