Feature: add Mixed(http+socks5) proxy listening (#685)
This commit is contained in:
parent
3638b077cd
commit
3a27cfc4a1
9 changed files with 186 additions and 5 deletions
|
@ -21,6 +21,8 @@ func (err Error) Error() string {
|
|||
// Command is request commands as defined in RFC 1928 section 4.
|
||||
type Command = uint8
|
||||
|
||||
const Version = 5
|
||||
|
||||
// SOCKS request commands as defined in RFC 1928 section 4.
|
||||
const (
|
||||
CmdConnect Command = 1
|
||||
|
|
|
@ -28,6 +28,7 @@ type General struct {
|
|||
Port int `json:"port"`
|
||||
SocksPort int `json:"socks-port"`
|
||||
RedirPort int `json:"redir-port"`
|
||||
MixedPort int `json:"mixed-port"`
|
||||
Authentication []string `json:"authentication"`
|
||||
AllowLan bool `json:"allow-lan"`
|
||||
BindAddress string `json:"bind-address"`
|
||||
|
@ -97,6 +98,7 @@ type RawConfig struct {
|
|||
Port int `yaml:"port"`
|
||||
SocksPort int `yaml:"socks-port"`
|
||||
RedirPort int `yaml:"redir-port"`
|
||||
MixedPort int `yaml:"mixed-port"`
|
||||
Authentication []string `yaml:"authentication"`
|
||||
AllowLan bool `yaml:"allow-lan"`
|
||||
BindAddress string `yaml:"bind-address"`
|
||||
|
@ -217,6 +219,7 @@ func parseGeneral(cfg *RawConfig) (*General, error) {
|
|||
port := cfg.Port
|
||||
socksPort := cfg.SocksPort
|
||||
redirPort := cfg.RedirPort
|
||||
mixedPort := cfg.MixedPort
|
||||
allowLan := cfg.AllowLan
|
||||
bindAddress := cfg.BindAddress
|
||||
externalController := cfg.ExternalController
|
||||
|
@ -237,6 +240,7 @@ func parseGeneral(cfg *RawConfig) (*General, error) {
|
|||
Port: port,
|
||||
SocksPort: socksPort,
|
||||
RedirPort: redirPort,
|
||||
MixedPort: mixedPort,
|
||||
AllowLan: allowLan,
|
||||
BindAddress: bindAddress,
|
||||
Mode: mode,
|
||||
|
|
|
@ -99,6 +99,7 @@ func GetGeneral() *config.General {
|
|||
Port: ports.Port,
|
||||
SocksPort: ports.SocksPort,
|
||||
RedirPort: ports.RedirPort,
|
||||
MixedPort: ports.MixedPort,
|
||||
Authentication: authenticator,
|
||||
AllowLan: P.AllowLan(),
|
||||
BindAddress: P.BindAddress(),
|
||||
|
@ -186,6 +187,10 @@ func updateGeneral(general *config.General) {
|
|||
if err := P.ReCreateRedir(general.RedirPort); err != nil {
|
||||
log.Errorln("Start Redir server error: %s", err.Error())
|
||||
}
|
||||
|
||||
if err := P.ReCreateMixed(general.MixedPort); err != nil {
|
||||
log.Errorln("Start Mixed(http and socks5) server error: %s", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func updateUsers(users []auth.AuthUser) {
|
||||
|
|
|
@ -26,6 +26,7 @@ type configSchema struct {
|
|||
Port *int `json:"port"`
|
||||
SocksPort *int `json:"socks-port"`
|
||||
RedirPort *int `json:"redir-port"`
|
||||
MixedPort *int `json:"mixed-port"`
|
||||
AllowLan *bool `json:"allow-lan"`
|
||||
BindAddress *string `json:"bind-address"`
|
||||
Mode *tunnel.TunnelMode `json:"mode"`
|
||||
|
@ -65,6 +66,7 @@ func patchConfigs(w http.ResponseWriter, r *http.Request) {
|
|||
P.ReCreateHTTP(pointerOrDefault(general.Port, ports.Port))
|
||||
P.ReCreateSocks(pointerOrDefault(general.SocksPort, ports.SocksPort))
|
||||
P.ReCreateRedir(pointerOrDefault(general.RedirPort, ports.RedirPort))
|
||||
P.ReCreateMixed(pointerOrDefault(general.MixedPort, ports.MixedPort))
|
||||
|
||||
if general.Mode != nil {
|
||||
tunnel.SetMode(*general.Mode)
|
||||
|
|
|
@ -41,7 +41,7 @@ func NewHttpProxy(addr string) (*HttpListener, error) {
|
|||
}
|
||||
continue
|
||||
}
|
||||
go handleConn(c, hl.cache)
|
||||
go HandleConn(c, hl.cache)
|
||||
}
|
||||
}()
|
||||
|
||||
|
@ -69,7 +69,7 @@ func canActivate(loginStr string, authenticator auth.Authenticator, cache *cache
|
|||
return
|
||||
}
|
||||
|
||||
func handleConn(conn net.Conn, cache *cache.Cache) {
|
||||
func HandleConn(conn net.Conn, cache *cache.Cache) {
|
||||
br := bufio.NewReader(conn)
|
||||
request, err := http.ReadRequest(br)
|
||||
if err != nil || request.URL.Host == "" {
|
||||
|
|
|
@ -7,6 +7,7 @@ import (
|
|||
|
||||
"github.com/Dreamacro/clash/log"
|
||||
"github.com/Dreamacro/clash/proxy/http"
|
||||
"github.com/Dreamacro/clash/proxy/mixed"
|
||||
"github.com/Dreamacro/clash/proxy/redir"
|
||||
"github.com/Dreamacro/clash/proxy/socks"
|
||||
)
|
||||
|
@ -20,6 +21,8 @@ var (
|
|||
httpListener *http.HttpListener
|
||||
redirListener *redir.RedirListener
|
||||
redirUDPListener *redir.RedirUDPListener
|
||||
mixedListener *mixed.MixedListener
|
||||
mixedUDPLister *socks.SockUDPListener
|
||||
)
|
||||
|
||||
type listener interface {
|
||||
|
@ -31,6 +34,7 @@ type Ports struct {
|
|||
Port int `json:"port"`
|
||||
SocksPort int `json:"socks-port"`
|
||||
RedirPort int `json:"redir-port"`
|
||||
MixedPort int `json:"mixed-port"`
|
||||
}
|
||||
|
||||
func AllowLan() bool {
|
||||
|
@ -159,6 +163,52 @@ func ReCreateRedir(port int) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func ReCreateMixed(port int) error {
|
||||
addr := genAddr(bindAddress, port, allowLan)
|
||||
|
||||
shouldTCPIgnore := false
|
||||
shouldUDPIgnore := false
|
||||
|
||||
if mixedListener != nil {
|
||||
if mixedListener.Address() != addr {
|
||||
mixedListener.Close()
|
||||
mixedListener = nil
|
||||
} else {
|
||||
shouldTCPIgnore = true
|
||||
}
|
||||
}
|
||||
if mixedUDPLister != nil {
|
||||
if mixedUDPLister.Address() != addr {
|
||||
mixedUDPLister.Close()
|
||||
mixedUDPLister = nil
|
||||
} else {
|
||||
shouldUDPIgnore = true
|
||||
}
|
||||
}
|
||||
|
||||
if shouldTCPIgnore && shouldUDPIgnore {
|
||||
return nil
|
||||
}
|
||||
|
||||
if portIsZero(addr) {
|
||||
return nil
|
||||
}
|
||||
|
||||
var err error
|
||||
mixedListener, err = mixed.NewMixedProxy(addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
mixedUDPLister, err = socks.NewSocksUDPProxy(addr)
|
||||
if err != nil {
|
||||
mixedListener.Close()
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetPorts return the ports of proxy servers
|
||||
func GetPorts() *Ports {
|
||||
ports := &Ports{}
|
||||
|
@ -181,6 +231,12 @@ func GetPorts() *Ports {
|
|||
ports.RedirPort = port
|
||||
}
|
||||
|
||||
if mixedListener != nil {
|
||||
_, portStr, _ := net.SplitHostPort(mixedListener.Address())
|
||||
port, _ := strconv.Atoi(portStr)
|
||||
ports.MixedPort = port
|
||||
}
|
||||
|
||||
return ports
|
||||
}
|
||||
|
||||
|
|
41
proxy/mixed/conn.go
Normal file
41
proxy/mixed/conn.go
Normal file
|
@ -0,0 +1,41 @@
|
|||
package mixed
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"net"
|
||||
)
|
||||
|
||||
type BufferedConn struct {
|
||||
r *bufio.Reader
|
||||
net.Conn
|
||||
}
|
||||
|
||||
func NewBufferedConn(c net.Conn) *BufferedConn {
|
||||
return &BufferedConn{bufio.NewReader(c), c}
|
||||
}
|
||||
|
||||
// Reader returns the internal bufio.Reader.
|
||||
func (c *BufferedConn) Reader() *bufio.Reader {
|
||||
return c.r
|
||||
}
|
||||
|
||||
// Peek returns the next n bytes without advancing the reader.
|
||||
func (c *BufferedConn) Peek(n int) ([]byte, error) {
|
||||
return c.r.Peek(n)
|
||||
}
|
||||
|
||||
func (c *BufferedConn) Read(p []byte) (int, error) {
|
||||
return c.r.Read(p)
|
||||
}
|
||||
|
||||
func (c *BufferedConn) ReadByte() (byte, error) {
|
||||
return c.r.ReadByte()
|
||||
}
|
||||
|
||||
func (c *BufferedConn) UnreadByte() error {
|
||||
return c.r.UnreadByte()
|
||||
}
|
||||
|
||||
func (c *BufferedConn) Buffered() int {
|
||||
return c.r.Buffered()
|
||||
}
|
69
proxy/mixed/mixed.go
Normal file
69
proxy/mixed/mixed.go
Normal file
|
@ -0,0 +1,69 @@
|
|||
package mixed
|
||||
|
||||
import (
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/Dreamacro/clash/common/cache"
|
||||
"github.com/Dreamacro/clash/component/socks5"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
|
||||
"github.com/Dreamacro/clash/proxy/http"
|
||||
"github.com/Dreamacro/clash/proxy/socks"
|
||||
)
|
||||
|
||||
type MixedListener struct {
|
||||
net.Listener
|
||||
address string
|
||||
closed bool
|
||||
cache *cache.Cache
|
||||
}
|
||||
|
||||
func NewMixedProxy(addr string) (*MixedListener, error) {
|
||||
l, err := net.Listen("tcp", addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ml := &MixedListener{l, addr, false, cache.New(30 * time.Second)}
|
||||
go func() {
|
||||
log.Infoln("Mixed(http+socks5) proxy listening at: %s", addr)
|
||||
|
||||
for {
|
||||
c, err := ml.Accept()
|
||||
if err != nil {
|
||||
if ml.closed {
|
||||
break
|
||||
}
|
||||
continue
|
||||
}
|
||||
go handleConn(c, ml.cache)
|
||||
}
|
||||
}()
|
||||
|
||||
return ml, nil
|
||||
}
|
||||
|
||||
func (l *MixedListener) Close() {
|
||||
l.closed = true
|
||||
l.Listener.Close()
|
||||
}
|
||||
|
||||
func (l *MixedListener) Address() string {
|
||||
return l.address
|
||||
}
|
||||
|
||||
func handleConn(conn net.Conn, cache *cache.Cache) {
|
||||
bufConn := NewBufferedConn(conn)
|
||||
head, err := bufConn.Peek(1)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if head[0] == socks5.Version {
|
||||
socks.HandleSocks(bufConn)
|
||||
return
|
||||
}
|
||||
|
||||
http.HandleConn(bufConn, cache)
|
||||
}
|
|
@ -36,7 +36,7 @@ func NewSocksProxy(addr string) (*SockListener, error) {
|
|||
}
|
||||
continue
|
||||
}
|
||||
go handleSocks(c)
|
||||
go HandleSocks(c)
|
||||
}
|
||||
}()
|
||||
|
||||
|
@ -52,13 +52,15 @@ func (l *SockListener) Address() string {
|
|||
return l.address
|
||||
}
|
||||
|
||||
func handleSocks(conn net.Conn) {
|
||||
func HandleSocks(conn net.Conn) {
|
||||
target, command, err := socks5.ServerHandshake(conn, authStore.Authenticator())
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
return
|
||||
}
|
||||
conn.(*net.TCPConn).SetKeepAlive(true)
|
||||
if c, ok := conn.(*net.TCPConn); ok {
|
||||
c.SetKeepAlive(true)
|
||||
}
|
||||
if command == socks5.CmdUDPAssociate {
|
||||
defer conn.Close()
|
||||
io.Copy(ioutil.Discard, conn)
|
||||
|
|
Loading…
Reference in a new issue