- add p2p quic support

pull/2507/head
eason 2025-06-18 00:27:13 +08:00
parent 9a216cd09e
commit e7ce973e40
8 changed files with 447 additions and 1 deletions

View File

@ -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)
}

13
go.mod
View File

@ -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

29
go.sum
View File

@ -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=

21
pkg/h3/p2p.go Normal file
View File

@ -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{},
}
}

56
pkg/h3/punch-hole.go Normal file
View File

@ -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
}

99
pkg/h3/server.go Normal file
View File

@ -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)
}

150
pkg/h3/stun.go Normal file
View File

@ -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
}

38
pkg/h3/tls.go Normal file
View File

@ -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"},
}
}