Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Conversation

Tey
Copy link

@Tey Tey commented Sep 21, 2025

When there is a proxy in front of tinyproxy, the latter does not see the actual IP address of the client. This is a problem when using tools like fail2ban that parse the tinyproxy log to find and ban clients that try to break the authentication using brute force. Some proxies like stunnel or HAProxy support the PROXY protocol to overcome that issue, so that the next proxy in the chain can tell what the IP address of the original client is.

See https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt

This commit adds support for clients that use the PROXY protocol to extract the real client IP address and report it in the logs. This is only implemented when the new ClientUsesProxyProtocol parameter is set to Yes. It should only be enabled when the client is trusted and known to be a proxy that support that protocol.

When there is a proxy in front of tinyproxy, the latter does not see the
actual IP address of the client. This is a problem when using tools like
fail2ban that parse the tinyproxy log to find and ban clients that try
to break the authentication using brute force. Some proxies like stunnel
or HAProxy support the PROXY protocol to overcome that issue, so that
the next proxy in the chain can tell what the IP address of the original
client is.

See https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt

This commit adds support for clients that use the PROXY protocol to
extract the real client IP address and report it in the logs. This is
only implemented when the new ClientUsesProxyProtocol parameter is set
to Yes. It should only be enabled when the client is trusted and known
to be a proxy that support that protocol.
@rofl0r
Copy link
Contributor

rofl0r commented Sep 21, 2025

reading the document, it appears the protocol is widely enough supported so it may make sense to support it too. however, some questions arise:

  • should acl and loop checks be done before or after the peer address is rewritten?
  • should conn_init_contents be called after the PROXY line is already retrieved?

as for the implementation presented:

  • only the client ip string seems to be updated in the current impl, but not the port
  • read_request_line is being repurposed to do 2 different things at once. imo there should be a separate function called specifically when the PROXY protocol is enabled, called from handle_request.
  • from code flow it would appear that the "Connect (file descriptor %d): %s" is currently printed with the frontend proxy's address, not the one passed in the PROXY line.
  • while efficient, the current implementation modifies the original string twice (adding and removing nul terminators), which is kinda frail and requires some cognitive load to decipher for correctness. also it raises the basically same error message in 2 places.
    so imo, the checking should be done once and the error raised immediately before modifying the string. we can for example compare whether it starts with "PROXY TCP" and then check the next char for '4' or '6' and scan the string for the right amount of spaces (eventually making a list of pointers or offsets of them) and only after it's checked proceed to the ip address field (and port, if it's needed) and only insert a NUL there.

also it needs to be documented that only v1 of the protocol is supported.

@Tey
Copy link
Author

Tey commented Sep 21, 2025

My main goal was the authentication failure log message, so I didn't think about the other things like ACL. What do you think of applying these changes:

  1. Move the PROXY line parsing logic into a new function like read_proxy_line().
  2. Call it just before the call to getpeer_information() in handle_connection(), so that it updates addr with the reported IP address of the client. That way, getpeer_information() will set peer_ipaddr to the reported IP address so all the log messages will use it and the ACL will be checked against it too.

I'm not sure it is useful to update the client port too. Is it ever used anywhere in the code?

I agree with the remarks about the implementation and will change it accordingly.

Tey added 2 commits September 21, 2025 21:30
Reading the PROXY line is the first thing done in handle_connection() so that
the client IP address is updated before it is needed by the rest of the code
(ACL, logging, etc.).

The parsing implementation is now less generic and updates the sockaddr_union
structure directly.
goto cleanup;
}

/*
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think the entire chomp block here is unneeded


#
# The tinyproxy's client is a proxy that uses the PROXY protocol, which
# inserts a line before the HTTP request line to indicates the IP
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/indicates/indicate/ , same for the manpage


/* Only extract the source address string */
src_addr = line + 11;
end = strchr (src_addr, ' ');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should check that end isn't NULL, else a malformed proxy line will crash the service

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, I was sure strchr() would return a pointer to the NUL character in this case, but was wrong.

goto cleanup;
}

is_ipv6 = line[9] == '6' ? TRUE : FALSE;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the use of the ternary here is redundant, is_ipv6 = line[9] == '6'; is doing the same.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

actually, might as well make that af = line[9] == '6' ? AF_INET6 : AF_INET; and use af further down to fill in the address family and for the inet_pton call.

*end = '\0';

/* Update "addr" with the given address */
if (is_ipv6) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the addr pointer coming is already zero-initialized so we could just unconditionally set addr->v4.sin_family to af as per my previous comment and call inet_pton with af and a ternary which sets pointer to either &addr->v6.sin6_addr or &addr->v4.sin_addr. there is actually a macro in sock.h doing just that: SOCKADDR_UNION_ADDRESS

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and we should add a comment to the func signature that addr needs to be zero-initialized.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

addr already stores the port and address of the other peer when read_proxy_line() is called, so it is not zero-initialized. Also, if the client proxy connects using IPv4 but the final client uses IPv6, then sin6_flowinfo will contain the proxy IPv4 address (sin_addr and sin6_flowinfo are located at the same offset), not 0.
On the other hand, tinyproxy only uses the family and address fields from sockaddr_union it seems, so it does not really matter if the other fields are wrong?
I'll make the changes for the other remarks.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in that case we can just memset it to all zeroes unconditionally.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants