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
?