Minecraft server and HAProxy

Hello can you please help me with understanding and fixing HAProxy redirection from subdomain to minecraft server, more precisely with fixing IF condition without IF condition everything works as it should.

here is the log when trying to connect to the server from the subdomain

tomik520i@tomik520i:~$ sudo tcpdump -i any port 25565 -A
tcpdump: data link type LINUX_SLL2
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on any, link-type LINUX_SLL2 (Linux cooked v2), snapshot length 262144 bytes
05:44:55.076954 eth0  In  IP _gateway.4995 > tomik520i.25565: Flags [S], seq 4037262724, win 64240, options [mss 1460,nop,wscale 8,nop,nop,sackOK], length 0
E..4..@.}.............c..............|..............
05:44:55.077045 eth0  Out IP tomik520i.25565 > _gateway.4995: Flags [S.], seq 1381928981, ack 4037262725, win 64240, options [mss 1460,nop,nop,sackOK,nop,wscale 7], length 0
E..4..@.@...........c...R^...........@..............
05:44:55.078584 eth0  In  IP _gateway.4995 > tomik520i.25565: Flags [.], ack 1, win 1026, length 0
E..(..@.}.............c.....R^..P.............
05:44:55.078585 eth0  In  IP _gateway.4995 > tomik520i.25565: Flags [P.], seq 1:30, ack 1, win 1026, length 29
E..E..@...............c.....R^..P....F.......minecraft.opicebot.czc..
05:44:55.078585 eth0  In  IP _gateway.4995 > tomik520i.25565: Flags [P.], seq 30:32, ack 1, win 1026, length 2
E..*..@...............c.....R^..P.............
05:44:55.078753 eth0  Out IP tomik520i.25565 > _gateway.4995: Flags [.], ack 30, win 502, length 0
E..(k.@.@.J.........c...R^......P....4..
05:44:55.078773 eth0  Out IP tomik520i.25565 > _gateway.4995: Flags [.], ack 32, win 502, length 0
E..(k.@.@.J.........c...R^......P....4..
05:45:00.081512 eth0  Out IP tomik520i.25565 > _gateway.4995: Flags [F.], seq 1, ack 32, win 502, length 0
E..(k.@.@.J.........c...R^......P....4..
05:45:00.082956 eth0  In  IP _gateway.4995 > tomik520i.25565: Flags [.], ack 2, win 1026, length 0
E..(..@...............c.....R^..P.............
05:45:00.082956 eth0  In  IP _gateway.4995 > tomik520i.25565: Flags [F.], seq 32, ack 2, win 1026, length 0
E..(..@.}.............c.....R^..P.............
05:45:00.083080 eth0  Out IP tomik520i.25565 > _gateway.4995: Flags [.], ack 33, win 502, length 0
E..(..@.@...........c...R^......P...!...
05:45:00.105553 eth0  In  IP _gateway.5003 > tomik520i.25565: Flags [S], seq 1168363033, win 64240, options [mss 1460,nop,wscale 8,nop,nop,sackOK], length 0
E..4..@.}.............c.E...........d...............
05:45:00.105638 eth0  Out IP tomik520i.25565 > _gateway.5003: Flags [S.], seq 795733300, ack 1168363034, win 64240, options [mss 1460,nop,nop,sackOK,nop,wscale 7], length 0
E..4..@.@...........c.../m.4E........@..............
05:45:00.107122 eth0  In  IP _gateway.5003 > tomik520i.25565: Flags [.], ack 1, win 1026, length 0
E..(..@.}.............c.E.../m.5P.............
05:45:00.107122 eth0  In  IP _gateway.5003 > tomik520i.25565: Flags [P.], seq 1:79, ack 1, win 1026, length 78
E..v..@...............c.E.../m.5P.............M.C.|.P.i.n.g.H.o.s.t.1....m.i.n.e.c.r.a.f.t...o.p.i.c.e.b.o.t...c.z..c.
05:45:00.107122 eth0  In  IP _gateway.5003 > tomik520i.25565: Flags [F.], seq 79, ack 1, win 1026, length 0
E..(..@.}.............c.E..h/m.5P.............
05:45:00.107274 eth0  Out IP tomik520i.25565 > _gateway.5003: Flags [.], ack 79, win 502, length 0
E..(:.@.@.{.........c.../m.5E..hP....4..
05:45:00.107890 eth0  Out IP tomik520i.25565 > _gateway.5003: Flags [F.], seq 1, ack 80, win 502, length 0
E..(:.@.@.{.........c.../m.5E..iP....4..
05:45:00.109063 eth0  In  IP _gateway.5003 > tomik520i.25565: Flags [.], ack 2, win 1026, length 0
E..(..@.}.............c.E..i/m.6P.............
^C
19 packets captured
21 packets received by filter
0 packets dropped by kernel

here I am attaching an extract from sudo tail -f /var/log/haproxy.log where it can be seen that haproxy did not assign the frontend to the backend

2024-10-22T05:45:00.081927+00:00 tomik520i haproxy[10301]: 192.168.1.1:4995 [22/Oct/2024:05:44:55.078] minecraft minecraft/<NOSRV> -1/-1/5002 0 SC 3/1/0/0/0 0/0
2024-10-22T05:45:00.107939+00:00 tomik520i haproxy[10301]: 192.168.1.1:5003 [22/Oct/2024:05:45:00.107] minecraft minecraft/<NOSRV> -1/-1/0 0 SC 3/1/0/0/0 0/0

here is my current setup of these two dependencies, a lot of it is copied from chatgpt something like this I’m really trying to do for the first time but I’m trying to start a home server where I’m going to run multiple things at once and this redirection through subdomains seems most ideal to me. https://bluemap.opicebot.cz here I’m already running the server view where you can see that the server is live and is reachable

frontend minecraft
    bind *:25565
    mode tcp
    tcp-request inspect-delay 5s
tcp-request content accept if WAIT_END
    

use_backend minecraft_server if { req.payload(0,100) -m str "minecraft.opicebot.cz" }


backend minecraft_server
    mode tcp
    server mc_server 192.168.1.159:25565

AFAIK it is not possible do perform hostname-based routing for minecraft protocol. Common way to achieve this is to use SRV records to give hint to the minecraft client how to connect to the server.

It is possible. Bungeecord also has a feature called “forced hosts”. I can assign different servers to different hostnames. But I couldn’t find any solutions yet with HAProxy. I’m sure Minecraft sends the hostname with the handshake, but I don’t know how to check it in HAProxy.

I can see my domain in the log if I use tcpdump -i any port 25565 -A -XX (I was just querying the server status with the hostname private.chronika.dev from the game). We just need a way to somehow redirect the traffic based on this.

tcpdump: data link type LINUX_SLL2
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on any, link-type LINUX_SLL2 (Linux cooked v2), snapshot length 262144 bytes
15:49:12.886452 wg0   In  IP 10.188.0.128.54734 > 10.188.0.1.25565: Flags [S], seq 1046458422, win 64860, options [mss 1380,sackOK,TS val 3253204673 ecr 0,nop,wscale 7], length 0
	0x0000:  0800 0000 0000 0003 fffe 0000 0000 0000  ................
	0x0010:  0000 0000 4500 003c 1876 4000 4006 0c4e  ....E..<.v@.@..N
	0x0020:  0abc 0080 0abc 0001 d5ce 63dd 3e5f b036  ..........c.>_.6
	0x0030:  0000 0000 a002 fd5c 540f 0000 0204 0564  .......\T......d
	0x0040:  0402 080a c1e7 f6c1 0000 0000 0103 0307  ................
15:49:12.886511 wg0   Out IP 10.188.0.1.25565 > 10.188.0.128.54734: Flags [S.], seq 3623135667, ack 1046458423, win 64296, options [mss 1380,sackOK,TS val 601158464 ecr 3253204673,nop,wscale 7], length 0
	0x0000:  0800 0000 0000 0003 fffe 0400 0000 0000  ................
	0x0010:  0000 0000 4500 003c 0000 4000 4006 24c4  ....E..<..@.@.$.
	0x0020:  0abc 0001 0abc 0080 63dd d5ce d7f4 a9b3  ........c.......
	0x0030:  3e5f b037 a012 fb28 1627 0000 0204 0564  >_.7...(.'.....d
	0x0040:  0402 080a 23d4 f340 c1e7 f6c1 0103 0307  ....#..@........
15:49:12.898506 wg0   In  IP 10.188.0.128.54734 > 10.188.0.1.25565: Flags [.], ack 1, win 507, options [nop,nop,TS val 3253204686 ecr 601158464], length 0
	0x0000:  0800 0000 0000 0003 fffe 0000 0000 0000  ................
	0x0010:  0000 0000 4500 0034 1877 4000 4006 0c55  ....E..4.w@.@..U
	0x0020:  0abc 0080 0abc 0001 d5ce 63dd 3e5f b037  ..........c.>_.7
	0x0030:  d7f4 a9b4 8010 01fb e511 0000 0101 080a  ................
	0x0040:  c1e7 f6ce 23d4 f340                      ....#..@
15:49:12.898587 wg0   In  IP 10.188.0.128.54734 > 10.188.0.1.25565: Flags [P.], seq 1:29, ack 1, win 507, options [nop,nop,TS val 3253204686 ecr 601158464], length 28
	0x0000:  0800 0000 0000 0003 fffe 0000 0000 0000  ................
	0x0010:  0000 0000 4500 0050 1878 4000 4006 0c38  ....E..P.x@.@..8
	0x0020:  0abc 0080 0abc 0001 d5ce 63dd 3e5f b037  ..........c.>_.7
	0x0030:  d7f4 a9b4 8018 01fb 4e9e 0000 0101 080a  ........N.......
	0x0040:  c1e7 f6ce 23d4 f340 1b00 8106 1470 7269  ....#..@.....pri
	0x0050:  7661 7465 2e63 6872 6f6e 696b 612e 6465  vate.chronika.de
	0x0060:  7663 dd01                                vc..
15:49:12.898609 wg0   Out IP 10.188.0.1.25565 > 10.188.0.128.54734: Flags [.], ack 29, win 503, options [nop,nop,TS val 601158476 ecr 3253204686], length 0
	0x0000:  0800 0000 0000 0003 fffe 0400 0000 0000  ................
	0x0010:  0000 0000 4500 0034 eaf1 4000 4006 39da  ....E..4..@.@.9.
	0x0020:  0abc 0001 0abc 0080 63dd d5ce d7f4 a9b4  ........c.......
	0x0030:  3e5f b053 8010 01f7 161f 0000 0101 080a  >_.S............
	0x0040:  23d4 f34c c1e7 f6ce                      #..L....
15:49:12.914743 wg0   In  IP 10.188.0.128.54734 > 10.188.0.1.25565: Flags [P.], seq 29:31, ack 1, win 507, options [nop,nop,TS val 3253204698 ecr 601158476], length 2
	0x0000:  0800 0000 0000 0003 fffe 0000 0000 0000  ................
	0x0010:  0000 0000 4500 0036 1879 4000 4006 0c51  ....E..6.y@.@..Q
	0x0020:  0abc 0080 0abc 0001 d5ce 63dd 3e5f b053  ..........c.>_.S
	0x0030:  d7f4 a9b4 8018 01fb e3d3 0000 0101 080a  ................
	0x0040:  c1e7 f6da 23d4 f34c 0100                 ....#..L..
15:49:12.914793 wg0   Out IP 10.188.0.1.25565 > 10.188.0.128.54734: Flags [.], ack 31, win 503, options [nop,nop,TS val 601158493 ecr 3253204698], length 0
	0x0000:  0800 0000 0000 0003 fffe 0400 0000 0000  ................
	0x0010:  0000 0000 4500 0034 eaf2 4000 4006 39d9  ....E..4..@.@.9.
	0x0020:  0abc 0001 0abc 0080 63dd d5ce d7f4 a9b4  ........c.......
	0x0030:  3e5f b055 8010 01f7 161f 0000 0101 080a  >_.U............
	0x0040:  23d4 f35d c1e7 f6da                      #..]....

Indeed it looks like the handshake includes that info: https://minecraft.wiki/w/Java_Edition_protocol/Packets#Handshake

However what I don’t know (yet) is that if the handshake occurs in the first TCP segment following client connect. If that’s the case then maybe (assuming the hostname may be found at a static offset), inspect request + req.payload() fetch combination from haproxy would do the trick to extract the hostname from the handshake.

Following on this, looks like (from the doc mentioned above), the hostname (encoded as String) would follow the protocol version number which is a varint encoded.

See https://minecraft.wiki/w/Java_Edition_protocol/Packets#VarInt_and_VarLong for Varint and https://minecraft.wiki/w/Java_Edition_protocol/Packets#Type:String for String encoding

I’m assuming that with custom fetches (ie: using Lua for instance) to decode Minecraft VarInt and String, it should be possible to extract hostname info from the initial TCP request.

But this confirms that there is no pure native way to do this with haproxy (as it requires extending haproxy with custom fetches to make haproxy extract some infos from the minecraft protocol which are dynamically encoded)

Thank you so much.
I found a script that works, but I had to modify it a little in the 97th line, because the domain had an extra “c” at the end.

Original:

local str = string_sub(payload[1], payload[2], payload[2] + str_len)

New:

local str = string_sub(payload[1], payload[2], payload[2] + str_len - 1)

I’m new in HAProxy btw.

1 Like

glad you got it to work and thanks for sharing your progress with us!

1 Like