yrutschle / sslh

Applicative Protocol Multiplexer (e.g. share SSH and HTTPS on the same port)
https://www.rutschle.net/tech/sslh/README.html
GNU General Public License v2.0
4.52k stars 367 forks source link

[SOLVED] Transparent to Nginx on the same host + two different backend-servers (timeout) #371

Closed netw0rk-noob closed 2 months ago

netw0rk-noob commented 1 year ago

I've tried to implement / adapt the tutorial by Sean Warner (https://github.com/yrutschle/sslh/blob/master/doc/tproxy.md#transparent-proxy-to-two-hosts) to my needs without success. The network / forwarding setup described below works perfectly fine with the transparent option disabled, but that makes it impossible to use services like fail2ban (at least for the backend servers as far as I understand).

Three (unprivileged) lxc containers on my Hypervisor (Proxmox Virtual Environment):

My Network setup, described as in Seans Tutorial: SSLH

contents of my /etc/sslh.cfg (works fine if I set transparent to false):

verbose: 2;
foreground: false;
inetd: false;
numeric: true;
transparent: true;
timeout: 2;
user: "root";
pidfile: "/var/run/sslh/sslh.pid";
syslog_facility: "auth";

listen:
( 
    { host: "192.168.178.143"; port: "443"; } 
);

protocols:
(
    { name: "tls"; host: "192.168.178.130"; port: "5223"; alpn_protocols: [ "xmpp-client" ]; log_level: 1;},
    { name: "openvpn"; host: "192.168.178.145"; port: "443"; log_level: 1;},
    { name: "tls"; host: "localhost"; port: "4443"; log_level: 1;},
    { name: "anyprot"; host: "localhost"; port: "4443"; log_level: 1;}
);

on-timeout: "timeout";

Regarding the user: option I tried using user: "root"; and user: "sslh"; and got the exact same results.

contents of /usr/local/sbin/set-sslh-iptables-rules.sh on LCX A (runs without complaining)

iptables -t mangle -N SSLH
iptables -t mangle -A PREROUTING -p tcp -m socket --transparent -j SSLH
iptables -t mangle -A OUTPUT --protocol tcp --out-interface eth0@if448 -m multiport --sport 443,4480 --jump SSLH
iptables -t mangle -A SSLH --jump MARK --set-mark 0x1
iptables -t mangle -A SSLH --jump ACCEPT
ip rule add fwmark 0x1 lookup 100
ip route add local 0.0.0.0/0 dev lo table 100

contents of /usr/local/sbin/set-sslh-iptables-rules.sh on LXC B (runs without complaining)

iptables -t mangle -N SSLHSSL
iptables -t mangle -A OUTPUT -o eth0 -p tcp -m multiport --sport 5223 -j SSLHSSL
iptables -t mangle -A SSLHSSL --jump MARK --set-mark 0x1
iptables -t mangle -A SSLHSSL --jump ACCEPT
ip rule add fwmark 0x1 lookup 100
ip route add default via 192.168.178.143 table 100
ip route flush cache

I changed the interfaces, the ports of the respective services and the LAN ip addresses to suit my network setup. (same with LXC C...)

The behaviour I'm seeing with this setup: HTTPS-Requests to my backend webservers through Nginx are working, including transparency. example output of journalctl --unit sslh.service --reverse when accessing one of my webpages:

Dec 10 20:53:44 LXC-A sslh[446]: tls:connection from <IP>:52552 to 192.168.178.143:443 forwarded from <IP>:52552 to 127.0.0.1:4443
Dec 10 20:53:44 LXC-A sslh[446]: connecting to 127.0.0.1:4443 family 2 len 16
Dec 10 20:53:44 LXC-A sslh[446]: probing for tls: PROBE_MATCH
Dec 10 20:53:44 LXC-A sslh[446]: probing for openvpn: PROBE_NEXT
Dec 10 20:53:44 LXC-A sslh[446]: probing for tls: PROBE_NEXT
Dec 10 20:53:44 LXC-A sslh[446]: Unknown ALPN name: http/1.1
Dec 10 20:53:44 LXC-A sslh[446]: matching [http/1.1] with [xmpp-client]

Requests to the other LXCs (in this case prosdoy / LXC B, but I get the same response with openvpn / LXC C) however are timing out, though being detected correctly. No connection to prosody is being established, according to its verbose log. (I'm using this tls probe for XMPP, because the xmpp probe seems to only detect STARTTLS-encrypted connections, no direct TLS-encrypted ones. It works when used without transparency.) example output of journalctl --unit sslh.service --reverse when trying to connect to my prosody server:

Dec 10 20:39:09 LXC-A sslh[379]: connection closed down
Dec 10 20:39:09 LXC-A sslh[379]: client socket closed
Dec 10 20:38:39 LXC-A sslh[382]: connecting to 192.168.178.130:5223 family 2 len 16
Dec 10 20:38:39 LXC-A sslh[382]: timed out, connect to tls
Dec 10 20:38:37 LXC-A sslh[381]: connecting to 192.168.178.130:5223 family 2 len 16
Dec 10 20:38:37 LXC-A sslh[381]: probing for tls: PROBE_MATCH
Dec 10 20:38:37 LXC-A sslh[381]: matching [xmpp-client] with [xmpp-client]

Tutorials/Docs I found when searching for a solution and tried to adapt/implement: https://community.hetzner.com/tutorials/prosody-debian9#optional-advanced-features https://wiki.mattrude.com/SRV_records_for_XMPP_over_TLS#Configuring_SSLH_for_XMPP_and_HTTPS_Traffic

According to yet another tutorial I found online I tested if anything of that connection even reaches LXC B: output of netstat -nat | grep 5223 on LXC A:

tcp        0      1 <IP>:46200     192.168.178.130:5223     SYN_SENT
tcp        0      1 <IP>:46194     192.168.178.130:5223     SYN_SENT
tcp        0      1 <IP>:46198     192.168.178.130:5223     SYN_SENT
tcp        0      1 <IP>:46204     192.168.178.130:5223     SYN_SENT
tcp        0      1 <IP>:46192     192.168.178.130:5223     SYN_SENT
tcp        0      1 <IP>:46206     192.168.178.130:5223     SYN_SENT

output of netstat -nat | grep 5223 on LXC B:

tcp        0      0 0.0.0.0:5223             0.0.0.0:*      LISTEN
tcp        0      0 192.168.178.130:5223     <IP>:46200     SYN_RECV
tcp        0      0 192.168.178.130:5223     <IP>:46198     SYN_RECV
tcp        0      0 192.168.178.130:5223     <IP>:46206     SYN_RECV
tcp6       0      0 :::5223                  :::*           LISTEN

To me (as an absolute networking noob) this looks as if the connection somehow does reach LXC B but doesn't reach prosody (which - with verbose logging enabled - does not register anything on connection try). I tried this via mobile phone network (to have a public IP which is different than the one the server is running on) and did see that IP in the oputput of netstat -nat | grep 5223 though, so I'd say transparency seems to work, which would point to a routing/firewalling problem on LXC B? Still the XMPP Client isn't able to connect. I tried to understand/analyze the iptables/routing commands to be able to alter them/try around, but as I've got only very limited network knowledge and nearly no experience with plain iptables at all I didn't manage to.

Reasons I suspect why it doesnt work, but didn't accomplish to confirm neither rule out by myself:

EDIT: To rule out two of these reasons as the cause of my problem I did compile the current version (sslh-fork v2.0-rc1-36-g40c616e-dirty) inside of a freshly created virtual machine using the same Makefile options Sean documented in his tutorial - still the same problem: Connection timed out. Therefore I'd pin it down to some sort of iptables / routing misconfiguration.

I'd really appreciate someone with a better understanding of networking, routing and iptables to take a look at this setup to see if I did any obvious mistakes trying to adapt Seans setup for my usecase. If any needed information is missing, please ask for it and I will add it. Thanks for any hint, as I'm at my wit's end right right now.

kganczarek commented 1 year ago

@netw0rk-noob hi, where you able to figure out how to configure it?

netw0rk-noob commented 6 months ago

@kganczarek Nope, After trying several times without any success I gave up on it. And as appearently no one cares that the documentation for this feature (transparency) is leading to a broken state (my issue is by far not the only one describing this problem) I guess its time to look for alternatives.

yrutschle commented 6 months ago

appearently no one cares

Thanks for the kind feedback. As indicated in the documentation, basically all transparent proxying issues posted here are not in fact issues with sslh, rather with the networking setup around it, and indeed no one seems to understand the iptables setup well enough to explain how or why it should work.

The documentation included in the repo is something that worked for someone at some point. As also indicated in the documentation, there are plenty of factors that might require changes to the setup.

With that in mind, the only approach I can suggest is:

All that being said, please post back if you find a working alternative (as I think the issue is not with sslh: if you find a way to set up transparent proxying with something else, it might very well work with sslh as well, and as you noticed, I'm happy to add to the current documentation)

netw0rk-noob commented 6 months ago

Thanks for the kind feedback.

Youre welcome. /s (Pardon my sarcasm. I'll conclude this with the reasons for my bitterness which lead to that tone.)

As indicated in the documentation, basically all transparent proxying issues posted here are not in fact issues with sslh, rather with the networking setup around it, […]

Honest, non-polemic question: Dont you think that listing / explaining which network setup requirements are necessary for one of the "advertised" features to work should be an essential part of the documentation for a networking tool like a port multiplexer?

and indeed no one seems to understand the iptables setup well enough to explain how or why it should work.

Assuming youre right and (almost) all of the transparent-mode-issues (which - after a quick glance - are approximately half or slightly less than half of the open issues) are iptables configuration issues: Shouldnt that be one more reason to clearly state the requirements to use that feature - ideally in a way even newbies like myself can grasp?

When I read

sslh has the bells and whistles expected from a mature daemon: privilege and capabilities dropping, inetd support, systemd support, transparent proxying, chroot, logging, IPv4 and IPv6, TCP and UDP, a fork-based, a select-based model, and yet another based on libev for larger installations.

that doesnt imply that setting this up requires a masters degree in network engineering or 30 years of professional experience in that field. It also does not imply that this feature only works for some people at some iptables-/sslh- and kernel-versions if they happen to have the perfectly compatible network setup which ones definition is nowhere to be found.

At least nowadays the "configuration" docs state

Set up can get complicated, so it has its own document.

(which - considering the amounts of issues from people failing to succeed - is a massive understatement, btw.) I cant tell for sure if that already was stated in the docs when I failed to set it up.

Reproduce exactly Sean's setup. Does not work? Maybe has something to do with the versions (not sslh' s version: iptables, kernel, and everything around it).

As you probably know if youve read my original issue I actually tried to reproduce his setup as closely as possible without having to rebuild my entire network. I did that to try making debugging as easy as possible. I even replicated his network diagram to point to the similarities between his original build and mine.

If that works, move it to unprivileged LXC. […]

As you may also have read in my issue I already tried replacing the nginx/sslh reverse-proxy lxc container with a full blown vm - to no avail.

All that being said, please post back if you find a working alternative (as I think the issue is not with sslh: if you find a way to set up transparent proxying with something else, it might very well work with sslh as well, and as you noticed, I'm happy to add to the current documentation)

Thats the main problem/point which caused my last reaction to be as cynical as it was.

Regarding the tone of my statement:

I found this tool - which, if you leave out the transparency option / its documentation - is a great and easy to use piece of software which I am (believe it or not) grateful for, as it solves the need of many homelabbers/selfhosters: Only having one public ipv4 address but several services they want to expose on :443. What I didnt notice at the time was the fact that one of the described features (transparent mode, to be specific) is an experimental one (or call it professionals-only if you prefer) which most of the potential users of sslh will have trouble and probably fail setting up. Im well aware of my lack of knowledge in this sphere, so if there would have been such a clear disclaimer I wouldnt have wasted literal days of trying around/debugging, starting again from scratch. A suggestion for such a disclaimer:

If you plan to use the transparent proxying option, be aware that configuring it requires in-depth iptables/routing knowledge and depends heavily on your specific network setup which is why we dont offer a general guide on how to configure your systems to get this option working, but only user-supplied config examples of specific setups which may only work in identically setup networks.

Edit: The Disclaimer on the "tproxy.md" page imho goes into the right direction:

Note that getting this to work is very tricky and detail-dependant: depending on whether the target server and sslh are on the same machine, different machines, or different dockers, and tool versions, all seem to change the required network configuration somewhat. If it doesn't work, it's almost certain that the problem is not linked to sslh but to the network setup that surrounds it. If in trouble, it might be worth trying to set up the network rules with a simpler server than sslh, such as socat

Im not sure if that did exist in that form when I failed to configure it. With that disclaimer in mind - ideally more prominent in the general readme - I wouldnt have bothered trying.

I took some time (probably longer that I would like to admit) to create an informative issue report to make it as easy as possible for you (or one of the few lucky sslh users who got transparent mode working on their own) to understand what I did and subsequently give me any hint on if and if yes where I made a (sslh/iptables/sysctl) configuration mistake. I totally get that you dont have the time to / want to help debugging issues which whole content consists out of "transparent mode does not work. Why?", which is why I on my end tried very hard to lower the bar of effort as much as possible to take a look at my setup.

Besides that I clearly stated that I myself am out of ideas on how to proceed with debugging/testing this on my own and asked for help to further progress. I even did so in a wildly polite manner:

I'd really appreciate someone with a better understanding of networking, routing and iptables to take a look at this setup to see if I did any obvious mistakes trying to adapt Seans setup for my usecase. If any needed information is missing, please ask for it and I will add it. Thanks for any hint, as I'm at my wit's end right right now.

If you dont want people to ask configuration questions in the issues - which imho would be a totally legitimate stance - please add a disclaimer to the "issues" section which clearly states that, as other projects with much more issues have actually done. If such a disclaimer would have been present I wouldnt have wasted time and effort to create an issue to no avail which - as you (probably rightly so) claim - was only caused by a misconfiguration on my side.

If my harsh criticism offended you, Im sorry for that. I just ask you to try understanding my perspective on this situation: Me taking days and days trying to figure this out (finding at best slight warnings about the required skills), investing effort in trying to create an informative feature request / question and getting no answer (by anyone) whatsoever in roughly 15 months while on the other hand I see that at least you do read these issues - because you answered after my harsh criticism - but dont bother to help debugging them(?)

I'm sorry to say that, but to me the only reasonable explaination to everything I described in the last few paragraphs is that you dont care if people are actually able to use the features your software does offer. Feel free to correct my point of view if Ive missed something.

yrutschle commented 6 months ago

Thanks for the long and constructive feedback (this time with no sarcasm).

I won't address all your points, as I have still nothing to add to the technical parts... Regarding some others (re-ordered for coherence of my discourse):

You explained the thought process that brought you to your state of mind. I have to clarify mine when I read a transparent proxy issue: "here I am, a C programmer progamming C. Oh, here is yet another transparent proxy issue [As you noted, probably about half of posted issues are tproxy ones] where I don't understand the first thing about that business of marking packets. Well at least this one has a lot of technical details, hopefully someone can help. Oh, here's a shiny issue with a segfault, I know C, I'll do that!"

Your non-polemic question is an interesting one. Clearly there is a gradation of whether issues belong on this specific Github. One end of the spectrum is "sslh crashes when it receives two connexions at the same time" (clearly belongs). The other end of the spectrum is "I unzipped the code on a Win98 machine, which we still use in my factory, and when I type make it fails." (clearly does not belong).

In that spectrum, I'd say "internal configuration" ("how do I create a SNI-matching rule with globs?") does, "external configuration" ("where do I change configuration on RedHat Linux?") doesn't. The transparent proxy stuff is... well I don't know. It mostly doesn't bother me that the issues are there, but mostly I can't answer them. More to the point, I am not sure what a better place to ask would be, so... why not here.

Now, I understand your frustration at the documentation. It's very possible you embarked upon your configuration trip when the disclaimers were not present, and I added them precisely faced with the stream of configuration issues posted on that topic. What I can do, is indeed clarify the disclaimer in config.md indicated that it's not merely "complicated". Building from your suggestion:

Configuration of transparent proxying is highly dependent on your network environment and infrastructure setup. There is no known generic approach, and if you do not find directions for your exact setup, you will probably need an extensive knowledge of network management and iptables setup".

I would say the disclaimer in tproxy.md is enough.

I'm unsure about removing "transparent proxying" from the feature list. As I said (and I'll stand by that), sslh itself supports it (the C is actually simple and the underlying concept is easy to understand from the application point of view: you just bind your local socket to the foreign address) and it's the supporting environment that's causing problems. You wouldn't argue to remove "capabilities dropping" because your operating system didn't support capabilities...

As it is, I propose to add the disclaimer above in the README and leave 'transparent proxying' as an advertised feature.

Thanks for the time you are taking to write on this issue, anyhow.

netw0rk-noob commented 6 months ago

[…] here I am, a C programmer progamming C. Oh, here is yet another transparent proxy issue [As you noted, probably about half of posted issues are tproxy ones] where I don't understand the first thing about that business of marking packets. Well at least this one has a lot of technical details, hopefully someone can help. […]

[…] The transparent proxy stuff is... well I don't know. It mostly doesn't bother me that the issues are there, but mostly I can't answer them. […]

I just assumed that you inevitably had to have a great deal of knowledge in this field to be able to write/maintain a networking tool like sslh. My conclusion (that you regularly didnt answere these kind of issues because you didnt care) was mostly based on that rationale. Considering your above statement that assumption has probably been false, and with it the conclusion build on it. Therefore I apologize for drawing that false conclusion and the resulting accusation.

[…] What I can do, is indeed clarify the disclaimer in config.md indicated that it's not merely "complicated". Building from your suggestion:

As it is, I propose to add the disclaimer above in the README and leave 'transparent proxying' as an advertised feature.

I'm unsure about removing "transparent proxying" from the feature list. […]

I would prefer to have the condensed disclaimer (which Im fine with) in the (main) README.md instead of the config.md as the supposedly easy availabillity of that feature could be one of the reasons (it surely was for me) to implement sslh. Hence in my opinion it would be helpful to be able to notice the possible trouble with its configuration at first glance. Based on the two contradictory statements regarding where to place that disclaimer Im not sure where you would prefer to place it.

Regarding third quoted statement: I actually didnt mean to suggest or even demand to take transparent proxying off the feature list, as it indeed does seem to work for some people. I would rather propose to link from transparent proxying (in that list) to that new disclaimer so people curious about that feature would quickly see what it takes to get that working.

Thanks for the time you are taking to write on this issue, anyhow.

Youre welcome - this time without any sarcasm. I neither like unresolved conflicts nor to critizise/attack people unjustified which is why I tried to resolve this situation when I noticed your rather calm response to my cynic complaint. So thank you for your understanding of my perspective and for explaining yours to me and thereby correcting my false assumptions.

yrutschle commented 5 months ago

Disclaimer added in 7ca567f.

I just assumed that you inevitably had to have a great deal of knowledge in this field to be able to write/maintain a networking tool like sslh.

No, programming C servers and advanced networking are really two different jobs. I also have shortcomings in modern containerisation technologies (notably Docker), and continuous testing pipelines. Of all these, it's the latter I'm most likely to spend time on, but it's also linked to Docker, and just too much to pick up as a side project for the moment...

yrutschle commented 3 months ago

Someone found another way to provide transparent proxying, which does not rely on iptables and seems quite a little more straightforward. It's been commited in 8271db2, you may want to check it out (I haven't tried it yet).

netw0rk-noob commented 3 months ago

Thank you very much for the follow-up, @yrutschle. That doc (https://github.com/yrutschle/sslh/blob/master/doc/simple_transparent_proxy.md) does indeed look promising.

I tried setting it up for my network (using a vm instead of lxc for the reverse proxy because of the need to configure /etc/network/interfaces) and succeeded for local connections (the nginx on the same vm as sslh logs the correct IP addresses). I didnt manage to setup transparent proxying to backend hosts though. Reading the explanation below Remote Setups I came up with the follow (probably very illiterate, please excuse my lack of knowledge) questions. (Maybe @ftasnetamot could elaborate further on that matter or - even better - describe an example?)

This concept can also be adapted for several setups, where the sshd (or any other target service) is running in a container, kvm-virtual machine, etc. Precondition is, that the target system is the next hop and uses the sslh-hosting system as default gateway.

In addition you need to bind an additional ip-address, solely used for sshd on the corresponding interface.

Than you can adapt the routing rule, routing traffic coming back from this ip to the sslh-routing-table.

In this case, you need to add a special route back to the sslh host, for all traffic with the sshd source ip address. This can be done similar to the two rules described above: # first define a name for the table in /etc/iproute2/rt_tables e.g. sslh-routeback ip rule from IPADRESS-OF-SERVIE table sslh-routeback ip route add default via IPADDRESS-OF_SSLH-HOST dev eth0 #or other

I assume that this is actually pretty simple once understood - as are many topics. I just didnt get it yet. I would very much appreciate an example configuration.

ftasnetamot commented 3 months ago

I added another document, where I try to show in three scenarios all possible solutions. All interfaces you need, are shown in the examples. I can't tell you anything about your ip addresses, but the core rule to all scenarios is: the hidden services, I refer to them as sshd in all examples, need a unique ip address, which is not used for any other services. As long, as the hidden services are on the same host, residing on dummy0, a /32 ip-address is the solution. In case you are routing to another host, you need either a second ip from the network, the interface resides, where the target services are binding to. Or you can setup a /30 network with any addresses from the private ranges, where you have two host besides network and broadcast. But that is all ip and routing basics, and has nothing to do with sslh. So from my side: You need a unique ip address, which can be connected from the sslh host. In your opening example from this thread, you need TWO uniqe ip addresses, for prosody and openvpn. When your hosts are not connected on other ways to the internet, both will be scenario 2. You need nothing special on those hosts, but the unique ip for the services.

in the sslh host you need now THREE instead TWO entries for deflection.

ip rule add from PROSODY_ADDRESS/32 table sslh
ip rule add from OPENVPN_ADDRESS/32 table sslh
ip route add local 0.0.0.0/0 dev lo table sslh

Thats all!

netw0rk-noob commented 3 months ago

Thank you very much for trying to explain this to a network (and especially iptables/netfilter) illiterate dummy like me, @ftasnetamot

I tried following your advise (only with openvpn for now, will continue with prosody once I get this up and running), set up a routing table on the reverse proxy as follows: /etc/iproute2/rt_tables

#
# reserved values
#
255     local
254     main
253     default
0       unspec
#
# local
#
#1      inr.ruhep
111     sslh

I've set up IP addresses as follows: /etc/network/interfaces on the reverse-proxy vm (loopback omitted)

# The primary network interface
allow-hotplug ens18
iface ens18 inet dhcp

auto dummy0
iface dummy0 inet static
    address 192.168.255.254/32
    pre-up modprobe dummy
    ## Attention! with kernels, not automatically creating a dummy0
    ## interface after module loading the following line should be:
    pre-up modprobe dummy; if [ ! -e /sys/class/net/dummy0 ]; then ip link add dummy0 type dummy ; fi
    post-up ip rule add from 192.168.255.254 table sslh
    post-up ip route add local 0.0.0.0/0 dev dummy0 table sslh
    pre-down ip route del local 0.0.0.0/0 dev dummy0 table sslh
    pre-down ip rule del from 192.168.255.254 table sslh

auto ens18:0
iface ens18:0 inet static
    # sslh subnet: 192.168.0.0/30
    address 192.168.0.1
    netmask 255.255.255.252
    # OpenVPN
    post-up ip rule add from 192.168.0.2/32 table sslh
    post-down ip rule del from 192.168.0.2/32 table sslh

/etc/network/interfaces on the openvpn VM (loopback omitted)

# The primary network interface
allow-hotplug ens18
iface ens18 inet dhcp

auto ens18:0
iface ens18:0 inet static
    address 192.168.0.2
    netmask 255.255.255.252
    gateway 192.168.0.1

These two IP addresses can ping each other successfully, I also succeeded when trying to communicate with netcat over port 443.

I enabled ip routing / kernel forwarding: sysctl -w net.ipv4.ip_forward=1

I adapted my /etc/sslh.cfg to the new addresses:

verbose: 2;
foreground: false;
inetd: false;
numeric: true;
transparent: true;
timeout: 2;
user: "sslh";
pidfile: "/var/run/sslh/sslh.pid";
syslog_facility: "auth";

listen:
(
        { host: "192.168.178.143"; port: "443"; }
);

protocols:
(
        { name: "openvpn"; host: "192.168.0.2"; port: "443"; log_level: 1;},

        { name: "tls"; host: "192.168.255.254"; port: "443"; log_level: 1;},

        { name: "anyprot"; host: "192.168.255.254"; port: "443"; log_level: 1;}
);

Output of netstat -tulpen on the reverse-proxy vm:

[…]
tcp        0      0 192.168.178.143:443     0.0.0.0:*               LISTEN      0          14891      570/sslh           
tcp        0      0 192.168.255.254:443     0.0.0.0:*               LISTEN      0          14374      497/nginx: master p
tcp        0      0 0.0.0.0:80              0.0.0.0:*               LISTEN      0          14373      497/nginx: master p
[…]

And finally I changed the binding IP address of openvpn on the openvpn vm. /etc/openvpn/server.conf

[…]
local 192.168.0.2
[…]

Output of netstat -tulpen on the openvpn vm:

[…]
tcp        0      0 192.168.0.2:443         0.0.0.0:*               LISTEN      0          14296      412/openvpn
[…]

At this point this should be working, according to my understanding of your explanation.

But while connections forwarded to the local nginx (192.168.255.254:443) do work transparently (nginx access log logs public ips) connections forwarded to openvpn (192.168.0.2:443) time out. journalctl -fu sslh.service while trying to connect to the openvpn service:

[…]
[…] probing for openvpn: PROBE_MATCH
[…] connecting to 192.168.0.2:443 family 2 len 16
[…] forward to openvpn failed:connect: Connection timed out
[…] sslh-fork.c:110:connect: Connection timed out

tail -F /var/log/openvpn.log on the openvpn vm doesnt show anything while trying to connect.

netstat -nat on the reverse-proxy:

tcp      224      0 192.168.178.143:443 <publicIP>:46784     VERBUNDEN
tcp        0      1 <publicIP>:46784    192.168.0.2:443      SYN_SENT

netstat -nat on the openvpn vm:

tcp        0      0 192.168.0.2:443 <publicIP>:46784     SYN_RECV

Once I change transparent: from true to false in /etc/sslh.cfg and restart sslh connections do not time out anymore, so I assume there is still something wrong with my iptables commands.

I deeply appreciate you taking the time to take a look at this.

ftasnetamot commented 3 months ago

Before digging any deeper into whatever: Please try to draw a better picture, showing your setup. If I spent time, looking into that picture, I am getting more and more confused. How is the real traffic flowing, how are things connected. Looks like, that you drawn just those things, you like, that they talk to each other. If those hosts are container, they are hosted in another host. This host has a network setup, and no traffic can flow to any container from outside, without hitting the host first. So just draw a network, like each container would be a separate box, connected with cables. Draw connections coresponding to your network setup in for your environment. Are they connected through dedicated virtual interfaces from the host? Are there one or more bridges involved? A bridge is in a network diagram something like a hub or switch. A network can't be show as a box round a few servers, a network has connections. When you have drawn that picture, that should include hints about the default gateway from each instance, make yourself familiar, how the traffic might flow, that will help you, making the right assumptions. Next: Setup sslh as a forwarding proxy, just without "--transparent", if that is not working, check your connectivity and routing. Dont have any of those rules in place, which should support transparent proxying. Just plain forwarding proxy. If that works, transparent proxy should also work. If that is not working, search where the connectivity problem is!

When drawing tcp connections: Please don't write the destination port number to origin interfaces, that hurts. The source port of all those connections is random chosen from the ephemeral port range. Watch your connections with netstat and you will see that.

netw0rk-noob commented 3 months ago

First let me answer the questions of your post. Probably that will clear things up somewhat already, as my setup isnt quiet the same anymore as it was in 2022. I will try to create a better overview diagram and attach it to this post later as that will take me somewhat more time to do.

How is the real traffic flowing, how are things connected. Looks like, that you drawn just those things, you like, that they talk to each other.

The connection paths are pretty much the same as shown in the 2022 diagram, except for the source ports which are obviously incorrectly marked as you pointed out correctly.

If those hosts are container, they are hosted in another host. This host has a network setup, and no traffic can flow to any container from outside, without hitting the host first.

Those hosts (at least the two involved in this right now - reverse proxy and openvpn) are not lxc containers anymore but VMs on a proxmox virtual environment hypervisor. The virtual NICs of both are connected to the proxmox default virtual bridge vmbr0 which makes them appear in my isp router as if they were seperate machines (except for the randomly generated mac addresses). Both are using dhcp reserved ip addresses from said isp router, hence using it as the default gateway for these interfaces. That are the interfaces referenced as iface ens18 inet dhcp in the /etc/network/interfaces excerpts.

I created the dummy interface on the reverse proxy vm according to your guide which works for transparent connections to the local nginx.

Additionally I created virtual/secondary ip addresses on the ens18 NIC (on both the reverse proxy and the openvpn VM) for the services (sslh and openvpn) to bind to. I did this according to this guide (section: Create Secondary IP address on Debian systems). That are the interface aliases(?) referenced to as iface ens18:0 inet static in the /etc/network/interfaces excerpts. I've set their default gateway to the corresponding ip address from the 192.168.0.0/30 subnet where sslh is listening on (192.168.0.2), as I've understood from your previous answer.

May it be that this method just does not work for this use case? That I'd need to create a whole separate virtual NIC connected to vmbr0 for each VM and assign it to them in pve?

Setup sslh as a forwarding proxy, just without "--transparent", if that is not working, check your connectivity and routing. Dont have any of those rules in place, which should support transparent proxying. Just plain forwarding proxy.

I had that exact scenario you're describing (non-transparent proxying, no iptables at all involved) working for the last two years. What I have in place now is (if I disable the transparency option in the sslh config) the same setup except that it is using the service-exclusive ip addresses set up according to your guide instead of the main ip addresses which the VMs use in my LAN. If I enable transparency it works for connections to the nginx, listening on 192.168.255.254:443 while connections to openvpn, listening on 192.168.0.2:443 on the openvpn vm are timing out.

Thanks again for your time and patience!

ftasnetamot commented 3 months ago

Again, its no problem, how do you assign a second ip address. So you are using the old way, where the interface gets subinterfaces added, like ens18:0 etc. But again: Is non transparent proxyying working, without any routing/firewall rules beeing in place?

If non transparent proxying is not working, we don't need to go further in configuration details.

netw0rk-noob commented 3 months ago

But again: Is non transparent proxyying working, without any routing/firewall rules beeing in place?

Yes. I just tried it by changing the following: Commenting out the according post-up and post-down commands in /etc/network/interfaces

auto dummy0
iface dummy0 inet static
    address 192.168.255.254/32
    pre-up modprobe dummy
    ## Attention! with kernels, not automatically creating a dummy0
    ## interface after module loading the following line should be:
    pre-up modprobe dummy; if [ ! -e /sys/class/net/dummy0 ]; then ip link add dummy0 type dummy ; fi
#    post-up ip rule add from 192.168.255.254 table sslh
#    post-up ip route add local 0.0.0.0/0 dev dummy0 table sslh
#    pre-down ip route del local 0.0.0.0/0 dev dummy0 table sslh
#    pre-down ip rule del from 192.168.255.254 table sslh

auto ens18:0
iface ens18:0 inet static
    # sslh subnet: 192.168.0.0/30
    address 192.168.0.1
    netmask 255.255.255.252
#    # OpenVPN
#    post-up ip rule add from 192.168.0.2/32 table sslh
#    post-down ip rule del from 192.168.0.2/32 table sslh

and changing the transparent option in the /etc/sslh.cfg file from true to false. Afterwards I restarted the reverse proxy VM.

Tried HTTPS and OpenVPN connections. Both work. Nginx (on the reverse proxy vm) logs 192.168.255.254 as the source address, OpenVPN (on the openvpn vm) logs 192.168.0.1, just as I'd expect them to.

ftasnetamot commented 3 months ago

If it works with forwarding proxy, and all your hosts are in a bridged network, follow the scenario 3. In this case you need no dummy interface, but the routing deflection on the target hosts, pointing back to the sslh host! And you need the routing deflection on the sslh host. Look to the pending pull request!

netw0rk-noob commented 3 months ago

If it works with forwarding proxy […]

I'm not sure about the meaning of the term "forwarding proxy" in this scenario. Isnt a forwarding proxy one that systems use to access the internet? Wouldnt this setup (allowing clients from the internet to reach a device on a local network) just be a non-transparent reverse proxy?

In this case you need no dummy interface, but the routing deflection on the target hosts, pointing back to the sslh host! And you need the routing deflection on the sslh host. Look to the pending pull request!

I already took a look at the diagram from your pull request but I'm afraid I wont be able to compose the "special routing rule" you mentioned in it on my own, due to my lack of understand regarding routing and iptables/netfilter.

Would you mind trying to explain to me why scenario 2 wouldnt work with the hosts being on a bridged network?

ftasnetamot commented 3 months ago

'm not sure about the meaning of the term "forwarding proxy" ... Just a proxy, which is forwarding connections and acts instead of the client. The transparent proxy hides it existence, while the classic proxy plays the role from the "man in the middle". I already took a look at the diagram from your pull request but I'm afraid I wont be able to compose the "special routing rule" you mentioned in it on my own, due to my lack of understand regarding routing and iptables/netfilter.

There is no iptables/netfilter in this solution. Forget about that. It is just two routing rules.

Would you mind trying to explain to me why scenario 2 wouldnt work with the hosts being on a bridged network?

Because you have not drawn a network diagram, showing how your virtual servers are connected together and how routing works. The bridge is not the reason, the reason is, that your router is tied to the bridge. If the bridge would only connect an outgoing interface of your sslh host and the single interfaces of the destination hosts, while the sslh host only is connected with a front facing interface to the router, it will be scenario 2!

When that is right, that all servers are tied to a bridge, where also your router is connected to, your setup is scenario 3. The targets have their own default gateway to the internet, and therefore the connection back would flow like the dotted line, which will not work.

netw0rk-noob commented 3 months ago

It is just two routing rules.

And which ones exactly would that be, based on the network configuration quoted in this post? Would they need to be set up as post-up/post-down commands like the ones on the reverse proxy? Do I need to set up an extra sslh routing table as on the reverse proxy? I'm sorry, I'm really dumb in terms of networking.

Edit: I guess I just figured it out by try and error. I've created a sslh routing table and setup the inverse of the post-up/post-down rules in place on the reverse proxy vm's ens18:0 subinterface on the openvpn vm's ens18:0 subinterface.

Subinterface ens18:0 as configured in /etc/network/interfaces on the reverse proxy vm:

auto ens18:0
iface ens18:0 inet static
    address 192.168.0.1
    netmask 255.255.255.252
    post-up ip rule add from 192.168.0.2/32 table sslh
    post-down ip rule del from 192.168.0.2/32 table sslh

/etc/iproute2/rt_tables (identical on both reverse proxy and openvpn vms):

#
# reserved values
#
255     local
254     main
253     default
0       unspec
#
# local
#
#1      inr.ruhep
111     sslh

subinterface ens18:0 as configured in /etc/network/interfaces on the openvpn vm:

uto ens18:0
iface ens18:0 inet static
    address 192.168.0.2
    netmask 255.255.255.252
    gateway 192.168.0.1
    post-up ip rule add from 192.168.0.1/32 table sslh
    post-down ip rule del from 192.168.0.1/32 table sslh

After setting this up it appearently works - meaning my openvpn client is able to connect without timeouts and the openvpn access log logging its external ip address! Therefore I assume these were the two routing rules you were talking about, @ftasnetamot ?

I still dont really understand how exactly this works, more precisely what exactly happens when the packets are sent to the sslh routing table, but I am very grateful for your hints and diagrams which lead me into the right direction!

One thing I noticed: In the diagram from your pull request you noted that for scenario 2 and 3 the host running sslh must have Kernel Forwarding (aka ip routing?) enabled for this to work. As I've only set net.ipv4.ip_forward temporarily using sysctl -w net.ipv4.ip_forward=1 and rebooted several times since then its disabled on my sslh vm right now, while the transparent proxying seems to work perfectly fine nonetheless.

Followup question: If I want to enable further machines to use the same setup, do I have to create another virtual address (one per vm) on the reverse proxy vm, adding one subinterface (and a seperate /30 subnet) for each additional vm or would it suffice to enlarge the 192.168.0.0/30 subnet to a /29 subnet and assigning the additionally available ips in it (e.g. 192.168.0.3, 192.168.0.4...) to the additional vms?

/Edit

Because you have not drawn a network diagram, showing how your virtual servers are connected together and how routing works.

How am I supposed to show in a diagram how routing works in this setup if I have no idea how routing works? I dont know a thing about the internals of how pve's bridge vmbr0 technically works.

The bridge is not the reason, the reason is, that your router is tied to the bridge. If the bridge would only connect an outgoing interface of your sslh host and the single interfaces of the destination hosts, while the sslh host only is connected with a front facing interface to the router, it will be scenario 2!

When that is right, that all servers are tied to a bridge, where also your router is connected to, your setup is scenario 3. The targets have their own default gateway to the internet, and therefore the connection back would flow like the dotted line, which will not work.

Another question of understanding: Are you referring to my isp router or to the sslh vm with the term router?

ftasnetamot commented 3 months ago

I assume, that your network looks like this:

                       .................................
                       .      server hosting           .
                       .      the containers           .
                       .             |                 .
                       .             |                 .
 internet-------ROUTER-----+----+----+----------+------. 
                       .   |         |          |      .  
                       .  sslh    prosody    openvpn   .
                       .  host     host        host    .
                       .................................

How am I supposed to show in a diagram how routing works in this setup if I have no idea how routing works? you REALLY should read some basic documents, about routing, tcp/ip and system configuration, before connecting devices to the internet!! Another question of understanding: Are you referring to my isp router or to the sslh vm with the term router?

Why the sslh host should be a router? This host looks like a router from your first wrong picture, but that is one system in a row of others. They can all talk to each other, but talking to the internet their gateway out is your router. Are you driving through public streets, following signs, telling you, which street you should take? In my early network years, I liked driving through France, because there was always one additional destination: toutes directions. This concept of traffic routing I saw in most other european countries much later. This is telling "other directions", and so most crossroads in the streets have perfect routing tables! Default gateway means, all other destinations go here!

Think about (virtual) servers as Middle Ages Cities, having only some City Gates. Those are the entrys and exits for all traffic, and interfaces are those gates. Hubs, Switches, Bridges or Cables are the streets. Routers are bigger or smaller crossroads, where traffic has to decide, how to go and opposite to reality, all packets are following the signs!

Your problem is not sslh configuration, your problem is your fundamental understanding of basics. You opened that post 18 month ago, and still lacking fundamental basics of setting up things.

How you can be sure, that your systems are secure and cannot be abused?

This will be my last answer, you have all pieces to make it working. Go and learn basics, please!

netw0rk-noob commented 3 months ago

Your problem is not sslh configuration, your problem is your fundamental understanding of basics. You opened that post 18 month ago, and still lacking fundamental basics of setting up things.

How you can be sure, that your systems are secure and cannot be abused?

This will be my last answer, you have all pieces to make it working. Go and learn basics, please!

Fair. You dont need to waste more of your time on a noob like me. Im still grateful that you did in the first place. As you may or may not have read in the edit of my last comment I in fact did get up and running, thanks to your hints.

And you are totally right pointing out the fact that I'm missing a (not so small) part of the networking essentials (even though I do know, what a default gateway is and how it works) and probably shouldnt be publishing any services on the internet. But as I'm only hosting services for myself for fun and learning purposes, not relying on them for critical services I'm willing to take the risks that come with that decision. I agree that I should look up some of the basics - especially regarding routing - to get a better understanding of why/how the setup I've now built with your help works exactly.

Regarding security: One of the main reasons I wanted to implement transparent proxying was the abillity to log the actual, external ips of (illegitimate) requests so I could deploy fail2ban on the backendservers, blocking every ip trying repeatedly to access my services illegitemately, hence increasing the security of my services. But sure: As with everything reachable from the internet you cant be 100% sure that its not breachable (or even breached already).

I assume, that your network looks like this:

                       .................................
                       .      server hosting           .
                       .      the containers           .
                       .             |                 .
                       .             |                 .
 internet-------ROUTER-----+----+----+----------+------. 
                       .   |         |          |      .  
                       .  sslh    prosody    openvpn   .
                       .  host     host        host    .
                       .................................

correct, except that the sslh and openvpn hosts are vms, not containers.

Edit: And it stopped working again for no appearent reason...

netw0rk-noob commented 2 months ago

I finally got it up and running reliably, mostly thanks to the simple transparent proxy-docs added by @ftasnetamot (whom I want to thank again for his time and patience and for improving the trancparency docs) two weeks ago. My setup is built according to his "Scenario 3".

As I have benefited greatly from said public docs and some other issues in this repo I will document my currently working setup and how I got there. This assumes you have a non-transparent proxying setup and all related services (sslh, nginx, prosody, openVPN) up and running (and enabled to automatically run at startup). I tried to create a better diagram showing my setup, even though it still only shows the incomming connection paths (TCP SYN packets), not the paths the TCP SYN-ACK packets (for which the routing tables, routing rules and routes are relevant) are taking. I still hope its helpful to understand the way my setup works.

SSLH-transparent-proxying-simple

Not sure if this is still of interest for you, @kganczarek - just notifying you in case it is.

A hint for other (proxmox) lxc users: Unlike originally suspected by me everything necessary to get this working is possible using unprivileged lxc containers - using VMs is not a necessity. The OpenVPN machine is a vm on my setup for different reasons. For the network configuration of the lxc in /etc/network/interfaces to take effect you need to set the ip configuration for that container to static without specifying anything (leaving IPv4/CIDR and Gateway (IPv4) empty) on the pve host (host -> Network -> double click on the NIC). (Source: https://graysonpeddie.com/pro-tip-for-proxmox-users-need-to-add-multiple-ip-addresses-for-a-single-nic-in-lxc/) lxc network settings Additionally I noticed that the (debian 12) lxc containers did not assign more than one additional ip address (two maximum) to a single interface when referencing said interface several times without using the old alias / sub-interface (:0, :1...) method for doing so. Therefore I used the old way of creating sub-interfaces like eth0:0, eth0:1 for each additional address. Background (from the simple transparent proxy docs):

There are two ways, how you can add multiple ip addresses to one device. The new ip addr add supports multiple add statements to one and the same interface name. So you can just duplicate the interface stancas in the /etc/network/interfaces configuration. The problem with this method is, that some older managment tools, like ifconfig are unable to show the additional addresses. So when you are used to some older tools, you may configure sub-interfaces like eth0:1. However my recommendation is, migrate to new tools, get used to it, as old tools don't show you the whole configuration!

Configuring LXC A (sslh/nginx)

Setup the sslh-routing table (name/number are arbitrary) by appending 111 sslh (tab separated) to /etc/iproute2/rt_tables. /etc/iproute2/rt_tables:

#
# reserved values
#
255     local
254     main
253     default
0       unspec
#
# local
#
#1      inr.ruhep
111     sslh

Setup the interface configuration (ip addresses and post-up / post-down commands) in /etc/network/interfaces (remember the hint above if this is an lxc container). /etc/network/interfaces:

auto lo
iface lo inet loopback

auto eth0
# this interface does always get 192.168.178.143/24 from a dhcp reservation on the ISP router - might as well be configured static though.
iface eth0 inet dhcp

auto dummy0
iface dummy0 inet static
        address 192.168.255.254/32
        pre-up modprobe dummy ; if [ ! -e /sys/class/net/dummy0 ] ; then ip link add dummy0 type dummy ; fi
        post-up ip rule add from 192.168.255.254 table sslh
        post-up ip route add local 0.0.0.0/0 dev dummy0 table sslh
        pre-down ip route del local 0.0.0.0/0 dev dummy0 table sslh
        pre-down ip rule del from 192.168.255.254 table sslh

auto eth0:0
iface eth0:0 inet static
        address 192.168.0.1
        # /30 network: 4 addresses minus net address and broadcast
        netmask 255.255.255.252
        post-up ip rule add from 192.168.0.2/32 table sslh
        post-up ip route add local 0.0.0.0/0 dev lo table sslh
        post-down ip rule del from 192.168.0.2/32 table sslh
        post-down ip route del local 0.0.0.0/0 dev lo table sslh

auto eth0:1
iface eth0:1 inet static
        address 192.168.1.1
        # /30 network: 4 addresses minus net address and broadcast
        netmask 255.255.255.252
        post-up ip rule add from 192.168.1.2/32 table sslh
        post-up ip route add local 0.0.0.0/0 dev lo table sslh
        post-down ip rule del from 192.168.1.2/32 table sslh
        post-down ip route del local 0.0.0.0/0 dev lo table sslh

Setup the sslh configuration. /etc/sslh.cf:

verbose: 1;
foreground: false;
inetd: false;
numeric: true;
transparent: true;
timeout: 2;
user: "sslh";
pidfile: "/var/run/sslh/sslh.pid";
syslog_facility: "auth";

listen:
(
    # listening only on "192.168.178.143" port 443
        { host: "192.168.178.143"; port: "443"; }
);

protocols:
(
    # forward direct-TLS XMPP c2s connections to 192.168.1.2:5223 (Prosody running on LXC B)
        { name: "tls"; host: "192.168.1.2"; port: "5223"; alpn_protocols: [ "xmpp-client" ]; log_level: 1;},
        # forward OpenVPN connections to 192.168.0.2:443 (OpenVPN running on VM C)
        { name: "openvpn"; host: "192.168.0.2"; port: "443"; log_level: 1;},
        # forward all other TLS connections to 192.168.254:443 (nginx running on LXC A - same machine where sslh is running on)
        { name: "tls"; host: "192.168.255.254"; port: "443"; log_level: 1;},
        # forward everything else to 192.168.255.254:443 (nginx running on LXC A - same machine where sslh is running on)
        { name: "anyprot"; host: "192.168.255.254"; port: "443"; log_level: 1;}
);

on-timeout: "timeout";

Tweak the the nginx configuration to make nginx listen only on the 192.168.255.254 address assigned to the dummy0 interface by replacing every listen ... ssl directive with listen 192.168.255.254:443 ssl;. Additionally you should make sure to set the X-Real-IP, X-Forwarded-For, X-Forwarded-Proto headers in this location block of your config, depending on what your backend service relies on to determine the actual/real IP address the request came from.

Restart LXC A for the changes to take effect.

Nginx didnt start on boot even though its service was enabled (In my case I had the problem that the nginx service didnt start at boot even tough it was enabled. When I started it manually after reboot it took a while (>= 30s) to start, but it did start and was working. Its logs didnt contain anything helpful but the system journal contained the line `systemd[1]: Failed to start networking.service - Raise network interfaces.` which I suspected to be connected to the nginx problem, as the nginx systemd unit file contains `After=network-online.target`. I found [this](https://askubuntu.com/questions/1485561/ifupdown-wait-online-service-even-if-disabed-xfce-takes-5-mins-to-load) online on that error and disabled the ifupdown-wait-online service (`systemctl disable ifupdown-wait-online.service`) and nginx does start at boot again since. I assume that nginx tried to start and bind to the `192.168.255.254` address while it was not assigned to the dummy0 interface yet, but thats only a speculation on my end. Im not sure if that service is critical for other services relying on it, but I didnt notice any problems since disabling it a few days ago.)

After a restart ip address should look like this (removed IPv6 for readabillity because it is irrelevant/not used for this setup):

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
2: eth0@if2360: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
    link/ether [MAC address of interface] ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 192.168.178.143/24 brd 192.168.178.255 scope global dynamic eth0
       valid_lft 25029sec preferred_lft 25029sec
    inet 192.168.0.1/30 brd 192.168.0.3 scope global eth0:0
       valid_lft forever preferred_lft forever
    inet 192.168.1.1/30 brd 192.168.1.3 scope global eth0:1
       valid_lft forever preferred_lft forever
3: dummy0: <BROADCAST,NOARP,UP,LOWER_UP> mtu 1500 qdisc noqueue state UNKNOWN group default qlen 1000
    link/ether [MAC address of interface] ff:ff:ff:ff:ff:ff
    inet 192.168.255.254/32 brd 192.168.255.254 scope global dummy0
       valid_lft forever preferred_lft forever

netstat -tulpen should show sslh and nginx listening on the following addresses/ports:

tcp        0      0 192.168.178.143:443     0.0.0.0:*               LISTEN      0          941297121  244/sslh
tcp        0      0 192.168.255.254:443     0.0.0.0:*               LISTEN      0          941297157  261/nginx: master p

ip route should list the following routes:

default via 192.168.178.1 dev eth0
192.168.0.0/30 dev eth0 proto kernel scope link src 192.168.0.1
192.168.1.0/30 dev eth0 proto kernel scope link src 192.168.1.1
192.168.178.0/24 dev eth0 proto kernel scope link src 192.168.178.143

Transparent Proxying of HTTPS connections to the local nginx should work at this point, verifiable in the Nginx Access log which should now contain public IP addresses for each access.

Configuring LXC B (prosody)

Setup the sslh_routeback-routing table (name/number are arbitrary) by appending 111 sslh_routeback (tab separated) to /etc/iproute2/rt_tables. /etc/iproute2/rt_tables:

#
# reserved values
#
255     local
254     main
253     default
0       unspec
#
# local
#
#1      inr.ruhep
111     sslh_routeback

Setup the interface configuration (ip addresses and post-up / post-down commands) in /etc/network/interfaces (remember the hint above if this is an lxc container). /etc/network/interfaces:

auto lo
iface lo inet loopback

auto eth0
# this interface does always get 192.168.178.130/24 from a dhcp reservation on the ISP router - might as well be configured static though.
iface eth0 inet dhcp

auto eth0:0
iface eth0:0 inet static
    # ip address prosody binds to/listens on
        address 192.168.1.2
        # /30 network: 4 addresses minus net address and broadcast
        netmask 255.255.255.252
        post-up ip rule add from 192.168.1.2/32 table sslh_routeback
        post-up ip route add default via 192.168.1.1 dev eth0 table sslh_routeback
        post-down ip rule del from 192.168.1.2/32 table sslh_routeback
        post-down ip route add default via 192.168.1.1 dev eth0 table sslh_routeback

Tweak the prosody configuration to make it listen only on the 192.168.1.2 address port 5223 for (and only for) directTLS c2s connections by adding the following lines to the /etc/prosody/prosody.cfg.lua:

c2s_direct_tls_interfaces = { "192.168.1.2" }
c2s_direct_tls_ports = 5223;

Restart LXC B for the changes to take effect.

After a restart ip address should look like this (removed IPv6 for readabillity because it is irrelevant/not used for this setup):

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
2: eth0@if2364: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
    link/ether [MAC address of interface] ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 192.168.178.130/24 brd 192.168.178.255 scope global dynamic eth0
       valid_lft 53914sec preferred_lft 53914sec
    inet 192.168.1.2/30 brd 192.168.1.3 scope global eth0:0
       valid_lft forever preferred_lft forever

netstat -tulpen should show prosody (lua) listening on the following address/port:

tcp        0      0 192.168.1.2:5223        0.0.0.0:*               LISTEN      103        946125396  960/lua

ip route should list the following routes:

default via 192.168.178.1 dev eth0 
192.168.1.0/30 dev eth0 proto kernel scope link src 192.168.1.2 
192.168.178.0/24 dev eth0 proto kernel scope link src 192.168.178.130

With LXC A and LXC B fully configured both machines should be able to ping each other via 192.168.1.2/192.168.1.1 and prosody should be listening on 192.168.1.2:5223 which can be verified using telnet 192.168.1.2 5223 from LXC A. A successful connection would produce the following output:

Trying 192.168.1.2...
Connected to 192.168.1.2.
Escape character is '^]'.

while a connection that cant be established would be stuck at Trying 192.168.1.2....

Transparent Proxying of directTLS-XMPP c2s connections to the prosody running on LXC B should work at this point, verifiable in the prosody debug log, if enabled. It should now contain public IP addresses for each access. Debug logging can be enabled by setting the debug log file location in the log scope of the /etc/prosody/prosody.cfg:

log = {
    error = "/var/log/prosody/prosody.error";
    warn = "/var/log/prosody/prosody.log";
    info = "/var/log/prosody/prosody.info" ;
    debug = "/var/log/prosody/prosody.debug" ;
}

Configuring VM C (OpenVPN)

Setup the sslh_routeback-routing table (name/number are arbitrary) by appending 111 sslh_routeback (tab separated) to /etc/iproute2/rt_tables. /etc/iproute2/rt_tables:

#
# reserved values
#
255     local
254     main
253     default
0       unspec
#
# local
#
#1      inr.ruhep
111     sslh_routeback

Setup the interface configuration (ip addresses and post-up / post-down commands) in /etc/network/interfaces (remember the hint above if this is an lxc container). /etc/network/interfaces:

auto lo
iface lo inet loopback

auto eth0
# this interface does always get 192.168.178.145/24 from a dhcp reservation on the ISP router - might as well be configured static though.
iface eth0 inet dhcp

auto ens18
iface ens18 inet static
        address 192.168.0.2
        netmask 255.255.255.252
        post-up ip rule add from 192.168.0.2/32 table sslh_routeback
        post-up ip route add default via 192.168.0.1 dev ens18 table sslh_routeback
        post-down ip rule del from 192.168.0.2/32 table sslh_routeback
        post-down ip route add default via 192.168.0.1 dev ens18 table sslh_routeback

Tweak the OpenVPN configuration to make it listen only on the 192.168.0.2 address port 443 for OpenVPN connections by adding local 192.168.0.2 to the /etc/openvpn/server.conf. This assumes that the OpenVPN server was already listening on port 443 for tcp connections before. In case it didnt, that can be specified by adding proto tcp (necessary for sslh to be able to forward the connection) and port 443 to the /etc/openvpn/server.conf.

Restart VM C for the changes to take effect.

After a restart ip address should look like this (removed IPv6 for readabillity because it is irrelevant/not used for this setup):

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
2: ens18: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
    link/ether [MAC address of interface] ff:ff:ff:ff:ff:ff
    altname enp0s18
    inet 192.168.178.145/24 brd 192.168.178.255 scope global dynamic ens18
       valid_lft 61514sec preferred_lft 61514sec
    inet 192.168.0.2/30 brd 192.168.0.3 scope global ens18
       valid_lft forever preferred_lft forever
3: tun0: <POINTOPOINT,MULTICAST,NOARP,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UNKNOWN group default qlen 500
    [details for OpenVPN tunnel interface removed for readabillity]

netstat -tulpen should show OpenVPN listening on the following address/port:

tcp        0      0 192.168.0.2:443         0.0.0.0:*               LISTEN      0          14341      448/openvpn

ip route should list the following routes (route to openvpn subnet removed):

default via 192.168.178.1 dev ens18
192.168.0.0/30 dev ens18 proto kernel scope link src 192.168.0.2
192.168.178.0/24 dev ens18 proto kernel scope link src 192.168.178.145

With LXC A and VM C fully configured both machines should be able to ping each other via 192.168.0.2/192.168.0.1 and OpenVPN should be listening on 192.168.0.2:443 which can be verified using telnet 192.168.0.2 443 from LXC A. A successful connection would produce the following output:

Trying 192.168.0.2...
Connected to 192.168.0.2.
Escape character is '^]'.

while a connection that cant be established would be stuck at Trying 192.168.0.2....

Transparent Proxying of OpenVPN connections to the OpenVPN server running on VM C should work at this point, verifiable in the OpenVPN log. It should now contain public IP addresses for each access.

Im closing this issue now, as it has been solved thanks to the new, simpler docs on transparent proxying.