New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
haproxy PROXY protocol support #49
Comments
Hi Iuri @iuri-gg great to hear from you :-) The In general I am interested to include that Cheers, Tom |
Hi Tom. Good to hear from you again too! I have the following information:
I was initially thinking to see how postfix (or another open-source SMTP server) implements it. The binary format (v2) is harder but the text format (v1) ended up being easy. I have not done the IP and port parsing yet though. Hope this helps. |
Hi Iuri, I checked the Proxy Protocol Doc but did not found enough SMTP related information yet. Just for my understanding of a configuration:
Is that the setup? When yes, then on a connection to the PROXY, it will use the Proxy Protocol and forward the traffic to the MidiSmtpServer instance. So then, we have to analyze the Are my assumptions correct? |
Yes Tom, your assumptions are correct.
then build the image and start the haproxy container:
this will forward SMTP traffic from port 3535 to port 2525. If you are on macOS your local IP in the proxy will not be localhost due to the internal networking of docker. I can also get you the SMTP log with and without a proxy if that will be helpful. |
Hey Iuri
yes, please - so I can have a first view. Thanks for the provided information. |
With haproxy. Note:
without haproxy:
and below is my naive implementation that works:
|
Iuri, could you dump the ctx once for both session please in addition? |
This is {
:server=>
{
:local_host=>"127.0.0.1",
:local_ip=>"127.0.0.1",
:local_port=>2525,
:local_response=>"smtp-test says welcome!",
:remote_host=>"172.17.0.1",
:remote_ip=>"172.17.0.1",
:remote_port=>"32808",
:helo=>"ms-invite.eml",
:helo_response=>"smtp-test at your service!",
:connected=>2023-07-23 18:01:24.903204 UTC,
:exceptions=>0,
:errors=>[],
:authorization_id=>"",
:authentication_id=>"",
:authenticated=>"",
:encrypted=>2023-07-23 18:01:25.026792 UTC
},
:envelope=>
{
},
:message=>
{
}
} without {
:server=>
{
:local_host=>"127.0.0.1",
:local_ip=>"127.0.0.1",
:local_port=>2525,
:local_response=>"smtp-test says welcome!",
:remote_host=>"127.0.0.1",
:remote_ip=>"127.0.0.1",
:remote_port=>63129,
:helo=>"ms-invite.eml",
:helo_response=>"smtp-test at your service!",
:connected=>2023-07-23 18:02:30.261189 UTC,
:exceptions=>0,
:errors=>[],
:authorization_id=>"",
:authentication_id=>"",
:authenticated=>"",
:encrypted=>2023-07-23 18:02:30.304146 UTC
},
:envelope=>
{
},
:message=>
{
} |
Hey Iuri, thanks for adding all the information. As a first note and reply:
But in any case - yes - I like to add the proxy feature as "in library feature". I prepare an PR and come up with a review to you. Thanks for that enhancement idea! Tom |
Thanks Tom. I like the idea of adding proxy information to context instead of replacing existing info. speaking of adding proxy info - there can be multiple proxies chained together which results adding extra proxy lines (they come in sequence). Just FYI. Please let me know how I can help with this. I can test the implementation with multiple proxies and check IPs whenever needed. |
Good evening Iuri, could you please create a log for a chain of e.g. 3 PROXIES is it like? :
|
actually, it is inverted. below is the log:
172.21.0.4 is listening on 5555 client (172.21.0.1) -> proxy1 (172.21.0.4:5555) -> proxy2 (172.21.0.3:4444) -> proxy3 (172.21.0.2:3333) -> smtp server (172.21.0.1:2525) |
Hi Iuri, thanks for the log. Just to make it sure:
the What ist your server = MySmtpd.new(ports: '2525', hosts: '172.21.0.1') Normally it should also show up the correct ip. |
Since I am using docker on macOS, some network translation is happening when going from within the docker to the host. If the SMTP server was running inside the docker container, then you would not see 127.0.0.1. Basically, host IP is 127.0.0.1 on the macOS side and 172.21.0.1 in the docker containesr - both IPs point to the same host.
hosts argument is |
Got it Iuri, thanks. |
Hey Iuri @iuri-gg I just started with implementation Here is the comment of the processing: # Documentation at https://github.com/haproxy/haproxy/blob/master/doc/proxy-protocol.txt
# syntax
# PROXY TCP? source-ip dest-ip source-port dest-port
# supported commands:
# PROXY TCP4 255.255.255.255 255.255.255.255 65535 65535
# PROXY TCP6 ffff:f...f:ffff ffff:f...f:ffff 65535 65535
# PROXY UNKNOWN
# PROXY UNKNOWN ffff:f...f:ffff ffff:f...f:ffff 65535 65535 Question: When reading documentation we will receive a number of PROXY command lines and allways the given In case of a chain of PROXY commands the Correct? |
Hey @TomFreudenberg. That is correct - the last Also, I figured that we don't need to return anything for |
Hi @iuri-gg I would reply the standard SMTP OK signal
|
An this are the possible returns: # 250 Requested mail action okay, completed
# 421 <domain> Service not available, closing transmission channel
# 500 Syntax error, command unrecognised
# 501 Syntax error in parameters or arguments |
I think |
I will merge the feature branch already while we are stil finishing that |
Hey @iuri-gg had you the time to check last commit if that fit's now and runs your tests? For me it is quit ready to push as this implementation is feature ready. Thanks for your feedback. Tom P.S.: Anything to add for the documentation?
|
Hey @TomFreudenberg. I just tested and both SSL and plain connections worked. However, I found 1 more edge case that might not be a big deal. If I disable |
Hey @iuri-gg Thanks, I will check that |
@iuri-gg that is the involved source: If you could simulate the client once with haproxy proxy between and pipelining disabled. Please just use a telnet session as client and just try telnet localhost port
# inside session just use
ehlo test will that raise pipelining exception as well already? |
no proxy:
3 proxies:
server log:
1 proxy:
server log:
|
when there are multiple proxies chained together, each proxy adds |
Thanks for testing Iuri. I will think about it. It needs a new command sequence on multiple proxies or enabling pipelining. Not sure what I like more ... |
happy to be of help!
Yes, I'm not sure either. Is pipelining backward compatible? If clients without pipelining support can still use servers with pipelining enabled, then maybe it should just be enabled all the time on the server. |
As you may read here https://midi-smtp-server.readthedocs.io/appendix_security/ Pipelining allows injection and for that I left it disabled. So that's why I don't like to enable Pipelining for proxy support |
@TomFreudenberg I was digging more into the proxy configuration I have set up and I think I have a solution. I think I had wrong proxy configuration - there should only be 1 PROXY command to start with. My configuration for proxy chaining was incorrect - I was missing I added I believe this is the correct behavior and we should not process more than 1 PROXY command and we should error out if more than 1 PROXY command is received. Otherwise, client can send their own PROXY commands and spoof the IP and prevent IP filtering or rate limiting on the server side. To summarize, if we only allow 1 PROXY command, we don't have issue with pipelining, we don't have issue with client spoofing IP through Sorry for the wild goose chase! |
Hey Iuri @iuri-gg just that I understand you correctly: There is no chain of Proxy just 1. If getting more than 1 Proxy command, there is a config error in the underlaying system. Is that the short message? |
Yes. That’s correct. |
Hey Iuri @iuri-gg, I read about the exim implementation and copy the specific content here: ---<<< from: https://www.exim.org/exim-html-current/doc/html/spec_html/ch-proxies.html 1. Inbound proxiesExim has support for receiving inbound SMTP connections via a proxy that uses “Proxy Protocol” to speak to it. To include this support, include “SUPPORT_PROXY=yes” in Local/Makefile. It was built on the HAProxy specification, found at https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt. The purpose of this facility is so that an application load balancer, such as HAProxy, can sit in front of several Exim servers to distribute load. Exim uses the local protocol communication with the proxy to obtain the remote SMTP system IP address and port information. There is no logging if a host passes or fails Proxy Protocol negotiation, but it can easily be determined and recorded in an ACL (example is below). Use of a proxy is enabled by setting the hosts_proxy main configuration option to a hostlist; connections from these hosts will use Proxy Protocol. Exim supports both version 1 and version 2 of the Proxy Protocol and automatically determines which version is in use. The Proxy Protocol header is the first data received on a TCP connection and is inserted before any TLS-on-connect handshake from the client; Exim negotiates TLS between Exim-as-server and the remote client, not between Exim and the proxy server. The Proxy Protocol header must be received within proxy_protocol_timeout, which defaults to 3s. The following expansion variables are usable (“internal” and “external” here refer to the interfaces of the proxy):
If $proxy_session is set but $proxy_external_address is empty there was a protocol error. The variables $sender_host_address and $sender_host_port will have values for the actual client system, not the proxy. --->>> In case of their (exim) variables they also support only ONE Proxy data information stored for the configuration. But when I read the original haproxy protocol L779-781 ---<<< Firewall FW1 receives traffic from internet-based clients and forwards it to --->>> I wonder what they mean there by the last part "and to emit it on output". Does ist mean to create a second PROXY command line or does it mean: put the source address from any previous incoming PROXY to output. Any suggestions on this? |
Here is the exim commit for first time experimental implementation of proxy protocol. https://lists.exim.org/lurker/message/20150505.115516.e01e0a1e.el.html |
Maybe we should align with that implementation for PROXY. The only difference I like, would be that the PROXY command in general is optional and not mandytory when enabled. For the reason, that the server could be reachable internal directly under the same internal address. |
Hi @iuri-gg please check updated master and documentation I guess this should fit now. Thanks for testing and feedback Tom |
To make it clear: following rules exist now for PROXY if not enabled by proxy_extension:
if enabled by proxy_extension
documented at: https://midi-smtp-server.readthedocs.io/feature_proxy/ |
Thank you @TomFreudenberg. This is great. |
Hey @iuri-gg everything ok from your side and ok to close and release it? Thanks for a short feedback. Tom |
Yes @TomFreudenberg - everything looks good. Tested with and without proxy, both pipelining on and off. I also tested that 2 proxy commands raised an error. |
Thanks for testing and feedback Iuri So I close here and proceed for a new release! Cheers |
Done! |
Hey @iuri-gg please add yourself to the contributors list and send me a PR https://github.com/4commerce-technologies-AG/midi-smtp-server/blob/master/CONTRIBUTORS.md Would like to see your credits there! Thanks for help |
Hey @TomFreudenberg. My previous handle was |
Is your feature request related to a problem? Please describe.
I want to add use
haproxy
for load-balancing and high availability (multiple servers)Describe the solution you'd like
I want server to handle
PROXY
command and update local and remote IPs, Ports, and hostnames accordingly.Describe alternatives you've considered
I was able to make it work by overriding
on_process_line_unknown_event
. What is left is to parse IPv4 or IPv6 addresses and ports. However, I am not sure it is a proper solution for handling and setting remote and local ips, ports and hostnames. I was wondering if it should be handled within the library itselfThe text was updated successfully, but these errors were encountered: