"Give" an interface's public IP to another host with local IP

When the internal machine initiates an outbound connection, its IP is NATed as the gateway's main address, not the address I'm trying to give it.

This should be solved with:

iptables -t nat -I POSTROUTING 1 -s 10.125.0.2 -o eth0 -j SNAT --to-source <FORWARD_IP>

It is added into the front, so it'll fire first, detect that packet is from this system and translate it into the specified IP, instead of picking an address from interface.

When receiving connections, they appear to be coming from the gateway's local IP, not the original IP (there should be a way around that as this is the default gateway).

This issue is connected to the following SNAT rule:

iptables -t nat -A POSTROUTING -j MASQUERADE

It is way too wide. You are SNATing everything in each direction, which is not efficient nor secure. It matches everything, including your DNATed packets. They are first destination-translated into private address, then their source is translated by this rule into address picked from a private interface where 10.x.x.x address is assigned.

I suspect you are trying to cover all public interfaces and all private networks with a single rule. While there are address lists in the Linux (IP sets), there is no interface lists, so it is not possible to do this correctly with just a single rule.

Filter it with at least the source address (e.g. which addresses to source-translate):

iptables -t nat -A POSTROUTING -s 10.125.0.0/24 -j MASQUERADE

If you have several private networks, add more rules of this kind.

I think it is better to create an individual rule for each public interface, so packets going out through private interface could never get source-translated whatever source address they have, e.g. iptables -t nat -A POSTROUTING -s 10.125.0.0/24 -o eth0 -j MASQUERADE and so on. This way packets from 10.x.x.x to 172.x.x.x will not get translated too, and your services in both networks will see each other's IP directly, still leaving the possibility to selectively filter them in the FORWARD filter chain.

If a service is listening on 0.0.0.0 on the gateway, connections on that port do not get redirected, they go to that service.

And, finally, I don't fully understand this point. I asked in the comment, you didn't answer. Which connections, from where, what's source IP?

The DNAT processing happens before the routing decision, that's why the chain is called "PREROUTING". It never checks if there is some local process listening on that port. The only way the local service to have chance to answer is to have the packet pass INPUT chain. For that, the routing code must decide it is destined to the local machine, so it must be ether not translated at all or be translated into some address this system has assigned.

With current rules, packets sent from private networks to the public IPs should not get translated, because they aren't coming in via eth0. So they are to be served by local services. But this doesn't depend on whether the local service is running or not; if it's not you will have the "connection refused".

If you need packets from LAN to the <FORWARD_IP> to be translated by the same DNAT rule, drop that -i eth0 match:

iptables -A PREROUTING -d <REDACTED_FORWARDING_IP>/32 -j DNAT --to-destination 10.125.0.2

However, I am against such a wide rule. In my opinion, it is better to have DNAT rules as tight as possible, so you better filter by the protos (tcp/udp) and ports of the services you are running on the 10.125.0.2. If that's a web server, only forward tcp/80 and tcp/443, like this: iptables -A PREROUTING -d <REDACTED_FORWARDING_IP>/32 -p tcp -m multiport --dports 80,443 -j DNAT --to-destination 10.125.0.2.

What's the problem? Say, you're checking something with the ping. This ping result will depend on this internal system state, it is the system you are pinging in reality. This is confusing. This is not the behaviour most sysadmins are expecting to see when they ping a router. So better leave ping to be answered by the host.