Style: code style
This commit is contained in:
parent
bcfc15e398
commit
6091fcdfec
27 changed files with 171 additions and 186 deletions
|
@ -18,9 +18,9 @@ import (
|
||||||
"github.com/Dreamacro/clash/config"
|
"github.com/Dreamacro/clash/config"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
"github.com/Dreamacro/clash/dns"
|
"github.com/Dreamacro/clash/dns"
|
||||||
|
P "github.com/Dreamacro/clash/listener"
|
||||||
|
authStore "github.com/Dreamacro/clash/listener/auth"
|
||||||
"github.com/Dreamacro/clash/log"
|
"github.com/Dreamacro/clash/log"
|
||||||
P "github.com/Dreamacro/clash/proxy"
|
|
||||||
authStore "github.com/Dreamacro/clash/proxy/auth"
|
|
||||||
"github.com/Dreamacro/clash/tunnel"
|
"github.com/Dreamacro/clash/tunnel"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -187,23 +187,26 @@ func updateGeneral(general *config.General, force bool) {
|
||||||
bindAddress := general.BindAddress
|
bindAddress := general.BindAddress
|
||||||
P.SetBindAddress(bindAddress)
|
P.SetBindAddress(bindAddress)
|
||||||
|
|
||||||
if err := P.ReCreateHTTP(general.Port); err != nil {
|
tcpIn := tunnel.TCPIn()
|
||||||
|
udpIn := tunnel.UDPIn()
|
||||||
|
|
||||||
|
if err := P.ReCreateHTTP(general.Port, tcpIn); err != nil {
|
||||||
log.Errorln("Start HTTP server error: %s", err.Error())
|
log.Errorln("Start HTTP server error: %s", err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := P.ReCreateSocks(general.SocksPort); err != nil {
|
if err := P.ReCreateSocks(general.SocksPort, tcpIn, udpIn); err != nil {
|
||||||
log.Errorln("Start SOCKS5 server error: %s", err.Error())
|
log.Errorln("Start SOCKS5 server error: %s", err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := P.ReCreateRedir(general.RedirPort); err != nil {
|
if err := P.ReCreateRedir(general.RedirPort, tcpIn, udpIn); err != nil {
|
||||||
log.Errorln("Start Redir server error: %s", err.Error())
|
log.Errorln("Start Redir server error: %s", err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := P.ReCreateTProxy(general.TProxyPort); err != nil {
|
if err := P.ReCreateTProxy(general.TProxyPort, tcpIn, udpIn); err != nil {
|
||||||
log.Errorln("Start TProxy server error: %s", err.Error())
|
log.Errorln("Start TProxy server error: %s", err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := P.ReCreateMixed(general.MixedPort); err != nil {
|
if err := P.ReCreateMixed(general.MixedPort, tcpIn, udpIn); err != nil {
|
||||||
log.Errorln("Start Mixed(http and socks5) server error: %s", err.Error())
|
log.Errorln("Start Mixed(http and socks5) server error: %s", err.Error())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,8 +7,8 @@ import (
|
||||||
"github.com/Dreamacro/clash/component/resolver"
|
"github.com/Dreamacro/clash/component/resolver"
|
||||||
"github.com/Dreamacro/clash/config"
|
"github.com/Dreamacro/clash/config"
|
||||||
"github.com/Dreamacro/clash/hub/executor"
|
"github.com/Dreamacro/clash/hub/executor"
|
||||||
|
P "github.com/Dreamacro/clash/listener"
|
||||||
"github.com/Dreamacro/clash/log"
|
"github.com/Dreamacro/clash/log"
|
||||||
P "github.com/Dreamacro/clash/proxy"
|
|
||||||
"github.com/Dreamacro/clash/tunnel"
|
"github.com/Dreamacro/clash/tunnel"
|
||||||
|
|
||||||
"github.com/go-chi/chi/v5"
|
"github.com/go-chi/chi/v5"
|
||||||
|
@ -66,11 +66,15 @@ func patchConfigs(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
ports := P.GetPorts()
|
ports := P.GetPorts()
|
||||||
P.ReCreateHTTP(pointerOrDefault(general.Port, ports.Port))
|
|
||||||
P.ReCreateSocks(pointerOrDefault(general.SocksPort, ports.SocksPort))
|
tcpIn := tunnel.TCPIn()
|
||||||
P.ReCreateRedir(pointerOrDefault(general.RedirPort, ports.RedirPort))
|
udpIn := tunnel.UDPIn()
|
||||||
P.ReCreateTProxy(pointerOrDefault(general.TProxyPort, ports.TProxyPort))
|
|
||||||
P.ReCreateMixed(pointerOrDefault(general.MixedPort, ports.MixedPort))
|
P.ReCreateHTTP(pointerOrDefault(general.Port, ports.Port), tcpIn)
|
||||||
|
P.ReCreateSocks(pointerOrDefault(general.SocksPort, ports.SocksPort), tcpIn, udpIn)
|
||||||
|
P.ReCreateRedir(pointerOrDefault(general.RedirPort, ports.RedirPort), tcpIn, udpIn)
|
||||||
|
P.ReCreateTProxy(pointerOrDefault(general.TProxyPort, ports.TProxyPort), tcpIn, udpIn)
|
||||||
|
P.ReCreateMixed(pointerOrDefault(general.MixedPort, ports.MixedPort), tcpIn, udpIn)
|
||||||
|
|
||||||
if general.Mode != nil {
|
if general.Mode != nil {
|
||||||
tunnel.SetMode(*general.Mode)
|
tunnel.SetMode(*general.Mode)
|
||||||
|
|
|
@ -11,28 +11,25 @@ import (
|
||||||
"github.com/Dreamacro/clash/adapter/inbound"
|
"github.com/Dreamacro/clash/adapter/inbound"
|
||||||
"github.com/Dreamacro/clash/common/cache"
|
"github.com/Dreamacro/clash/common/cache"
|
||||||
"github.com/Dreamacro/clash/component/auth"
|
"github.com/Dreamacro/clash/component/auth"
|
||||||
|
C "github.com/Dreamacro/clash/constant"
|
||||||
|
authStore "github.com/Dreamacro/clash/listener/auth"
|
||||||
"github.com/Dreamacro/clash/log"
|
"github.com/Dreamacro/clash/log"
|
||||||
authStore "github.com/Dreamacro/clash/proxy/auth"
|
|
||||||
"github.com/Dreamacro/clash/tunnel"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type HTTPListener struct {
|
type Listener struct {
|
||||||
net.Listener
|
net.Listener
|
||||||
address string
|
address string
|
||||||
closed bool
|
closed bool
|
||||||
cache *cache.Cache
|
cache *cache.Cache
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewHTTPProxy(addr string) (*HTTPListener, error) {
|
func New(addr string, in chan<- C.ConnContext) (*Listener, error) {
|
||||||
l, err := net.Listen("tcp", addr)
|
l, err := net.Listen("tcp", addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
hl := &HTTPListener{l, addr, false, cache.New(30 * time.Second)}
|
hl := &Listener{l, addr, false, cache.New(30 * time.Second)}
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
log.Infoln("HTTP proxy listening at: %s", addr)
|
|
||||||
|
|
||||||
for {
|
for {
|
||||||
c, err := hl.Accept()
|
c, err := hl.Accept()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -41,19 +38,19 @@ func NewHTTPProxy(addr string) (*HTTPListener, error) {
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
go HandleConn(c, hl.cache)
|
go HandleConn(c, in, hl.cache)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
return hl, nil
|
return hl, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *HTTPListener) Close() {
|
func (l *Listener) Close() {
|
||||||
l.closed = true
|
l.closed = true
|
||||||
l.Listener.Close()
|
l.Listener.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *HTTPListener) Address() string {
|
func (l *Listener) Address() string {
|
||||||
return l.address
|
return l.address
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -70,7 +67,7 @@ func canActivate(loginStr string, authenticator auth.Authenticator, cache *cache
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func HandleConn(conn net.Conn, cache *cache.Cache) {
|
func HandleConn(conn net.Conn, in chan<- C.ConnContext, cache *cache.Cache) {
|
||||||
br := bufio.NewReader(conn)
|
br := bufio.NewReader(conn)
|
||||||
|
|
||||||
keepAlive:
|
keepAlive:
|
||||||
|
@ -106,9 +103,9 @@ keepAlive:
|
||||||
conn.Close()
|
conn.Close()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
tunnel.Add(inbound.NewHTTPS(request, conn))
|
in <- inbound.NewHTTPS(request, conn)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
tunnel.Add(inbound.NewHTTP(request, conn))
|
in <- inbound.NewHTTP(request, conn)
|
||||||
}
|
}
|
|
@ -6,26 +6,29 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"github.com/Dreamacro/clash/adapter/inbound"
|
||||||
|
C "github.com/Dreamacro/clash/constant"
|
||||||
|
"github.com/Dreamacro/clash/listener/http"
|
||||||
|
"github.com/Dreamacro/clash/listener/mixed"
|
||||||
|
"github.com/Dreamacro/clash/listener/redir"
|
||||||
|
"github.com/Dreamacro/clash/listener/socks"
|
||||||
|
"github.com/Dreamacro/clash/listener/tproxy"
|
||||||
"github.com/Dreamacro/clash/log"
|
"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"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
allowLan = false
|
allowLan = false
|
||||||
bindAddress = "*"
|
bindAddress = "*"
|
||||||
|
|
||||||
socksListener *socks.SockListener
|
socksListener *socks.Listener
|
||||||
socksUDPListener *socks.SockUDPListener
|
socksUDPListener *socks.UDPListener
|
||||||
httpListener *http.HTTPListener
|
httpListener *http.Listener
|
||||||
redirListener *redir.RedirListener
|
redirListener *redir.Listener
|
||||||
redirUDPListener *redir.RedirUDPListener
|
redirUDPListener *tproxy.UDPListener
|
||||||
tproxyListener *redir.TProxyListener
|
tproxyListener *tproxy.Listener
|
||||||
tproxyUDPListener *redir.RedirUDPListener
|
tproxyUDPListener *tproxy.UDPListener
|
||||||
mixedListener *mixed.MixedListener
|
mixedListener *mixed.Listener
|
||||||
mixedUDPLister *socks.SockUDPListener
|
mixedUDPLister *socks.UDPListener
|
||||||
|
|
||||||
// lock for recreate function
|
// lock for recreate function
|
||||||
socksMux sync.Mutex
|
socksMux sync.Mutex
|
||||||
|
@ -59,7 +62,7 @@ func SetBindAddress(host string) {
|
||||||
bindAddress = host
|
bindAddress = host
|
||||||
}
|
}
|
||||||
|
|
||||||
func ReCreateHTTP(port int) error {
|
func ReCreateHTTP(port int, tcpIn chan<- C.ConnContext) error {
|
||||||
httpMux.Lock()
|
httpMux.Lock()
|
||||||
defer httpMux.Unlock()
|
defer httpMux.Unlock()
|
||||||
|
|
||||||
|
@ -78,15 +81,16 @@ func ReCreateHTTP(port int) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
httpListener, err = http.NewHTTPProxy(addr)
|
httpListener, err = http.New(addr, tcpIn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.Infoln("HTTP proxy listening at: %s", httpListener.Address())
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func ReCreateSocks(port int) error {
|
func ReCreateSocks(port int, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.PacketAdapter) error {
|
||||||
socksMux.Lock()
|
socksMux.Lock()
|
||||||
defer socksMux.Unlock()
|
defer socksMux.Unlock()
|
||||||
|
|
||||||
|
@ -121,12 +125,12 @@ func ReCreateSocks(port int) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
tcpListener, err := socks.NewSocksProxy(addr)
|
tcpListener, err := socks.New(addr, tcpIn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
udpListener, err := socks.NewSocksUDPProxy(addr)
|
udpListener, err := socks.NewUDP(addr, udpIn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
tcpListener.Close()
|
tcpListener.Close()
|
||||||
return err
|
return err
|
||||||
|
@ -135,10 +139,11 @@ func ReCreateSocks(port int) error {
|
||||||
socksListener = tcpListener
|
socksListener = tcpListener
|
||||||
socksUDPListener = udpListener
|
socksUDPListener = udpListener
|
||||||
|
|
||||||
|
log.Infoln("SOCKS5 proxy listening at: %s", socksListener.Address())
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func ReCreateRedir(port int) error {
|
func ReCreateRedir(port int, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.PacketAdapter) error {
|
||||||
redirMux.Lock()
|
redirMux.Lock()
|
||||||
defer redirMux.Unlock()
|
defer redirMux.Unlock()
|
||||||
|
|
||||||
|
@ -165,20 +170,21 @@ func ReCreateRedir(port int) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
redirListener, err = redir.NewRedirProxy(addr)
|
redirListener, err = redir.New(addr, tcpIn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
redirUDPListener, err = redir.NewRedirUDPProxy(addr)
|
redirUDPListener, err = tproxy.NewUDP(addr, udpIn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warnln("Failed to start Redir UDP Listener: %s", err)
|
log.Warnln("Failed to start Redir UDP Listener: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.Infoln("Redirect proxy listening at: %s", redirListener.Address())
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func ReCreateTProxy(port int) error {
|
func ReCreateTProxy(port int, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.PacketAdapter) error {
|
||||||
tproxyMux.Lock()
|
tproxyMux.Lock()
|
||||||
defer tproxyMux.Unlock()
|
defer tproxyMux.Unlock()
|
||||||
|
|
||||||
|
@ -205,20 +211,21 @@ func ReCreateTProxy(port int) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
tproxyListener, err = redir.NewTProxy(addr)
|
tproxyListener, err = tproxy.New(addr, tcpIn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
tproxyUDPListener, err = redir.NewRedirUDPProxy(addr)
|
tproxyUDPListener, err = tproxy.NewUDP(addr, udpIn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warnln("Failed to start TProxy UDP Listener: %s", err)
|
log.Warnln("Failed to start TProxy UDP Listener: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.Infoln("TProxy server listening at: %s", tproxyListener.Address())
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func ReCreateMixed(port int) error {
|
func ReCreateMixed(port int, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.PacketAdapter) error {
|
||||||
mixedMux.Lock()
|
mixedMux.Lock()
|
||||||
defer mixedMux.Unlock()
|
defer mixedMux.Unlock()
|
||||||
|
|
||||||
|
@ -253,17 +260,18 @@ func ReCreateMixed(port int) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
mixedListener, err = mixed.NewMixedProxy(addr)
|
mixedListener, err = mixed.New(addr, tcpIn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
mixedUDPLister, err = socks.NewSocksUDPProxy(addr)
|
mixedUDPLister, err = socks.NewUDP(addr, udpIn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
mixedListener.Close()
|
mixedListener.Close()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.Infoln("Mixed(http+socks5) proxy listening at: %s", mixedListener.Address())
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,29 +5,27 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/common/cache"
|
"github.com/Dreamacro/clash/common/cache"
|
||||||
"github.com/Dreamacro/clash/log"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
"github.com/Dreamacro/clash/proxy/http"
|
"github.com/Dreamacro/clash/listener/http"
|
||||||
"github.com/Dreamacro/clash/proxy/socks"
|
"github.com/Dreamacro/clash/listener/socks"
|
||||||
"github.com/Dreamacro/clash/transport/socks5"
|
"github.com/Dreamacro/clash/transport/socks5"
|
||||||
)
|
)
|
||||||
|
|
||||||
type MixedListener struct {
|
type Listener struct {
|
||||||
net.Listener
|
net.Listener
|
||||||
address string
|
address string
|
||||||
closed bool
|
closed bool
|
||||||
cache *cache.Cache
|
cache *cache.Cache
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewMixedProxy(addr string) (*MixedListener, error) {
|
func New(addr string, in chan<- C.ConnContext) (*Listener, error) {
|
||||||
l, err := net.Listen("tcp", addr)
|
l, err := net.Listen("tcp", addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
ml := &MixedListener{l, addr, false, cache.New(30 * time.Second)}
|
ml := &Listener{l, addr, false, cache.New(30 * time.Second)}
|
||||||
go func() {
|
go func() {
|
||||||
log.Infoln("Mixed(http+socks5) proxy listening at: %s", addr)
|
|
||||||
|
|
||||||
for {
|
for {
|
||||||
c, err := ml.Accept()
|
c, err := ml.Accept()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -36,23 +34,23 @@ func NewMixedProxy(addr string) (*MixedListener, error) {
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
go handleConn(c, ml.cache)
|
go handleConn(c, in, ml.cache)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
return ml, nil
|
return ml, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *MixedListener) Close() {
|
func (l *Listener) Close() {
|
||||||
l.closed = true
|
l.closed = true
|
||||||
l.Listener.Close()
|
l.Listener.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *MixedListener) Address() string {
|
func (l *Listener) Address() string {
|
||||||
return l.address
|
return l.address
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleConn(conn net.Conn, cache *cache.Cache) {
|
func handleConn(conn net.Conn, in chan<- C.ConnContext, cache *cache.Cache) {
|
||||||
bufConn := NewBufferedConn(conn)
|
bufConn := NewBufferedConn(conn)
|
||||||
head, err := bufConn.Peek(1)
|
head, err := bufConn.Peek(1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -60,9 +58,9 @@ func handleConn(conn net.Conn, cache *cache.Cache) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if head[0] == socks5.Version {
|
if head[0] == socks5.Version {
|
||||||
socks.HandleSocks(bufConn)
|
socks.HandleSocks(bufConn, in)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
http.HandleConn(bufConn, cache)
|
http.HandleConn(bufConn, in, cache)
|
||||||
}
|
}
|
|
@ -5,25 +5,22 @@ import (
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/adapter/inbound"
|
"github.com/Dreamacro/clash/adapter/inbound"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
"github.com/Dreamacro/clash/log"
|
|
||||||
"github.com/Dreamacro/clash/tunnel"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type RedirListener struct {
|
type Listener struct {
|
||||||
net.Listener
|
net.Listener
|
||||||
address string
|
address string
|
||||||
closed bool
|
closed bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRedirProxy(addr string) (*RedirListener, error) {
|
func New(addr string, in chan<- C.ConnContext) (*Listener, error) {
|
||||||
l, err := net.Listen("tcp", addr)
|
l, err := net.Listen("tcp", addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
rl := &RedirListener{l, addr, false}
|
rl := &Listener{l, addr, false}
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
log.Infoln("Redir proxy listening at: %s", addr)
|
|
||||||
for {
|
for {
|
||||||
c, err := l.Accept()
|
c, err := l.Accept()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -32,28 +29,28 @@ func NewRedirProxy(addr string) (*RedirListener, error) {
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
go handleRedir(c)
|
go handleRedir(c, in)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
return rl, nil
|
return rl, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *RedirListener) Close() {
|
func (l *Listener) Close() {
|
||||||
l.closed = true
|
l.closed = true
|
||||||
l.Listener.Close()
|
l.Listener.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *RedirListener) Address() string {
|
func (l *Listener) Address() string {
|
||||||
return l.address
|
return l.address
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleRedir(conn net.Conn) {
|
func handleRedir(conn net.Conn, in chan<- C.ConnContext) {
|
||||||
target, err := parserPacket(conn)
|
target, err := parserPacket(conn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
conn.Close()
|
conn.Close()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
conn.(*net.TCPConn).SetKeepAlive(true)
|
conn.(*net.TCPConn).SetKeepAlive(true)
|
||||||
tunnel.Add(inbound.NewSocket(target, conn, C.REDIR))
|
in <- inbound.NewSocket(target, conn, C.REDIR)
|
||||||
}
|
}
|
|
@ -7,27 +7,24 @@ import (
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/adapter/inbound"
|
"github.com/Dreamacro/clash/adapter/inbound"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
"github.com/Dreamacro/clash/log"
|
authStore "github.com/Dreamacro/clash/listener/auth"
|
||||||
authStore "github.com/Dreamacro/clash/proxy/auth"
|
|
||||||
"github.com/Dreamacro/clash/transport/socks5"
|
"github.com/Dreamacro/clash/transport/socks5"
|
||||||
"github.com/Dreamacro/clash/tunnel"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type SockListener struct {
|
type Listener struct {
|
||||||
net.Listener
|
net.Listener
|
||||||
address string
|
address string
|
||||||
closed bool
|
closed bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSocksProxy(addr string) (*SockListener, error) {
|
func New(addr string, in chan<- C.ConnContext) (*Listener, error) {
|
||||||
l, err := net.Listen("tcp", addr)
|
l, err := net.Listen("tcp", addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
sl := &SockListener{l, addr, false}
|
sl := &Listener{l, addr, false}
|
||||||
go func() {
|
go func() {
|
||||||
log.Infoln("SOCKS proxy listening at: %s", addr)
|
|
||||||
for {
|
for {
|
||||||
c, err := l.Accept()
|
c, err := l.Accept()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -36,23 +33,23 @@ func NewSocksProxy(addr string) (*SockListener, error) {
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
go HandleSocks(c)
|
go HandleSocks(c, in)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
return sl, nil
|
return sl, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *SockListener) Close() {
|
func (l *Listener) Close() {
|
||||||
l.closed = true
|
l.closed = true
|
||||||
l.Listener.Close()
|
l.Listener.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *SockListener) Address() string {
|
func (l *Listener) Address() string {
|
||||||
return l.address
|
return l.address
|
||||||
}
|
}
|
||||||
|
|
||||||
func HandleSocks(conn net.Conn) {
|
func HandleSocks(conn net.Conn, in chan<- C.ConnContext) {
|
||||||
target, command, err := socks5.ServerHandshake(conn, authStore.Authenticator())
|
target, command, err := socks5.ServerHandshake(conn, authStore.Authenticator())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
conn.Close()
|
conn.Close()
|
||||||
|
@ -66,5 +63,5 @@ func HandleSocks(conn net.Conn) {
|
||||||
io.Copy(ioutil.Discard, conn)
|
io.Copy(ioutil.Discard, conn)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
tunnel.Add(inbound.NewSocket(target, conn, C.SOCKS))
|
in <- inbound.NewSocket(target, conn, C.SOCKS)
|
||||||
}
|
}
|
|
@ -9,27 +9,25 @@ import (
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
"github.com/Dreamacro/clash/log"
|
"github.com/Dreamacro/clash/log"
|
||||||
"github.com/Dreamacro/clash/transport/socks5"
|
"github.com/Dreamacro/clash/transport/socks5"
|
||||||
"github.com/Dreamacro/clash/tunnel"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type SockUDPListener struct {
|
type UDPListener struct {
|
||||||
net.PacketConn
|
net.PacketConn
|
||||||
address string
|
address string
|
||||||
closed bool
|
closed bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSocksUDPProxy(addr string) (*SockUDPListener, error) {
|
func NewUDP(addr string, in chan<- *inbound.PacketAdapter) (*UDPListener, error) {
|
||||||
l, err := net.ListenPacket("udp", addr)
|
l, err := net.ListenPacket("udp", addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = sockopt.UDPReuseaddr(l.(*net.UDPConn))
|
if err := sockopt.UDPReuseaddr(l.(*net.UDPConn)); err != nil {
|
||||||
if err != nil {
|
|
||||||
log.Warnln("Failed to Reuse UDP Address: %s", err)
|
log.Warnln("Failed to Reuse UDP Address: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
sl := &SockUDPListener{l, addr, false}
|
sl := &UDPListener{l, addr, false}
|
||||||
go func() {
|
go func() {
|
||||||
for {
|
for {
|
||||||
buf := pool.Get(pool.RelayBufferSize)
|
buf := pool.Get(pool.RelayBufferSize)
|
||||||
|
@ -41,23 +39,23 @@ func NewSocksUDPProxy(addr string) (*SockUDPListener, error) {
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
handleSocksUDP(l, buf[:n], remoteAddr)
|
handleSocksUDP(l, in, buf[:n], remoteAddr)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
return sl, nil
|
return sl, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *SockUDPListener) Close() error {
|
func (l *UDPListener) Close() error {
|
||||||
l.closed = true
|
l.closed = true
|
||||||
return l.PacketConn.Close()
|
return l.PacketConn.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *SockUDPListener) Address() string {
|
func (l *UDPListener) Address() string {
|
||||||
return l.address
|
return l.address
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleSocksUDP(pc net.PacketConn, buf []byte, addr net.Addr) {
|
func handleSocksUDP(pc net.PacketConn, in chan<- *inbound.PacketAdapter, buf []byte, addr net.Addr) {
|
||||||
target, payload, err := socks5.DecodeUDPPacket(buf)
|
target, payload, err := socks5.DecodeUDPPacket(buf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Unresolved UDP packet, return buffer to the pool
|
// Unresolved UDP packet, return buffer to the pool
|
||||||
|
@ -70,5 +68,8 @@ func handleSocksUDP(pc net.PacketConn, buf []byte, addr net.Addr) {
|
||||||
payload: payload,
|
payload: payload,
|
||||||
bufRef: buf,
|
bufRef: buf,
|
||||||
}
|
}
|
||||||
tunnel.AddPacket(inbound.NewPacket(target, packet, C.SOCKS))
|
select {
|
||||||
|
case in <- inbound.NewPacket(target, packet, C.TPROXY):
|
||||||
|
default:
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package redir
|
package tproxy
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net"
|
"net"
|
|
@ -1,6 +1,6 @@
|
||||||
// +build linux
|
// +build linux
|
||||||
|
|
||||||
package redir
|
package tproxy
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net"
|
"net"
|
|
@ -1,6 +1,6 @@
|
||||||
// +build !linux
|
// +build !linux
|
||||||
|
|
||||||
package redir
|
package tproxy
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
|
@ -1,22 +1,20 @@
|
||||||
package redir
|
package tproxy
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/adapter/inbound"
|
"github.com/Dreamacro/clash/adapter/inbound"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
"github.com/Dreamacro/clash/log"
|
|
||||||
"github.com/Dreamacro/clash/transport/socks5"
|
"github.com/Dreamacro/clash/transport/socks5"
|
||||||
"github.com/Dreamacro/clash/tunnel"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type TProxyListener struct {
|
type Listener struct {
|
||||||
net.Listener
|
net.Listener
|
||||||
address string
|
address string
|
||||||
closed bool
|
closed bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewTProxy(addr string) (*TProxyListener, error) {
|
func New(addr string, in chan<- C.ConnContext) (*Listener, error) {
|
||||||
l, err := net.Listen("tcp", addr)
|
l, err := net.Listen("tcp", addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -33,13 +31,12 @@ func NewTProxy(addr string) (*TProxyListener, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
rl := &TProxyListener{
|
rl := &Listener{
|
||||||
Listener: l,
|
Listener: l,
|
||||||
address: addr,
|
address: addr,
|
||||||
}
|
}
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
log.Infoln("TProxy server listening at: %s", addr)
|
|
||||||
for {
|
for {
|
||||||
c, err := l.Accept()
|
c, err := l.Accept()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -48,24 +45,24 @@ func NewTProxy(addr string) (*TProxyListener, error) {
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
go rl.handleTProxy(c)
|
go rl.handleTProxy(c, in)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
return rl, nil
|
return rl, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *TProxyListener) Close() {
|
func (l *Listener) Close() {
|
||||||
l.closed = true
|
l.closed = true
|
||||||
l.Listener.Close()
|
l.Listener.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *TProxyListener) Address() string {
|
func (l *Listener) Address() string {
|
||||||
return l.address
|
return l.address
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *TProxyListener) handleTProxy(conn net.Conn) {
|
func (l *Listener) handleTProxy(conn net.Conn, in chan<- C.ConnContext) {
|
||||||
target := socks5.ParseAddrToSocksAddr(conn.LocalAddr())
|
target := socks5.ParseAddrToSocksAddr(conn.LocalAddr())
|
||||||
conn.(*net.TCPConn).SetKeepAlive(true)
|
conn.(*net.TCPConn).SetKeepAlive(true)
|
||||||
tunnel.Add(inbound.NewSocket(target, conn, C.TPROXY))
|
in <- inbound.NewSocket(target, conn, C.TPROXY)
|
||||||
}
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package redir
|
package tproxy
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net"
|
"net"
|
||||||
|
@ -7,22 +7,21 @@ import (
|
||||||
"github.com/Dreamacro/clash/common/pool"
|
"github.com/Dreamacro/clash/common/pool"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
"github.com/Dreamacro/clash/transport/socks5"
|
"github.com/Dreamacro/clash/transport/socks5"
|
||||||
"github.com/Dreamacro/clash/tunnel"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type RedirUDPListener struct {
|
type UDPListener struct {
|
||||||
net.PacketConn
|
net.PacketConn
|
||||||
address string
|
address string
|
||||||
closed bool
|
closed bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRedirUDPProxy(addr string) (*RedirUDPListener, error) {
|
func NewUDP(addr string, in chan<- *inbound.PacketAdapter) (*UDPListener, error) {
|
||||||
l, err := net.ListenPacket("udp", addr)
|
l, err := net.ListenPacket("udp", addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
rl := &RedirUDPListener{l, addr, false}
|
rl := &UDPListener{l, addr, false}
|
||||||
|
|
||||||
c := l.(*net.UDPConn)
|
c := l.(*net.UDPConn)
|
||||||
|
|
||||||
|
@ -53,27 +52,30 @@ func NewRedirUDPProxy(addr string) (*RedirUDPListener, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
handleRedirUDP(l, buf[:n], lAddr, rAddr)
|
handlePacketConn(l, in, buf[:n], lAddr, rAddr)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
return rl, nil
|
return rl, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *RedirUDPListener) Close() error {
|
func (l *UDPListener) Close() error {
|
||||||
l.closed = true
|
l.closed = true
|
||||||
return l.PacketConn.Close()
|
return l.PacketConn.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *RedirUDPListener) Address() string {
|
func (l *UDPListener) Address() string {
|
||||||
return l.address
|
return l.address
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleRedirUDP(pc net.PacketConn, buf []byte, lAddr *net.UDPAddr, rAddr *net.UDPAddr) {
|
func handlePacketConn(pc net.PacketConn, in chan<- *inbound.PacketAdapter, buf []byte, lAddr *net.UDPAddr, rAddr *net.UDPAddr) {
|
||||||
target := socks5.ParseAddrToSocksAddr(rAddr)
|
target := socks5.ParseAddrToSocksAddr(rAddr)
|
||||||
pkt := &packet{
|
pkt := &packet{
|
||||||
lAddr: lAddr,
|
lAddr: lAddr,
|
||||||
buf: buf,
|
buf: buf,
|
||||||
}
|
}
|
||||||
tunnel.AddPacket(inbound.NewPacket(target, pkt, C.TPROXY))
|
select {
|
||||||
|
case in <- inbound.NewPacket(target, pkt, C.TPROXY):
|
||||||
|
default:
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -1,8 +1,10 @@
|
||||||
// +build linux
|
// +build linux
|
||||||
|
|
||||||
package redir
|
package tproxy
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
|
@ -10,6 +12,11 @@ import (
|
||||||
"syscall"
|
"syscall"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
IPV6_TRANSPARENT = 0x4b
|
||||||
|
IPV6_RECVORIGDSTADDR = 0x4a
|
||||||
|
)
|
||||||
|
|
||||||
// dialUDP acts like net.DialUDP for transparent proxy.
|
// dialUDP acts like net.DialUDP for transparent proxy.
|
||||||
// It binds to a non-local address(`lAddr`).
|
// It binds to a non-local address(`lAddr`).
|
||||||
func dialUDP(network string, lAddr *net.UDPAddr, rAddr *net.UDPAddr) (*net.UDPConn, error) {
|
func dialUDP(network string, lAddr *net.UDPAddr, rAddr *net.UDPAddr) (*net.UDPConn, error) {
|
||||||
|
@ -94,3 +101,24 @@ func udpAddrFamily(net string, lAddr, rAddr *net.UDPAddr) int {
|
||||||
}
|
}
|
||||||
return syscall.AF_INET6
|
return syscall.AF_INET6
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getOrigDst(oob []byte, oobn int) (*net.UDPAddr, error) {
|
||||||
|
msgs, err := syscall.ParseSocketControlMessage(oob[:oobn])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, msg := range msgs {
|
||||||
|
if msg.Header.Level == syscall.SOL_IP && msg.Header.Type == syscall.IP_RECVORIGDSTADDR {
|
||||||
|
ip := net.IP(msg.Data[4:8])
|
||||||
|
port := binary.BigEndian.Uint16(msg.Data[2:4])
|
||||||
|
return &net.UDPAddr{IP: ip, Port: int(port)}, nil
|
||||||
|
} else if msg.Header.Level == syscall.SOL_IPV6 && msg.Header.Type == IPV6_RECVORIGDSTADDR {
|
||||||
|
ip := net.IP(msg.Data[8:24])
|
||||||
|
port := binary.BigEndian.Uint16(msg.Data[2:4])
|
||||||
|
return &net.UDPAddr{IP: ip, Port: int(port)}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, errors.New("cannot find origDst")
|
||||||
|
}
|
|
@ -1,12 +1,16 @@
|
||||||
// +build !linux
|
// +build !linux
|
||||||
|
|
||||||
package redir
|
package tproxy
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"net"
|
"net"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func getOrigDst(oob []byte, oobn int) (*net.UDPAddr, error) {
|
||||||
|
return nil, errors.New("UDP redir not supported on current platform")
|
||||||
|
}
|
||||||
|
|
||||||
func dialUDP(network string, lAddr *net.UDPAddr, rAddr *net.UDPAddr) (*net.UDPConn, error) {
|
func dialUDP(network string, lAddr *net.UDPAddr, rAddr *net.UDPAddr) (*net.UDPConn, error) {
|
||||||
return nil, errors.New("UDP redir not supported on current platform")
|
return nil, errors.New("UDP redir not supported on current platform")
|
||||||
}
|
}
|
|
@ -1,36 +0,0 @@
|
||||||
// +build linux
|
|
||||||
|
|
||||||
package redir
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/binary"
|
|
||||||
"errors"
|
|
||||||
"net"
|
|
||||||
"syscall"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
IPV6_TRANSPARENT = 0x4b
|
|
||||||
IPV6_RECVORIGDSTADDR = 0x4a
|
|
||||||
)
|
|
||||||
|
|
||||||
func getOrigDst(oob []byte, oobn int) (*net.UDPAddr, error) {
|
|
||||||
msgs, err := syscall.ParseSocketControlMessage(oob[:oobn])
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, msg := range msgs {
|
|
||||||
if msg.Header.Level == syscall.SOL_IP && msg.Header.Type == syscall.IP_RECVORIGDSTADDR {
|
|
||||||
ip := net.IP(msg.Data[4:8])
|
|
||||||
port := binary.BigEndian.Uint16(msg.Data[2:4])
|
|
||||||
return &net.UDPAddr{IP: ip, Port: int(port)}, nil
|
|
||||||
} else if msg.Header.Level == syscall.SOL_IPV6 && msg.Header.Type == IPV6_RECVORIGDSTADDR {
|
|
||||||
ip := net.IP(msg.Data[8:24])
|
|
||||||
port := binary.BigEndian.Uint16(msg.Data[2:4])
|
|
||||||
return &net.UDPAddr{IP: ip, Port: int(port)}, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, errors.New("cannot find origDst")
|
|
||||||
}
|
|
|
@ -1,12 +0,0 @@
|
||||||
// +build !linux
|
|
||||||
|
|
||||||
package redir
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"net"
|
|
||||||
)
|
|
||||||
|
|
||||||
func getOrigDst(oob []byte, oobn int) (*net.UDPAddr, error) {
|
|
||||||
return nil, errors.New("UDP redir not supported on current platform")
|
|
||||||
}
|
|
|
@ -37,17 +37,14 @@ func init() {
|
||||||
go process()
|
go process()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add request to queue
|
// TCPIn return fan-in queue
|
||||||
func Add(ctx C.ConnContext) {
|
func TCPIn() chan<- C.ConnContext {
|
||||||
tcpQueue <- ctx
|
return tcpQueue
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddPacket add udp Packet to queue
|
// UDPIn return fan-in udp queue
|
||||||
func AddPacket(packet *inbound.PacketAdapter) {
|
func UDPIn() chan<- *inbound.PacketAdapter {
|
||||||
select {
|
return udpQueue
|
||||||
case udpQueue <- packet:
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rules return all rules
|
// Rules return all rules
|
||||||
|
|
Loading…
Reference in a new issue