haraka / Haraka

A fast, highly extensible, and event driven SMTP server
https://haraka.github.io
MIT License
5.09k stars 661 forks source link

Multiple outbound ip issue in 3.0.4 #3410

Closed gamalan closed 2 weeks ago

gamalan commented 1 month ago

Describe the bug

Before Haraka 3.0.4. Using multiple outbound could be set using get_mx_hook or using notes.outbound_ip. Which is helpful, because we could set it without messing the mx lookup in haraka itself. But in the haraka 3.0.4 it's being deprecated. While it's understandable to be deprecated, this also means we need to use get_mx_hook. @baudehlo give example in #442

var outbound = require('./outbound');

var i = 0;
var ips = ['127.0.0.1', '127.0.0.2', '127.0.0.3'];

exports.hook_get_mx = function (next, hmail, domain) {
    outbound.lookup_mx(domain, function (err, mxs) {
        if (err) return next(DENY, err);
        // TODO: decide which outbound IP to use. For argument I'll use round-robin here.
        mxs.forEach(function (mx) {
            mx.bind = ips[i];
            i++;
            if (i == ips.length) i = 0;
        });
        next(OK, mxs);
    })    
}

Which is somehow outbound.lookup_mx doesn't exist anymore in 3.0.4. While using notes.outbound_ip is still possible it also produce some error. Namely it fail to bind the socket first time, but it works in subsequent try, which introducing a delay in delivery process.

Sep 26 22:46:26 dev haraka[199655]: [ERROR] [A5940E5C-D15C-4ECC-8C72-3A60B5D22404.1.1] [outbound] notes.outbound_ip is deprecated. Use get_mx.bind instead!
Sep 26 22:46:26 dev haraka[199655]: [ERROR] [A5940E5C-D15C-4ECC-8C72-3A60B5D22404.1.1] [outbound] Failed to get socket: bind EINVAL 103.171.18.245
Sep 26 22:46:26 dev haraka[199655]: [ERROR] [A5940E5C-D15C-4ECC-8C72-3A60B5D22404.1.1] [outbound] notes.outbound_ip is deprecated. Use get_mx.bind instead!
Sep 26 22:46:26 dev haraka[199657]: [NOTICE] [EE34D140-3BF5-41A6-B694-ABD6E069CF2C] [core] connect ip=::1 port=37980 local_ip=::1 local_port=587
Sep 26 22:46:26 dev haraka[199655]: [INFO] [A5940E5C-D15C-4ECC-8C72-3A60B5D22404.1.1] [outbound] secured verified=true cipher=TLS_AES_256_GCM_SHA384 version=TLSv1.3 cn=mx.google.com organization="" issuer="Google Trust Services" expires="Nov 18 07:12:13 2024 GMT" fingerprint=E9:92:77:5E:10:16:BB:D6:79:E7:4D:18:0F:8A:C0:86:AA:B0:FD:61
Sep 26 22:46:27 dev haraka[199655]: [NOTICE] [A5940E5C-D15C-4ECC-8C72-3A60B5D22404.1.1] [outbound]  delivered file=1727405185980_1727405185980_0_199655_WGub1r_2_dev.kirimemail.com domain=gmail.com host=142.251.175.27 ip=142.251.175.27 port=25 mode=SMTP tls=Y auth=N response="OK  1727405187 d9443c01a7336-20b37e82821si10966165ad.606 - gsmtp" delay=1.8 fails=0 rcpts=1/0/0

My question is,

  1. Is this normal behavior when binding to specific ip, or there are issue in sockaddr library?
  2. What recommended binding to specific ip, in version 3.0.4 without messing too much with haraka internal get_mx process?

System Info

Please report your OS, Node version, and Haraka version by running this shell script on your Haraka server and replacing this section with the output.

Haraka Haraka.js — Version: 3.0.4/ae409b48
Node v20.17.0
OS Linux dev.kirimemail.com 5.10.0-32-amd64 #1 SMP Debian 5.10.223-1 (2024-08-10) x86_64 GNU/Linux
openssl OpenSSL 1.1.1w 11 Sep 2023
msimerson commented 1 month ago

Hi @gamalan , glad you noticed that deprecation warning. I was hoping it would surface someone(s) who actually used that feature. Answers:

  1. I don't know.
  2. The old functionality of lookup_mx is now handled by haraka-net-utils.get_mx. So you could instead do something like this:
const nu = require('haraka-net-utils')
let mxes = await nu.get_mx('gmail.com')
for const (mx of mxes) {
  mx.bind = 'X.X.X.X'  // maybe with conditional logic
}
// the rest of your code

Example 1

➜ node
Welcome to Node.js v22.7.0.
> const nu = require('haraka-net-utils')
> await nu.get_mx('gmail.com')
[
  HarakaMx {
    exchange: 'alt3.gmail-smtp-in.l.google.com',
    priority: 30,
    from_dns: 'gmail.com',
    bind_helo: 'home.simerson.net'
  },
  HarakaMx {
    exchange: 'gmail-smtp-in.l.google.com',
    priority: 5,
    from_dns: 'gmail.com',
    bind_helo: 'home.simerson.net'
  },
  HarakaMx {
    exchange: 'alt2.gmail-smtp-in.l.google.com',
    priority: 20,
    from_dns: 'gmail.com',
    bind_helo: 'home.simerson.net'
  },
  HarakaMx {
    exchange: 'alt4.gmail-smtp-in.l.google.com',
    priority: 40,
    from_dns: 'gmail.com',
    bind_helo: 'home.simerson.net'
  },
  HarakaMx {
    exchange: 'alt1.gmail-smtp-in.l.google.com',
    priority: 10,
    from_dns: 'gmail.com',
    bind_helo: 'home.simerson.net'
  }
]

One of the issues you might encounter is that you need to know if the remote IP is IPv4 or IPv6, and use that to choose which local IP you bind to. You can do that as well (something that's far too late to set a note for):

Example 2

> const nu = require('haraka-net-utils')
> const mxes = await nu.get_mx('gmail.com')
> const ips = await nu.resolve_mx_hosts(mxes)
> console.log(ips)
[
  {
    exchange: '2607:f8b0:4023:1004::1a',
    priority: 10,
    from_dns: 'alt1.gmail-smtp-in.l.google.com',
    bind_helo: 'home.simerson.net'
  },
  {
    exchange: '142.250.115.27',
    priority: 10,
    from_dns: 'alt1.gmail-smtp-in.l.google.com',
    bind_helo: 'home.simerson.net'
  },
  {
    exchange: '2607:f8b0:4001:c56::1b',
    priority: 30,
    from_dns: 'alt3.gmail-smtp-in.l.google.com',
    bind_helo: 'home.simerson.net'
  },
  {
    exchange: '142.250.152.26',
    priority: 30,
    from_dns: 'alt3.gmail-smtp-in.l.google.com',
    bind_helo: 'home.simerson.net'
  },
  {
    exchange: '2607:f8b0:400e:c03::1a',
    priority: 5,
    from_dns: 'gmail-smtp-in.l.google.com',
    bind_helo: 'home.simerson.net'
  },
  {
    exchange: '74.125.195.26',
    priority: 5,
    from_dns: 'gmail-smtp-in.l.google.com',
    bind_helo: 'home.simerson.net'
  },
  {
    exchange: '2607:f8b0:4003:c04::1a',
    priority: 20,
    from_dns: 'alt2.gmail-smtp-in.l.google.com',
    bind_helo: 'home.simerson.net'
  },
  {
    exchange: '108.177.104.27',
    priority: 20,
    from_dns: 'alt2.gmail-smtp-in.l.google.com',
    bind_helo: 'home.simerson.net'
  },
  {
    exchange: '2607:f8b0:4023:1::1b',
    priority: 40,
    from_dns: 'alt4.gmail-smtp-in.l.google.com',
    bind_helo: 'home.simerson.net'
  },
  {
    exchange: '172.253.113.27',
    priority: 40,
    from_dns: 'alt4.gmail-smtp-in.l.google.com',
    bind_helo: 'home.simerson.net'
  }
]

Does that help?

gamalan commented 1 month ago

@msimerson that helps. need to update our internal plugin then.