From caf489afe160ac10d550d1e5cfcb21a195d80cb3 Mon Sep 17 00:00:00 2001 From: cqdetdisc Date: Sat, 27 Jun 2026 15:16:23 -0400 Subject: [PATCH 1/5] feat: first class (opt-in) nethernet support --- .gitignore | 4 ++ config.toml | 9 ++++ go.mod | 36 ++++++++++++---- go.sum | 97 ++++++++++++++++++++++++++++++++++--------- server/conf.go | 44 +++++++++++++++++++- server/listener.go | 100 ++++++++++++++++++++++++++++++++++++++++++--- 6 files changed, 256 insertions(+), 34 deletions(-) diff --git a/.gitignore b/.gitignore index 2502e81c2e..fab28081da 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,7 @@ /world/ /players/ /resources/ + +# Local NetherNet TLS material +/cert.pem +/key.pem diff --git a/config.toml b/config.toml index 9d5cbdd905..f725fc5635 100644 --- a/config.toml +++ b/config.toml @@ -3,6 +3,15 @@ # The address of the server, including the port. The server will be listening on this address. If another # server is already running on this port, please select a different port. Address = ":19132" + # Transport controls which network transports are listened on. Valid values are raknet, nethernet and both. + Transport = "raknet" + + [Network.NetherNet] + # Address is the TCP address used for NetherNet HTTP signaling. If empty, Network.Address is used. + Address = "" + # CertificateFile and KeyFile enable HTTPS signaling. If both are empty, plaintext HTTP is used. + CertificateFile = "" + KeyFile = "" [Server] # The name as it shows up in the server list. Minecraft colour codes may be used in this name to format the diff --git a/go.mod b/go.mod index 381b9dfcd5..512494130b 100644 --- a/go.mod +++ b/go.mod @@ -5,17 +5,18 @@ go 1.26.0 require ( github.com/brentp/intintmap v0.0.0-20251106190759-56907b1f8479 github.com/cespare/xxhash/v2 v2.3.0 + github.com/df-mc/go-nethernet v1.0.17 github.com/df-mc/goleveldb v1.1.9 github.com/df-mc/worldupgrader v1.0.21 github.com/go-gl/mathgl v1.2.0 github.com/google/uuid v1.6.0 github.com/pelletier/go-toml v1.9.5 - github.com/sandertv/gophertunnel v1.57.0 + github.com/sandertv/gophertunnel v1.57.1-0.20260627131634-44d232dc9d42 github.com/segmentio/fasthash v1.0.3 golang.org/x/exp v0.0.0-20250103183323-7d7fa50e5329 - golang.org/x/mod v0.22.0 - golang.org/x/text v0.23.0 - golang.org/x/tools v0.28.0 + golang.org/x/mod v0.32.0 + golang.org/x/text v0.34.0 + golang.org/x/tools v0.41.0 ) require ( @@ -23,12 +24,33 @@ require ( github.com/df-mc/go-playfab v1.0.0 // indirect github.com/df-mc/go-xsapi v1.0.1 // indirect github.com/df-mc/jsonc v1.0.5 // indirect - github.com/go-jose/go-jose/v4 v4.1.3 // indirect + github.com/go-jose/go-jose/v4 v4.1.4 // indirect github.com/golang/snappy v0.0.4 // indirect github.com/klauspost/compress v1.18.4 // indirect + github.com/nxadm/tail v1.4.11 // indirect + github.com/pion/datachannel v1.6.0 // indirect + github.com/pion/dtls/v3 v3.1.2 // indirect + github.com/pion/ice/v4 v4.2.1 // indirect + github.com/pion/interceptor v0.1.44 // indirect + github.com/pion/logging v0.2.4 // indirect + github.com/pion/mdns/v2 v2.1.0 // indirect + github.com/pion/randutil v0.1.0 // indirect + github.com/pion/rtcp v1.2.16 // indirect + github.com/pion/rtp v1.10.1 // indirect + github.com/pion/sctp v1.9.2 // indirect + github.com/pion/sdp/v3 v3.0.18 // indirect + github.com/pion/srtp/v3 v3.0.10 // indirect + github.com/pion/stun/v3 v3.1.1 // indirect + github.com/pion/transport/v4 v4.0.1 // indirect + github.com/pion/turn/v4 v4.1.4 // indirect + github.com/pion/webrtc/v4 v4.2.10-0.20260224155637-aa3b95c72dd2 // indirect github.com/sandertv/go-raknet v1.15.1-0.20260112202637-beca0b10c217 // indirect - golang.org/x/net v0.38.0 // indirect + github.com/wlynxg/anet v0.0.5 // indirect + golang.org/x/crypto v0.48.0 // indirect + golang.org/x/net v0.50.0 // indirect golang.org/x/oauth2 v0.28.0 // indirect - golang.org/x/sync v0.12.0 // indirect + golang.org/x/sync v0.19.0 // indirect + golang.org/x/sys v0.41.0 // indirect + golang.org/x/time v0.10.0 // indirect gopkg.in/yaml.v2 v2.3.0 // indirect ) diff --git a/go.sum b/go.sum index 4bc90dae7e..630716f19b 100644 --- a/go.sum +++ b/go.sum @@ -4,6 +4,10 @@ github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UF github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/coreos/go-oidc/v3 v3.17.0 h1:hWBGaQfbi0iVviX4ibC7bk8OKT5qNr4klBaCHVNvehc= github.com/coreos/go-oidc/v3 v3.17.0/go.mod h1:wqPbKFrVnE90vty060SB40FCJ8fTHTxSwyXJqZH+sI8= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/df-mc/go-nethernet v1.0.17 h1:3lbw/N+Zy4eU8dUREVMFhq9Xl8qXJsJomuHIwH2VO9Q= +github.com/df-mc/go-nethernet v1.0.17/go.mod h1:4yrQZMotdkZJvdYnRQr5L8OnokZtSzqJ6EU3dntAaIU= github.com/df-mc/go-playfab v1.0.0 h1:6gVukk3aQbJ934GJFdcZJHVIw9lhauK+KHOevbwJA10= github.com/df-mc/go-playfab v1.0.0/go.mod h1:nGOlE+JFGOH5Z0iidEgJapHhndFi/oNk17RN9pKCF+k= github.com/df-mc/go-xsapi v1.0.1 h1:H1SbxYr4rXOqZSB8MwiODbDUsHRihxbHf+YOljUWgXw= @@ -15,10 +19,12 @@ github.com/df-mc/jsonc v1.0.5/go.mod h1:+Q++JuCE9IKiP8v7sWImdf/RjQX0nfXyfX6PdfTT github.com/df-mc/worldupgrader v1.0.21 h1:Qr4/QB8ek7En0vkTuRXYq4FrZM0HHSOXsJOL7Ko4Cjg= github.com/df-mc/worldupgrader v1.0.21/go.mod h1:tsSOLTRm9mpG7VHvYpAjjZrkRHWmSbKZAm9bOLNnlDk= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= +github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= github.com/go-gl/mathgl v1.2.0 h1:v2eOj/y1B2afDxF6URV1qCYmo1KW08lAMtTbOn3KXCY= github.com/go-gl/mathgl v1.2.0/go.mod h1:pf9+b5J3LFP7iZ4XXaVzZrCle0Q/vNpB/vDe5+3ulRE= -github.com/go-jose/go-jose/v4 v4.1.3 h1:CVLmWDhDVRa6Mi/IgCgaopNosCaHz7zrMeF9MlZRkrs= -github.com/go-jose/go-jose/v4 v4.1.3/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= +github.com/go-jose/go-jose/v4 v4.1.4 h1:moDMcTHmvE6Groj34emNPLs/qtYXRVcd6S7NHbHz3kA= +github.com/go-jose/go-jose/v4 v4.1.4/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= @@ -27,49 +33,100 @@ github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/klauspost/compress v1.18.4 h1:RPhnKRAQ4Fh8zU2FY/6ZFDwTVTxgJ/EMydqSTzE9a2c= github.com/klauspost/compress v1.18.4/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4= +github.com/nxadm/tail v1.4.11 h1:8feyoE3OzPrcshW5/MJ4sGESc5cqmGkGCWlco4l0bqY= +github.com/nxadm/tail v1.4.11/go.mod h1:OTaG3NK980DZzxbRq6lEuzgU+mug70nY11sMd4JXXHc= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU= +github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= +github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.17.0 h1:9Luw4uT5HTjHTN8+aNcSThgH1vdXnmdJ8xIfZ4wyTRE= +github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= +github.com/pion/datachannel v1.6.0 h1:XecBlj+cvsxhAMZWFfFcPyUaDZtd7IJvrXqlXD/53i0= +github.com/pion/datachannel v1.6.0/go.mod h1:ur+wzYF8mWdC+Mkis5Thosk+u/VOL287apDNEbFpsIk= +github.com/pion/dtls/v3 v3.1.2 h1:gqEdOUXLtCGW+afsBLO0LtDD8GnuBBjEy6HRtyofZTc= +github.com/pion/dtls/v3 v3.1.2/go.mod h1:Hw/igcX4pdY69z1Hgv5x7wJFrUkdgHwAn/Q/uo7YHRo= +github.com/pion/ice/v4 v4.2.1 h1:XPRYXaLiFq3LFDG7a7bMrmr3mFr27G/gtXN3v/TVfxY= +github.com/pion/ice/v4 v4.2.1/go.mod h1:2quLV1S5v1tAx3VvAJaH//KGitRXvo4RKlX6D3tnN+c= +github.com/pion/interceptor v0.1.44 h1:sNlZwM8dWXU9JQAkJh8xrarC0Etn8Oolcniukmuy0/I= +github.com/pion/interceptor v0.1.44/go.mod h1:4atVlBkcgXuUP+ykQF0qOCGU2j7pQzX2ofvPRFsY5RY= +github.com/pion/logging v0.2.4 h1:tTew+7cmQ+Mc1pTBLKH2puKsOvhm32dROumOZ655zB8= +github.com/pion/logging v0.2.4/go.mod h1:DffhXTKYdNZU+KtJ5pyQDjvOAh/GsNSyv1lbkFbe3so= +github.com/pion/mdns/v2 v2.1.0 h1:3IJ9+Xio6tWYjhN6WwuY142P/1jA0D5ERaIqawg/fOY= +github.com/pion/mdns/v2 v2.1.0/go.mod h1:pcez23GdynwcfRU1977qKU0mDxSeucttSHbCSfFOd9A= +github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA= +github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8= +github.com/pion/rtcp v1.2.16 h1:fk1B1dNW4hsI78XUCljZJlC4kZOPk67mNRuQ0fcEkSo= +github.com/pion/rtcp v1.2.16/go.mod h1:/as7VKfYbs5NIb4h6muQ35kQF/J0ZVNz2Z3xKoCBYOo= +github.com/pion/rtp v1.10.1 h1:xP1prZcCTUuhO2c83XtxyOHJteISg6o8iPsE2acaMtA= +github.com/pion/rtp v1.10.1/go.mod h1:rF5nS1GqbR7H/TCpKwylzeq6yDM+MM6k+On5EgeThEM= +github.com/pion/sctp v1.9.2 h1:HxsOzEV9pWoeggv7T5kewVkstFNcGvhMPx0GvUOUQXo= +github.com/pion/sctp v1.9.2/go.mod h1:OTOlsQ5EDQ6mQ0z4MUGXt2CgQmKyafBEXhUVqLRB6G8= +github.com/pion/sdp/v3 v3.0.18 h1:l0bAXazKHpepazVdp+tPYnrsy9dfh7ZbT8DxesH5ZnI= +github.com/pion/sdp/v3 v3.0.18/go.mod h1:ZREGo6A9ZygQ9XkqAj5xYCQtQpif0i6Pa81HOiAdqQ8= +github.com/pion/srtp/v3 v3.0.10 h1:tFirkpBb3XccP5VEXLi50GqXhv5SKPxqrdlhDCJlZrQ= +github.com/pion/srtp/v3 v3.0.10/go.mod h1:3mOTIB0cq9qlbn59V4ozvv9ClW/BSEbRp4cY0VtaR7M= +github.com/pion/stun/v3 v3.1.1 h1:CkQxveJ4xGQjulGSROXbXq94TAWu8gIX2dT+ePhUkqw= +github.com/pion/stun/v3 v3.1.1/go.mod h1:qC1DfmcCTQjl9PBaMa5wSn3x9IPmKxSdcCsxBcDBndM= +github.com/pion/transport/v3 v3.1.1 h1:Tr684+fnnKlhPceU+ICdrw6KKkTms+5qHMgw6bIkYOM= +github.com/pion/transport/v3 v3.1.1/go.mod h1:+c2eewC5WJQHiAA46fkMMzoYZSuGzA/7E2FPrOYHctQ= +github.com/pion/transport/v4 v4.0.1 h1:sdROELU6BZ63Ab7FrOLn13M6YdJLY20wldXW2Cu2k8o= +github.com/pion/transport/v4 v4.0.1/go.mod h1:nEuEA4AD5lPdcIegQDpVLgNoDGreqM/YqmEx3ovP4jM= +github.com/pion/turn/v4 v4.1.4 h1:EU11yMXKIsK43FhcUnjLlrhE4nboHZq+TXBIi3QpcxQ= +github.com/pion/turn/v4 v4.1.4/go.mod h1:ES1DXVFKnOhuDkqn9hn5VJlSWmZPaRJLyBXoOeO/BmQ= +github.com/pion/webrtc/v4 v4.2.10-0.20260224155637-aa3b95c72dd2 h1:k2diaAQwjS83Zg1k1vhCOvw/VBK7ru0UGIij1TpVI7Q= +github.com/pion/webrtc/v4 v4.2.10-0.20260224155637-aa3b95c72dd2/go.mod h1:9EmLZve0H76eTzf8v2FmchZ6tcBXtDgpfTEu+drW6SY= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/sandertv/go-raknet v1.15.1-0.20260112202637-beca0b10c217 h1:UZQq2253Q+7co/C9Et62RYPBggzz+L+2yqGlvQhSNM8= github.com/sandertv/go-raknet v1.15.1-0.20260112202637-beca0b10c217/go.mod h1:/yysjwfCXm2+2OY8mBazLzcxJ3irnylKCyG3FLgUPVU= -github.com/sandertv/gophertunnel v1.57.0 h1:UkgVg1xLCsOSm79rP09WmodGSHgA8M7+l4quL01cIL8= -github.com/sandertv/gophertunnel v1.57.0/go.mod h1:W4VnrX9AIPIVXNDMEIKMIRj1T80EdOgdqXpGbQpyAbE= +github.com/sandertv/gophertunnel v1.57.1-0.20260627131634-44d232dc9d42 h1:ZkcjthBR8uuVpJCYbuogDuP32MVx+1FWwt/H/wiXcB4= +github.com/sandertv/gophertunnel v1.57.1-0.20260627131634-44d232dc9d42/go.mod h1:Frjuk3g1EEGRYjzyrEMS4j/hDvTcez2STfuRHkRPlNU= github.com/segmentio/fasthash v1.0.3 h1:EI9+KE1EwvMLBWwjpRDc+fEM+prwxDYbslddQGtrmhM= github.com/segmentio/fasthash v1.0.3/go.mod h1:waKX8l2N8yckOgmSsXJi7x1ZfdKZ4x7KRMzBtS3oedY= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/wlynxg/anet v0.0.5 h1:J3VJGi1gvo0JwZ/P1/Yc/8p63SoW98B5dHkYDmpgvvU= +github.com/wlynxg/anet v0.0.5/go.mod h1:eay5PRQr7fIVAMbTbchTnO9gG65Hg/uYGdc7mguHxoA= +golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts= +golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos= golang.org/x/exp v0.0.0-20250103183323-7d7fa50e5329 h1:9kj3STMvgqy3YA4VQXBrN7925ICMxD5wzMRcgA30588= golang.org/x/exp v0.0.0-20250103183323-7d7fa50e5329/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c= -golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= -golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= +golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c= +golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= -golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= +golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60= +golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM= golang.org/x/oauth2 v0.28.0 h1:CrgCKl8PPAVtLnU3c+EDw6x11699EWlsDeWNWKdIOkc= golang.org/x/oauth2 v0.28.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= -golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= +golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= -golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k= +golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg= +golang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= -golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= -golang.org/x/tools v0.28.0 h1:WuB6qZ4RPCQo5aP3WdKZS7i595EdWqWR8vqJTlwTVK8= -golang.org/x/tools v0.28.0/go.mod h1:dcIOrVd3mfQKTgrDVQHqCPMWy6lnhfhtX3hLXYVLfRw= +golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk= +golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA= +golang.org/x/time v0.10.0 h1:3usCWA8tQn0L8+hFJQNgzpWbd89begxN66o1Ojdn5L4= +golang.org/x/time v0.10.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc= +golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/server/conf.go b/server/conf.go index 16acc68379..46a8eafef6 100644 --- a/server/conf.go +++ b/server/conf.go @@ -6,6 +6,7 @@ import ( "os" "path/filepath" "slices" + "strings" "time" _ "unsafe" @@ -191,6 +192,7 @@ func (conf Config) New() *Server { l, err := lf(conf) if err != nil { conf.Log.Error("create listener: " + err.Error()) + continue } srv.listeners = append(srv.listeners, l) } @@ -215,6 +217,19 @@ type UserConfig struct { // Address is the address on which the server should listen. Players may // connect to this address in order to join. Address string + // Transport controls which network transports are listened on. Valid values + // are "raknet", "nethernet" and "both". If empty, "raknet" is used. + Transport string + // NetherNet holds settings for the NetherNet HTTP signaling listener. + NetherNet struct { + // Address is the TCP address used for NetherNet HTTP signaling. If empty, + // Network.Address is used. + Address string + // CertificateFile and KeyFile enable HTTPS signaling. If both are empty, + // the listener serves plaintext HTTP. + CertificateFile string + KeyFile string + } } Server struct { // Name is the name of the server as it shows up in the server list. @@ -285,6 +300,10 @@ func (uc UserConfig) Config(log *slog.Logger) (Config, error) { MaxChunkRadius: uc.Players.MaximumChunkRadius, DisableResourceBuilding: !uc.Resources.AutoBuildPack, } + transport, err := parseNetworkTransport(uc.Network.Transport) + if err != nil { + return conf, err + } if !uc.Server.DisableJoinQuitMessages { conf.JoinMessage, conf.QuitMessage = chat.MessageJoin, chat.MessageQuit } @@ -304,10 +323,32 @@ func (uc UserConfig) Config(log *slog.Logger) (Config, error) { return conf, fmt.Errorf("create player provider: %w", err) } } - conf.Listeners = append(conf.Listeners, uc.listenerFunc) + if transport.raknet { + conf.Listeners = append(conf.Listeners, uc.listenerFunc) + } + if transport.nethernet { + conf.Listeners = append(conf.Listeners, uc.netherNetListenerFunc) + } return conf, nil } +type networkTransport struct { + raknet, nethernet bool +} + +func parseNetworkTransport(s string) (networkTransport, error) { + switch strings.ToLower(strings.TrimSpace(s)) { + case "", "raknet": + return networkTransport{raknet: true}, nil + case "nethernet": + return networkTransport{nethernet: true}, nil + case "both": + return networkTransport{raknet: true, nethernet: true}, nil + default: + return networkTransport{}, fmt.Errorf("unknown network transport %q", s) + } +} + // loadResources loads all resource packs found in a directory passed. func loadResources(dir string) ([]*resource.Pack, error) { _ = os.MkdirAll(dir, 0777) @@ -345,6 +386,7 @@ func loadGenerator(dim world.Dimension) world.Generator { func DefaultConfig() UserConfig { c := UserConfig{} c.Network.Address = ":19132" + c.Network.Transport = "raknet" c.Server.Name = "Dragonfly Server" c.Server.AuthEnabled = true c.World.SaveData = true diff --git a/server/listener.go b/server/listener.go index 0c7e46aa2f..058058da2f 100644 --- a/server/listener.go +++ b/server/listener.go @@ -2,11 +2,18 @@ package server import ( "context" + "crypto/tls" + "errors" "fmt" "io" "log/slog" + "net" + "net/http" + "time" "github.com/df-mc/dragonfly/server/session" + "github.com/df-mc/go-nethernet" + "github.com/df-mc/go-nethernet/endpoint" "github.com/sandertv/gophertunnel/minecraft" ) @@ -24,6 +31,80 @@ type Listener interface { // listenerFunc may be used to return a *minecraft.Listener using a Config. It // is the standard listener used when UserConfig.Config() is called. func (uc UserConfig) listenerFunc(conf Config) (Listener, error) { + cfg := listenerConfig(conf) + l, err := cfg.Listen("raknet", uc.Network.Address) + if err != nil { + return nil, fmt.Errorf("create minecraft listener: %w", err) + } + conf.Log.Info("Listener running.", "addr", l.Addr()) + return listener{Listener: l}, nil +} + +func (uc UserConfig) netherNetListenerFunc(conf Config) (Listener, error) { + nnConf := uc.Network.NetherNet + if (nnConf.CertificateFile == "") != (nnConf.KeyFile == "") { + return nil, errors.New("create NetherNet listener: certificate and key files must both be set") + } + address := nnConf.Address + if address == "" { + address = uc.Network.Address + } + tcp, err := net.Listen("tcp", address) + if err != nil { + return nil, fmt.Errorf("listen NetherNet HTTP: %w", err) + } + if nnConf.CertificateFile != "" { + if _, err := tls.LoadX509KeyPair(nnConf.CertificateFile, nnConf.KeyFile); err != nil { + _ = tcp.Close() + return nil, fmt.Errorf("read NetherNet certificate key pair: %w", err) + } + } + log := conf.Log.With("net origin", "nethernet-http") + handler := endpoint.HandlerConfig{Logger: log}.New() + cfg := listenerConfig(conf) + l, err := cfg.ListenNetwork(minecraft.NetherNet{ + Signaling: handler, + ListenConfig: nethernet.ListenConfig{ + Log: conf.Log.With("net origin", "nethernet"), + AllowAnonymous: conf.AuthDisabled, + DisableTrickleICE: true, + }, + }, handler.NetworkID()) + if err != nil { + _ = tcp.Close() + return nil, fmt.Errorf("create NetherNet listener: %w", err) + } + + httpServer := &http.Server{ + Addr: address, + Handler: logHTTPRequests(log, handler), + ReadHeaderTimeout: 5 * time.Second, + ReadTimeout: 10 * time.Second, + IdleTimeout: 30 * time.Second, + } + go func() { + var err error + if nnConf.CertificateFile != "" { + err = httpServer.ServeTLS(tcp, nnConf.CertificateFile, nnConf.KeyFile) + } else { + err = httpServer.Serve(tcp) + } + if err != nil && !errors.Is(err, http.ErrServerClosed) && !errors.Is(err, net.ErrClosed) { + conf.Log.Error("NetherNet HTTP listener closed unexpectedly: " + err.Error()) + } + }() + conf.Log.Info("NetherNet listener running.", "addr", tcp.Addr(), "https", nnConf.CertificateFile != "") + return listener{Listener: l, close: httpServer.Close}, nil +} + +func logHTTPRequests(log *slog.Logger, next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + log.Info("NetherNet HTTP request.", "method", r.Method, "path", r.URL.Path, "raddr", r.RemoteAddr) + next.ServeHTTP(w, r) + }) +} + +func listenerConfig(conf Config) minecraft.ListenConfig { cfg := minecraft.ListenConfig{ MaximumPlayers: conf.MaxPlayers, StatusProvider: conf.StatusProvider, @@ -35,18 +116,14 @@ func (uc UserConfig) listenerFunc(conf Config) (Listener, error) { if conf.Log.Enabled(context.Background(), slog.LevelDebug) { cfg.ErrorLog = conf.Log.With("net origin", "gophertunnel") } - l, err := cfg.Listen("raknet", uc.Network.Address) - if err != nil { - return nil, fmt.Errorf("create minecraft listener: %w", err) - } - conf.Log.Info("Listener running.", "addr", l.Addr()) - return listener{l}, nil + return cfg } // listener is a Listener implementation that wraps around a minecraft.Listener so that it can be listened on by // Server. type listener struct { *minecraft.Listener + close func() error } // Accept blocks until the next connection is established and returns it. An error is returned if the Listener was @@ -63,3 +140,14 @@ func (l listener) Accept() (session.Conn, error) { func (l listener) Disconnect(conn session.Conn, reason string) error { return l.Listener.Disconnect(conn.(*minecraft.Conn), reason) } + +// Close closes the Minecraft listener and any sidecar listener it depends on. +func (l listener) Close() error { + err := l.Listener.Close() + if l.close != nil { + if closeErr := l.close(); err == nil { + err = closeErr + } + } + return err +} From 3f563b2500ab473c5cf5a27b77bedf44b34a1cca Mon Sep 17 00:00:00 2001 From: HashimTheArab Date: Wed, 15 Jul 2026 01:23:33 -0400 Subject: [PATCH 2/5] nethernet: leave TLS termination to operators --- config.toml | 4 +--- server/conf.go | 7 ++----- server/listener.go | 19 ++----------------- server/listener_test.go | 15 +++++++++++++++ 4 files changed, 20 insertions(+), 25 deletions(-) create mode 100644 server/listener_test.go diff --git a/config.toml b/config.toml index f725fc5635..f08ebf46e5 100644 --- a/config.toml +++ b/config.toml @@ -8,10 +8,8 @@ [Network.NetherNet] # Address is the TCP address used for NetherNet HTTP signaling. If empty, Network.Address is used. + # The listener serves plaintext HTTP. Terminate HTTPS with a reverse proxy such as Caddy or nginx. Address = "" - # CertificateFile and KeyFile enable HTTPS signaling. If both are empty, plaintext HTTP is used. - CertificateFile = "" - KeyFile = "" [Server] # The name as it shows up in the server list. Minecraft colour codes may be used in this name to format the diff --git a/server/conf.go b/server/conf.go index 46a8eafef6..5bf35d1634 100644 --- a/server/conf.go +++ b/server/conf.go @@ -223,12 +223,9 @@ type UserConfig struct { // NetherNet holds settings for the NetherNet HTTP signaling listener. NetherNet struct { // Address is the TCP address used for NetherNet HTTP signaling. If empty, - // Network.Address is used. + // Network.Address is used. The listener serves plaintext HTTP; HTTPS + // should be terminated by a reverse proxy. Address string - // CertificateFile and KeyFile enable HTTPS signaling. If both are empty, - // the listener serves plaintext HTTP. - CertificateFile string - KeyFile string } } Server struct { diff --git a/server/listener.go b/server/listener.go index 058058da2f..4949925346 100644 --- a/server/listener.go +++ b/server/listener.go @@ -2,7 +2,6 @@ package server import ( "context" - "crypto/tls" "errors" "fmt" "io" @@ -42,9 +41,6 @@ func (uc UserConfig) listenerFunc(conf Config) (Listener, error) { func (uc UserConfig) netherNetListenerFunc(conf Config) (Listener, error) { nnConf := uc.Network.NetherNet - if (nnConf.CertificateFile == "") != (nnConf.KeyFile == "") { - return nil, errors.New("create NetherNet listener: certificate and key files must both be set") - } address := nnConf.Address if address == "" { address = uc.Network.Address @@ -53,12 +49,6 @@ func (uc UserConfig) netherNetListenerFunc(conf Config) (Listener, error) { if err != nil { return nil, fmt.Errorf("listen NetherNet HTTP: %w", err) } - if nnConf.CertificateFile != "" { - if _, err := tls.LoadX509KeyPair(nnConf.CertificateFile, nnConf.KeyFile); err != nil { - _ = tcp.Close() - return nil, fmt.Errorf("read NetherNet certificate key pair: %w", err) - } - } log := conf.Log.With("net origin", "nethernet-http") handler := endpoint.HandlerConfig{Logger: log}.New() cfg := listenerConfig(conf) @@ -83,17 +73,12 @@ func (uc UserConfig) netherNetListenerFunc(conf Config) (Listener, error) { IdleTimeout: 30 * time.Second, } go func() { - var err error - if nnConf.CertificateFile != "" { - err = httpServer.ServeTLS(tcp, nnConf.CertificateFile, nnConf.KeyFile) - } else { - err = httpServer.Serve(tcp) - } + err := httpServer.Serve(tcp) if err != nil && !errors.Is(err, http.ErrServerClosed) && !errors.Is(err, net.ErrClosed) { conf.Log.Error("NetherNet HTTP listener closed unexpectedly: " + err.Error()) } }() - conf.Log.Info("NetherNet listener running.", "addr", tcp.Addr(), "https", nnConf.CertificateFile != "") + conf.Log.Info("NetherNet listener running.", "addr", tcp.Addr()) return listener{Listener: l, close: httpServer.Close}, nil } diff --git a/server/listener_test.go b/server/listener_test.go new file mode 100644 index 0000000000..75968ffae6 --- /dev/null +++ b/server/listener_test.go @@ -0,0 +1,15 @@ +package server + +import ( + "reflect" + "testing" +) + +func TestNetherNetConfigDoesNotOwnTLSCertificates(t *testing.T) { + typ := reflect.TypeOf(UserConfig{}.Network.NetherNet) + for _, field := range []string{"CertificateFile", "KeyFile"} { + if _, ok := typ.FieldByName(field); ok { + t.Errorf("Network.NetherNet must not expose %s", field) + } + } +} From 229a7d3440549474fdd4af42c62feaa4085ec51d Mon Sep 17 00:00:00 2001 From: HashimTheArab Date: Wed, 15 Jul 2026 01:25:59 -0400 Subject: [PATCH 3/5] nethernet: drop TLS config reflection test --- server/listener_test.go | 15 --------------- 1 file changed, 15 deletions(-) delete mode 100644 server/listener_test.go diff --git a/server/listener_test.go b/server/listener_test.go deleted file mode 100644 index 75968ffae6..0000000000 --- a/server/listener_test.go +++ /dev/null @@ -1,15 +0,0 @@ -package server - -import ( - "reflect" - "testing" -) - -func TestNetherNetConfigDoesNotOwnTLSCertificates(t *testing.T) { - typ := reflect.TypeOf(UserConfig{}.Network.NetherNet) - for _, field := range []string{"CertificateFile", "KeyFile"} { - if _, ok := typ.FieldByName(field); ok { - t.Errorf("Network.NetherNet must not expose %s", field) - } - } -} From 98eeba56bcde2e5b6c7c889dadc17636ffea50c9 Mon Sep 17 00:00:00 2001 From: HashimTheArab Date: Wed, 15 Jul 2026 01:50:50 -0400 Subject: [PATCH 4/5] nethernet: use stable transport releases --- .gitignore | 4 ---- go.mod | 30 +++++++++++++-------------- go.sum | 60 +++++++++++++++++++++++++++--------------------------- 3 files changed, 45 insertions(+), 49 deletions(-) diff --git a/.gitignore b/.gitignore index fab28081da..2502e81c2e 100644 --- a/.gitignore +++ b/.gitignore @@ -7,7 +7,3 @@ /world/ /players/ /resources/ - -# Local NetherNet TLS material -/cert.pem -/key.pem diff --git a/go.mod b/go.mod index 512494130b..3de43be969 100644 --- a/go.mod +++ b/go.mod @@ -5,13 +5,13 @@ go 1.26.0 require ( github.com/brentp/intintmap v0.0.0-20251106190759-56907b1f8479 github.com/cespare/xxhash/v2 v2.3.0 - github.com/df-mc/go-nethernet v1.0.17 + github.com/df-mc/go-nethernet v1.0.18 github.com/df-mc/goleveldb v1.1.9 github.com/df-mc/worldupgrader v1.0.21 github.com/go-gl/mathgl v1.2.0 github.com/google/uuid v1.6.0 github.com/pelletier/go-toml v1.9.5 - github.com/sandertv/gophertunnel v1.57.1-0.20260627131634-44d232dc9d42 + github.com/sandertv/gophertunnel v1.57.1 github.com/segmentio/fasthash v1.0.3 golang.org/x/exp v0.0.0-20250103183323-7d7fa50e5329 golang.org/x/mod v0.32.0 @@ -28,22 +28,22 @@ require ( github.com/golang/snappy v0.0.4 // indirect github.com/klauspost/compress v1.18.4 // indirect github.com/nxadm/tail v1.4.11 // indirect - github.com/pion/datachannel v1.6.0 // indirect - github.com/pion/dtls/v3 v3.1.2 // indirect - github.com/pion/ice/v4 v4.2.1 // indirect - github.com/pion/interceptor v0.1.44 // indirect + github.com/pion/datachannel v1.6.2 // indirect + github.com/pion/dtls/v3 v3.1.4 // indirect + github.com/pion/ice/v4 v4.2.7 // indirect + github.com/pion/interceptor v0.1.45 // indirect github.com/pion/logging v0.2.4 // indirect github.com/pion/mdns/v2 v2.1.0 // indirect github.com/pion/randutil v0.1.0 // indirect github.com/pion/rtcp v1.2.16 // indirect - github.com/pion/rtp v1.10.1 // indirect - github.com/pion/sctp v1.9.2 // indirect - github.com/pion/sdp/v3 v3.0.18 // indirect - github.com/pion/srtp/v3 v3.0.10 // indirect - github.com/pion/stun/v3 v3.1.1 // indirect - github.com/pion/transport/v4 v4.0.1 // indirect - github.com/pion/turn/v4 v4.1.4 // indirect - github.com/pion/webrtc/v4 v4.2.10-0.20260224155637-aa3b95c72dd2 // indirect + github.com/pion/rtp v1.10.2 // indirect + github.com/pion/sctp v1.10.2 // indirect + github.com/pion/sdp/v3 v3.0.19 // indirect + github.com/pion/srtp/v3 v3.0.12 // indirect + github.com/pion/stun/v3 v3.1.6 // indirect + github.com/pion/transport/v4 v4.0.2 // indirect + github.com/pion/turn/v5 v5.0.10 // indirect + github.com/pion/webrtc/v4 v4.2.16-0.20260627075746-7a223a6f4d4f // indirect github.com/sandertv/go-raknet v1.15.1-0.20260112202637-beca0b10c217 // indirect github.com/wlynxg/anet v0.0.5 // indirect golang.org/x/crypto v0.48.0 // indirect @@ -51,6 +51,6 @@ require ( golang.org/x/oauth2 v0.28.0 // indirect golang.org/x/sync v0.19.0 // indirect golang.org/x/sys v0.41.0 // indirect - golang.org/x/time v0.10.0 // indirect + golang.org/x/time v0.14.0 // indirect gopkg.in/yaml.v2 v2.3.0 // indirect ) diff --git a/go.sum b/go.sum index 630716f19b..41fd9c30ad 100644 --- a/go.sum +++ b/go.sum @@ -6,8 +6,8 @@ github.com/coreos/go-oidc/v3 v3.17.0 h1:hWBGaQfbi0iVviX4ibC7bk8OKT5qNr4klBaCHVNv github.com/coreos/go-oidc/v3 v3.17.0/go.mod h1:wqPbKFrVnE90vty060SB40FCJ8fTHTxSwyXJqZH+sI8= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/df-mc/go-nethernet v1.0.17 h1:3lbw/N+Zy4eU8dUREVMFhq9Xl8qXJsJomuHIwH2VO9Q= -github.com/df-mc/go-nethernet v1.0.17/go.mod h1:4yrQZMotdkZJvdYnRQr5L8OnokZtSzqJ6EU3dntAaIU= +github.com/df-mc/go-nethernet v1.0.18 h1:SSlGPhweIux3c9PKhkmyxbMtBc/V2+b9+cJYdyqxbAo= +github.com/df-mc/go-nethernet v1.0.18/go.mod h1:11MfQo5F7PDKfHg1H0zM5mMkrCv29jRE0lK5B4egW9w= github.com/df-mc/go-playfab v1.0.0 h1:6gVukk3aQbJ934GJFdcZJHVIw9lhauK+KHOevbwJA10= github.com/df-mc/go-playfab v1.0.0/go.mod h1:nGOlE+JFGOH5Z0iidEgJapHhndFi/oNk17RN9pKCF+k= github.com/df-mc/go-xsapi v1.0.1 h1:H1SbxYr4rXOqZSB8MwiODbDUsHRihxbHf+YOljUWgXw= @@ -48,14 +48,14 @@ github.com/onsi/gomega v1.17.0 h1:9Luw4uT5HTjHTN8+aNcSThgH1vdXnmdJ8xIfZ4wyTRE= github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= -github.com/pion/datachannel v1.6.0 h1:XecBlj+cvsxhAMZWFfFcPyUaDZtd7IJvrXqlXD/53i0= -github.com/pion/datachannel v1.6.0/go.mod h1:ur+wzYF8mWdC+Mkis5Thosk+u/VOL287apDNEbFpsIk= -github.com/pion/dtls/v3 v3.1.2 h1:gqEdOUXLtCGW+afsBLO0LtDD8GnuBBjEy6HRtyofZTc= -github.com/pion/dtls/v3 v3.1.2/go.mod h1:Hw/igcX4pdY69z1Hgv5x7wJFrUkdgHwAn/Q/uo7YHRo= -github.com/pion/ice/v4 v4.2.1 h1:XPRYXaLiFq3LFDG7a7bMrmr3mFr27G/gtXN3v/TVfxY= -github.com/pion/ice/v4 v4.2.1/go.mod h1:2quLV1S5v1tAx3VvAJaH//KGitRXvo4RKlX6D3tnN+c= -github.com/pion/interceptor v0.1.44 h1:sNlZwM8dWXU9JQAkJh8xrarC0Etn8Oolcniukmuy0/I= -github.com/pion/interceptor v0.1.44/go.mod h1:4atVlBkcgXuUP+ykQF0qOCGU2j7pQzX2ofvPRFsY5RY= +github.com/pion/datachannel v1.6.2 h1:7EXQ8TH3vTouBUdRWYbcX2edSx9Yj6k5zl5P+qyxEPc= +github.com/pion/datachannel v1.6.2/go.mod h1:pzbdAZvyGtXbcHM1hBbsFaOTf40lZizU/dNlvVOak6E= +github.com/pion/dtls/v3 v3.1.4 h1:QhvtMflMfu9Kf0RcDC5BJBle4caPskByrKQR6uuYqpY= +github.com/pion/dtls/v3 v3.1.4/go.mod h1:cr/qotLISUw/9C1m83ZPNZtj9WnXkYLpfCptPqbkInc= +github.com/pion/ice/v4 v4.2.7 h1:zDEbC6MiEdhQpF8TxBOTws+NU6ZgGpveHrQq4Lc1kao= +github.com/pion/ice/v4 v4.2.7/go.mod h1:9SNPaq0c7El/ki8leJzyCkK10zsskprR3zTNbO3monY= +github.com/pion/interceptor v0.1.45 h1:6PUo/5829bIfRFIPPJQzuDn8EjxRTSB/CSD7QVCOaqo= +github.com/pion/interceptor v0.1.45/go.mod h1:gNDYM/uFKcLe/B3gS2/7+aw6z+RDiMy2qKTnF1LO31w= github.com/pion/logging v0.2.4 h1:tTew+7cmQ+Mc1pTBLKH2puKsOvhm32dROumOZ655zB8= github.com/pion/logging v0.2.4/go.mod h1:DffhXTKYdNZU+KtJ5pyQDjvOAh/GsNSyv1lbkFbe3so= github.com/pion/mdns/v2 v2.1.0 h1:3IJ9+Xio6tWYjhN6WwuY142P/1jA0D5ERaIqawg/fOY= @@ -64,30 +64,30 @@ github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA= github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8= github.com/pion/rtcp v1.2.16 h1:fk1B1dNW4hsI78XUCljZJlC4kZOPk67mNRuQ0fcEkSo= github.com/pion/rtcp v1.2.16/go.mod h1:/as7VKfYbs5NIb4h6muQ35kQF/J0ZVNz2Z3xKoCBYOo= -github.com/pion/rtp v1.10.1 h1:xP1prZcCTUuhO2c83XtxyOHJteISg6o8iPsE2acaMtA= -github.com/pion/rtp v1.10.1/go.mod h1:rF5nS1GqbR7H/TCpKwylzeq6yDM+MM6k+On5EgeThEM= -github.com/pion/sctp v1.9.2 h1:HxsOzEV9pWoeggv7T5kewVkstFNcGvhMPx0GvUOUQXo= -github.com/pion/sctp v1.9.2/go.mod h1:OTOlsQ5EDQ6mQ0z4MUGXt2CgQmKyafBEXhUVqLRB6G8= -github.com/pion/sdp/v3 v3.0.18 h1:l0bAXazKHpepazVdp+tPYnrsy9dfh7ZbT8DxesH5ZnI= -github.com/pion/sdp/v3 v3.0.18/go.mod h1:ZREGo6A9ZygQ9XkqAj5xYCQtQpif0i6Pa81HOiAdqQ8= -github.com/pion/srtp/v3 v3.0.10 h1:tFirkpBb3XccP5VEXLi50GqXhv5SKPxqrdlhDCJlZrQ= -github.com/pion/srtp/v3 v3.0.10/go.mod h1:3mOTIB0cq9qlbn59V4ozvv9ClW/BSEbRp4cY0VtaR7M= -github.com/pion/stun/v3 v3.1.1 h1:CkQxveJ4xGQjulGSROXbXq94TAWu8gIX2dT+ePhUkqw= -github.com/pion/stun/v3 v3.1.1/go.mod h1:qC1DfmcCTQjl9PBaMa5wSn3x9IPmKxSdcCsxBcDBndM= +github.com/pion/rtp v1.10.2 h1:l+f6tTDcAH6xwepaAoW791ddhuYsJlqRATOzirO04Mo= +github.com/pion/rtp v1.10.2/go.mod h1:Au8fc6cEByy8RLTwKTQTEeQqDB/SJDxwL4mZuxYA5Pk= +github.com/pion/sctp v1.10.2 h1:6aezYsMrHAwpjJ6kUdyCiWPqZwgToT00ponT7seJ6a4= +github.com/pion/sctp v1.10.2/go.mod h1:7KFmTwLcoYgJs/Z+99nJvsWL0qDpuyloSI0RbAqlrz0= +github.com/pion/sdp/v3 v3.0.19 h1:1VMKs3gIkTQV5M3hNKfTAPrDXSNrYtOlmOD8+mSZUGQ= +github.com/pion/sdp/v3 v3.0.19/go.mod h1:dE5WOSlzXrtiE/iuZqe9n+AcEbOjtAd3k5m5NtlV/qU= +github.com/pion/srtp/v3 v3.0.12 h1:U7V17bckl7sI4mb3sepiojByDuBY0wNCqQE+6IlQBbc= +github.com/pion/srtp/v3 v3.0.12/go.mod h1:EeZOi/sd6glM1EXapg051gdNWO9yWT1YSsgQ4SlJkns= +github.com/pion/stun/v3 v3.1.6 h1:WnhsD0eHCiwCfKNkVx0VJJwr2Y3eV4Ueih3KJ+dfZy8= +github.com/pion/stun/v3 v3.1.6/go.mod h1:zRUghXSQU32Lx5orJsz3uYMkIihweXb3mu5gIns02fs= github.com/pion/transport/v3 v3.1.1 h1:Tr684+fnnKlhPceU+ICdrw6KKkTms+5qHMgw6bIkYOM= github.com/pion/transport/v3 v3.1.1/go.mod h1:+c2eewC5WJQHiAA46fkMMzoYZSuGzA/7E2FPrOYHctQ= -github.com/pion/transport/v4 v4.0.1 h1:sdROELU6BZ63Ab7FrOLn13M6YdJLY20wldXW2Cu2k8o= -github.com/pion/transport/v4 v4.0.1/go.mod h1:nEuEA4AD5lPdcIegQDpVLgNoDGreqM/YqmEx3ovP4jM= -github.com/pion/turn/v4 v4.1.4 h1:EU11yMXKIsK43FhcUnjLlrhE4nboHZq+TXBIi3QpcxQ= -github.com/pion/turn/v4 v4.1.4/go.mod h1:ES1DXVFKnOhuDkqn9hn5VJlSWmZPaRJLyBXoOeO/BmQ= -github.com/pion/webrtc/v4 v4.2.10-0.20260224155637-aa3b95c72dd2 h1:k2diaAQwjS83Zg1k1vhCOvw/VBK7ru0UGIij1TpVI7Q= -github.com/pion/webrtc/v4 v4.2.10-0.20260224155637-aa3b95c72dd2/go.mod h1:9EmLZve0H76eTzf8v2FmchZ6tcBXtDgpfTEu+drW6SY= +github.com/pion/transport/v4 v4.0.2 h1:ifYlPqNwsy6aKQ9y8yzxXlHae5431ZrH2avkD/Rn6Tk= +github.com/pion/transport/v4 v4.0.2/go.mod h1:06hFI+jCFcok2X2MekVufNZ/uzNZXivGBPfviSVcjgM= +github.com/pion/turn/v5 v5.0.10 h1:mOMZjudflXpte5OsCnXztpUKwNXcpXIAzMBnq9TXOSQ= +github.com/pion/turn/v5 v5.0.10/go.mod h1:u3XjBqy2Z4+NhCUpDoOSsNuQDrPLvKStlCGWk6sTQ1E= +github.com/pion/webrtc/v4 v4.2.16-0.20260627075746-7a223a6f4d4f h1:m39L6AcxVDq1Gwm9/c6QGCiksJ9xoYE4TQyebl9pMu4= +github.com/pion/webrtc/v4 v4.2.16-0.20260627075746-7a223a6f4d4f/go.mod h1:g/C+nTxS7qM2dBr1hRK56OTTD9zFzQDwZi3fMfZxFNM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/sandertv/go-raknet v1.15.1-0.20260112202637-beca0b10c217 h1:UZQq2253Q+7co/C9Et62RYPBggzz+L+2yqGlvQhSNM8= github.com/sandertv/go-raknet v1.15.1-0.20260112202637-beca0b10c217/go.mod h1:/yysjwfCXm2+2OY8mBazLzcxJ3irnylKCyG3FLgUPVU= -github.com/sandertv/gophertunnel v1.57.1-0.20260627131634-44d232dc9d42 h1:ZkcjthBR8uuVpJCYbuogDuP32MVx+1FWwt/H/wiXcB4= -github.com/sandertv/gophertunnel v1.57.1-0.20260627131634-44d232dc9d42/go.mod h1:Frjuk3g1EEGRYjzyrEMS4j/hDvTcez2STfuRHkRPlNU= +github.com/sandertv/gophertunnel v1.57.1 h1:P14qngVxDCnSedLvGJtkLrzDOVBQEL1fFliBrT3E9Mc= +github.com/sandertv/gophertunnel v1.57.1/go.mod h1:Frjuk3g1EEGRYjzyrEMS4j/hDvTcez2STfuRHkRPlNU= github.com/segmentio/fasthash v1.0.3 h1:EI9+KE1EwvMLBWwjpRDc+fEM+prwxDYbslddQGtrmhM= github.com/segmentio/fasthash v1.0.3/go.mod h1:waKX8l2N8yckOgmSsXJi7x1ZfdKZ4x7KRMzBtS3oedY= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= @@ -117,8 +117,8 @@ golang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk= golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA= -golang.org/x/time v0.10.0 h1:3usCWA8tQn0L8+hFJQNgzpWbd89begxN66o1Ojdn5L4= -golang.org/x/time v0.10.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= +golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc= golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= From d6d8ee1a2bafed8933ea048542470ada71e6a00c Mon Sep 17 00:00:00 2001 From: lactyy2 <284460485+lactyy2@users.noreply.github.com> Date: Thu, 16 Jul 2026 17:15:27 +0900 Subject: [PATCH 5/5] server: allow specifying private key for NetherNet listener (#1332) * server/conf.go: Allow specifying private key for NetherNet * config.toml: Change default transport value to raknet * server: harden NetherNet identity keys --------- Co-authored-by: HashimTheArab --- .gitignore | 4 ++ config.toml | 6 +++ server/conf.go | 8 ++++ server/listener.go | 117 ++++++++++++++++++++++++++++++++++++++++++--- 4 files changed, 129 insertions(+), 6 deletions(-) diff --git a/.gitignore b/.gitignore index 2502e81c2e..9c74edab2f 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,7 @@ /world/ /players/ /resources/ + +# NetherNet server identity key +identity.pem +nethernet_identity.pem diff --git a/config.toml b/config.toml index f08ebf46e5..714a94756d 100644 --- a/config.toml +++ b/config.toml @@ -10,6 +10,12 @@ # Address is the TCP address used for NetherNet HTTP signaling. If empty, Network.Address is used. # The listener serves plaintext HTTP. Terminate HTTPS with a reverse proxy such as Caddy or nginx. Address = "" + # KeyFile is the path to the PEM file containing the P-384 ECDSA private key used to identify + # this listener when clients connect over plain HTTP. If the file does not exist, a new key is + # generated and saved there. If empty, a temporary key is generated and not saved. + KeyFile = "nethernet_identity.pem" + # Domain is the domain that may be displayed to players connecting over plain HTTP. + Domain = "self" [Server] # The name as it shows up in the server list. Minecraft colour codes may be used in this name to format the diff --git a/server/conf.go b/server/conf.go index 5bf35d1634..a4a8018f0b 100644 --- a/server/conf.go +++ b/server/conf.go @@ -226,6 +226,13 @@ type UserConfig struct { // Network.Address is used. The listener serves plaintext HTTP; HTTPS // should be terminated by a reverse proxy. Address string + // KeyFile is the path to the PEM file containing the P-384 ECDSA private + // key used to identify this listener when clients connect over plain HTTP. + // If the file does not exist, a new key is generated and saved there. If + // empty, a temporary key is generated and not saved. + KeyFile string + // Domain is the domain that may be displayed to players connecting over plain HTTP. + Domain string } } Server struct { @@ -384,6 +391,7 @@ func DefaultConfig() UserConfig { c := UserConfig{} c.Network.Address = ":19132" c.Network.Transport = "raknet" + c.Network.NetherNet.KeyFile, c.Network.NetherNet.Domain = "nethernet_identity.pem", "self" c.Server.Name = "Dragonfly Server" c.Server.AuthEnabled = true c.World.SaveData = true diff --git a/server/listener.go b/server/listener.go index 4949925346..e39ee809cf 100644 --- a/server/listener.go +++ b/server/listener.go @@ -2,12 +2,18 @@ package server import ( "context" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/x509" + "encoding/pem" "errors" "fmt" "io" "log/slog" "net" "net/http" + "os" "time" "github.com/df-mc/dragonfly/server/session" @@ -39,12 +45,115 @@ func (uc UserConfig) listenerFunc(conf Config) (Listener, error) { return listener{Listener: l}, nil } +// importPrivateKey reads a PEM file containing a P-384 [ecdsa.PrivateKey] and +// returns it for use by the NetherNet listener. +func importPrivateKey(path string) (*ecdsa.PrivateKey, error) { + b, err := os.ReadFile(path) + if err != nil { + return nil, err // already wrapped in os.PathError + } + block, _ := pem.Decode(b) + if block == nil { + return nil, errors.New("invalid PEM block") + } + var key *ecdsa.PrivateKey + switch block.Type { + case "EC PRIVATE KEY": + key, err = x509.ParseECPrivateKey(block.Bytes) + case "PRIVATE KEY": + var parsed any + parsed, err = x509.ParsePKCS8PrivateKey(block.Bytes) + if err != nil { + return nil, fmt.Errorf("parse private key: %w", err) + } + var ok bool + key, ok = parsed.(*ecdsa.PrivateKey) + if !ok { + return nil, fmt.Errorf("must be *ecdsa.PrivateKey: %T", parsed) + } + default: + return nil, fmt.Errorf("invalid block type: %s", block.Type) + } + if err != nil { + return nil, fmt.Errorf("parse private key: %w", err) + } + if key.Curve != elliptic.P384() { + return nil, fmt.Errorf("private key must use P-384, got %s", key.Curve.Params().Name) + } + return key, nil +} + +// exportPrivateKey writes a PEM file containing the [ecdsa.PrivateKey]. +func exportPrivateKey(path string, key *ecdsa.PrivateKey) error { + keyBytes, err := x509.MarshalECPrivateKey(key) + if err != nil { + return fmt.Errorf("encode: %w", err) + } + b := pem.EncodeToMemory(&pem.Block{ + Type: "EC PRIVATE KEY", + Bytes: keyBytes, + }) + f, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0600) + if err != nil { + return err + } + if _, err := f.Write(b); err != nil { + _ = f.Close() + _ = os.Remove(path) + return fmt.Errorf("write: %w", err) + } + if err := f.Close(); err != nil { + _ = os.Remove(path) + return fmt.Errorf("close: %w", err) + } + return nil +} + func (uc UserConfig) netherNetListenerFunc(conf Config) (Listener, error) { nnConf := uc.Network.NetherNet address := nnConf.Address if address == "" { address = uc.Network.Address } + if nnConf.Domain == "" { + nnConf.Domain = "self" + } + + lcfg := nethernet.ListenConfig{ + Log: conf.Log.With("net origin", "nethernet"), + AllowAnonymous: conf.AuthDisabled, + } + var key *ecdsa.PrivateKey + if nnConf.KeyFile != "" { + var err error + key, err = importPrivateKey(nnConf.KeyFile) + if os.IsNotExist(err) { + key, err = ecdsa.GenerateKey(elliptic.P384(), rand.Reader) + if err != nil { + return nil, fmt.Errorf("generate key: %w", err) + } + // If we generated a new key for the NetherNet listener, save it. + // Otherwise, players may be prompted to trust the server identity + // after every restart. + if err := exportPrivateKey(nnConf.KeyFile, key); err != nil { + return nil, fmt.Errorf("export private key: %w", err) + } + lcfg.Log.Info("Generated a private key for NetherNet listener.", "path", nnConf.KeyFile) + } else if err != nil { + return nil, fmt.Errorf("import key file: %w", err) + } + } else { + var err error + key, err = ecdsa.GenerateKey(elliptic.P384(), rand.Reader) + if err != nil { + return nil, fmt.Errorf("generate key: %w", err) + } + lcfg.Log.Warn("Using a temporary private key for the NetherNet listener. Players connecting over plain HTTP may see the TOFU (Trust On First Use) prompt every time the server restarts.") + } + lcfg.IssueServerIdentity = func(ctx context.Context) (*nethernet.Identity, error) { + return nethernet.GenerateServerIdentity(key, nnConf.Domain) + } + tcp, err := net.Listen("tcp", address) if err != nil { return nil, fmt.Errorf("listen NetherNet HTTP: %w", err) @@ -53,12 +162,8 @@ func (uc UserConfig) netherNetListenerFunc(conf Config) (Listener, error) { handler := endpoint.HandlerConfig{Logger: log}.New() cfg := listenerConfig(conf) l, err := cfg.ListenNetwork(minecraft.NetherNet{ - Signaling: handler, - ListenConfig: nethernet.ListenConfig{ - Log: conf.Log.With("net origin", "nethernet"), - AllowAnonymous: conf.AuthDisabled, - DisableTrickleICE: true, - }, + Signaling: handler, + ListenConfig: lcfg, }, handler.NetworkID()) if err != nil { _ = tcp.Close()