mirror of
https://github.com/yusing/godoxy.git
synced 2026-01-11 21:10:30 +01:00
feat(route): add bind address support for TCP/UDP routes
- Introduced a new `Bind` field in the route configuration to specify the address to listen on for TCP and UDP routes. - Defaulted the bind address to "0.0.0.0" if not provided. - Enhanced validation to ensure the bind address is a valid IP. - Updated stream initialization to use the correct network type (tcp4/tcp6 or udp4/udp6) based on the bind address. - Refactored stream creation functions to accept the network type as a parameter.
This commit is contained in:
@@ -4189,6 +4189,11 @@
|
||||
"x-nullable": false,
|
||||
"x-omitempty": false
|
||||
},
|
||||
"bind": {
|
||||
"description": "for TCP and UDP routes, bind address to listen on",
|
||||
"type": "string",
|
||||
"x-nullable": true
|
||||
},
|
||||
"container": {
|
||||
"description": "Docker only",
|
||||
"allOf": [
|
||||
@@ -5327,6 +5332,11 @@
|
||||
"x-nullable": false,
|
||||
"x-omitempty": false
|
||||
},
|
||||
"bind": {
|
||||
"description": "for TCP and UDP routes, bind address to listen on",
|
||||
"type": "string",
|
||||
"x-nullable": true
|
||||
},
|
||||
"container": {
|
||||
"description": "Docker only",
|
||||
"allOf": [
|
||||
|
||||
@@ -879,6 +879,10 @@ definitions:
|
||||
type: string
|
||||
alias:
|
||||
type: string
|
||||
bind:
|
||||
description: for TCP and UDP routes, bind address to listen on
|
||||
type: string
|
||||
x-nullable: true
|
||||
container:
|
||||
allOf:
|
||||
- $ref: '#/definitions/Container'
|
||||
@@ -1495,6 +1499,10 @@ definitions:
|
||||
type: string
|
||||
alias:
|
||||
type: string
|
||||
bind:
|
||||
description: for TCP and UDP routes, bind address to listen on
|
||||
type: string
|
||||
x-nullable: true
|
||||
container:
|
||||
allOf:
|
||||
- $ref: '#/definitions/Container'
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/url"
|
||||
"os"
|
||||
"reflect"
|
||||
@@ -47,6 +48,9 @@ type (
|
||||
Host string `json:"host,omitempty"`
|
||||
Port route.Port `json:"port"`
|
||||
|
||||
// for TCP and UDP routes, bind address to listen on
|
||||
Bind string `json:"bind,omitempty" validate:"omitempty,ip_addr" extensions:"x-nullable"`
|
||||
|
||||
Root string `json:"root,omitempty"`
|
||||
SPA bool `json:"spa,omitempty"` // Single-page app mode: serves index for non-existent paths
|
||||
Index string `json:"index,omitempty"` // Index file to serve for single-page app mode
|
||||
@@ -278,7 +282,28 @@ func (r *Route) validate() gperr.Error {
|
||||
r.ProxyURL = gperr.Collect(&errs, nettypes.ParseURL, fmt.Sprintf("%s://%s:%d", r.Scheme, r.Host, r.Port.Proxy))
|
||||
case route.SchemeTCP, route.SchemeUDP:
|
||||
if !r.ShouldExclude() {
|
||||
r.LisURL = gperr.Collect(&errs, nettypes.ParseURL, fmt.Sprintf("%s://:%d", r.Scheme, r.Port.Listening))
|
||||
if r.Bind == "" {
|
||||
r.Bind = "0.0.0.0"
|
||||
}
|
||||
bindIP := net.ParseIP(r.Bind)
|
||||
if bindIP == nil {
|
||||
return gperr.Errorf("invalid bind address %s", r.Bind)
|
||||
}
|
||||
var scheme string
|
||||
if bindIP.To4() == nil { // IPv6
|
||||
if r.Scheme == route.SchemeTCP {
|
||||
scheme = "tcp6"
|
||||
} else {
|
||||
scheme = "udp6"
|
||||
}
|
||||
} else {
|
||||
if r.Scheme == route.SchemeTCP {
|
||||
scheme = "tcp4"
|
||||
} else {
|
||||
scheme = "udp4"
|
||||
}
|
||||
}
|
||||
r.LisURL = gperr.Collect(&errs, nettypes.ParseURL, fmt.Sprintf("%s://%s:%d", scheme, r.Bind, r.Port.Listening))
|
||||
}
|
||||
r.ProxyURL = gperr.Collect(&errs, nettypes.ParseURL, fmt.Sprintf("%s://%s:%d", r.Scheme, r.Host, r.Port.Proxy))
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/rs/zerolog/log"
|
||||
@@ -30,7 +31,7 @@ func NewStreamRoute(base *Route) (types.Route, gperr.Error) {
|
||||
return &StreamRoute{
|
||||
Route: base,
|
||||
l: log.With().
|
||||
Str("type", string(base.Scheme)).
|
||||
Str("type", base.LisURL.Scheme).
|
||||
Str("name", base.Name()).
|
||||
Logger(),
|
||||
}, nil
|
||||
@@ -99,7 +100,9 @@ func (r *StreamRoute) LocalAddr() net.Addr {
|
||||
|
||||
func (r *StreamRoute) initStream() (nettypes.Stream, error) {
|
||||
lurl, rurl := r.LisURL, r.ProxyURL
|
||||
if lurl != nil && lurl.Scheme != rurl.Scheme {
|
||||
// lurl scheme is either tcp4/tcp6 -> tcp, udp4/udp6 -> udp
|
||||
// rurl scheme does not have the trailing 4/6
|
||||
if strings.TrimRight(lurl.Scheme, "46") != rurl.Scheme {
|
||||
return nil, fmt.Errorf("incoherent scheme is not yet supported: %s != %s", lurl.Scheme, rurl.Scheme)
|
||||
}
|
||||
|
||||
@@ -110,9 +113,9 @@ func (r *StreamRoute) initStream() (nettypes.Stream, error) {
|
||||
|
||||
switch rurl.Scheme {
|
||||
case "tcp":
|
||||
return stream.NewTCPTCPStream(laddr, rurl.Host)
|
||||
return stream.NewTCPTCPStream(r.LisURL.Scheme, laddr, rurl.Host)
|
||||
case "udp":
|
||||
return stream.NewUDPUDPStream(laddr, rurl.Host)
|
||||
return stream.NewUDPUDPStream(r.LisURL.Scheme, laddr, rurl.Host)
|
||||
}
|
||||
return nil, fmt.Errorf("unknown scheme: %s", rurl.Scheme)
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
)
|
||||
|
||||
type TCPTCPStream struct {
|
||||
network string
|
||||
listener net.Listener
|
||||
laddr *net.TCPAddr
|
||||
dst *net.TCPAddr
|
||||
@@ -24,21 +25,21 @@ type TCPTCPStream struct {
|
||||
closed atomic.Bool
|
||||
}
|
||||
|
||||
func NewTCPTCPStream(listenAddr, dstAddr string) (nettypes.Stream, error) {
|
||||
dst, err := net.ResolveTCPAddr("tcp", dstAddr)
|
||||
func NewTCPTCPStream(network, listenAddr, dstAddr string) (nettypes.Stream, error) {
|
||||
dst, err := net.ResolveTCPAddr(network, dstAddr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
laddr, err := net.ResolveTCPAddr("tcp", listenAddr)
|
||||
laddr, err := net.ResolveTCPAddr(network, listenAddr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &TCPTCPStream{laddr: laddr, dst: dst}, nil
|
||||
return &TCPTCPStream{network: network, laddr: laddr, dst: dst}, nil
|
||||
}
|
||||
|
||||
func (s *TCPTCPStream) ListenAndServe(ctx context.Context, preDial, onRead nettypes.HookFunc) {
|
||||
var err error
|
||||
s.listener, err = net.ListenTCP("tcp", s.laddr)
|
||||
s.listener, err = net.ListenTCP(s.network, s.laddr)
|
||||
if err != nil {
|
||||
logErr(s, err, "failed to listen")
|
||||
return
|
||||
|
||||
@@ -17,7 +17,7 @@ import (
|
||||
)
|
||||
|
||||
type UDPUDPStream struct {
|
||||
name string
|
||||
network string
|
||||
listener net.PacketConn
|
||||
|
||||
laddr *net.UDPAddr
|
||||
@@ -51,25 +51,26 @@ const (
|
||||
|
||||
var bufPool = synk.GetSizedBytesPool()
|
||||
|
||||
func NewUDPUDPStream(listenAddr, dstAddr string) (nettypes.Stream, error) {
|
||||
dst, err := net.ResolveUDPAddr("udp", dstAddr)
|
||||
func NewUDPUDPStream(network, listenAddr, dstAddr string) (nettypes.Stream, error) {
|
||||
dst, err := net.ResolveUDPAddr(network, dstAddr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
laddr, err := net.ResolveUDPAddr("udp", listenAddr)
|
||||
laddr, err := net.ResolveUDPAddr(network, listenAddr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &UDPUDPStream{
|
||||
laddr: laddr,
|
||||
dst: dst,
|
||||
conns: make(map[string]*udpUDPConn),
|
||||
network: network,
|
||||
laddr: laddr,
|
||||
dst: dst,
|
||||
conns: make(map[string]*udpUDPConn),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *UDPUDPStream) ListenAndServe(ctx context.Context, preDial, onRead nettypes.HookFunc) {
|
||||
var err error
|
||||
s.listener, err = net.ListenUDP("udp", s.laddr)
|
||||
s.listener, err = net.ListenUDP(s.network, s.laddr)
|
||||
if err != nil {
|
||||
logErr(s, err, "failed to listen")
|
||||
return
|
||||
@@ -114,9 +115,6 @@ func (s *UDPUDPStream) LocalAddr() net.Addr {
|
||||
|
||||
func (s *UDPUDPStream) MarshalZerologObject(e *zerolog.Event) {
|
||||
e.Str("protocol", "udp-udp")
|
||||
if s.name != "" {
|
||||
e.Str("name", s.name)
|
||||
}
|
||||
if s.dst != nil {
|
||||
e.Str("dst", s.dst.String())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user