parse real client IP

This commit is contained in:
ginuerzh
2025-07-15 19:59:10 +08:00
parent d9a2f44a78
commit afcf4a5252
24 changed files with 232 additions and 74 deletions
+6 -8
View File
@@ -2,10 +2,10 @@ package v5
import (
"bytes"
"math"
"net"
"time"
"github.com/go-gost/core/common/bufpool"
"github.com/go-gost/core/logger"
"github.com/go-gost/gosocks5"
)
@@ -28,15 +28,14 @@ type udpRelayConn struct {
udpConn *net.UDPConn
tcpConn net.Conn
taddr net.Addr
bufferSize int
rbuf [math.MaxUint16]byte
wbuf [math.MaxInt16]byte
logger logger.Logger
}
func (c *udpRelayConn) ReadFrom(b []byte) (n int, addr net.Addr, err error) {
buf := bufpool.Get(c.bufferSize)
defer bufpool.Put(buf)
nn, err := c.udpConn.Read(buf)
buf := c.rbuf[:]
nn, err := c.udpConn.Read(buf[:])
if err != nil {
return
}
@@ -82,8 +81,7 @@ func (c *udpRelayConn) WriteTo(b []byte, addr net.Addr) (n int, err error) {
Data: b,
}
buf := bufpool.Get(c.bufferSize)
defer bufpool.Put(buf)
buf := c.wbuf[:]
nn, err := dgram.WriteTo(bytes.NewBuffer(buf[:0]))
if err != nil {
-1
View File
@@ -228,7 +228,6 @@ func (c *socks5Connector) relayUDP(ctx context.Context, conn net.Conn, addr net.
udpConn: cc.(*net.UDPConn),
tcpConn: conn,
taddr: addr,
bufferSize: c.md.udpBufferSize,
logger: log,
}, nil
}
+1 -10
View File
@@ -4,19 +4,14 @@ import (
"time"
mdata "github.com/go-gost/core/metadata"
mdutil "github.com/go-gost/x/metadata/util"
"github.com/go-gost/x/internal/util/mux"
)
const (
defaultUDPBufferSize = 4096
mdutil "github.com/go-gost/x/metadata/util"
)
type metadata struct {
connectTimeout time.Duration
noTLS bool
relay string
udpBufferSize int
udpTimeout time.Duration
muxCfg *mux.Config
}
@@ -25,10 +20,6 @@ func (c *socks5Connector) parseMetadata(md mdata.Metadata) (err error) {
c.md.connectTimeout = mdutil.GetDuration(md, "timeout")
c.md.noTLS = mdutil.GetBool(md, "notls")
c.md.relay = mdutil.GetString(md, "relay")
c.md.udpBufferSize = mdutil.GetInt(md, "udp.bufferSize", "udpBufferSize")
if c.md.udpBufferSize <= 0 {
c.md.udpBufferSize = defaultUDPBufferSize
}
c.md.udpTimeout = mdutil.GetDuration(md, "udp.timeout")
c.md.muxCfg = &mux.Config{
+5
View File
@@ -665,6 +665,11 @@ func (h *httpHandler) dial(ctx context.Context, network, addr string) (conn net.
conn, err = h.options.Router.Dial(ctxvalue.ContextWithBuffer(ctx, &buf), network, addr)
if ro := ctx_internal.RecorderObjectFromContext(ctx); ro != nil {
ro.Route = buf.String()
if conn != nil {
ro.Src = conn.LocalAddr().String()
ro.Dst = conn.RemoteAddr().String()
}
}
return
+4 -1
View File
@@ -49,9 +49,12 @@ type metadata struct {
func (h *httpHandler) parseMetadata(md mdata.Metadata) error {
h.md.readTimeout = mdutil.GetDuration(md, "readTimeout")
if h.md.readTimeout <= 0 {
if h.md.readTimeout == 0 {
h.md.readTimeout = 15 * time.Second
}
if h.md.readTimeout < 0 {
h.md.readTimeout = 0
}
if m := mdutil.GetStringMapString(md, "http.header", "header"); len(m) > 0 {
hd := http.Header{}
+3
View File
@@ -87,6 +87,9 @@ func (h *socks5Handler) bindLocal(ctx context.Context, conn net.Conn, network, a
// Issue: may not reachable when host has multi-interface
socksAddr.Host, _, _ = net.SplitHostPort(conn.LocalAddr().String())
if h.md.publicAddr != "" {
socksAddr.Host = h.md.publicAddr
}
socksAddr.Type = 0
reply := gosocks5.NewReply(gosocks5.Succeeded, &socksAddr)
log.Trace(reply)
+3
View File
@@ -88,6 +88,9 @@ func (h *socks5Handler) muxBindLocal(ctx context.Context, conn net.Conn, network
// Issue: may not reachable when host has multi-interface
socksAddr.Host, _, _ = net.SplitHostPort(conn.LocalAddr().String())
if h.md.publicAddr != "" {
socksAddr.Host = h.md.publicAddr
}
socksAddr.Type = 0
reply := gosocks5.NewReply(gosocks5.Succeeded, &socksAddr)
log.Trace(reply)
+2
View File
@@ -14,6 +14,7 @@ import (
)
type metadata struct {
publicAddr string
readTimeout time.Duration
noTLS bool
enableBind bool
@@ -40,6 +41,7 @@ type metadata struct {
}
func (h *socks5Handler) parseMetadata(md mdata.Metadata) (err error) {
h.md.publicAddr = mdutil.GetString(md, "socks.publicAddr", "publicAddr")
h.md.readTimeout = mdutil.GetDuration(md, "readTimeout")
if h.md.readTimeout <= 0 {
h.md.readTimeout = 15 * time.Second
+11 -3
View File
@@ -17,6 +17,7 @@ import (
"github.com/go-gost/x/internal/net/udp"
"github.com/go-gost/x/internal/util/socks"
traffic_wrapper "github.com/go-gost/x/limiter/traffic/wrapper"
metrics "github.com/go-gost/x/metrics/wrapper"
xstats "github.com/go-gost/x/observer/stats"
stats_wrapper "github.com/go-gost/x/observer/stats/wrapper"
xrecorder "github.com/go-gost/x/recorder"
@@ -37,8 +38,8 @@ func (h *socks5Handler) handleUDP(ctx context.Context, conn net.Conn, ro *xrecor
lc := xnet.ListenConfig{
Netns: h.options.Netns,
}
laddr := &net.UDPAddr{IP: conn.LocalAddr().(*net.TCPAddr).IP, Port: 0} // use out-going interface's IP
cc, err := lc.ListenPacket(ctx, "udp", laddr.String())
cc, err := lc.ListenPacket(ctx, "udp", "")
if err != nil {
log.Error(err)
reply := gosocks5.NewReply(gosocks5.Failure, nil)
@@ -56,6 +57,12 @@ func (h *socks5Handler) handleUDP(ctx context.Context, conn net.Conn, ro *xrecor
saddr := gosocks5.Addr{}
saddr.ParseFrom(cc.LocalAddr().String())
saddr.Host, _, _ = net.SplitHostPort(conn.LocalAddr().String())
if v := net.ParseIP(h.md.publicAddr); v != nil {
saddr.Host = h.md.publicAddr
}
saddr.Type = 0
reply := gosocks5.NewReply(gosocks5.Succeeded, &saddr)
log.Trace(reply)
if err := reply.Write(conn); err != nil {
@@ -81,7 +88,9 @@ func (h *socks5Handler) handleUDP(ctx context.Context, conn net.Conn, ro *xrecor
log.Error(err)
return err
}
pc = metrics.WrapPacketConn(ro.Service, pc)
{
pStats := xstats.Stats{}
cc = stats_wrapper.WrapPacketConn(cc, &pStats)
@@ -90,7 +99,6 @@ func (h *socks5Handler) handleUDP(ctx context.Context, conn net.Conn, ro *xrecor
ro.OutputBytes = pStats.Get(stats.KindOutputBytes)
}()
{
clientID := ctxvalue.ClientIDFromContext(ctx)
cc = traffic_wrapper.WrapPacketConn(
cc,
+10
View File
@@ -186,10 +186,20 @@ func (h *tunnelHandler) initEntrypoint() (err error) {
func (h *tunnelHandler) Handle(ctx context.Context, conn net.Conn, opts ...handler.HandleOption) (err error) {
start := time.Now()
clientIP := conn.RemoteAddr().String()
if clientAddr := ctxvalue.ClientAddrFromContext(ctx); clientAddr != "" {
clientIP = string(clientAddr)
}
if h, _, _ := net.SplitHostPort(clientIP); h != "" {
clientIP = h
}
log := h.log.WithFields(map[string]any{
"remote": conn.RemoteAddr().String(),
"local": conn.LocalAddr().String(),
"sid": ctxvalue.SidFromContext(ctx),
"client": clientIP,
})
log.Infof("%s <> %s", conn.RemoteAddr(), conn.LocalAddr())
+13
View File
@@ -10,6 +10,7 @@ import (
type conn struct {
session *wt.Session
stream *wt.Stream
clientAddr net.Addr
}
func Conn(session *wt.Session, stream *wt.Stream) net.Conn {
@@ -19,6 +20,14 @@ func Conn(session *wt.Session, stream *wt.Stream) net.Conn {
}
}
func ConnWithClientAddr(session *wt.Session, stream *wt.Stream, clientAddr net.Addr) net.Conn {
return &conn{
session: session,
stream: stream,
clientAddr: clientAddr,
}
}
func (c *conn) Read(b []byte) (n int, err error) {
return c.stream.Read(b)
}
@@ -39,6 +48,10 @@ func (c *conn) RemoteAddr() net.Addr {
return c.session.RemoteAddr()
}
func (c *conn) ClientAddr() net.Addr {
return c.clientAddr
}
func (c *conn) SetDeadline(t time.Time) error {
return c.stream.SetDeadline(t)
}
+7
View File
@@ -106,6 +106,13 @@ func (c *limitConn) Metadata() metadata.Metadata {
return nil
}
func (c *limitConn) ClientAddr() net.Addr {
if sc, ok := c.Conn.(xnet.ClientAddr); ok {
return sc.ClientAddr()
}
return nil
}
type packetConn struct {
net.PacketConn
limiter traffic.TrafficLimiter
+39
View File
@@ -4,10 +4,12 @@ import (
"errors"
"io"
"net"
"strings"
"time"
"github.com/go-gost/core/logger"
pb "github.com/go-gost/x/internal/util/grpc/proto"
mdata "google.golang.org/grpc/metadata"
"google.golang.org/grpc/peer"
)
@@ -28,6 +30,11 @@ func (s *server) Tunnel(srv pb.GostTunel_TunnelServer) error {
if p, ok := peer.FromContext(srv.Context()); ok {
c.remoteAddr = p.Addr
}
if md, ok := mdata.FromIncomingContext(srv.Context()); ok {
if cip := getClientIP(md); cip != nil {
c.clientAddr = &net.IPAddr{IP: cip}
}
}
select {
case s.cqueue <- c:
@@ -41,11 +48,39 @@ func (s *server) Tunnel(srv pb.GostTunel_TunnelServer) error {
return nil
}
func getClientIP(md mdata.MD) net.IP {
if md == nil {
return nil
}
var cip string
// cloudflare CDN
if v := md.Get("CF-Connecting-IP"); len(v) > 0 {
cip = v[0]
}
if cip == "" {
if v := md.Get("X-Forwarded-For"); len(v) > 0 {
ss := strings.Split(v[0], ",")
if len(ss) > 0 && ss[0] != "" {
cip = ss[0]
}
}
}
if cip == "" {
if v := md.Get("X-Real-Ip"); len(v) > 0 {
cip = v[0]
}
}
return net.ParseIP(cip)
}
type conn struct {
s pb.GostTunel_TunnelServer
rb []byte
localAddr net.Addr
remoteAddr net.Addr
clientAddr net.Addr
closed chan struct{}
}
@@ -111,6 +146,10 @@ func (c *conn) RemoteAddr() net.Addr {
return c.remoteAddr
}
func (c *conn) ClientAddr() net.Addr {
return c.clientAddr
}
func (c *conn) SetDeadline(t time.Time) error {
return &net.OpError{Op: "set", Net: "grpc", Source: nil, Addr: nil, Err: errors.New("deadline not supported")}
}
+5
View File
@@ -16,6 +16,7 @@ type conn struct {
w http.ResponseWriter
laddr net.Addr
raddr net.Addr
clientAddr net.Addr
closed chan struct{}
}
@@ -44,6 +45,10 @@ func (c *conn) RemoteAddr() net.Addr {
return c.raddr
}
func (c *conn) ClientAddr() net.Addr {
return c.clientAddr
}
func (c *conn) SetDeadline(t time.Time) error {
return &net.OpError{Op: "set", Net: "http2", Source: nil, Addr: nil, Err: errors.New("deadline not supported")}
}
+5
View File
@@ -14,6 +14,7 @@ type conn struct {
w io.Writer
remoteAddr net.Addr
localAddr net.Addr
clientAddr net.Addr
closed chan struct{}
}
@@ -49,6 +50,10 @@ func (c *conn) RemoteAddr() net.Addr {
return c.remoteAddr
}
func (c *conn) ClientAddr() net.Addr {
return c.clientAddr
}
func (c *conn) SetDeadline(t time.Time) error {
return &net.OpError{Op: "set", Net: "http2", Source: nil, Addr: nil, Err: errors.New("deadline not supported")}
}
+8
View File
@@ -15,6 +15,7 @@ import (
md "github.com/go-gost/core/metadata"
admission "github.com/go-gost/x/admission/wrapper"
xnet "github.com/go-gost/x/internal/net"
xhttp "github.com/go-gost/x/internal/net/http"
"github.com/go-gost/x/internal/net/proxyproto"
climiter "github.com/go-gost/x/limiter/conn/wrapper"
limiter_wrapper "github.com/go-gost/x/limiter/traffic/wrapper"
@@ -198,11 +199,18 @@ func (l *h2Listener) upgrade(w http.ResponseWriter, r *http.Request) (*conn, err
Port: 0,
}
}
var clientAddr net.Addr
if clientIP := xhttp.GetClientIP(r); clientIP != nil {
clientAddr = &net.IPAddr{IP: clientIP}
}
return &conn{
r: r.Body,
w: flushWriter{w},
localAddr: l.addr,
remoteAddr: remoteAddr,
clientAddr: clientAddr,
closed: make(chan struct{}),
}, nil
}
+6
View File
@@ -13,6 +13,7 @@ import (
md "github.com/go-gost/core/metadata"
admission "github.com/go-gost/x/admission/wrapper"
xnet "github.com/go-gost/x/internal/net"
xhttp "github.com/go-gost/x/internal/net/http"
"github.com/go-gost/x/internal/net/proxyproto"
climiter "github.com/go-gost/x/limiter/conn/wrapper"
limiter_wrapper "github.com/go-gost/x/limiter/traffic/wrapper"
@@ -148,6 +149,11 @@ func (l *http2Listener) handleFunc(w http.ResponseWriter, r *http.Request) {
"w": w,
}),
}
if clientIP := xhttp.GetClientIP(r); clientIP != nil {
conn.clientAddr = &net.IPAddr{IP: clientIP}
}
select {
case l.cqueue <- conn:
default:
+5 -3
View File
@@ -3,7 +3,6 @@ package http3
import (
"errors"
"net"
"net/http"
"time"
mdata "github.com/go-gost/core/metadata"
@@ -12,10 +11,9 @@ import (
// a dummy HTTP3 server conn used by HTTP3 handler
type conn struct {
md mdata.Metadata
r *http.Request
w http.ResponseWriter
laddr net.Addr
raddr net.Addr
clientAddr net.Addr
closed chan struct{}
}
@@ -44,6 +42,10 @@ func (c *conn) RemoteAddr() net.Addr {
return c.raddr
}
func (c *conn) ClientAddr() net.Addr {
return c.clientAddr
}
func (c *conn) SetDeadline(t time.Time) error {
return &net.OpError{Op: "set", Net: "http3", Source: nil, Addr: nil, Err: errors.New("deadline not supported")}
}
+6
View File
@@ -9,6 +9,7 @@ import (
"github.com/go-gost/core/logger"
md "github.com/go-gost/core/metadata"
xnet "github.com/go-gost/x/internal/net"
xhttp "github.com/go-gost/x/internal/net/http"
mdx "github.com/go-gost/x/metadata"
"github.com/go-gost/x/registry"
"github.com/quic-go/quic-go"
@@ -134,6 +135,11 @@ func (l *http3Listener) handleFunc(w http.ResponseWriter, r *http.Request) {
"w": w,
}),
}
if clientIP := xhttp.GetClientIP(r); clientIP != nil {
conn.clientAddr = &net.IPAddr{IP: clientIP}
}
select {
case l.cqueue <- conn:
default:
+15 -3
View File
@@ -11,6 +11,7 @@ import (
md "github.com/go-gost/core/metadata"
admission "github.com/go-gost/x/admission/wrapper"
xnet "github.com/go-gost/x/internal/net"
xhttp "github.com/go-gost/x/internal/net/http"
wt_util "github.com/go-gost/x/internal/util/wt"
traffic_limiter "github.com/go-gost/x/limiter/traffic"
limiter_wrapper "github.com/go-gost/x/limiter/traffic/wrapper"
@@ -153,9 +154,15 @@ func (l *wtListener) Close() (err error) {
}
func (l *wtListener) upgrade(w http.ResponseWriter, r *http.Request) {
clientIP := xhttp.GetClientIP(r)
cip := ""
if clientIP != nil {
cip = clientIP.String()
}
log := l.logger.WithFields(map[string]any{
"local": l.addr.String(),
"remote": r.RemoteAddr,
"client": cip,
})
if l.logger.IsLevelEnabled(logger.TraceLevel) {
dump, _ := httputil.DumpRequest(r, false)
@@ -169,10 +176,15 @@ func (l *wtListener) upgrade(w http.ResponseWriter, r *http.Request) {
return
}
l.mux(s, log)
var clientAddr net.Addr
if clientIP != nil {
clientAddr = &net.IPAddr{IP: clientIP}
}
func (l *wtListener) mux(s *wt.Session, log logger.Logger) (err error) {
l.mux(s, clientAddr, log)
}
func (l *wtListener) mux(s *wt.Session, clientAddr net.Addr, log logger.Logger) (err error) {
defer func() {
if err != nil {
s.CloseWithError(1, err.Error())
@@ -190,7 +202,7 @@ func (l *wtListener) mux(s *wt.Session, log logger.Logger) (err error) {
}
select {
case l.cqueue <- wt_util.Conn(s, stream):
case l.cqueue <- wt_util.ConnWithClientAddr(s, stream, clientAddr):
default:
stream.Close()
l.logger.Warnf("connection queue is full, stream %v discarded", stream.StreamID())
+25 -5
View File
@@ -14,6 +14,7 @@ import (
md "github.com/go-gost/core/metadata"
admission "github.com/go-gost/x/admission/wrapper"
xnet "github.com/go-gost/x/internal/net"
xhttp "github.com/go-gost/x/internal/net/http"
"github.com/go-gost/x/internal/net/proxyproto"
"github.com/go-gost/x/internal/util/mux"
ws_util "github.com/go-gost/x/internal/util/ws"
@@ -162,26 +163,36 @@ func (l *mwsListener) Addr() net.Addr {
}
func (l *mwsListener) upgrade(w http.ResponseWriter, r *http.Request) {
clientIP := xhttp.GetClientIP(r)
cip := ""
if clientIP != nil {
cip = clientIP.String()
}
log := l.logger.WithFields(map[string]any{
"local": l.addr.String(),
"remote": r.RemoteAddr,
"client": cip,
})
if l.logger.IsLevelEnabled(logger.TraceLevel) {
if log.IsLevelEnabled(logger.TraceLevel) {
dump, _ := httputil.DumpRequest(r, false)
log.Trace(string(dump))
}
conn, err := l.upgrader.Upgrade(w, r, l.md.header)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
log.Error(err)
return
}
l.mux(ws_util.Conn(conn), log)
var clientAddr net.Addr
if clientIP != nil {
clientAddr = &net.IPAddr{IP: clientIP}
}
func (l *mwsListener) mux(conn net.Conn, log logger.Logger) {
l.mux(ws_util.Conn(conn), clientAddr, log)
}
func (l *mwsListener) mux(conn net.Conn, clientAddr net.Addr, log logger.Logger) {
defer conn.Close()
session, err := mux.ServerSession(conn, l.md.muxCfg)
@@ -199,10 +210,19 @@ func (l *mwsListener) mux(conn net.Conn, log logger.Logger) {
}
select {
case l.cqueue <- stream:
case l.cqueue <- &connWithClientAddr{Conn: stream, clientAddr: clientAddr}:
default:
stream.Close()
log.Warnf("connection queue is full, client %s discarded", stream.RemoteAddr())
}
}
}
type connWithClientAddr struct {
net.Conn
clientAddr net.Addr
}
func (c *connWithClientAddr) ClientAddr() net.Addr {
return c.clientAddr
}
+6 -7
View File
@@ -159,24 +159,23 @@ func (l *wsListener) Addr() net.Addr {
func (l *wsListener) upgrade(w http.ResponseWriter, r *http.Request) {
clientIP := xhttp.GetClientIP(r)
if l.logger.IsLevelEnabled(logger.TraceLevel) {
sip := ""
cip := ""
if clientIP != nil {
sip = clientIP.String()
cip = clientIP.String()
}
log := l.logger.WithFields(map[string]any{
"local": l.addr.String(),
"remote": r.RemoteAddr,
"clientIP": sip,
"client": cip,
})
if log.IsLevelEnabled(logger.TraceLevel) {
dump, _ := httputil.DumpRequest(r, false)
log.Trace(string(dump))
}
conn, err := l.upgrader.Upgrade(w, r, l.md.header)
if err != nil {
l.logger.Error(err)
w.WriteHeader(http.StatusInternalServerError)
log.Error(err)
return
}
@@ -189,6 +188,6 @@ func (l *wsListener) upgrade(w http.ResponseWriter, r *http.Request) {
case l.cqueue <- ws_util.ConnWithClientAddr(conn, clientAddr):
default:
conn.Close()
l.logger.Warnf("connection queue is full, client %s discarded", conn.RemoteAddr())
log.Warnf("connection queue is full, client %s discarded", conn.RemoteAddr())
}
}
+7
View File
@@ -80,6 +80,13 @@ func (c *serverConn) Metadata() metadata.Metadata {
return nil
}
func (c *serverConn) ClientAddr() net.Addr {
if sc, ok := c.Conn.(xnet.ClientAddr); ok {
return sc.ClientAddr()
}
return nil
}
type packetConn struct {
net.PacketConn
service string
+7
View File
@@ -82,6 +82,13 @@ func (c *conn) Metadata() metadata.Metadata {
return nil
}
func (c *conn) ClientAddr() net.Addr {
if sc, ok := c.Conn.(xnet.ClientAddr); ok {
return sc.ClientAddr()
}
return nil
}
type packetConn struct {
net.PacketConn
stats stats.Stats