From e7ce973e40124cf32feb85841601cd2ae486667e Mon Sep 17 00:00:00 2001 From: eason Date: Wed, 18 Jun 2025 00:27:13 +0800 Subject: [PATCH 1/2] - add p2p quic support --- application/application.go | 42 ++++++++++- go.mod | 13 ++++ go.sum | 29 +++++++ pkg/h3/p2p.go | 21 ++++++ pkg/h3/punch-hole.go | 56 ++++++++++++++ pkg/h3/server.go | 99 ++++++++++++++++++++++++ pkg/h3/stun.go | 150 +++++++++++++++++++++++++++++++++++++ pkg/h3/tls.go | 38 ++++++++++ 8 files changed, 447 insertions(+), 1 deletion(-) create mode 100644 pkg/h3/p2p.go create mode 100644 pkg/h3/punch-hole.go create mode 100644 pkg/h3/server.go create mode 100644 pkg/h3/stun.go create mode 100644 pkg/h3/tls.go diff --git a/application/application.go b/application/application.go index 2078881..e114524 100644 --- a/application/application.go +++ b/application/application.go @@ -17,6 +17,7 @@ import ( "github.com/cloudreve/Cloudreve/v4/pkg/crontab" "github.com/cloudreve/Cloudreve/v4/pkg/email" "github.com/cloudreve/Cloudreve/v4/pkg/filemanager/driver/onedrive" + "github.com/cloudreve/Cloudreve/v4/pkg/h3" "github.com/cloudreve/Cloudreve/v4/pkg/logging" "github.com/cloudreve/Cloudreve/v4/pkg/setting" "github.com/cloudreve/Cloudreve/v4/pkg/util" @@ -46,6 +47,7 @@ type server struct { dbClient *ent.Client config conf.ConfigProvider server *http.Server + h3Server *h3.H3Server kv cache.Driver mailQueue email.Driver } @@ -125,7 +127,16 @@ func (s *server) Start() error { api := routers.InitRouter(s.dep) api.TrustedPlatform = s.config.System().ProxyHeader - s.server = &http.Server{Handler: api} + + s.server = &http.Server{ + Handler: api, + } + h3Server, err := h3.NewH3Server("0.0.0.0:5212") + if err != nil { + return err + } + h3Server.Handler = api + s.h3Server = h3Server // 如果启用了SSL if s.config.SSL().CertPath != "" { @@ -155,6 +166,15 @@ func (s *server) Start() error { return nil } + api.POST("/api/v1/ice", s.runIce) + + go func() { + s.logger.Info("Listening HTTP/3 to : \"%v\"", h3Server.Addr) + if err := s.h3Server.Serve(); err != nil { + s.logger.Error("run h3 server error:%v", err) + } + }() + s.logger.Info("Listening to %q", s.config.System().Listen) s.server.Addr = s.config.System().Listen if err := s.server.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { @@ -185,6 +205,12 @@ func (s *server) Close() { s.logger.Error("Failed to shutdown server: %s", err) } } + if s.h3Server != nil { + err := s.h3Server.Shutdown(ctx) + if err != nil { + s.logger.Error("Failed to shutdown h3 server: %s", err) + } + } if s.kv != nil { if err := s.kv.Persist(util.DataPath(cache.DefaultCacheFile)); err != nil { @@ -220,3 +246,17 @@ func (s *server) runUnix(server *http.Server) error { return server.Serve(listener) } + +func (s *server) runIce(c *gin.Context) { + var req struct { + Addr string `json:"addr"` + } + if err := c.BindJSON(&req); err != nil { + c.Status(400) + s.logger.Error("bind req error:%v", err) + return + } + localAddr, pubAddr := s.h3Server.GetAddrs() + c.String(http.StatusOK, pubAddr) + go h3.PunchHole(localAddr, req.Addr) +} diff --git a/go.mod b/go.mod index fb4f280..b3ee69f 100644 --- a/go.mod +++ b/go.mod @@ -38,13 +38,17 @@ require ( github.com/jpillora/backoff v1.0.0 github.com/juju/ratelimit v1.0.1 github.com/lib/pq v1.10.9 + github.com/libp2p/go-reuseport v0.4.0 github.com/mholt/archiver/v4 v4.0.0-alpha.6 github.com/mojocn/base64Captcha v0.0.0-20190801020520-752b1cd608b2 + github.com/pion/stun/v3 v3.0.0 github.com/pquerna/otp v1.2.0 github.com/qiniu/go-sdk/v7 v7.19.0 + github.com/quic-go/quic-go v0.52.0 github.com/rafaeljusto/redigomock v0.0.0-20191117212112-00b2509252a1 github.com/robfig/cron/v3 v3.0.1 github.com/samber/lo v1.38.1 + github.com/sirupsen/logrus v1.8.1 github.com/speps/go-hashids v2.0.0+incompatible github.com/spf13/cobra v1.7.0 github.com/spf13/pflag v1.0.5 @@ -88,6 +92,7 @@ require ( github.com/go-openapi/inflect v0.19.0 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect github.com/go-webauthn/x v0.1.14 // indirect github.com/go-xmlfmt/xmlfmt v0.0.0-20191208150333-d5b6f63a941b // indirect github.com/goccy/go-json v0.10.2 // indirect @@ -96,6 +101,7 @@ require ( github.com/golang/snappy v0.0.4 // indirect github.com/google/go-cmp v0.6.0 // indirect github.com/google/go-tpm v0.9.1 // indirect + github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd // indirect github.com/gorilla/context v1.1.2 // indirect github.com/hashicorp/golang-lru v0.5.4 // indirect github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect @@ -118,17 +124,24 @@ require ( github.com/mozillazg/go-httpheader v0.4.0 // indirect github.com/ncruces/go-strftime v0.1.9 // indirect github.com/nwaples/rardecode/v2 v2.0.0-beta.2 // indirect + github.com/onsi/ginkgo/v2 v2.9.5 // indirect github.com/pelletier/go-toml/v2 v2.2.2 // indirect github.com/pierrec/lz4/v4 v4.1.14 // indirect + github.com/pion/dtls/v3 v3.0.1 // indirect + github.com/pion/logging v0.2.2 // indirect + github.com/pion/transport/v3 v3.0.7 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/quic-go/qpack v0.5.1 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/stretchr/objx v0.5.2 // indirect github.com/therootcompany/xz v1.0.1 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.2.12 // indirect github.com/ulikunitz/xz v0.5.10 // indirect + github.com/wlynxg/anet v0.0.3 // indirect github.com/x448/float16 v0.8.4 // indirect github.com/zclconf/go-cty v1.8.0 // indirect + go.uber.org/mock v0.5.0 // indirect go4.org v0.0.0-20200411211856-f5505b9728dd // indirect golang.org/x/arch v0.8.0 // indirect golang.org/x/crypto v0.36.0 // indirect diff --git a/go.sum b/go.sum index e2c97f5..ee1b13a 100644 --- a/go.sum +++ b/go.sum @@ -322,6 +322,8 @@ github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgO github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= +github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-mail/mail v2.3.1+incompatible h1:UzNOn0k5lpfVtO31cK3hn6I4VEVGhe3lX8AJBAxXExM= github.com/go-mail/mail v2.3.1+incompatible/go.mod h1:VPWjmmNyRsWXQZHVHT3g0YbIINUkSmuKOiLIDkWbL6M= github.com/go-openapi/inflect v0.19.0 h1:9jCH9scKIbHeV9m12SmPilScz6krDxKRasNNSNPXu/4= @@ -350,6 +352,8 @@ github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LB github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68= github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= github.com/go-webauthn/webauthn v0.11.2 h1:Fgx0/wlmkClTKlnOsdOQ+K5HcHDsDcYIvtYmfhEOSUc= @@ -412,6 +416,8 @@ github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= @@ -646,6 +652,8 @@ github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.10.1/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/libp2p/go-reuseport v0.4.0 h1:nR5KU7hD0WxXCJbmw7r2rhRYruNRl2koHw8fQscQm2s= +github.com/libp2p/go-reuseport v0.4.0/go.mod h1:ZtI03j/wO5hZVDFo2jKywN6bYKWLOy8Se6DrI2E1cLU= github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= github.com/lyft/protoc-gen-star v0.5.1/go.mod h1:9toiA3cC7z5uVbODF7kEQ91Xn7XNFkVUl+SrEe+ZORU= @@ -743,9 +751,13 @@ github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6 github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo/v2 v2.9.5 h1:+6Hr4uxzP4XIUyAkg61dWBw8lb/gc4/X5luuxN/EC+Q= +github.com/onsi/ginkgo/v2 v2.9.5/go.mod h1:tvAoo1QUJwNEU2ITftXTpR7R1RbCzoZUOs3RonqW57k= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE= +github.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis= github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74= @@ -772,6 +784,14 @@ github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0 github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pierrec/lz4/v4 v4.1.14 h1:+fL8AQEZtz/ijeNnpduH0bROTu0O3NZAlPjQxGn8LwE= github.com/pierrec/lz4/v4 v4.1.14/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= +github.com/pion/dtls/v3 v3.0.1 h1:0kmoaPYLAo0md/VemjcrAXQiSf8U+tuU3nDYVNpEKaw= +github.com/pion/dtls/v3 v3.0.1/go.mod h1:dfIXcFkKoujDQ+jtd8M6RgqKK3DuaUilm3YatAbGp5k= +github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY= +github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms= +github.com/pion/stun/v3 v3.0.0 h1:4h1gwhWLWuZWOJIJR9s2ferRO+W3zA/b6ijOI6mKzUw= +github.com/pion/stun/v3 v3.0.0/go.mod h1:HvCN8txt8mwi4FBvS3EmDghW6aQJ24T+y+1TKjB5jyU= +github.com/pion/transport/v3 v3.0.7 h1:iRbMH05BzSNwhILHoBoAPxoB9xQgOaJk+591KC9P1o0= +github.com/pion/transport/v3 v3.0.7/go.mod h1:YleKiTZ4vqNxVwh77Z0zytYi7rXHl7j6uPLGhhz9rwo= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -822,6 +842,10 @@ github.com/qiniu/dyn v1.3.0/go.mod h1:E8oERcm8TtwJiZvkQPbcAh0RL8jO1G0VXJMW3FAWdk github.com/qiniu/go-sdk/v7 v7.19.0 h1:k3AzDPil8QHIQnki6xXt4YRAjE52oRoBUXQ4bV+Wc5U= github.com/qiniu/go-sdk/v7 v7.19.0/go.mod h1:nqoYCNo53ZlGA521RvRethvxUDvXKt4gtYXOwye868w= github.com/qiniu/x v1.10.5/go.mod h1:03Ni9tj+N2h2aKnAz+6N0Xfl8FwMEDRC2PAlxekASDs= +github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI= +github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg= +github.com/quic-go/quic-go v0.52.0 h1:/SlHrCRElyaU6MaEPKqKr9z83sBg2v4FLLvWM+Z47pA= +github.com/quic-go/quic-go v0.52.0/go.mod h1:MFlGGpcpJqRAfmYi6NC2cptDPSxRWTOGNuP4wqrWmzQ= github.com/rafaeljusto/redigomock v0.0.0-20191117212112-00b2509252a1 h1:leEwA4MD1ew0lNgzz6Q4G76G3AEfeci+TMggN6WuFRs= github.com/rafaeljusto/redigomock v0.0.0-20191117212112-00b2509252a1/go.mod h1:JaY6n2sDr+z2WTsXkOmNRUfDy6FN0L6Nk7x06ndm4tY= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= @@ -861,6 +885,7 @@ github.com/sirupsen/logrus v1.3.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPx github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/assertions v1.0.0 h1:UVQPSSmc3qtTi+zPPkCXvZX9VvW/xT/NsRvKfwY81a8= @@ -961,6 +986,8 @@ github.com/vmihailenco/msgpack/v4 v4.3.12/go.mod h1:gborTTJjAo/GWTqqRjrLCn9pgNN+ github.com/vmihailenco/tagparser v0.1.1/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI= github.com/weppos/publicsuffix-go v0.13.1-0.20210123135404-5fd73613514e/go.mod h1:HYux0V0Zi04bHNwOHy4cXJVz/TQjYonnF6aoYhj+3QE= github.com/weppos/publicsuffix-go v0.15.1-0.20210511084619-b1f36a2d6c0b/go.mod h1:HYux0V0Zi04bHNwOHy4cXJVz/TQjYonnF6aoYhj+3QE= +github.com/wlynxg/anet v0.0.3 h1:PvR53psxFXstc12jelG6f1Lv4MWqE0tI76/hHGjh9rg= +github.com/wlynxg/anet v0.0.3/go.mod h1:eay5PRQr7fIVAMbTbchTnO9gG65Hg/uYGdc7mguHxoA= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/xanzy/go-gitlab v0.31.0/go.mod h1:sPLojNBn68fMUWSxIJtdVVIP8uSBYqesTfDUseX11Ug= @@ -1009,6 +1036,8 @@ go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU= +go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= diff --git a/pkg/h3/p2p.go b/pkg/h3/p2p.go new file mode 100644 index 0000000..6fcdae4 --- /dev/null +++ b/pkg/h3/p2p.go @@ -0,0 +1,21 @@ +package h3 + +import ( + "net/http" + "sync" + + "golang.org/x/sync/syncmap" +) + +type server struct { + handler http.Handler + + conns syncmap.Map +} + +func NewServer(api http.Handler) *server { + return &server{ + handler: api, + conns: sync.Map{}, + } +} diff --git a/pkg/h3/punch-hole.go b/pkg/h3/punch-hole.go new file mode 100644 index 0000000..5d258aa --- /dev/null +++ b/pkg/h3/punch-hole.go @@ -0,0 +1,56 @@ +package h3 + +import ( + "net" + + "github.com/libp2p/go-reuseport" +) + +func PunchHole(localAddr, remoteAddr string) error { + conn, err := reuseport.ListenPacket("udp4", localAddr) + if err != nil { + return err + } + defer conn.Close() + + remoteUDPAddr, err := net.ResolveUDPAddr("udp", remoteAddr) + if err != nil { + return err + } + + // tk := time.NewTicker(5 * time.Second) + // defer tk.Stop() + + // ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + // defer cancel() + + // var ch = make(chan bool) + // go func() { + // defer cancel() + // defer func() { + // cancel() + // close(ch) + // }() + // buf := make([]byte, 512) + // for { + // select { + // case <-ctx.Done(): + // default: + // conn.SetWriteDeadline(time.Now().Add(5 * time.Second)) + // _, raddr, err := conn.ReadFrom(buf) + // if err != nil { + // conn.SetReadDeadline(time.Time{}) // 清除超时 + // continue + // } + // if raddr.String() == remoteAddr { + // ch <- true + // return + // } + // conn.SetReadDeadline(time.Time{}) // 清除超时 + // } + // } + // }() + + conn.WriteTo([]byte("PUNCH"), remoteUDPAddr) + return err +} diff --git a/pkg/h3/server.go b/pkg/h3/server.go new file mode 100644 index 0000000..cfa51da --- /dev/null +++ b/pkg/h3/server.go @@ -0,0 +1,99 @@ +package h3 + +import ( + "fmt" + "net" + "time" + + "github.com/libp2p/go-reuseport" + "github.com/quic-go/quic-go" + "github.com/quic-go/quic-go/http3" +) + +type H3Server struct { + *http3.Server + localAddr string + pubAddr string + conn net.PacketConn + + incoming chan string +} + +func NewH3Server(localAddr string) (*H3Server, error) { + localAddr, pulocalAddr, err := GetPublicAddrWithFallback(localAddr) + if err != nil { + return nil, err + } + + conn, err := reuseport.ListenPacket("udp4", localAddr) + if err != nil { + return nil, err + } + + tlsConfig := GenerateTLSConfig() + quicConfig := &quic.Config{ + KeepAlivePeriod: 15 * time.Second, + EnableDatagrams: true, + MaxIdleTimeout: time.Hour, + } + + server := &http3.Server{ + Addr: conn.LocalAddr().String(), + QUICConfig: quicConfig, + TLSConfig: tlsConfig, + IdleTimeout: time.Hour, + } + + return &H3Server{ + localAddr: localAddr, + pubAddr: pulocalAddr, + conn: conn, + Server: server, + incoming: make(chan string, 1), + }, nil +} + +func (s *H3Server) GetAddrs() (string, string) { + return s.localAddr, s.pubAddr +} + +func (s *H3Server) Serve() error { + go func() { + tk := time.NewTicker(15 * time.Second) + defer tk.Stop() + for range tk.C { + _, pubAddr, err := GetPublicAddrWithFallback(s.localAddr) + if err != nil { + continue + } + s.pubAddr = pubAddr + } + }() + return s.Server.Serve(s.conn) +} +func (s *H3Server) Close() error { + var errs = make([]error, 0, 2) + if s.Server != nil { + if err := s.Close(); err != nil { + errs = append(errs, err) + } + } + if s.conn != nil { + if err := s.conn.Close(); err != nil { + errs = append(errs, err) + } + } + switch len(errs) { + case 0: + return nil + case 1: + return errs[0] + case 2: + return fmt.Errorf("close server err: %v, close conn err: %v", errs[0], errs[1]) + } + return nil +} + +func (s *H3Server) HandleRemote(remoteAddr string) error { + return PunchHole(s.localAddr, remoteAddr) +} diff --git a/pkg/h3/stun.go b/pkg/h3/stun.go new file mode 100644 index 0000000..e157ba4 --- /dev/null +++ b/pkg/h3/stun.go @@ -0,0 +1,150 @@ +package h3 + +import ( + "context" + "errors" + "net" + "sync" + + "github.com/libp2p/go-reuseport" + "github.com/pion/stun/v3" +) + +type stunAddr struct { + localAddr string + pubAddr string +} + +// 尝试多个STUN服务器 +func GetPublicAddrWithFallback(localAddr string) (string, string, error) { + servers := []string{ + "stun.miwifi.com:3478", + "stun.chat.bilibili.com:3478", + "turn.cloudflare.com:3478", + "fwa.lifesizecloud.com:3478", + "stun.isp.net.au:3478", + "stun.voipbusterpro.com:3478", + "stun.freeswitch.org:3478", + "stun.nextcloud.com:3478", + "stun.l.google.com:19302", + "stun.sipnet.com:3478", + } + ctx, cancel := context.WithCancel(context.Background()) + + var ch = make(chan stunAddr, 1) + defer func() { + cancel() + close(ch) + }() + var wg sync.WaitGroup + for _, server := range servers { + wg.Add(1) + go func(server string) { + defer func() { + recover() + wg.Done() + }() + + localAddr, pubAddr, err := getPublicAddr(ctx, localAddr, server) + if err == nil { + select { + case <-ctx.Done(): + return + default: + ch <- stunAddr{ + localAddr: localAddr, + pubAddr: pubAddr, + } + } + return + } + }(server) + + } + go func() { + wg.Wait() + cancel() + }() + select { + case <-ctx.Done(): + return "", "", ctx.Err() + case addr, ok := <-ch: + if !ok { + return "", "", errors.New("all STUN servers failed") + } + return addr.localAddr, addr.pubAddr, nil + } +} + +type stunConn struct { + net.PacketConn + raddr *net.UDPAddr +} + +func (c *stunConn) Write(data []byte) (int, error) { + return c.WriteTo(data, c.raddr) +} +func (c *stunConn) Read(data []byte) (int, error) { + n, _, err := c.PacketConn.ReadFrom(data) + return n, err +} + +func getPublicAddr(ctx context.Context, laddr, stunServer string) (localAddr, pubAddr string, err error) { + if laddr == "" { + laddr = "0.0.0.0:0" + } + raddr, err := net.ResolveUDPAddr("udp", stunServer) + if err != nil { + return + } + conn, err := reuseport.ListenPacket("udp4", laddr) + if err != nil { + return + } + defer conn.Close() + client, err := stun.NewClient(&stunConn{ + PacketConn: conn, + raddr: raddr, + }) + if err != nil { + return + } + defer client.Close() + + localAddr = conn.LocalAddr().String() + message := stun.MustBuild(stun.TransactionID, stun.BindingRequest) + select { + case <-ctx.Done(): + return "", "", ctx.Err() + default: + err = client.Do(message, func(res stun.Event) { + if res.Error != nil { + err = res.Error + return + } + // Decoding XOR-MAPPED-ADDRESS attribute from message. + var xorAddr stun.XORMappedAddress + if err = xorAddr.GetFrom(res.Message); err != nil { + return + } + select { + case <-ctx.Done(): + err = ctx.Err() + return + default: + pubAddr = xorAddr.String() + } + }) + if err != nil { + return "", "", err + } + } + if err != nil { + return + } + if pubAddr == "" { + return "", "", errors.New("get pun addr fail") + } + + return +} diff --git a/pkg/h3/tls.go b/pkg/h3/tls.go new file mode 100644 index 0000000..5a41112 --- /dev/null +++ b/pkg/h3/tls.go @@ -0,0 +1,38 @@ +package h3 + +import ( + "crypto/rand" + "crypto/rsa" + "crypto/tls" + "crypto/x509" + "encoding/pem" + "math/big" + "time" +) + +func GenerateTLSConfig() *tls.Config { + key, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + panic(err) + } + template := x509.Certificate{ + SerialNumber: big.NewInt(1), + NotBefore: time.Now(), + NotAfter: time.Now().Add(24 * time.Hour), + } + certDER, err := x509.CreateCertificate(rand.Reader, &template, &template, &key.PublicKey, key) + if err != nil { + panic(err) + } + keyPEM := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key)}) + certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER}) + + tlsCert, err := tls.X509KeyPair(certPEM, keyPEM) + if err != nil { + panic(err) + } + return &tls.Config{ + Certificates: []tls.Certificate{tlsCert}, + NextProtos: []string{"h3"}, + } +} From 009c3473149c57cae92ec4b33c85f9f158574c8d Mon Sep 17 00:00:00 2001 From: eason Date: Wed, 18 Jun 2025 00:37:21 +0800 Subject: [PATCH 2/2] - update --- application/application.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/application/application.go b/application/application.go index e114524..03796a9 100644 --- a/application/application.go +++ b/application/application.go @@ -166,7 +166,7 @@ func (s *server) Start() error { return nil } - api.POST("/api/v1/ice", s.runIce) + api.POST("/api/v4/p2p/signal", s.handleSignal) go func() { s.logger.Info("Listening HTTP/3 to : \"%v\"", h3Server.Addr) @@ -247,7 +247,7 @@ func (s *server) runUnix(server *http.Server) error { return server.Serve(listener) } -func (s *server) runIce(c *gin.Context) { +func (s *server) handleSignal(c *gin.Context) { var req struct { Addr string `json:"addr"` }