2

I have a postfix server that was working fine (inbound and outbound) until I made some networking changes. I added an interface (for a second public IP address, via a VPN tunnel. I intend to run multiple postfix servers, to support separate domains on separate IP addresses, certificates etc.)

After the change, postfix receives inbound mail fine, but outbound traffic is going out the wrong interface, and so mail cannot be delivered (port 25 is blocked on that interface.) mail.log shows "connection refused" or "network unreachable" to any foreign SMTP server.

Sample error message from mail.log:

Oct 18 17:13:10 vox postfix/smtp[22694]: connect to mx-asp.jvlicenses.com[198.199.107.159]:25: Connection timed out
Oct 18 17:13:10 vox postfix/smtp[22694]: 39DCBA6227: to=<alissandra@jvlicenses.com>, relay=none, delay=1096, delays=1066/0.02/30/0, dsn=4.4.1, status=deferred (connect to mx-asp.jvlicenses.com[198.199.107.159]:25: Connection timed out)

I'm using the smtp_bind_address parameter in master.cf to specify the source address that Postfix should use. I have also tried using inet_addresses in master.cf, but that doesn't seem to work either. The traffic always goes out the default gateway, rather then the desired interface. (When all of this was working, I think the default route was probably the desired route, but I can't remember for sure.)

What I want is for postfix to send mail with source IP 10.8.0.8, which by my routing rules should go out interface tun45. Instead, as best I can tell, postfix is sending with IP 192.168.122.185 on device enp1s0 -- which is the default route on the host.

I'm running postfix version 3.4.14 on Debian 4.19.118-2 (2020-04-24).

Some additional configuration details follow, along with steps I used to test.

Postfix binds to 10.8.0.8, as shown by netstat:

# netstat -ntlp|grep master
tcp        0      0 10.8.0.8:25             0.0.0.0:*               LISTEN      22293/master        
tcp        0      0 10.8.0.8:587            0.0.0.0:*               LISTEN      22293/master        
tcp        0      0 10.8.0.8:465            0.0.0.0:*               LISTEN      22293/master

That address is device tun45 on the host:

# ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host 
       valid_lft forever preferred_lft forever
2: enp1s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether 52:54:00:4b:2c:5b brd ff:ff:ff:ff:ff:ff
    inet 192.168.122.185/24 brd 192.168.122.255 scope global dynamic noprefixroute enp1s0
       valid_lft 2672sec preferred_lft 2672sec
    inet6 fe80::5054:ff:fe4b:2c5b/64 scope link noprefixroute 
       valid_lft forever preferred_lft forever
9: tun45: <POINTOPOINT,MULTICAST,NOARP,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UNKNOWN group default qlen 100
    link/none 
    inet 10.8.0.8/24 brd 10.8.0.255 scope global tun45
       valid_lft forever preferred_lft forever
    inet6 fe80::e401:70cf:ba68:88b1/64 scope link stable-privacy 
       valid_lft forever preferred_lft forever
11: tun66: <POINTOPOINT,MULTICAST,NOARP,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UNKNOWN group default qlen 100
    link/none 
    inet 10.8.0.7/24 brd 10.8.0.255 scope global tun66
       valid_lft forever preferred_lft forever
    inet6 fe80::41d:c2e1:9428:5630/64 scope link stable-privacy 
       valid_lft forever preferred_lft forever

Route to any external address (in this case, the MTA for jvlicenses.com) from IP 10.8.0.8, should go via device tun45:

# ip route get 198.199.107.159 from 10.8.0.8
198.199.107.159 from 10.8.0.8 dev tun45 table t1 uid 0 
    cache 

Routing table t1 has only one entry:

# ip route show table t1
default dev tun45 scope link

I can connect to the external server post 25 using netcat, with that same source address:

# nc -s 10.8.0.8 198.199.107.159 25
220 mx-asp.jvlicenses.com ESMTP Postfix
QUIT
221 2.0.0 Bye

tcpdump shows that the netcat connection has the right source address etc:

# tcpdump -ni tun45 dst port 25
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on tun45, link-type RAW (Raw IP), capture size 262144 bytes
16:27:02.522570 IP 10.8.0.8.42427 > 198.199.107.159.25: Flags [S], seq 4118387792, win 64240, options [mss 1460,sackOK,TS val 2496426612 ecr 0,nop,wscale 8], length 0
16:27:02.689912 IP 10.8.0.8.42427 > 198.199.107.159.25: Flags [.], ack 1806817019, win 251, options [nop,nop,TS val 2496426779 ecr 75983973], length 0
16:27:02.857545 IP 10.8.0.8.42427 > 198.199.107.159.25: Flags [.], ack 42, win 251, options [nop,nop,TS val 2496426947 ecr 75984142], length 0
16:27:14.393645 IP 10.8.0.8.42427 > 198.199.107.159.25: Flags [P.], seq 0:5, ack 42, win 251, options [nop,nop,TS val 2496438483 ecr 75984142], length 5: SMTP: QUIT
16:27:14.650912 IP 10.8.0.8.42427 > 198.199.107.159.25: Flags [.], ack 57, win 251, options [nop,nop,TS val 2496438740 ecr 75995841], length 0
16:27:14.651089 IP 10.8.0.8.42427 > 198.199.107.159.25: Flags [F.], seq 5, ack 58, win 251, options [nop,nop,TS val 2496438740 ecr 75995842], length 0

But when I send an email to that server, there's no traffic on tun45. Instead, I see it going out the default route on device enp1s0:

# tcpdump -n dst port 25 -vv
tcpdump: listening on enp1s0, link-type EN10MB (Ethernet), capture size 262144 bytes
16:42:37.321194 IP (tos 0x0, ttl 64, id 1567, offset 0, flags [DF], proto TCP (6), length 60)
    192.168.122.185.34050 > 198.199.107.159.25: Flags [S], cksum 0x6df7 (incorrect -> 0xc5e2), seq 3824536030, win 64240, options [mss 1460,sackOK,TS val 17622374 ecr 0,nop,wscale 8], length 0

Confirmation that this is in fact the default route for the host, i.e., when the source IP is not 10.8.0.8 so that routing table t1 is triggered:

# ip route
default via 192.168.122.1 dev enp1s0 proto dhcp metric 100 
10.8.0.0/24 dev tun45 proto kernel scope link src 10.8.0.8 
10.8.0.0/24 dev tun66 proto kernel scope link src 10.8.0.7
<...elided entries...>

# ip route get 198.199.107.159
198.199.107.159 via 192.168.122.1 dev enp1s0 src 192.168.122.185 uid 0 
    cache 

master.cf config:

10.8.0.8:smtp      inet  n       -       y       -       -       smtpd -v
  -o smtpd_tls_key_file=/etc/letsencrypt/live/<domain>/privkey.pem
  -o smtpd_tls_cert_file=/etc/letsencrypt/live/<domain>/fullchain.pem
  -o smtp_bind_address=10.8.0.8
  -o myhostname=<host.domain>

10.8.0.8:submission inet n       -       y       -       -       smtpd -v
  -o syslog_name=postfix/submission
  -o smtpd_tls_security_level=encrypt
  -o smtpd_sasl_auth_enable=yes
  -o smtpd_sasl_type=dovecot
  -o smtpd_sasl_path=private/auth
  -o smtpd_reject_unlisted_recipient=no
  -o smtpd_client_restrictions=permit_sasl_authenticated,reject
  -o milter_macro_daemon_name=ORIGINATING
  -o smtpd_tls_key_file=/etc/letsencrypt/live/<domain>/privkey.pem
  -o smtpd_tls_cert_file=/etc/letsencrypt/live/<domain>/fullchain.pem
  -o smtp_bind_address=10.8.0.8
  -o myhostname=<host.domain>

10.8.0.8:smtps     inet  n       -       y       -       -       smtpd -v
  -o syslog_name=postfix/smtps
  -o smtpd_tls_wrappermode=yes
  -o smtpd_sasl_auth_enable=yes
  -o smtpd_sasl_type=dovecot
  -o smtpd_sasl_path=private/auth
  -o smtpd_client_restrictions=permit_sasl_authenticated,reject
  -o milter_macro_daemon_name=ORIGINATING
  -o smtpd_tls_key_file=/etc/letsencrypt/live/<domain>/privkey.pem
  -o smtpd_tls_cert_file=/etc/letsencrypt/live/<domain>/fullchain.pem
  -o smtp_bind_address=10.8.0.8
  -o myhostname=<host.domain>

And postconf -n output:

# postconf -n
alias_database = hash:/etc/aliases
alias_maps = hash:/etc/aliases
append_dot_mydomain = no
biff = no
compatibility_level = 2
debug_peer_list = 81.3.6.165, 45.55.104.203, 34.209.113.130
delay_warning_time = 4h
disable_vrfy_command = yes
inet_interfaces = all
inet_protocols = all
invalid_hostname_reject_code = 550
mailbox_size_limit = 0
maximal_backoff_time = 3h
milter_default_action = accept
milter_protocol = 6
minimal_backoff_time = 180s
mydestination = localhost
mynetworks = 127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128
myorigin = /etc/mailname
non_fqdn_reject_code = 550
non_smtpd_milters = $smtpd_milters
policyd-spf_time_limit = 3600s
readme_directory = no
recipient_delimiter = +
relayhost =
smtp_always_send_ehlo = yes
smtp_helo_timeout = 15s
smtp_rcpt_timeout = 15s
smtp_tls_security_level = may
smtp_tls_session_cache_database = btree:${data_directory}/smtp_scache
smtpd_banner = $myhostname ESMTP $mail_name (Debian/GNU)
smtpd_delay_reject = yes
smtpd_helo_required = yes
smtpd_helo_restrictions = permit_mynetworks, permit_sasl_authenticated, reject_invalid_helo_hostname, reject_non_fqdn_helo_hostname
smtpd_milters = local:opendkim/opendkim.sock
smtpd_recipient_limit = 40
smtpd_recipient_restrictions = permit_mynetworks, permit_sasl_authenticated, reject_non_fqdn_recipient, reject_unknown_recipient_domain, reject_unlisted_recipient, reject_unauth_destination, check_policy_service unix:private/policyd-spf
smtpd_relay_restrictions = permit_mynetworks, permit_sasl_authenticated, defer_unauth_destination
smtpd_sasl_auth_enable = yes
smtpd_sasl_path = private/auth
smtpd_sasl_security_options = noanonymous, noplaintext
smtpd_sasl_tls_security_options = noanonymous
smtpd_sasl_type = dovecot
smtpd_sender_restrictions = permit_mynetworks, permit_sasl_authenticated, reject_non_fqdn_sender, reject_unknown_sender_domain, reject_unknown_reverse_client_hostname
smtpd_timeout = 30s
smtpd_tls_auth_only = yes
smtpd_tls_security_level = may
smtpd_tls_session_cache_database = btree:${data_directory}/smtpd_scache
smtpd_use_tls = yes
strict_rfc821_envelopes = yes
unknown_address_reject_code = 550
unknown_client_reject_code = 550
unknown_hostname_reject_code = 550
unverified_recipient_reject_code = 550
unverified_sender_reject_code = 550
virtual_alias_maps = mysql:/etc/postfix/mysql-virtual-alias-maps.cf, mysql:/etc/postfix/mysql-virtual-email2email.cf
virtual_mailbox_domains = mysql:/etc/postfix/mysql-virtual-mailbox-domains.cf
virtual_mailbox_maps = mysql:/etc/postfix/mysql-virtual-mailbox-maps.cf
virtual_transport = lmtp:unix:private/dovecot-lmtp

I am doing no SNATing, masquerading or other iptables stuff on this host. (That all happens at the other end of the VPN tunnel.)

What am I missing here? Why is outbound postfix traffic going out on enp1s0 instead of tun45? And why does it not have the source IP that is set in smtp_bind_address?

  • Update, I've also tried setting inet_interfaces=all along with the smtp_bind_address parameter, as described here: https://serverfault.com/questions/966467/postfix-not-sending-mail-using-specific-outgoing-ip-address But I get the same result. smtp_bind_address is ignored for outgoing connections and the default route is used. – fear_no_eval Oct 19 '20 at 16:08
  • Also, setting inet_interfaces=tun45 causes an error in mail.log: ```Oct 19 12:47:18 postfix/smtpd[12070]: fatal: config variable inet_interfaces: host not found: tun45```. It appears you cannot use a tunnel device name in that variable. – fear_no_eval Oct 19 '20 at 16:57
  • Take a look at this: http://www.postfix.org/postconf.5.html#inet_interfaces "When inet_interfaces specifies just one IPv4 and/or IPv6 address that is not a loopback address, the Postfix SMTP client will use this address as the IP source address for outbound mail. Support for IPv6 is available in Postfix version 2.2 and later. " – Jesús Ángel Oct 20 '20 at 18:00
  • inet_interfaces doesn't appear to change the behavior in this case. Whether I don't set it, set it to "all", or set it to "10.8.0.8", the behavior is the same -- the traffic goes out enp1s0 with IP 192.168.122.185 -- NOT the smtp_bind_address. I've also tried just setting inet_interfaces="10.8.0.8" and not setting smtp_bind_address. Same result. Either I'm missing something or inet_interfaces and smtp_bind_address are not working as advertised. – fear_no_eval Oct 20 '20 at 21:08
  • Since no bites, maybe this helps: this question can be summarized as "why does smtp_bind_address (and inet_interfaces) work for outgoing traffic for devices like eth0 (or its IP address) but not for tunnel interface labels, such as "tun45"? Because that is what I'm seeing. For inbound (listening) daemons, these parameters work as advertised on any interface. – fear_no_eval Oct 27 '20 at 00:10
  • Maybe the tunnel interface starts after postfix, so it discard non existing interfaces/ips at start. Can you add a dependency to the postfix service unit to the VPN service so postfix will start after the VPN is established? – Jesús Ángel Oct 27 '20 at 08:28

2 Answers2

2

This was a configuration problem in master.cf. The configuration shown above (in my question) only sets smtp_bind_address for inbound traffic, meaning for the listening daemons.

For outbound traffic, meaning when the postfix daemon sends mail to other servers, the following line in master.cf must also have the bind address specified. In the default/sample master.cf the line looks like this:

smtp      unix  -       -       y       -       -       smtp

It's buried down below a lot of other parameters, easy to miss.

Change it to add the bind address with e.g.:

smtp      unix  -       -       y       -       -       smtp
  -o smtp_bind_address=10.8.0.9

Since I run multiple postfix daemons on the same server in order to serve multiple domains with separate public IPs and certificates, I changed mine to:

outbound_domain1.com unix  -       -       y       -       -       smtp
  -o smtp_bind_address=10.8.0.8
  -o smtp_helo_name=domain1.com

...and then added a line in /etc/postfix/sender_transport to map this outbound daemon to the email domain it serves:

@domain1.com outbound_domain1.com

And of course you also have to tell postfix to use that transport map, in main.cf:

sender_dependent_default_transport_maps = hash:/etc/postfix/sender_transport

And then run postmap to generate the hash files that postfix uses for these lookups.

0

Please, take a look at this question: Prevent Postfix dispatching email over wrong network interface.

In that post, people stated smtp_bind_address will failover to another inet_interface if the configured one is not available. In your case, smtp_bind_address points to a static IP bound to tun0. It will pass out via tun0 when it's up. If tun0 is down then the client bind will fail causing postfix to fall back to the default behaviour of not binding the client TCP endpoint and it will therefore use the default route which is connected to eth0.

Jesús Ángel
  • 518
  • 1
  • 6
  • But the tunnel is up when these failures occur. For instance, postfix listener daemons bind to the address and postfix is receiving mail over that interface just fine, at the same time that outbound mail is failing. It appears to me that the configuration is not working for outbound mail. Could it be a routing problem? I have provided some routing details in the question also. – fear_no_eval Nov 02 '20 at 07:35