Mihomo improved
This commit is contained in:
@@ -45,9 +45,12 @@ func HandleInterfaces(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
type iface struct {
|
||||
*network.InterfaceStats
|
||||
Pending bool `json:"pending"`
|
||||
Pending bool `json:"pending"`
|
||||
Label string `json:"label,omitempty"`
|
||||
}
|
||||
|
||||
appCfg, _ := config.Load()
|
||||
|
||||
result := make([]iface, 0, len(names))
|
||||
existingNames := map[string]bool{}
|
||||
for _, name := range names {
|
||||
@@ -59,8 +62,14 @@ func HandleInterfaces(w http.ResponseWriter, r *http.Request) {
|
||||
if cfg, ok := fileCfg[name]; ok {
|
||||
s.Mode = cfg.Mode
|
||||
}
|
||||
_, hasPending := network.GetPendingConfig(name), network.GetPendingConfig(name) != nil
|
||||
result = append(result, iface{s, hasPending})
|
||||
hasPending := network.GetPendingConfig(name) != nil
|
||||
label := ""
|
||||
if appCfg != nil && appCfg.Interfaces != nil {
|
||||
if ic, ok := appCfg.Interfaces[name]; ok {
|
||||
label = ic.Label
|
||||
}
|
||||
}
|
||||
result = append(result, iface{s, hasPending, label})
|
||||
}
|
||||
|
||||
// Also include pending VLAN configs not yet present in the system.
|
||||
@@ -79,7 +88,13 @@ func HandleInterfaces(w http.ResponseWriter, r *http.Request) {
|
||||
s.IPv4Mask = cfg.Netmask
|
||||
s.Gateway = cfg.Gateway
|
||||
}
|
||||
result = append(result, iface{s, true})
|
||||
label := cfg.Label
|
||||
if label == "" && appCfg != nil && appCfg.Interfaces != nil {
|
||||
if ic, ok := appCfg.Interfaces[name]; ok {
|
||||
label = ic.Label
|
||||
}
|
||||
}
|
||||
result = append(result, iface{s, true, label})
|
||||
}
|
||||
|
||||
ok(w, result)
|
||||
@@ -149,8 +164,18 @@ func HandleConfig(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
switch r.Method {
|
||||
case http.MethodGet:
|
||||
appCfg, _ := config.Load()
|
||||
label := ""
|
||||
if appCfg != nil && appCfg.Interfaces != nil {
|
||||
if ic, ok2 := appCfg.Interfaces[name]; ok2 {
|
||||
label = ic.Label
|
||||
}
|
||||
}
|
||||
if cfg := network.GetPendingConfig(name); cfg != nil {
|
||||
ok(w, map[string]interface{}{"config": cfg, "pending": true})
|
||||
if cfg.Label != "" {
|
||||
label = cfg.Label
|
||||
}
|
||||
ok(w, map[string]interface{}{"config": cfg, "pending": true, "label": label})
|
||||
return
|
||||
}
|
||||
fileCfg, err := network.ParseConfig()
|
||||
@@ -159,11 +184,12 @@ func HandleConfig(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
if cfg, exists := fileCfg[name]; exists {
|
||||
ok(w, map[string]interface{}{"config": cfg, "pending": false})
|
||||
ok(w, map[string]interface{}{"config": cfg, "pending": false, "label": label})
|
||||
} else {
|
||||
ok(w, map[string]interface{}{
|
||||
"config": &network.InterfaceConfig{Name: name, Auto: true, Mode: "dhcp", Extra: map[string]string{}},
|
||||
"pending": false,
|
||||
"label": label,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -188,6 +214,7 @@ func HandleConfig(w http.ResponseWriter, r *http.Request) {
|
||||
appCfg.Interfaces = map[string]*config.InterfaceConfig{}
|
||||
}
|
||||
appCfg.Interfaces[name] = &config.InterfaceConfig{
|
||||
Label: cfg.Label,
|
||||
Auto: cfg.Auto,
|
||||
Mode: cfg.Mode,
|
||||
Address: cfg.Address,
|
||||
|
||||
217
handlers/mihomo_proxy.go
Normal file
217
handlers/mihomo_proxy.go
Normal file
@@ -0,0 +1,217 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"alpine-router/mihomo"
|
||||
)
|
||||
|
||||
func getMihomoAPIBase() string {
|
||||
cfg, err := mihomo.LoadConfig()
|
||||
if err != nil {
|
||||
return "http://127.0.0.1:9090"
|
||||
}
|
||||
ec, _ := cfg["external-controller"].(string)
|
||||
if ec == "" {
|
||||
ec = "0.0.0.0:9090"
|
||||
}
|
||||
if strings.HasPrefix(ec, "0.0.0.0:") || strings.HasPrefix(ec, ":") {
|
||||
ec = "127.0.0.1" + strings.TrimPrefix(ec, "0.0.0.0")
|
||||
}
|
||||
if !strings.HasPrefix(ec, "http") {
|
||||
ec = "http://" + ec
|
||||
}
|
||||
return ec
|
||||
}
|
||||
|
||||
func getMihomoSecret() string {
|
||||
cfg, err := mihomo.LoadConfig()
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
s, _ := cfg["secret"].(string)
|
||||
return s
|
||||
}
|
||||
|
||||
func getMihomoHostPort() (string, string) {
|
||||
u, err := url.Parse(getMihomoAPIBase())
|
||||
if err != nil {
|
||||
return "127.0.0.1", "9090"
|
||||
}
|
||||
host := u.Hostname()
|
||||
if host == "0.0.0.0" || host == "" {
|
||||
host = "127.0.0.1"
|
||||
}
|
||||
port := u.Port()
|
||||
if port == "" {
|
||||
if u.Scheme == "https" {
|
||||
port = "443"
|
||||
} else {
|
||||
port = "80"
|
||||
}
|
||||
}
|
||||
return host, port
|
||||
}
|
||||
|
||||
func HandleMihomoAPIProxy(w http.ResponseWriter, r *http.Request) {
|
||||
base := getMihomoAPIBase()
|
||||
secret := getMihomoSecret()
|
||||
|
||||
suffix := strings.TrimPrefix(r.URL.Path, "/api/mihomo/api/")
|
||||
target, err := url.Parse(base + "/" + suffix)
|
||||
if err != nil {
|
||||
fail(w, http.StatusInternalServerError, "parse mihomo url: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
q := r.URL.Query()
|
||||
if r.Method == http.MethodGet {
|
||||
q.Set("nonce", "1")
|
||||
}
|
||||
target.RawQuery = q.Encode()
|
||||
|
||||
proxyReq, err := http.NewRequest(r.Method, target.String(), r.Body)
|
||||
if err != nil {
|
||||
fail(w, http.StatusInternalServerError, "create proxy request: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
for k, vv := range r.Header {
|
||||
if strings.EqualFold(k, "Authorization") {
|
||||
continue
|
||||
}
|
||||
for _, v := range vv {
|
||||
proxyReq.Header.Add(k, v)
|
||||
}
|
||||
}
|
||||
if secret != "" {
|
||||
proxyReq.Header.Set("Authorization", "Bearer "+secret)
|
||||
}
|
||||
|
||||
client := &http.Client{}
|
||||
resp, err := client.Do(proxyReq)
|
||||
if err != nil {
|
||||
fail(w, http.StatusBadGateway, "mihomo api unreachable: "+err.Error())
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
for k, vv := range resp.Header {
|
||||
if strings.EqualFold(k, "Content-Length") {
|
||||
continue
|
||||
}
|
||||
for _, v := range vv {
|
||||
w.Header().Add(k, v)
|
||||
}
|
||||
}
|
||||
|
||||
w.WriteHeader(resp.StatusCode)
|
||||
_, err = io.Copy(w, resp.Body)
|
||||
if err != nil {
|
||||
log.Printf("proxy copy error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func HandleMihomoWSProxy(w http.ResponseWriter, r *http.Request) {
|
||||
secret := getMihomoSecret()
|
||||
host, port := getMihomoHostPort()
|
||||
|
||||
suffix := strings.TrimPrefix(r.URL.Path, "/api/mihomo/ws/")
|
||||
path := "/" + suffix
|
||||
if r.URL.RawQuery != "" {
|
||||
path += "?" + r.URL.RawQuery
|
||||
}
|
||||
|
||||
hj, ok := w.(http.Hijacker)
|
||||
if !ok {
|
||||
fail(w, http.StatusInternalServerError, "websocket hijack not supported")
|
||||
return
|
||||
}
|
||||
|
||||
clientConn, _, err := hj.Hijack()
|
||||
if err != nil {
|
||||
fail(w, http.StatusInternalServerError, "hijack failed: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
remoteConn, err := net.Dial("tcp", net.JoinHostPort(host, port))
|
||||
if err != nil {
|
||||
fail(w, http.StatusBadGateway, "mihomo ws connect failed: "+err.Error())
|
||||
clientConn.Close()
|
||||
return
|
||||
}
|
||||
|
||||
upgradeReq := "GET " + path + " HTTP/1.1\r\n"
|
||||
upgradeReq += "Host: " + host + ":" + port + "\r\n"
|
||||
upgradeReq += "Upgrade: websocket\r\n"
|
||||
upgradeReq += "Connection: Upgrade\r\n"
|
||||
upgradeReq += "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
|
||||
upgradeReq += "Sec-WebSocket-Version: 13\r\n"
|
||||
if secret != "" {
|
||||
upgradeReq += "Authorization: Bearer " + secret + "\r\n"
|
||||
}
|
||||
for k, vv := range r.Header {
|
||||
if strings.EqualFold(k, "Upgrade") || strings.EqualFold(k, "Connection") ||
|
||||
strings.EqualFold(k, "Sec-Websocket-Key") || strings.EqualFold(k, "Sec-Websocket-Version") ||
|
||||
strings.EqualFold(k, "Sec-Websocket-Extensions") || strings.EqualFold(k, "Sec-Websocket-Protocol") ||
|
||||
strings.EqualFold(k, "Authorization") {
|
||||
continue
|
||||
}
|
||||
for _, v := range vv {
|
||||
upgradeReq += k + ": " + v + "\r\n"
|
||||
}
|
||||
}
|
||||
for k, v := range r.Header {
|
||||
if strings.EqualFold(k, "Sec-WebSocket-Protocol") {
|
||||
upgradeReq += "Sec-WebSocket-Protocol: " + strings.Join(v, ", ") + "\r\n"
|
||||
}
|
||||
if strings.EqualFold(k, "Sec-WebSocket-Extensions") {
|
||||
upgradeReq += "Sec-WebSocket-Extensions: " + strings.Join(v, ", ") + "\r\n"
|
||||
}
|
||||
}
|
||||
upgradeReq += "\r\n"
|
||||
|
||||
_, err = remoteConn.Write([]byte(upgradeReq))
|
||||
if err != nil {
|
||||
clientConn.Close()
|
||||
remoteConn.Close()
|
||||
return
|
||||
}
|
||||
|
||||
buf := make([]byte, 4096)
|
||||
n, err := remoteConn.Read(buf)
|
||||
if err != nil {
|
||||
clientConn.Close()
|
||||
remoteConn.Close()
|
||||
return
|
||||
}
|
||||
|
||||
respStr := string(buf[:n])
|
||||
if !strings.Contains(respStr, "101") {
|
||||
clientConn.Write(buf[:n])
|
||||
clientConn.Close()
|
||||
remoteConn.Close()
|
||||
return
|
||||
}
|
||||
|
||||
headerEnd := strings.Index(respStr, "\r\n\r\n")
|
||||
if headerEnd >= 0 {
|
||||
clientConn.Write(buf[:n])
|
||||
} else {
|
||||
clientConn.Write(buf[:n])
|
||||
}
|
||||
|
||||
go func() {
|
||||
io.Copy(remoteConn, clientConn)
|
||||
remoteConn.Close()
|
||||
}()
|
||||
go func() {
|
||||
io.Copy(clientConn, remoteConn)
|
||||
clientConn.Close()
|
||||
}()
|
||||
}
|
||||
Reference in New Issue
Block a user