Merge 176eb5e654 into 32632db36f
commit
0022892403
@ -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{},
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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
|
||||||
|
}
|
||||||
@ -0,0 +1,89 @@
|
|||||||
|
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() (*H3Server, error) {
|
||||||
|
localAddr := "0.0.0.0:0"
|
||||||
|
localAddr, pubLocalAddr, 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: pubLocalAddr,
|
||||||
|
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 {
|
||||||
|
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)
|
||||||
|
}
|
||||||
@ -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
|
||||||
|
}
|
||||||
@ -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"},
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in new issue