Compare commits

...

11 Commits

16 changed files with 268 additions and 100 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

@ -41,6 +41,7 @@ type VmessOption struct {
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"`
WSHeaders map[string]string `proxy:"ws-headers,omitempty"`
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"` SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
} }
@ -79,6 +80,7 @@ func NewVmess(option VmessOption) (*Vmess, error) {
Port: strconv.Itoa(option.Port), Port: strconv.Itoa(option.Port),
NetWork: option.Network, NetWork: option.Network,
WebSocketPath: option.WSPath, WebSocketPath: option.WSPath,
WebSocketHeaders: option.WSHeaders,
SkipCertVerify: option.SkipCertVerify, SkipCertVerify: option.SkipCertVerify,
SessionCacahe: getClientSessionCache(), SessionCacahe: getClientSessionCache(),
}) })

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

@ -83,6 +83,7 @@ type Config struct {
Port string Port string
NetWork string NetWork string
WebSocketPath string WebSocketPath string
WebSocketHeaders map[string]string
SkipCertVerify bool SkipCertVerify bool
SessionCacahe tls.ClientSessionCache SessionCacahe tls.ClientSessionCache
} }
@ -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

@ -131,12 +131,14 @@ func (r *Resolver) resolveIP(m *D.Msg) (msg *D.Msg, err error) {
return nil, errors.New("GeoIP can't use") return nil, errors.New("GeoIP can't use")
} }
ips, _ := r.msgToIP(res.Msg) ips, err := r.msgToIP(res.Msg)
if err == nil {
if record, _ := mmdb.Country(ips[0]); record.Country.IsoCode == "CN" || record.Country.IsoCode == "" { if record, _ := mmdb.Country(ips[0]); record.Country.IsoCode == "CN" || record.Country.IsoCode == "" {
// release channel // release channel
go func() { <-fallbackMsg }() go func() { <-fallbackMsg }()
msg = res.Msg msg = res.Msg
return return msg, err
}
} }
} }
@ -172,21 +174,13 @@ func (r *Resolver) msgToIP(msg *D.Msg) ([]net.IP, error) {
var ips []net.IP var ips []net.IP
for _, answer := range msg.Answer { for _, answer := range msg.Answer {
if r.ipv6 { switch ans := answer.(type) {
ans, ok := answer.(*D.AAAA) case *D.AAAA:
if !ok {
continue
}
ips = append(ips, ans.AAAA) ips = append(ips, ans.AAAA)
continue case *D.A:
}
ans, ok := answer.(*D.A)
if !ok {
continue
}
ips = append(ips, ans.A) ips = append(ips, ans.A)
} }
}
if len(ips) == 0 { if len(ips) == 0 {
return nil, errors.New("Can't parse msg") return nil, errors.New("Can't parse msg")
@ -250,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
@ -36,7 +42,7 @@ func Start(addr string, secret string) {
cors := cors.New(cors.Options{ cors := cors.New(cors.Options{
AllowedOrigins: []string{"*"}, AllowedOrigins: []string{"*"},
AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"}, AllowedMethods: []string{"GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"},
AllowedHeaders: []string{"Content-Type", "Authorization"}, AllowedHeaders: []string{"Content-Type", "Authorization"},
MaxAge: 300, MaxAge: 300,
}) })
@ -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