Skip to content

Commit

Permalink
support hijack bmclapi
Browse files Browse the repository at this point in the history
  • Loading branch information
zyxkad committed Dec 13, 2023
1 parent 9605484 commit 66c8cb9
Show file tree
Hide file tree
Showing 7 changed files with 277 additions and 125 deletions.
19 changes: 11 additions & 8 deletions README.MD
Original file line number Diff line number Diff line change
Expand Up @@ -52,13 +52,16 @@
例:
```javascript
{
"debug": false, // 是否打印调试日志, 默认为否
"show_serve_info": false, // 是否打印访问信息, 默认为否 (这个选项对于压缩日志文件十分有用)
"ignore_serve_error": true, // 是否忽略http serve error, 默认打开 (这个选项对于压缩日志文件十分有用)
"public_host": "example.com", // public host, 同 CLUSTER_IP
"public_port": 8080, // 实际开放的公网端口, 同 CLUSTER_PUBLIC_PORT
"port": 80, // 要监听的本地端口, 同 CLUSTER_PORT
"cluster_id": "${CLUSTER_ID}", // CLUSTER_ID
"cluster_secret": "${CLUSTER_SECRET}" // CLUSTER_SECRET
"debug": false, // 是否打印调试日志, 默认为否
"show_serve_info": false, // 是否打印访问信息, 默认为否 (这个选项对于压缩日志文件十分有用)
"ignore_serve_error": true, // 是否忽略http serve error, 默认打开 (这个选项对于压缩日志文件十分有用)
"public_host": "example.com", // public host, 同 CLUSTER_IP
"public_port": 8080, // 实际开放的公网端口, 同 CLUSTER_PUBLIC_PORT
"port": 80, // 要监听的本地端口, 同 CLUSTER_PORT
"cluster_id": "${CLUSTER_ID}", // CLUSTER_ID
"cluster_secret": "${CLUSTER_SECRET}", // CLUSTER_SECRET
"hijack": false, // 是否启动 bmclapi 劫持代理
"hijack_port": 8090, // 劫持代理监听的端口
"anti_hijack_dns": "8.8.8.8:32" // 用于绕过DNS劫持的DNS服务器
}
```
21 changes: 12 additions & 9 deletions config.json
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
{
"debug": false,
"show_serve_info": false,
"ignore_serve_error": true,
"nohttps": false,
"public_host": "example.com",
"public_port": 8080,
"port": 4000,
"cluster_id": "${CLUSTER_ID}",
"cluster_secret": "${CLUSTER_SECRET}"
"debug": false,
"show_serve_info": false,
"ignore_serve_error": true,
"nohttps": false,
"public_host": "example.com",
"public_port": 8080,
"port": 4000,
"cluster_id": "${CLUSTER_ID}",
"cluster_secret": "${CLUSTER_SECRET}",
"hijack": true,
"hijack_port": 8090,
"anti_hijack_dns": "8.8.8.8:53"
}
62 changes: 42 additions & 20 deletions src/cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ package main
import (
"context"
"crypto"
"crypto/tls"
"encoding/hex"
"errors"
"fmt"
"io"
"net"
"net/http"
"os"
"path/filepath"
Expand All @@ -31,6 +31,7 @@ type Cluster struct {
version string
useragent string
prefix string
byoc bool

cachedir string
maxConn int
Expand All @@ -50,10 +51,16 @@ type Cluster struct {
}

func NewCluster(
ctx context.Context,
ctx context.Context, cacheDir string,
host string, publicPort uint16,
username string, password string,
version string, address string) (cr *Cluster) {
version string, address string,
byoc bool, dialer *net.Dialer,
) (cr *Cluster) {
transport := &http.Transport{}
if dialer != nil {
transport.DialContext = dialer.DialContext
}
cr = &Cluster{
ctx: ctx,

Expand All @@ -64,17 +71,14 @@ func NewCluster(
version: version,
useragent: "openbmclapi-cluster/" + version,
prefix: "https://openbmclapi.bangbang93.com",
byoc: byoc,

cachedir: "cache",
cachedir: cacheDir,
maxConn: 400,

client: &http.Client{
Timeout: time.Second * 60,
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
// InsecureSkipVerify: true, // Skip verify because the author was lazy
},
},
Timeout: time.Second * 60,
Transport: transport,
},
Server: &http.Server{
Addr: address,
Expand All @@ -99,7 +103,7 @@ func (cr *Cluster) Connect() bool {
header.Set("User-Agent", cr.useragent)

connectCh := make(chan struct{}, 0)
connected := sync.OnceFunc(func(){
connected := sync.OnceFunc(func() {
close(connectCh)
})

Expand Down Expand Up @@ -152,7 +156,7 @@ func (cr *Cluster) Enable() (err error) {
"host": cr.host,
"port": cr.publicPort,
"version": cr.version,
"byoc": USE_HTTPS,
"byoc": cr.byoc,
})
if err != nil {
return
Expand Down Expand Up @@ -397,7 +401,7 @@ RESYNC:
}

// sort the files in descending order of size
sort.Slice(files, func(i, j int) bool { return files[i].Size > files[j].Size })
sort.Slice(files, func(i, j int) bool { return files[i].Size < files[j].Size })

var stats syncStats
stats.slots = make(chan struct{}, cr.maxConn)
Expand Down Expand Up @@ -505,7 +509,9 @@ func (cr *Cluster) dlhandle(ctx context.Context, f *FileInfo) (err error) {
}

for i := 0; i < 3; i++ {
cr.downloadFileBuf(ctx, f, hashMethod, buf)
if err = cr.downloadFileBuf(ctx, f, hashMethod, buf); err == nil {
return
}
}
return
}
Expand Down Expand Up @@ -597,7 +603,8 @@ func (cr *Cluster) downloadFileBuf(ctx context.Context, f *FileInfo, hashMethod

hw := hashMethod.New()

if fd, err = os.Create(cr.getHashPath(f.Hash)); err != nil {
hspt := cr.getHashPath(f.Hash)
if fd, err = os.Create(hspt); err != nil {
return
}

Expand All @@ -612,13 +619,28 @@ func (cr *Cluster) downloadFileBuf(ctx context.Context, f *FileInfo, hashMethod
} else if hs := hex.EncodeToString(hw.Sum(buf[:0])); hs != f.Hash {
err = fmt.Errorf("File hash not match, got %s, expect %s", hs, f.Hash)
}
if DEBUG && err != nil {
f0, _ := os.Open(cr.getHashPath(f.Hash))
b0, _ := io.ReadAll(f0)
if len(b0) < 16*1024 {
logDebug("File content:", (string)(b0), "//for", f.Path)
if err != nil {
if config.Debug {
f0, _ := os.Open(cr.getHashPath(f.Hash))
b0, _ := io.ReadAll(f0)
if len(b0) < 16*1024 {
logDebug("File content:", (string)(b0), "//for", f.Path)
}
}
return
}

if config.Hijack {
if !strings.HasPrefix(f.Path, "/openbmclapi/download/") {
target := filepath.Join(hijackPath, filepath.FromSlash(f.Path))
dir := filepath.Dir(target)
os.MkdirAll(dir, 0755)
if rp, err := filepath.Rel(dir, hspt); err == nil {
os.Symlink(rp, target)
}
}
}

return
}

Expand Down
10 changes: 5 additions & 5 deletions src/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,12 @@ func (cr *Cluster) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
method := req.Method
url := req.URL
rawpath := url.EscapedPath()
if SHOW_SERVE_INFO {
go logInfo("serve url:", url.String())
if config.ShowServeInfo {
logInfo("serve url:", url.String())
}
switch {
case strings.HasPrefix(rawpath, "/download/"):
if method == "GET" {
if method == http.MethodGet {
hash := rawpath[len("/download/"):]
path := cr.getHashPath(hash)
if ufile.IsNotExist(path) {
Expand Down Expand Up @@ -68,7 +68,7 @@ func (cr *Cluster) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
}
_, err = rw.Write(buf[:n])
if err != nil {
if !IGNORE_SERVE_ERROR {
if !config.IgnoreServeError {
logError("Error when serving download:", err)
}
return
Expand All @@ -80,7 +80,7 @@ func (cr *Cluster) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
return
}
case strings.HasPrefix(rawpath, "/measure/"):
if method == "GET" {
if method == http.MethodGet {
if req.Header.Get("x-openbmclapi-secret") != cr.password {
rw.WriteHeader(http.StatusForbidden)
return
Expand Down
81 changes: 81 additions & 0 deletions src/hijacker.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package main

import (
"context"
"io"
"net"
"net/http"
"os"
"path/filepath"
"time"
)

func getDialerWithDNS(dnsaddr string) *net.Dialer {
resolver := &net.Resolver{
PreferGo: true,
Dial: func(ctx context.Context, network, address string) (net.Conn, error) {
return net.Dial(network, dnsaddr)
},
}
return &net.Dialer{
Resolver: resolver,
}
}

type HjProxy struct {
dialer *net.Dialer
path string
client *http.Client
}

func NewHjProxy(dialer *net.Dialer, path string) (h *HjProxy) {
return &HjProxy{
dialer: dialer,
path: path,
client: &http.Client{
Timeout: time.Second * 15,
Transport: &http.Transport{
DialContext: dialer.DialContext,
},
},
}
}

const hijackingHost = "bmclapi2.bangbang93.com"

func (h *HjProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
if req.Method == http.MethodGet || req.Method == http.MethodHead {
target := filepath.Join(h.path, filepath.Clean(filepath.FromSlash(req.URL.Path)))
fd, err := os.Open(target)
if err == nil {
defer fd.Close()
if stat, err := fd.Stat(); err == nil {
if !stat.IsDir() {
modTime := stat.ModTime()
http.ServeContent(rw, req, filepath.Base(target), modTime, fd)
return
}
}
}
}

u := *req.URL
u.Scheme = "https"
u.Host = hijackingHost
req2, err := http.NewRequestWithContext(req.Context(), req.Method, u.String(), req.Body)
if err != nil {
http.Error(rw, err.Error(), http.StatusBadGateway)
return
}
res, err := h.client.Do(req2)
if err != nil {
http.Error(rw, err.Error(), http.StatusBadGateway)
return
}
defer res.Body.Close()
for k, v := range res.Header {
rw.Header()[k] = v
}
rw.WriteHeader(res.StatusCode)
io.Copy(rw, res.Body)
}
4 changes: 2 additions & 2 deletions src/logger.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,13 +55,13 @@ func logXf(x string, format string, args ...any) {
}

func logDebug(args ...any) {
if DEBUG {
if config.Debug {
logX("DBUG", args...)
}
}

func logDebugf(format string, args ...any) {
if DEBUG {
if config.Debug {
logXf("DBUG", format, args...)
}
}
Expand Down
Loading

0 comments on commit 66c8cb9

Please sign in to comment.