mirror of
https://github.com/Gouryella/drip.git
synced 2026-02-24 05:10:43 +00:00
feat: Add IP access control functionality
- Implement IP whitelist/blacklist access control mechanism - Add --allow-ip and --deny-ip command-line arguments to configure IP access rules - Support CIDR format for IP range configuration - Enable IP access control in HTTP, HTTPS, and TCP tunnels - Add IP access check logic to server-side proxy handling - Update documentation to explain how to use IP access control
This commit is contained in:
18
README.md
18
README.md
@@ -192,6 +192,7 @@ sudo journalctl -u drip-server -f
|
||||
**Security**
|
||||
- TLS 1.3 encryption for all connections
|
||||
- Token-based authentication
|
||||
- IP whitelist/blacklist access control
|
||||
- No legacy protocol support
|
||||
|
||||
**Flexibility**
|
||||
@@ -248,6 +249,21 @@ drip http 8080 -a 172.17.0.3
|
||||
drip tcp 5432 -a db-container
|
||||
```
|
||||
|
||||
**IP Access Control**
|
||||
```bash
|
||||
# Only allow access from specific networks (CIDR)
|
||||
drip http 3000 --allow-ip 192.168.0.0/16,10.0.0.0/8
|
||||
|
||||
# Only allow specific IP addresses
|
||||
drip http 3000 --allow-ip 192.168.1.100,192.168.1.101
|
||||
|
||||
# Block specific IP addresses
|
||||
drip http 3000 --deny-ip 1.2.3.4,5.6.7.8
|
||||
|
||||
# Combine whitelist and blacklist
|
||||
drip tcp 5432 --allow-ip 192.168.1.0/24 --deny-ip 192.168.1.100
|
||||
```
|
||||
|
||||
## Command Reference
|
||||
|
||||
```bash
|
||||
@@ -258,6 +274,8 @@ drip http <port> [flags]
|
||||
-d, --daemon Run in background
|
||||
-s, --server Server address
|
||||
-t, --token Auth token
|
||||
--allow-ip Allow only these IPs or CIDR ranges
|
||||
--deny-ip Deny these IPs or CIDR ranges
|
||||
|
||||
# HTTPS tunnel (same flags as http)
|
||||
drip https <port> [flags]
|
||||
|
||||
19
README_CN.md
19
README_CN.md
@@ -163,6 +163,7 @@ server {
|
||||
|
||||
location / {
|
||||
proxy_pass https://127.0.0.1:8443;
|
||||
proxy_ssl_protocols TLSv1.3;
|
||||
proxy_ssl_verify off;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
@@ -191,6 +192,7 @@ sudo journalctl -u drip-server -f
|
||||
**安全性**
|
||||
- 所有连接使用 TLS 1.3 加密
|
||||
- 基于 Token 的身份验证
|
||||
- IP 白名单/黑名单访问控制
|
||||
- 不支持任何遗留协议
|
||||
|
||||
**灵活性**
|
||||
@@ -247,6 +249,21 @@ drip http 8080 -a 172.17.0.3
|
||||
drip tcp 5432 -a db-container
|
||||
```
|
||||
|
||||
**IP 访问控制**
|
||||
```bash
|
||||
# 只允许特定网段访问(CIDR)
|
||||
drip http 3000 --allow-ip 192.168.0.0/16,10.0.0.0/8
|
||||
|
||||
# 只允许特定 IP 访问
|
||||
drip http 3000 --allow-ip 192.168.1.100,192.168.1.101
|
||||
|
||||
# 拒绝特定 IP
|
||||
drip http 3000 --deny-ip 1.2.3.4,5.6.7.8
|
||||
|
||||
# 组合白名单和黑名单
|
||||
drip tcp 5432 --allow-ip 192.168.1.0/24 --deny-ip 192.168.1.100
|
||||
```
|
||||
|
||||
## 命令参考
|
||||
|
||||
```bash
|
||||
@@ -257,6 +274,8 @@ drip http <端口> [参数]
|
||||
-d, --daemon 后台运行
|
||||
-s, --server 服务器地址
|
||||
-t, --token 认证 token
|
||||
--allow-ip 只允许这些 IP 或 CIDR 访问
|
||||
--deny-ip 拒绝这些 IP 或 CIDR 访问
|
||||
|
||||
# HTTPS 隧道(参数同 http)
|
||||
drip https <端口> [参数]
|
||||
|
||||
@@ -15,6 +15,8 @@ var (
|
||||
daemonMode bool
|
||||
daemonMarker bool
|
||||
localAddress string
|
||||
allowIPs []string
|
||||
denyIPs []string
|
||||
)
|
||||
|
||||
var httpCmd = &cobra.Command{
|
||||
@@ -25,6 +27,9 @@ var httpCmd = &cobra.Command{
|
||||
Example:
|
||||
drip http 3000 Tunnel localhost:3000
|
||||
drip http 8080 --subdomain myapp Use custom subdomain
|
||||
drip http 3000 --allow-ip 192.168.0.0/16 Only allow IPs from 192.168.x.x
|
||||
drip http 3000 --allow-ip 10.0.0.1 Allow single IP
|
||||
drip http 3000 --deny-ip 1.2.3.4 Block specific IP
|
||||
|
||||
Configuration:
|
||||
First time: Run 'drip config init' to save server and token
|
||||
@@ -39,6 +44,8 @@ func init() {
|
||||
httpCmd.Flags().StringVarP(&subdomain, "subdomain", "n", "", "Custom subdomain (optional)")
|
||||
httpCmd.Flags().BoolVarP(&daemonMode, "daemon", "d", false, "Run in background (daemon mode)")
|
||||
httpCmd.Flags().StringVarP(&localAddress, "address", "a", "127.0.0.1", "Local address to forward to (default: 127.0.0.1)")
|
||||
httpCmd.Flags().StringSliceVar(&allowIPs, "allow-ip", nil, "Allow only these IPs or CIDR ranges (e.g., 192.168.1.1,10.0.0.0/8)")
|
||||
httpCmd.Flags().StringSliceVar(&denyIPs, "deny-ip", nil, "Deny these IPs or CIDR ranges (e.g., 1.2.3.4,192.168.1.0/24)")
|
||||
httpCmd.Flags().BoolVar(&daemonMarker, "daemon-child", false, "Internal flag for daemon child process")
|
||||
httpCmd.Flags().MarkHidden("daemon-child")
|
||||
rootCmd.AddCommand(httpCmd)
|
||||
@@ -67,6 +74,8 @@ func runHTTP(_ *cobra.Command, args []string) error {
|
||||
LocalPort: port,
|
||||
Subdomain: subdomain,
|
||||
Insecure: insecure,
|
||||
AllowIPs: allowIPs,
|
||||
DenyIPs: denyIPs,
|
||||
}
|
||||
|
||||
var daemon *DaemonInfo
|
||||
|
||||
@@ -18,6 +18,9 @@ var httpsCmd = &cobra.Command{
|
||||
Example:
|
||||
drip https 443 Tunnel localhost:443
|
||||
drip https 8443 --subdomain myapp Use custom subdomain
|
||||
drip https 443 --allow-ip 192.168.0.0/16 Only allow IPs from 192.168.x.x
|
||||
drip https 443 --allow-ip 10.0.0.1 Allow single IP
|
||||
drip https 443 --deny-ip 1.2.3.4 Block specific IP
|
||||
|
||||
Configuration:
|
||||
First time: Run 'drip config init' to save server and token
|
||||
@@ -32,6 +35,8 @@ func init() {
|
||||
httpsCmd.Flags().StringVarP(&subdomain, "subdomain", "n", "", "Custom subdomain (optional)")
|
||||
httpsCmd.Flags().BoolVarP(&daemonMode, "daemon", "d", false, "Run in background (daemon mode)")
|
||||
httpsCmd.Flags().StringVarP(&localAddress, "address", "a", "127.0.0.1", "Local address to forward to (default: 127.0.0.1)")
|
||||
httpsCmd.Flags().StringSliceVar(&allowIPs, "allow-ip", nil, "Allow only these IPs or CIDR ranges (e.g., 192.168.1.1,10.0.0.0/8)")
|
||||
httpsCmd.Flags().StringSliceVar(&denyIPs, "deny-ip", nil, "Deny these IPs or CIDR ranges (e.g., 1.2.3.4,192.168.1.0/24)")
|
||||
httpsCmd.Flags().BoolVar(&daemonMarker, "daemon-child", false, "Internal flag for daemon child process")
|
||||
httpsCmd.Flags().MarkHidden("daemon-child")
|
||||
rootCmd.AddCommand(httpsCmd)
|
||||
@@ -60,6 +65,8 @@ func runHTTPS(_ *cobra.Command, args []string) error {
|
||||
LocalPort: port,
|
||||
Subdomain: subdomain,
|
||||
Insecure: insecure,
|
||||
AllowIPs: allowIPs,
|
||||
DenyIPs: denyIPs,
|
||||
}
|
||||
|
||||
var daemon *DaemonInfo
|
||||
|
||||
@@ -20,6 +20,9 @@ Example:
|
||||
drip tcp 3306 Tunnel MySQL
|
||||
drip tcp 22 Tunnel SSH
|
||||
drip tcp 6379 --subdomain myredis Tunnel Redis with custom subdomain
|
||||
drip tcp 5432 --allow-ip 192.168.0.0/16 Only allow IPs from 192.168.x.x
|
||||
drip tcp 22 --allow-ip 10.0.0.1 Allow single IP
|
||||
drip tcp 22 --deny-ip 1.2.3.4 Block specific IP
|
||||
|
||||
Supported Services:
|
||||
- Databases: PostgreSQL (5432), MySQL (3306), Redis (6379), MongoDB (27017)
|
||||
@@ -39,6 +42,8 @@ func init() {
|
||||
tcpCmd.Flags().StringVarP(&subdomain, "subdomain", "n", "", "Custom subdomain (optional)")
|
||||
tcpCmd.Flags().BoolVarP(&daemonMode, "daemon", "d", false, "Run in background (daemon mode)")
|
||||
tcpCmd.Flags().StringVarP(&localAddress, "address", "a", "127.0.0.1", "Local address to forward to (default: 127.0.0.1)")
|
||||
tcpCmd.Flags().StringSliceVar(&allowIPs, "allow-ip", nil, "Allow only these IPs or CIDR ranges (e.g., 192.168.1.1,10.0.0.0/8)")
|
||||
tcpCmd.Flags().StringSliceVar(&denyIPs, "deny-ip", nil, "Deny these IPs or CIDR ranges (e.g., 1.2.3.4,192.168.1.0/24)")
|
||||
tcpCmd.Flags().BoolVar(&daemonMarker, "daemon-child", false, "Internal flag for daemon child process")
|
||||
tcpCmd.Flags().MarkHidden("daemon-child")
|
||||
rootCmd.AddCommand(tcpCmd)
|
||||
@@ -67,6 +72,8 @@ func runTCP(_ *cobra.Command, args []string) error {
|
||||
LocalPort: port,
|
||||
Subdomain: subdomain,
|
||||
Insecure: insecure,
|
||||
AllowIPs: allowIPs,
|
||||
DenyIPs: denyIPs,
|
||||
}
|
||||
|
||||
var daemon *DaemonInfo
|
||||
|
||||
@@ -24,6 +24,9 @@ type ConnectorConfig struct {
|
||||
PoolSize int
|
||||
PoolMin int
|
||||
PoolMax int
|
||||
|
||||
AllowIPs []string
|
||||
DenyIPs []string
|
||||
}
|
||||
|
||||
type TunnelClient interface {
|
||||
|
||||
@@ -63,6 +63,9 @@ type PoolClient struct {
|
||||
lastScale time.Time
|
||||
|
||||
logger *zap.Logger
|
||||
|
||||
allowIPs []string
|
||||
denyIPs []string
|
||||
}
|
||||
|
||||
// NewPoolClient creates a new pool client.
|
||||
@@ -126,6 +129,8 @@ func NewPoolClient(cfg *ConnectorConfig, logger *zap.Logger) *PoolClient {
|
||||
doneCh: make(chan struct{}),
|
||||
dataSessions: make(map[string]*sessionHandle),
|
||||
logger: logger,
|
||||
allowIPs: cfg.AllowIPs,
|
||||
denyIPs: cfg.DenyIPs,
|
||||
}
|
||||
|
||||
if tunnelType == protocol.TunnelTypeHTTP || tunnelType == protocol.TunnelTypeHTTPS {
|
||||
@@ -156,6 +161,13 @@ func (c *PoolClient) Connect() error {
|
||||
},
|
||||
}
|
||||
|
||||
if len(c.allowIPs) > 0 || len(c.denyIPs) > 0 {
|
||||
req.IPAccess = &protocol.IPAccessControl{
|
||||
AllowIPs: c.allowIPs,
|
||||
DenyIPs: c.denyIPs,
|
||||
}
|
||||
}
|
||||
|
||||
payload, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
_ = primaryConn.Close()
|
||||
|
||||
@@ -81,6 +81,14 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
if tconn.HasIPAccessControl() {
|
||||
clientIP := h.extractClientIP(r)
|
||||
if !tconn.IsIPAllowed(clientIP) {
|
||||
http.Error(w, "Access denied: your IP is not allowed", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
tType := tconn.GetTunnelType()
|
||||
if tType != "" && tType != protocol.TunnelTypeHTTP && tType != protocol.TunnelTypeHTTPS {
|
||||
http.Error(w, "Tunnel does not accept HTTP traffic", http.StatusBadGateway)
|
||||
@@ -328,6 +336,32 @@ func (h *Handler) extractSubdomain(host string) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// extractClientIP extracts the client IP from the request.
|
||||
// It checks X-Forwarded-For and X-Real-IP headers first (for reverse proxy setups),
|
||||
// then falls back to the remote address.
|
||||
func (h *Handler) extractClientIP(r *http.Request) string {
|
||||
// Check X-Forwarded-For header (may contain multiple IPs)
|
||||
if xff := r.Header.Get("X-Forwarded-For"); xff != "" {
|
||||
// Take the first IP (original client)
|
||||
if idx := strings.Index(xff, ","); idx != -1 {
|
||||
return strings.TrimSpace(xff[:idx])
|
||||
}
|
||||
return strings.TrimSpace(xff)
|
||||
}
|
||||
|
||||
// Check X-Real-IP header
|
||||
if xri := r.Header.Get("X-Real-IP"); xri != "" {
|
||||
return strings.TrimSpace(xri)
|
||||
}
|
||||
|
||||
// Fall back to remote address
|
||||
host, _, err := net.SplitHostPort(r.RemoteAddr)
|
||||
if err != nil {
|
||||
return r.RemoteAddr
|
||||
}
|
||||
return host
|
||||
}
|
||||
|
||||
func (h *Handler) serveHomePage(w http.ResponseWriter, r *http.Request) {
|
||||
html := `<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
@@ -181,6 +181,15 @@ func (c *Connection) Handle() error {
|
||||
c.tunnelConn.SetTunnelType(req.TunnelType)
|
||||
c.tunnelType = req.TunnelType
|
||||
|
||||
if req.IPAccess != nil && (len(req.IPAccess.AllowIPs) > 0 || len(req.IPAccess.DenyIPs) > 0) {
|
||||
c.tunnelConn.SetIPAccessControl(req.IPAccess.AllowIPs, req.IPAccess.DenyIPs)
|
||||
c.logger.Info("IP access control configured",
|
||||
zap.String("subdomain", subdomain),
|
||||
zap.Strings("allow_ips", req.IPAccess.AllowIPs),
|
||||
zap.Strings("deny_ips", req.IPAccess.DenyIPs),
|
||||
)
|
||||
}
|
||||
|
||||
c.logger.Info("Tunnel registered",
|
||||
zap.String("subdomain", subdomain),
|
||||
zap.String("tunnel_type", string(req.TunnelType)),
|
||||
|
||||
@@ -32,6 +32,8 @@ type Proxy struct {
|
||||
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
|
||||
checkIPAccess func(ip string) bool
|
||||
}
|
||||
|
||||
type trafficStats interface {
|
||||
@@ -66,6 +68,11 @@ func NewProxy(ctx context.Context, port int, subdomain string, openStream func()
|
||||
}
|
||||
}
|
||||
|
||||
// SetIPAccessCheck sets the IP access control check function.
|
||||
func (p *Proxy) SetIPAccessCheck(check func(ip string) bool) {
|
||||
p.checkIPAccess = check
|
||||
}
|
||||
|
||||
func (p *Proxy) Start() error {
|
||||
addr := fmt.Sprintf("0.0.0.0:%d", p.port)
|
||||
|
||||
@@ -156,6 +163,17 @@ func (p *Proxy) handleConn(conn net.Conn) {
|
||||
defer p.wg.Done()
|
||||
defer conn.Close()
|
||||
|
||||
if p.checkIPAccess != nil {
|
||||
clientIP := netutil.ExtractIP(conn.RemoteAddr().String())
|
||||
if !p.checkIPAccess(clientIP) {
|
||||
p.logger.Debug("IP access denied",
|
||||
zap.String("ip", clientIP),
|
||||
zap.Int("port", p.port),
|
||||
)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if p.sem != nil {
|
||||
select {
|
||||
case p.sem <- struct{}{}:
|
||||
|
||||
@@ -44,6 +44,10 @@ func (c *Connection) handleTCPTunnel(reader *bufio.Reader) error {
|
||||
}
|
||||
|
||||
c.proxy = NewProxy(c.ctx, c.port, c.subdomain, openStream, c.tunnelConn, c.logger)
|
||||
if c.tunnelConn != nil && c.tunnelConn.HasIPAccessControl() {
|
||||
c.proxy.SetIPAccessCheck(c.tunnelConn.IsIPAllowed)
|
||||
}
|
||||
|
||||
if err := c.proxy.Start(); err != nil {
|
||||
return fmt.Errorf("failed to start tcp proxy: %w", err)
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"time"
|
||||
|
||||
"drip/internal/server/metrics"
|
||||
"drip/internal/shared/netutil"
|
||||
"drip/internal/shared/protocol"
|
||||
"github.com/gorilla/websocket"
|
||||
"go.uber.org/zap"
|
||||
@@ -29,6 +30,8 @@ type Connection struct {
|
||||
bytesIn atomic.Int64
|
||||
bytesOut atomic.Int64
|
||||
activeConnections atomic.Int64
|
||||
|
||||
ipAccessChecker *netutil.IPAccessChecker
|
||||
}
|
||||
|
||||
// NewConnection creates a new tunnel connection
|
||||
@@ -182,6 +185,32 @@ func (c *Connection) GetActiveConnections() int64 {
|
||||
return c.activeConnections.Load()
|
||||
}
|
||||
|
||||
// SetIPAccessControl sets the IP access control rules for this tunnel.
|
||||
func (c *Connection) SetIPAccessControl(allowCIDRs, denyIPs []string) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
c.ipAccessChecker = netutil.NewIPAccessChecker(allowCIDRs, denyIPs)
|
||||
}
|
||||
|
||||
// IsIPAllowed checks if the given IP address is allowed to access this tunnel.
|
||||
func (c *Connection) IsIPAllowed(ip string) bool {
|
||||
c.mu.RLock()
|
||||
checker := c.ipAccessChecker
|
||||
c.mu.RUnlock()
|
||||
|
||||
if checker == nil {
|
||||
return true // No access control configured
|
||||
}
|
||||
return checker.IsAllowed(ip)
|
||||
}
|
||||
|
||||
// HasIPAccessControl returns true if IP access control is configured.
|
||||
func (c *Connection) HasIPAccessControl() bool {
|
||||
c.mu.RLock()
|
||||
defer c.mu.RUnlock()
|
||||
return c.ipAccessChecker != nil && c.ipAccessChecker.HasRules()
|
||||
}
|
||||
|
||||
// StartWritePump starts the write pump for sending messages
|
||||
func (c *Connection) StartWritePump() {
|
||||
if c.Conn == nil {
|
||||
|
||||
119
internal/shared/netutil/ipaccess.go
Normal file
119
internal/shared/netutil/ipaccess.go
Normal file
@@ -0,0 +1,119 @@
|
||||
package netutil
|
||||
|
||||
import (
|
||||
"net"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// IPAccessChecker checks if an IP address is allowed based on whitelist/blacklist rules.
|
||||
type IPAccessChecker struct {
|
||||
allowNets []*net.IPNet // Allowed CIDR ranges (whitelist)
|
||||
denyIPs []net.IP // Denied IP addresses (blacklist)
|
||||
hasAllow bool // Whether whitelist is configured
|
||||
hasDeny bool // Whether blacklist is configured
|
||||
}
|
||||
|
||||
// NewIPAccessChecker creates a new IP access checker from CIDR and IP lists.
|
||||
// allowCIDRs: list of CIDR ranges to allow (e.g., "192.168.1.0/24", "10.0.0.0/8")
|
||||
// denyIPs: list of IP addresses to deny (e.g., "1.2.3.4", "5.6.7.8")
|
||||
func NewIPAccessChecker(allowCIDRs, denyIPs []string) *IPAccessChecker {
|
||||
checker := &IPAccessChecker{}
|
||||
|
||||
// Parse allowed CIDRs
|
||||
for _, cidr := range allowCIDRs {
|
||||
cidr = strings.TrimSpace(cidr)
|
||||
if cidr == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
// If no "/" in the string, treat it as a single IP (/32 for IPv4, /128 for IPv6)
|
||||
if !strings.Contains(cidr, "/") {
|
||||
ip := net.ParseIP(cidr)
|
||||
if ip != nil {
|
||||
if ip.To4() != nil {
|
||||
cidr = cidr + "/32"
|
||||
} else {
|
||||
cidr = cidr + "/128"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_, ipNet, err := net.ParseCIDR(cidr)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
checker.allowNets = append(checker.allowNets, ipNet)
|
||||
}
|
||||
checker.hasAllow = len(checker.allowNets) > 0
|
||||
|
||||
// Parse denied IPs
|
||||
for _, ipStr := range denyIPs {
|
||||
ipStr = strings.TrimSpace(ipStr)
|
||||
if ipStr == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
ip := net.ParseIP(ipStr)
|
||||
if ip != nil {
|
||||
checker.denyIPs = append(checker.denyIPs, ip)
|
||||
}
|
||||
}
|
||||
checker.hasDeny = len(checker.denyIPs) > 0
|
||||
|
||||
return checker
|
||||
}
|
||||
|
||||
// IsAllowed checks if the given IP address is allowed.
|
||||
// Rules:
|
||||
// 1. If IP is in deny list, reject
|
||||
// 2. If whitelist is configured and IP is not in whitelist, reject
|
||||
// 3. Otherwise, allow
|
||||
func (c *IPAccessChecker) IsAllowed(ipStr string) bool {
|
||||
if c == nil || (!c.hasAllow && !c.hasDeny) {
|
||||
return true // No rules configured, allow all
|
||||
}
|
||||
|
||||
ip := net.ParseIP(ipStr)
|
||||
if ip == nil {
|
||||
return false // Invalid IP, reject
|
||||
}
|
||||
|
||||
// Check deny list first (blacklist takes priority)
|
||||
if c.hasDeny {
|
||||
for _, denyIP := range c.denyIPs {
|
||||
if ip.Equal(denyIP) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check allow list (whitelist)
|
||||
if c.hasAllow {
|
||||
for _, allowNet := range c.allowNets {
|
||||
if allowNet.Contains(ip) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false // Whitelist configured but IP not in it
|
||||
}
|
||||
|
||||
return true // No whitelist, and not in blacklist
|
||||
}
|
||||
|
||||
// HasRules returns true if any access control rules are configured.
|
||||
func (c *IPAccessChecker) HasRules() bool {
|
||||
return c != nil && (c.hasAllow || c.hasDeny)
|
||||
}
|
||||
|
||||
// ExtractIP extracts the IP address from a remote address string (e.g., "192.168.1.1:12345").
|
||||
func ExtractIP(remoteAddr string) string {
|
||||
host, _, err := net.SplitHostPort(remoteAddr)
|
||||
if err != nil {
|
||||
// Maybe it's just an IP without port
|
||||
if ip := net.ParseIP(remoteAddr); ip != nil {
|
||||
return remoteAddr
|
||||
}
|
||||
return ""
|
||||
}
|
||||
return host
|
||||
}
|
||||
@@ -8,6 +8,12 @@ type PoolCapabilities struct {
|
||||
Version int `json:"version"` // Protocol version for pool features
|
||||
}
|
||||
|
||||
// IPAccessControl defines IP-based access control rules for a tunnel
|
||||
type IPAccessControl struct {
|
||||
AllowIPs []string `json:"allow_ips,omitempty"` // Allowed IPs or CIDR ranges (whitelist)
|
||||
DenyIPs []string `json:"deny_ips,omitempty"` // Denied IPs or CIDR ranges (blacklist)
|
||||
}
|
||||
|
||||
// RegisterRequest is sent by client to register a tunnel
|
||||
type RegisterRequest struct {
|
||||
Token string `json:"token"` // Authentication token
|
||||
@@ -19,6 +25,9 @@ type RegisterRequest struct {
|
||||
ConnectionType string `json:"connection_type,omitempty"` // "primary" or empty for legacy
|
||||
TunnelID string `json:"tunnel_id,omitempty"` // For data connections to join
|
||||
PoolCapabilities *PoolCapabilities `json:"pool_capabilities,omitempty"` // Client pool capabilities
|
||||
|
||||
// Access control (optional)
|
||||
IPAccess *IPAccessControl `json:"ip_access,omitempty"` // IP-based access control rules
|
||||
}
|
||||
|
||||
// RegisterResponse is sent by server after successful registration
|
||||
|
||||
Reference in New Issue
Block a user