Compare commits

..

8 Commits

16 changed files with 255 additions and 81 deletions

View File

@ -5,7 +5,7 @@
<br> <br>
</h1> </h1>
<h4 align="center">A rule based tunnel in Go.</h4> <h4 align="center">A rule-based tunnel in Go.</h4>
<p align="center"> <p align="center">
<a href="https://travis-ci.org/Dreamacro/clash"> <a href="https://travis-ci.org/Dreamacro/clash">
@ -96,6 +96,10 @@ log-level: info
# A RESTful API for clash # A RESTful API for clash
external-controller: 127.0.0.1:9090 external-controller: 127.0.0.1:9090
# you can put the static web resource (such as clash-dashboard) to a directory, and clash would serve in `${API}/ui`
# input is a relative path to the configuration directory or an absolute path
# external-ui: folder
# Secret for RESTful API (Optional) # Secret for RESTful API (Optional)
# secret: "" # secret: ""
@ -126,8 +130,8 @@ Proxy:
- { name: "vmess", type: vmess, server: server, port: 443, uuid: uuid, alterId: 32, cipher: auto, tls: true } - { name: "vmess", type: vmess, server: server, port: 443, uuid: uuid, alterId: 32, cipher: auto, tls: true }
# with tls and skip-cert-verify # with tls and skip-cert-verify
- { name: "vmess", type: vmess, server: server, port: 443, uuid: uuid, alterId: 32, cipher: auto, tls: true, skip-cert-verify: true } - { name: "vmess", type: vmess, server: server, port: 443, uuid: uuid, alterId: 32, cipher: auto, tls: true, skip-cert-verify: true }
# with ws # with ws-path and ws-headers
- { name: "vmess", type: vmess, server: server, port: 443, uuid: uuid, alterId: 32, cipher: auto, network: ws, ws-path: /path } - { name: "vmess", type: vmess, server: server, port: 443, uuid: uuid, alterId: 32, cipher: auto, network: ws, ws-path: /path, ws-headers: { Host: v2ray.com } }
# with ws + tls # with ws + tls
- { name: "vmess", type: vmess, server: server, port: 443, uuid: uuid, alterId: 32, cipher: auto, network: ws, ws-path: /path, tls: true } - { name: "vmess", type: vmess, server: server, port: 443, uuid: uuid, alterId: 32, cipher: auto, network: ws, ws-path: /path, tls: true }

View File

@ -32,16 +32,17 @@ type Vmess struct {
} }
type VmessOption struct { type VmessOption struct {
Name string `proxy:"name"` Name string `proxy:"name"`
Server string `proxy:"server"` Server string `proxy:"server"`
Port int `proxy:"port"` Port int `proxy:"port"`
UUID string `proxy:"uuid"` UUID string `proxy:"uuid"`
AlterID int `proxy:"alterId"` AlterID int `proxy:"alterId"`
Cipher string `proxy:"cipher"` Cipher string `proxy:"cipher"`
TLS bool `proxy:"tls,omitempty"` TLS bool `proxy:"tls,omitempty"`
Network string `proxy:"network,omitempty"` Network string `proxy:"network,omitempty"`
WSPath string `proxy:"ws-path,omitempty"` WSPath string `proxy:"ws-path,omitempty"`
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"` WSHeaders map[string]string `proxy:"ws-headers,omitempty"`
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
} }
func (v *Vmess) Name() string { func (v *Vmess) Name() string {
@ -71,16 +72,17 @@ func (v *Vmess) MarshalJSON() ([]byte, error) {
func NewVmess(option VmessOption) (*Vmess, error) { func NewVmess(option VmessOption) (*Vmess, error) {
security := strings.ToLower(option.Cipher) security := strings.ToLower(option.Cipher)
client, err := vmess.NewClient(vmess.Config{ client, err := vmess.NewClient(vmess.Config{
UUID: option.UUID, UUID: option.UUID,
AlterID: uint16(option.AlterID), AlterID: uint16(option.AlterID),
Security: security, Security: security,
TLS: option.TLS, TLS: option.TLS,
HostName: option.Server, HostName: option.Server,
Port: strconv.Itoa(option.Port), Port: strconv.Itoa(option.Port),
NetWork: option.Network, NetWork: option.Network,
WebSocketPath: option.WSPath, WebSocketPath: option.WSPath,
SkipCertVerify: option.SkipCertVerify, WebSocketHeaders: option.WSHeaders,
SessionCacahe: getClientSessionCache(), SkipCertVerify: option.SkipCertVerify,
SessionCacahe: getClientSessionCache(),
}) })
if err != nil { if err != nil {
return nil, err return nil, err

View File

@ -1,5 +1,7 @@
package structure package structure
// references: https://github.com/mitchellh/mapstructure
import ( import (
"fmt" "fmt"
"reflect" "reflect"
@ -70,6 +72,8 @@ func (d *Decoder) decode(name string, data interface{}, val reflect.Value) error
return d.decodeBool(name, data, val) return d.decodeBool(name, data, val)
case reflect.Slice: case reflect.Slice:
return d.decodeSlice(name, data, val) return d.decodeSlice(name, data, val)
case reflect.Map:
return d.decodeMap(name, data, val)
default: default:
return fmt.Errorf("type %s not support", val.Kind().String()) return fmt.Errorf("type %s not support", val.Kind().String())
} }
@ -158,3 +162,70 @@ func (d *Decoder) decodeSlice(name string, data interface{}, val reflect.Value)
val.Set(valSlice) val.Set(valSlice)
return nil return nil
} }
func (d *Decoder) decodeMap(name string, data interface{}, val reflect.Value) error {
valType := val.Type()
valKeyType := valType.Key()
valElemType := valType.Elem()
valMap := val
if valMap.IsNil() {
mapType := reflect.MapOf(valKeyType, valElemType)
valMap = reflect.MakeMap(mapType)
}
dataVal := reflect.Indirect(reflect.ValueOf(data))
if dataVal.Kind() != reflect.Map {
return fmt.Errorf("'%s' expected a map, got '%s'", name, dataVal.Kind())
}
return d.decodeMapFromMap(name, dataVal, val, valMap)
}
func (d *Decoder) decodeMapFromMap(name string, dataVal reflect.Value, val reflect.Value, valMap reflect.Value) error {
valType := val.Type()
valKeyType := valType.Key()
valElemType := valType.Elem()
errors := make([]string, 0)
if dataVal.Len() == 0 {
if dataVal.IsNil() {
if !val.IsNil() {
val.Set(dataVal)
}
} else {
val.Set(valMap)
}
return nil
}
for _, k := range dataVal.MapKeys() {
fieldName := fmt.Sprintf("%s[%s]", name, k)
currentKey := reflect.Indirect(reflect.New(valKeyType))
if err := d.decode(fieldName, k.Interface(), currentKey); err != nil {
errors = append(errors, err.Error())
continue
}
v := dataVal.MapIndex(k).Interface()
currentVal := reflect.Indirect(reflect.New(valElemType))
if err := d.decode(fieldName, v, currentVal); err != nil {
errors = append(errors, err.Error())
continue
}
valMap.SetMapIndex(currentKey, currentVal)
}
val.Set(valMap)
if len(errors) > 0 {
return fmt.Errorf(strings.Join(errors, ","))
}
return nil
}

View File

@ -75,16 +75,17 @@ type Client struct {
// Config of vmess // Config of vmess
type Config struct { type Config struct {
UUID string UUID string
AlterID uint16 AlterID uint16
Security string Security string
TLS bool TLS bool
HostName string HostName string
Port string Port string
NetWork string NetWork string
WebSocketPath string WebSocketPath string
SkipCertVerify bool WebSocketHeaders map[string]string
SessionCacahe tls.ClientSessionCache SkipCertVerify bool
SessionCacahe tls.ClientSessionCache
} }
// New return a Conn with net.Conn and DstAddr // New return a Conn with net.Conn and DstAddr
@ -149,6 +150,7 @@ func NewClient(config Config) (*Client, error) {
wsConfig = &websocketConfig{ wsConfig = &websocketConfig{
host: host, host: host,
path: config.WebSocketPath, path: config.WebSocketPath,
headers: config.WebSocketHeaders,
tls: config.TLS, tls: config.TLS,
tlsConfig: tlsConfig, tlsConfig: tlsConfig,
} }

View File

@ -5,6 +5,7 @@ import (
"fmt" "fmt"
"io" "io"
"net" "net"
"net/http"
"net/url" "net/url"
"strings" "strings"
"time" "time"
@ -21,6 +22,7 @@ type websocketConn struct {
type websocketConfig struct { type websocketConfig struct {
host string host string
path string path string
headers map[string]string
tls bool tls bool
tlsConfig *tls.Config tlsConfig *tls.Config
} }
@ -127,7 +129,14 @@ func newWebsocketConn(conn net.Conn, c *websocketConfig) (net.Conn, error) {
Path: c.path, Path: c.path,
} }
wsConn, resp, err := dialer.Dial(uri.String(), nil) headers := http.Header{}
if c.headers != nil {
for k, v := range c.headers {
headers.Set(k, v)
}
}
wsConn, resp, err := dialer.Dial(uri.String(), headers)
if err != nil { if err != nil {
var reason string var reason string
if resp != nil { if resp != nil {

View File

@ -6,6 +6,7 @@ import (
"net" "net"
"net/url" "net/url"
"os" "os"
"path/filepath"
"strings" "strings"
adapters "github.com/Dreamacro/clash/adapters/outbound" adapters "github.com/Dreamacro/clash/adapters/outbound"
@ -27,8 +28,9 @@ type General struct {
AllowLan bool `json:"allow-lan"` AllowLan bool `json:"allow-lan"`
Mode T.Mode `json:"mode"` Mode T.Mode `json:"mode"`
LogLevel log.LogLevel `json:"log-level"` LogLevel log.LogLevel `json:"log-level"`
ExternalController string `json:"external-controller,omitempty"` ExternalController string
Secret string `json:"secret,omitempty"` ExternalUI string
Secret string
} }
// DNS config // DNS config
@ -66,9 +68,10 @@ type rawConfig struct {
Mode T.Mode `yaml:"mode"` Mode T.Mode `yaml:"mode"`
LogLevel log.LogLevel `yaml:"log-level"` LogLevel log.LogLevel `yaml:"log-level"`
ExternalController string `yaml:"external-controller"` ExternalController string `yaml:"external-controller"`
ExternalUI string `yaml:"external-ui"`
Secret string `yaml:"secret"` Secret string `yaml:"secret"`
DNS *rawDNS `yaml:"dns"` DNS rawDNS `yaml:"dns"`
Proxy []map[string]interface{} `yaml:"Proxy"` Proxy []map[string]interface{} `yaml:"Proxy"`
ProxyGroup []map[string]interface{} `yaml:"Proxy Group"` ProxyGroup []map[string]interface{} `yaml:"Proxy Group"`
Rule []string `yaml:"Rule"` Rule []string `yaml:"Rule"`
@ -95,7 +98,7 @@ func readConfig(path string) (*rawConfig, error) {
Rule: []string{}, Rule: []string{},
Proxy: []map[string]interface{}{}, Proxy: []map[string]interface{}{},
ProxyGroup: []map[string]interface{}{}, ProxyGroup: []map[string]interface{}{},
DNS: &rawDNS{ DNS: rawDNS{
Enable: false, Enable: false,
}, },
} }
@ -145,10 +148,19 @@ func parseGeneral(cfg *rawConfig) (*General, error) {
redirPort := cfg.RedirPort redirPort := cfg.RedirPort
allowLan := cfg.AllowLan allowLan := cfg.AllowLan
externalController := cfg.ExternalController externalController := cfg.ExternalController
externalUI := cfg.ExternalUI
secret := cfg.Secret secret := cfg.Secret
mode := cfg.Mode mode := cfg.Mode
logLevel := cfg.LogLevel logLevel := cfg.LogLevel
if !filepath.IsAbs(externalUI) {
externalUI = filepath.Join(C.Path.HomeDir(), externalUI)
}
if _, err := os.Stat(externalUI); os.IsNotExist(err) {
return nil, fmt.Errorf("external-ui: %s not exist", externalUI)
}
general := &General{ general := &General{
Port: port, Port: port,
SocksPort: socksPort, SocksPort: socksPort,
@ -157,6 +169,7 @@ func parseGeneral(cfg *rawConfig) (*General, error) {
Mode: mode, Mode: mode,
LogLevel: logLevel, LogLevel: logLevel,
ExternalController: externalController, ExternalController: externalController,
ExternalUI: externalUI,
Secret: secret, Secret: secret,
} }
return general, nil return general, nil
@ -353,7 +366,6 @@ func hostWithDefaultPort(host string, defPort string) (string, error) {
func parseNameServer(servers []string) ([]dns.NameServer, error) { func parseNameServer(servers []string) ([]dns.NameServer, error) {
nameservers := []dns.NameServer{} nameservers := []dns.NameServer{}
log.Debugln("%#v", servers)
for idx, server := range servers { for idx, server := range servers {
// parse without scheme .e.g 8.8.8.8:53 // parse without scheme .e.g 8.8.8.8:53
@ -387,7 +399,7 @@ func parseNameServer(servers []string) ([]dns.NameServer, error) {
return nameservers, nil return nameservers, nil
} }
func parseDNS(cfg *rawDNS) (*DNS, error) { func parseDNS(cfg rawDNS) (*DNS, error) {
if cfg.Enable && len(cfg.NameServer) == 0 { if cfg.Enable && len(cfg.NameServer) == 0 {
return nil, fmt.Errorf("If DNS configuration is turned on, NameServer cannot be empty") return nil, fmt.Errorf("If DNS configuration is turned on, NameServer cannot be empty")
} }

View File

@ -244,8 +244,6 @@ func New(config Config) *Resolver {
mmdb, _ = geoip2.Open(C.Path.MMDB()) mmdb, _ = geoip2.Open(C.Path.MMDB())
}) })
println(config.EnhancedMode)
r := &Resolver{ r := &Resolver{
main: transform(config.Main), main: transform(config.Main),
ipv6: config.IPv6, ipv6: config.IPv6,

View File

@ -27,7 +27,6 @@ func ApplyConfig(cfg *config.Config, force bool) {
} }
updateProxies(cfg.Proxies) updateProxies(cfg.Proxies)
updateRules(cfg.Rules) updateRules(cfg.Rules)
updateGeneral(cfg.General)
updateDNS(cfg.DNS) updateDNS(cfg.DNS)
} }

View File

@ -12,6 +12,10 @@ func Parse() error {
return err return err
} }
if cfg.General.ExternalUI != "" {
route.SetUIPath(cfg.General.ExternalUI)
}
if cfg.General.ExternalController != "" { if cfg.General.ExternalController != "" {
go route.Start(cfg.General.ExternalController, cfg.General.Secret) go route.Start(cfg.General.ExternalController, cfg.General.Secret)
} }

View File

@ -32,7 +32,7 @@ type configSchema struct {
func getConfigs(w http.ResponseWriter, r *http.Request) { func getConfigs(w http.ResponseWriter, r *http.Request) {
general := executor.GetGeneral() general := executor.GetGeneral()
render.Respond(w, r, general) render.JSON(w, r, general)
} }
func pointerOrDefault(p *int, def int) int { func pointerOrDefault(p *int, def int) int {
@ -46,8 +46,8 @@ func pointerOrDefault(p *int, def int) int {
func patchConfigs(w http.ResponseWriter, r *http.Request) { func patchConfigs(w http.ResponseWriter, r *http.Request) {
general := &configSchema{} general := &configSchema{}
if err := render.DecodeJSON(r.Body, general); err != nil { if err := render.DecodeJSON(r.Body, general); err != nil {
w.WriteHeader(http.StatusBadRequest) render.Status(r, http.StatusBadRequest)
render.Respond(w, r, ErrBadRequest) render.JSON(w, r, ErrBadRequest)
return return
} }
@ -68,7 +68,7 @@ func patchConfigs(w http.ResponseWriter, r *http.Request) {
log.SetLevel(*general.LogLevel) log.SetLevel(*general.LogLevel)
} }
w.WriteHeader(http.StatusNoContent) render.NoContent(w, r)
} }
type updateConfigRequest struct { type updateConfigRequest struct {
@ -78,25 +78,25 @@ type updateConfigRequest struct {
func updateConfigs(w http.ResponseWriter, r *http.Request) { func updateConfigs(w http.ResponseWriter, r *http.Request) {
req := updateConfigRequest{} req := updateConfigRequest{}
if err := render.DecodeJSON(r.Body, &req); err != nil { if err := render.DecodeJSON(r.Body, &req); err != nil {
w.WriteHeader(http.StatusBadRequest) render.Status(r, http.StatusBadRequest)
render.Respond(w, r, ErrBadRequest) render.JSON(w, r, ErrBadRequest)
return return
} }
if !filepath.IsAbs(req.Path) { if !filepath.IsAbs(req.Path) {
w.WriteHeader(http.StatusBadRequest) render.Status(r, http.StatusBadRequest)
render.Respond(w, r, newError("path is not a absoluted path")) render.JSON(w, r, newError("path is not a absoluted path"))
return return
} }
force := r.URL.Query().Get("force") == "true" force := r.URL.Query().Get("force") == "true"
cfg, err := executor.ParseWithPath(req.Path) cfg, err := executor.ParseWithPath(req.Path)
if err != nil { if err != nil {
w.WriteHeader(http.StatusBadRequest) render.Status(r, http.StatusBadRequest)
render.Respond(w, r, newError(err.Error())) render.JSON(w, r, newError(err.Error()))
return return
} }
executor.ApplyConfig(cfg, force) executor.ApplyConfig(cfg, force)
w.WriteHeader(http.StatusNoContent) render.NoContent(w, r)
} }

View File

@ -47,8 +47,8 @@ func findProxyByName(next http.Handler) http.Handler {
proxies := T.Instance().Proxies() proxies := T.Instance().Proxies()
proxy, exist := proxies[name] proxy, exist := proxies[name]
if !exist { if !exist {
w.WriteHeader(http.StatusNotFound) render.Status(r, http.StatusNotFound)
render.Respond(w, r, ErrNotFound) render.JSON(w, r, ErrNotFound)
return return
} }
@ -59,14 +59,14 @@ func findProxyByName(next http.Handler) http.Handler {
func getProxies(w http.ResponseWriter, r *http.Request) { func getProxies(w http.ResponseWriter, r *http.Request) {
proxies := T.Instance().Proxies() proxies := T.Instance().Proxies()
render.Respond(w, r, map[string]map[string]C.Proxy{ render.JSON(w, r, map[string]map[string]C.Proxy{
"proxies": proxies, "proxies": proxies,
}) })
} }
func getProxy(w http.ResponseWriter, r *http.Request) { func getProxy(w http.ResponseWriter, r *http.Request) {
proxy := r.Context().Value(CtxKeyProxy).(C.Proxy) proxy := r.Context().Value(CtxKeyProxy).(C.Proxy)
render.Respond(w, r, proxy) render.JSON(w, r, proxy)
} }
type UpdateProxyRequest struct { type UpdateProxyRequest struct {
@ -76,8 +76,8 @@ type UpdateProxyRequest struct {
func updateProxy(w http.ResponseWriter, r *http.Request) { func updateProxy(w http.ResponseWriter, r *http.Request) {
req := UpdateProxyRequest{} req := UpdateProxyRequest{}
if err := render.DecodeJSON(r.Body, &req); err != nil { if err := render.DecodeJSON(r.Body, &req); err != nil {
w.WriteHeader(http.StatusBadRequest) render.Status(r, http.StatusBadRequest)
render.Respond(w, r, ErrBadRequest) render.JSON(w, r, ErrBadRequest)
return return
} }
@ -85,18 +85,18 @@ func updateProxy(w http.ResponseWriter, r *http.Request) {
selector, ok := proxy.(*A.Selector) selector, ok := proxy.(*A.Selector)
if !ok { if !ok {
w.WriteHeader(http.StatusBadRequest) render.Status(r, http.StatusBadRequest)
render.Respond(w, r, ErrBadRequest) render.JSON(w, r, ErrBadRequest)
return return
} }
if err := selector.Set(req.Name); err != nil { if err := selector.Set(req.Name); err != nil {
w.WriteHeader(http.StatusBadRequest) render.Status(r, http.StatusBadRequest)
render.Respond(w, r, newError(fmt.Sprintf("Selector update error: %s", err.Error()))) render.JSON(w, r, newError(fmt.Sprintf("Selector update error: %s", err.Error())))
return return
} }
w.WriteHeader(http.StatusNoContent) render.NoContent(w, r)
} }
func getProxyDelay(w http.ResponseWriter, r *http.Request) { func getProxyDelay(w http.ResponseWriter, r *http.Request) {
@ -104,8 +104,8 @@ func getProxyDelay(w http.ResponseWriter, r *http.Request) {
url := query.Get("url") url := query.Get("url")
timeout, err := strconv.ParseInt(query.Get("timeout"), 10, 16) timeout, err := strconv.ParseInt(query.Get("timeout"), 10, 16)
if err != nil { if err != nil {
w.WriteHeader(http.StatusBadRequest) render.Status(r, http.StatusBadRequest)
render.Respond(w, r, ErrBadRequest) render.JSON(w, r, ErrBadRequest)
return return
} }
@ -122,14 +122,14 @@ func getProxyDelay(w http.ResponseWriter, r *http.Request) {
select { select {
case <-time.After(time.Millisecond * time.Duration(timeout)): case <-time.After(time.Millisecond * time.Duration(timeout)):
w.WriteHeader(http.StatusRequestTimeout) render.Status(r, http.StatusRequestTimeout)
render.Respond(w, r, ErrRequestTimeout) render.JSON(w, r, ErrRequestTimeout)
case t := <-sigCh: case t := <-sigCh:
if t == 0 { if t == 0 {
w.WriteHeader(http.StatusServiceUnavailable) render.Status(r, http.StatusServiceUnavailable)
render.Respond(w, r, newError("An error occurred in the delay test")) render.JSON(w, r, newError("An error occurred in the delay test"))
} else { } else {
render.Respond(w, r, map[string]int16{ render.JSON(w, r, map[string]int16{
"delay": t, "delay": t,
}) })
} }

View File

@ -33,8 +33,7 @@ func getRules(w http.ResponseWriter, r *http.Request) {
}) })
} }
w.WriteHeader(http.StatusOK) render.JSON(w, r, map[string][]Rule{
render.Respond(w, r, map[string][]Rule{
"rules": rules, "rules": rules,
}) })
} }

View File

@ -17,6 +17,8 @@ import (
var ( var (
serverSecret = "" serverSecret = ""
serverAddr = "" serverAddr = ""
uiPath = ""
) )
type Traffic struct { type Traffic struct {
@ -24,6 +26,10 @@ type Traffic struct {
Down int64 `json:"down"` Down int64 `json:"down"`
} }
func SetUIPath(path string) {
uiPath = path
}
func Start(addr string, secret string) { func Start(addr string, secret string) {
if serverAddr != "" { if serverAddr != "" {
return return
@ -49,6 +55,14 @@ func Start(addr string, secret string) {
r.Mount("/proxies", proxyRouter()) r.Mount("/proxies", proxyRouter())
r.Mount("/rules", ruleRouter()) r.Mount("/rules", ruleRouter())
if uiPath != "" {
fs := http.StripPrefix("/ui", http.FileServer(http.Dir(uiPath)))
r.Get("/ui", http.RedirectHandler("/ui/", 301).ServeHTTP)
r.Get("/ui/*", func(w http.ResponseWriter, r *http.Request) {
fs.ServeHTTP(w, r)
})
}
log.Infoln("RESTful API listening at: %s", addr) log.Infoln("RESTful API listening at: %s", addr)
err := http.ListenAndServe(addr, r) err := http.ListenAndServe(addr, r)
if err != nil { if err != nil {
@ -77,8 +91,8 @@ func authentication(next http.Handler) http.Handler {
hasUnvalidHeader := text[0] != "Bearer" hasUnvalidHeader := text[0] != "Bearer"
hasUnvalidSecret := len(text) == 2 && text[1] != serverSecret hasUnvalidSecret := len(text) == 2 && text[1] != serverSecret
if hasUnvalidHeader || hasUnvalidSecret { if hasUnvalidHeader || hasUnvalidSecret {
w.WriteHeader(http.StatusUnauthorized) render.Status(r, http.StatusUnauthorized)
render.Respond(w, r, ErrUnauthorized) render.JSON(w, r, ErrUnauthorized)
return return
} }
next.ServeHTTP(w, r) next.ServeHTTP(w, r)
@ -87,7 +101,7 @@ func authentication(next http.Handler) http.Handler {
} }
func traffic(w http.ResponseWriter, r *http.Request) { func traffic(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK) render.Status(r, http.StatusOK)
tick := time.NewTicker(time.Second) tick := time.NewTicker(time.Second)
t := T.Instance().Traffic() t := T.Instance().Traffic()
@ -116,8 +130,8 @@ func getLogs(w http.ResponseWriter, r *http.Request) {
level, ok := log.LogLevelMapping[levelText] level, ok := log.LogLevelMapping[levelText]
if !ok { if !ok {
w.WriteHeader(http.StatusBadRequest) render.Status(r, http.StatusBadRequest)
render.Respond(w, r, ErrBadRequest) render.JSON(w, r, ErrBadRequest)
return return
} }

View File

@ -0,0 +1,52 @@
package redir
import (
"errors"
"net"
"syscall"
"unsafe"
"github.com/Dreamacro/go-shadowsocks2/socks"
)
const (
SO_ORIGINAL_DST = 80 // from linux/include/uapi/linux/netfilter_ipv4.h
IP6T_SO_ORIGINAL_DST = 80 // from linux/include/uapi/linux/netfilter_ipv6/ip6_tables.h
)
func parserPacket(conn net.Conn) (socks.Addr, error) {
c, ok := conn.(*net.TCPConn)
if !ok {
return nil, errors.New("only work with TCP connection")
}
rc, err := c.SyscallConn()
if err != nil {
return nil, err
}
var addr socks.Addr
rc.Control(func(fd uintptr) {
addr, err = getorigdst(fd)
})
return addr, err
}
// Call getorigdst() from linux/net/ipv4/netfilter/nf_conntrack_l3proto_ipv4.c
func getorigdst(fd uintptr) (socks.Addr, error) {
raw := syscall.RawSockaddrInet4{}
siz := unsafe.Sizeof(raw)
_, _, err := syscall.Syscall6(syscall.SYS_GETSOCKOPT, fd, syscall.IPPROTO_IP, SO_ORIGINAL_DST, uintptr(unsafe.Pointer(&raw)), uintptr(unsafe.Pointer(&siz)), 0);
if err != 0 {
return nil, err
}
addr := make([]byte, 1+net.IPv4len+2)
addr[0] = socks.AtypIPv4
copy(addr[1:1+net.IPv4len], raw.Addr[:])
port := (*[2]byte)(unsafe.Pointer(&raw.Port)) // big-endian
addr[1+net.IPv4len], addr[1+net.IPv4len+1] = port[0], port[1]
return addr, nil
}

View File

@ -5,6 +5,7 @@ import (
"io" "io"
"net" "net"
"net/http" "net/http"
"strings"
"sync" "sync"
"time" "time"
@ -25,8 +26,13 @@ func (t *Tunnel) handleHTTP(request *adapters.HTTPAdapter, proxy C.ProxyAdapter)
conn := newTrafficTrack(proxy.Conn(), t.traffic) conn := newTrafficTrack(proxy.Conn(), t.traffic)
req := request.R req := request.R
host := req.Host host := req.Host
keepalive := true
for { for {
if strings.ToLower(req.Header.Get("Connection")) == "close" {
keepalive = false
}
req.Header.Set("Connection", "close") req.Header.Set("Connection", "close")
req.RequestURI = "" req.RequestURI = ""
adapters.RemoveHopByHopHeaders(req.Header) adapters.RemoveHopByHopHeaders(req.Header)
@ -53,6 +59,10 @@ func (t *Tunnel) handleHTTP(request *adapters.HTTPAdapter, proxy C.ProxyAdapter)
break break
} }
if !keepalive {
break
}
req, err = http.ReadRequest(bufio.NewReader(request.Conn())) req, err = http.ReadRequest(bufio.NewReader(request.Conn()))
if err != nil { if err != nil {
break break

View File

@ -120,8 +120,6 @@ func (t *Tunnel) handleConn(localConn C.ServerAdapter) {
log.Debugln("[DNS] %s --> %s", metadata.Host, ip.String()) log.Debugln("[DNS] %s --> %s", metadata.Host, ip.String())
metadata.IP = &ip metadata.IP = &ip
} }
} else {
log.Debugln("[DNS] unknown%#v", metadata)
} }
var proxy C.Proxy var proxy C.Proxy