Compare commits
10 Commits
899f42d5f8
...
5fccea247e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5fccea247e | ||
|
|
7d82bdcdfe | ||
|
|
e1b45989df | ||
|
|
43152b1296 | ||
|
|
45a0dc5b6b | ||
|
|
5f25222e64 | ||
|
|
cbb7de1453 | ||
|
|
83e848f262 | ||
|
|
4559e269e0 | ||
|
|
c4a1c9ce98 |
@@ -15,6 +15,8 @@ type Config struct {
|
|||||||
TLSInsecure bool
|
TLSInsecure bool
|
||||||
Limiter *rate.Limiter
|
Limiter *rate.Limiter
|
||||||
DNS string
|
DNS string
|
||||||
|
TCPProxy string
|
||||||
|
TCPProxyTLS bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewConfig() *Config {
|
func NewConfig() *Config {
|
||||||
@@ -23,9 +25,11 @@ func NewConfig() *Config {
|
|||||||
as.Append(args.INT, "p", "port to listen on", 61113)
|
as.Append(args.INT, "p", "port to listen on", 61113)
|
||||||
as.Append(args.INT, "kbps", "kilobytes per sec limit", -1)
|
as.Append(args.INT, "kbps", "kilobytes per sec limit", -1)
|
||||||
as.Append(args.BOOL, "tls-insecure", "permit tls insecure", false)
|
as.Append(args.BOOL, "tls-insecure", "permit tls insecure", false)
|
||||||
|
as.Append(args.BOOL, "tcp-proxy-tls", "tcp proxy uses tls", true)
|
||||||
as.Append(args.DURATION, "t", "timeout", time.Minute)
|
as.Append(args.DURATION, "t", "timeout", time.Minute)
|
||||||
|
|
||||||
as.Append(args.STRING, "dns", "dns ip:port", "1.1.1.1:53")
|
as.Append(args.STRING, "dns", "dns ip:port", "1.1.1.1:53")
|
||||||
|
as.Append(args.STRING, "tcp-proxy", "host:port to tcp proxy to instead", "")
|
||||||
|
|
||||||
if err := as.Parse(); err != nil {
|
if err := as.Parse(); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
@@ -43,5 +47,7 @@ func NewConfig() *Config {
|
|||||||
TLSInsecure: as.GetBool("tls-insecure"),
|
TLSInsecure: as.GetBool("tls-insecure"),
|
||||||
Limiter: limiter,
|
Limiter: limiter,
|
||||||
DNS: as.GetString("dns"),
|
DNS: as.GetString("dns"),
|
||||||
|
TCPProxy: as.GetString("tcp-proxy"),
|
||||||
|
TCPProxyTLS: as.GetBool("tcp-proxy-tls"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
6
go.mod
6
go.mod
@@ -7,6 +7,10 @@ require (
|
|||||||
local/args v0.0.0-00010101000000-000000000000
|
local/args v0.0.0-00010101000000-000000000000
|
||||||
)
|
)
|
||||||
|
|
||||||
require gopkg.in/yaml.v2 v2.4.0 // indirect
|
require (
|
||||||
|
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 // indirect
|
||||||
|
golang.org/x/net v0.9.0 // indirect
|
||||||
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
|
)
|
||||||
|
|
||||||
replace local/args => ../../local/args
|
replace local/args => ../../local/args
|
||||||
|
|||||||
4
go.sum
4
go.sum
@@ -1,3 +1,7 @@
|
|||||||
|
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
|
||||||
|
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
|
||||||
|
golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM=
|
||||||
|
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
|
||||||
golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9 h1:ftMN5LMiBFjbzleLqtoBZk7KdJwhuybIU+FckUHgoyQ=
|
golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9 h1:ftMN5LMiBFjbzleLqtoBZk7KdJwhuybIU+FckUHgoyQ=
|
||||||
golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
|
|||||||
15
main.go
15
main.go
@@ -7,10 +7,17 @@ import (
|
|||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
config := NewConfig()
|
config := NewConfig()
|
||||||
server := NewServer(config)
|
|
||||||
|
|
||||||
log.Printf("config: %+v", *config)
|
log.Printf("config: %+v", *config)
|
||||||
if err := http.ListenAndServe(config.Listen, server); err != nil {
|
|
||||||
panic(err)
|
if config.TCPProxy != "" {
|
||||||
|
server := NewTCPServer(config)
|
||||||
|
if err := server.Listen(); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
server := NewServer(config)
|
||||||
|
if err := http.ListenAndServe(config.Listen, server); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
63
server.go
63
server.go
@@ -10,17 +10,33 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"golang.org/x/time/rate"
|
"golang.org/x/time/rate"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Server struct {
|
type Server struct {
|
||||||
Transport http.RoundTripper
|
Transport http.RoundTripper
|
||||||
resolver *net.Resolver
|
resolver *net.Resolver
|
||||||
limiter *rate.Limiter
|
limiter *rate.Limiter
|
||||||
Timeout time.Duration
|
Timeout time.Duration
|
||||||
dnsCache map[string]string
|
dnsCacheLock sync.Mutex
|
||||||
|
dnsCache map[string]dns
|
||||||
|
}
|
||||||
|
|
||||||
|
type dns struct {
|
||||||
|
result string
|
||||||
|
err error
|
||||||
|
ts time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dns dns) ok() bool {
|
||||||
|
dur := time.Minute * 10
|
||||||
|
if dns.err != nil {
|
||||||
|
dur = time.Minute * 1
|
||||||
|
}
|
||||||
|
return time.Since(dns.ts) < dur
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewServer(c *Config) *Server {
|
func NewServer(c *Config) *Server {
|
||||||
@@ -28,12 +44,12 @@ func NewServer(c *Config) *Server {
|
|||||||
PreferGo: true,
|
PreferGo: true,
|
||||||
Dial: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
Dial: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||||
d := net.Dialer{Timeout: c.Timeout}
|
d := net.Dialer{Timeout: c.Timeout}
|
||||||
return d.DialContext(ctx, network, c.DNS)
|
if c.DNS != "" {
|
||||||
|
addr = c.DNS
|
||||||
|
}
|
||||||
|
return d.DialContext(ctx, network, addr)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
if c.DNS == "" {
|
|
||||||
resolver = &net.Resolver{}
|
|
||||||
}
|
|
||||||
transport := &http.Transport{}
|
transport := &http.Transport{}
|
||||||
transport.TLSClientConfig = &tls.Config{
|
transport.TLSClientConfig = &tls.Config{
|
||||||
InsecureSkipVerify: c.TLSInsecure,
|
InsecureSkipVerify: c.TLSInsecure,
|
||||||
@@ -43,7 +59,7 @@ func NewServer(c *Config) *Server {
|
|||||||
Transport: transport,
|
Transport: transport,
|
||||||
Timeout: c.Timeout,
|
Timeout: c.Timeout,
|
||||||
resolver: resolver,
|
resolver: resolver,
|
||||||
dnsCache: map[string]string{},
|
dnsCache: map[string]dns{},
|
||||||
}
|
}
|
||||||
transport.DialContext = func(ctx context.Context, network, addr string) (net.Conn, error) {
|
transport.DialContext = func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||||
addr, err := s.dig(ctx, addr)
|
addr, err := s.dig(ctx, addr)
|
||||||
@@ -84,12 +100,18 @@ func (s *Server) Connect(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
dest, err := net.DialTimeout("tcp", host, s.Timeout)
|
network := "tcp"
|
||||||
|
if n := strings.Count(host, ":"); n > 3 {
|
||||||
|
network = "tcp6"
|
||||||
|
}
|
||||||
|
dest, err := net.DialTimeout(network, host, s.Timeout)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.Error(r, w, fmt.Errorf("error dialing w timeout %s=>%s: %w", r.Host, host, err))
|
s.Error(r, w, fmt.Errorf("error dialing w timeout %s=>%s: %w", r.Host, host, err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
|
||||||
client, _, err := hijacker.Hijack()
|
client, _, err := hijacker.Hijack()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.Error(r, w, err)
|
s.Error(r, w, err)
|
||||||
@@ -134,8 +156,10 @@ func (s *Server) dig(ctx context.Context, host string) (string, error) {
|
|||||||
search := host
|
search := host
|
||||||
search = strings.TrimPrefix(search, "https://")
|
search = strings.TrimPrefix(search, "https://")
|
||||||
search = strings.TrimPrefix(search, "http://")
|
search = strings.TrimPrefix(search, "http://")
|
||||||
if v, ok := s.dnsCache[host]; ok {
|
s.dnsCacheLock.Lock()
|
||||||
return v, nil
|
defer s.dnsCacheLock.Unlock()
|
||||||
|
if v, ok := s.dnsCache[host]; ok && v.ok() {
|
||||||
|
return v.result, v.err
|
||||||
}
|
}
|
||||||
port := ""
|
port := ""
|
||||||
if splithost, splitport, err := net.SplitHostPort(host); err == nil {
|
if splithost, splitport, err := net.SplitHostPort(host); err == nil {
|
||||||
@@ -143,14 +167,13 @@ func (s *Server) dig(ctx context.Context, host string) (string, error) {
|
|||||||
port = ":" + splitport
|
port = ":" + splitport
|
||||||
}
|
}
|
||||||
ip, err := s.resolver.LookupHost(ctx, search)
|
ip, err := s.resolver.LookupHost(ctx, search)
|
||||||
if err != nil {
|
result := ""
|
||||||
|
if len(ip) > 0 {
|
||||||
|
result = strings.TrimPrefix(ip[0], "//") + port
|
||||||
|
}
|
||||||
|
s.dnsCache[host] = dns{result: result, err: err, ts: time.Now()}
|
||||||
|
if err != nil || result == "" {
|
||||||
return "", fmt.Errorf("failed to dns lookup %s: %v", search, err)
|
return "", fmt.Errorf("failed to dns lookup %s: %v", search, err)
|
||||||
}
|
}
|
||||||
if len(ip) == 0 {
|
|
||||||
return "", errors.New("name does not resolve")
|
|
||||||
}
|
|
||||||
result := strings.TrimPrefix(ip[0], "//") + port
|
|
||||||
s.dnsCache[host] = result
|
|
||||||
//log.Printf("dug %s => %s => %s", host, search, result)
|
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|||||||
61
tcp.go
Normal file
61
tcp.go
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TCP struct {
|
||||||
|
config *Config
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTCPServer(c *Config) TCP {
|
||||||
|
return TCP{config: c}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tcp TCP) Listen() error {
|
||||||
|
ln, err := net.Listen("tcp", tcp.config.Listen)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer ln.Close()
|
||||||
|
|
||||||
|
log.Println("accepting tcp on", tcp.config.Listen)
|
||||||
|
for {
|
||||||
|
conn, err := ln.Accept()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
go func() {
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
conn2, err := func() (net.Conn, error) {
|
||||||
|
if tcp.config.TCPProxyTLS {
|
||||||
|
return tls.Dial("tcp", tcp.config.TCPProxy, &tls.Config{})
|
||||||
|
}
|
||||||
|
return net.Dial("tcp", tcp.config.TCPProxy)
|
||||||
|
}()
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer conn2.Close()
|
||||||
|
|
||||||
|
errc := make(chan error)
|
||||||
|
go func() {
|
||||||
|
_, err := io.Copy(conn, conn2)
|
||||||
|
errc <- err
|
||||||
|
}()
|
||||||
|
go func() {
|
||||||
|
_, err := io.Copy(conn2, conn)
|
||||||
|
errc <- err
|
||||||
|
}()
|
||||||
|
for i := 0; i < 2; i++ {
|
||||||
|
<-errc
|
||||||
|
}
|
||||||
|
close(errc)
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user