Postfix Bind Sender Outgoing IP, Based On GeoIP Location

This morning, when I took my daughter to school, I got the idea to experiment with postfix and GeoIP location. the idea is, if mx emails are in a geo targeted a specific location, mail delivery will be done with a certain ip address.

Ie:

  • Every emails with the mx hosts that have IP addresses/host mapped to the US country code, will be bind to ip 1.2.3.4.
  • Every emails with the mx hosts that have IP addresses/host mapped to the HK country code, will be bind to ip 5.6.7.8.

or

  • Every emails with the mx hosts that have IP addresses/host mapped to the CN country code, will be relay to our smtp nexthop in china.

And so on..

what is geolocation?

Geolocation is used to deduce the geolocation (geographic location) of another party. For example, on the Internet, one geolocation approach is to identify the subject party’s IP address, then determine what country (including down to the city and post/ZIP code level), organization, or user the IP address has been assigned to, and finally, determine that party’s location. Other methods include examination of a MAC address, image metadata, or credit card information.

But, in this experiment we just need ip/host to country code map and perl script.

Perl module required:

Net::DNS
Geo::IP
Sys::Syslog

Basic Usage perl geoip

#!/usr/bin/perl
use Geo::IP;
my $gi = Geo::IP->new(GEOIP_STANDARD);
print $gi->country_name_by_name("amazon.com");

I would still be using transport_maps and tcp_table to interact with Perl scripts. so here’s the prototype.

In Postfix part, we have custom transport like this in master.cf:

smtp-JP  unix -       -       n       -       -       smtp
	-o syslog_name=postfix-smtp-JP
	-o smtp_helo_name=smtp-JP.example.com
	-o smtp_bind_address=1.2.3.1
smtp-US  unix -       -       n       -       -       smtp
	-o syslog_name=postfix-smtp-US
	-o smtp_helo_name=smtp-US.example.com
	-o smtp_bind_address=1.2.3.2
smtp-ID  unix -       -       n       -       -       smtp
	-o syslog_name=postfix-smtp-ID
	-o smtp_helo_name=smtp-ID.example.com
	-o smtp_bind_address=1.2.3.3
smtp-CN  unix -       -       n       -       -       smtp
	-o syslog_name=postfix-smtp-CN
	-o smtp_helo_name=smtp-CN.example.com
	-o smtp_bind_address=1.2.3.4
smtp-HK  unix -       -       n       -       -       smtp
	-o syslog_name=postfix-smtp-HK
	-o smtp_helo_name=smtp-HK.example.com
	-o smtp_bind_address=1.2.3.5


We’ll run perl script using spawn daemon service

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

In Main.cf:

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

Now, Perl Script part:

#!/usr/bin/perl
use strict;
use warnings;
use Net::DNS;
use Sys::Syslog qw(:DEFAULT setlogsock);
use Geo::IP;

my $gi = Geo::IP->new(GEOIP_STANDARD);

# maps
my %smtp_geo_map = (
	"JP"  => "smtp-JP:",
	"US"  => "smtp-US:",
	"ID"  => "smtp-ID:",
	"CN"  => "smtp-CN:100.2.3.4",
	"HK"  => "smtp-HK:"
);

#
# Create our resolver object.
#
my $dns = Net::DNS::Resolver->new(
	nameservers  => [qw(10.11.12.13)],
	udp_timeout  => 2,
	retry        => 2,
);

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

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

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

		# initiate default result
		my $geo_smtp = "smtp:";

		# resolve MXs
		my @mx = mx($dns, $domain);
		my @mx_list;
		foreach my $record (@mx) {
			push(@mx_list, $record->exchange);
		}

		# find country code base on random mx
		my $country_code = $gi->country_code_by_name($mx_list[rand]);

		if (defined $country_code)
		{
			$geo_smtp = $smtp_geo_map{"$country_code"};
			if (defined $geo_smtp)
			{
				print "200 $geo_smtp\n";
				syslog("info","We are using: %s", $geo_smtp);
			} else {
				print "200 smtp:\n";
			}
		} else {
			print "200 smtp:\n";
		}
		next;
	}
	print "200 smtp:\n";
}

Take a note on this:

	"CN"  => "smtp-CN:100.2.3.4",

If Perl script geoip lookup result is CN country code, mail will be relay to nexthop at 100.2.3.4 (our smtp relay in china for example). Well, this is just my prototype. i’ve never really try these experiment on real smtp system with traffic. But The script and postfix configuration is tested.

now I have to pick up my daughter at school.

cheers 🙂

3 Comments

  1. Rodrigo

    Excelent article, I also tested and it helps me as described.

    Do you even know a way to use sender outgoing IP address based on listen IP address? I mean, if my postfix is listening on 1.2.3.4, 1.2.3.5, 1.2.3.6, when I connect on 1.2.3.5, remote server receive my mails at this IP and just this IP address.

    • i think you should be using multiple instances for those purpose 🙂

  2. Alexei

    Hello.
    Excellent post.

    somebody know how extract domain name from From field,
    because if I understand this part

    # extract domain from email
    my @email = split(/@/, $1);
    my $domain = $email[1];

    extract from “To” field

Leave a Reply

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