forked from Mxmilu666/frp
Compare commits
28 Commits
a0ae9879c0
...
v0.66.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
655dc3cb2a
|
|||
|
9894342f46
|
|||
|
e7cc706c86
|
|||
|
92ac2b9153
|
|||
|
1ed369e962
|
|||
|
b74a8d0232
|
|||
|
d2180081a0
|
|||
|
51f4e065b5
|
|||
|
e58f774086
|
|||
|
178e381a26
|
|||
|
26b93ae3a3
|
|||
|
a2aeee28e4
|
|||
|
0416caef71
|
|||
|
1004473e42
|
|||
|
f386996928
|
|||
|
4eb4b202c5
|
|||
|
ac5bdad507
|
|||
|
42f4ea7f87
|
|||
|
36e5ac094b
|
|||
|
72f79d3357
|
|||
|
e1f905f63f
|
|||
|
eb58f09268
|
|||
|
46955ffc80
|
|||
|
2d63296576
|
|||
|
a76ba823ee
|
|||
|
|
ef96481f58 | ||
|
|
7526d7a69a | ||
|
|
2bdf25bae6 |
4
.github/FUNDING.yml
vendored
4
.github/FUNDING.yml
vendored
@@ -1,4 +0,0 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: [fatedier]
|
||||
custom: ["https://afdian.com/a/fatedier"]
|
||||
3
.github/pull_request_template.md
vendored
3
.github/pull_request_template.md
vendored
@@ -1,3 +0,0 @@
|
||||
### WHY
|
||||
|
||||
<!-- author to complete -->
|
||||
185
.github/workflows/build-all.yaml
vendored
Normal file
185
.github/workflows/build-all.yaml
vendored
Normal file
@@ -0,0 +1,185 @@
|
||||
name: Build FRP Binaries
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- '**'
|
||||
workflow_dispatch:
|
||||
workflow_call:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build FRP ${{ matrix.goos }}-${{ matrix.goarch }}
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
goos: [linux, windows, darwin, freebsd, openbsd, android]
|
||||
goarch: [amd64, arm, arm64]
|
||||
exclude:
|
||||
- goos: darwin
|
||||
goarch: arm
|
||||
- goos: freebsd
|
||||
goarch: arm
|
||||
- goos: openbsd
|
||||
goarch: arm
|
||||
- goos: android
|
||||
goarch: amd64
|
||||
# 排除 Android ARM 32位,在单独的 job 中处理
|
||||
- goos: android
|
||||
goarch: arm
|
||||
|
||||
steps:
|
||||
- name: Checkout source
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: '1.22'
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt-get update -y
|
||||
sudo apt-get install -y zip tar make gcc g++ upx
|
||||
|
||||
- name: Build FRP for ${{ matrix.goos }}-${{ matrix.goarch }}
|
||||
run: |
|
||||
mkdir -p release/packages
|
||||
|
||||
echo "Building for ${{ matrix.goos }}-${{ matrix.goarch }}"
|
||||
|
||||
# 构建版本号
|
||||
make
|
||||
version=$(./bin/frps --version)
|
||||
echo "Detected version: $version"
|
||||
|
||||
export GOOS=${{ matrix.goos }}
|
||||
export GOARCH=${{ matrix.goarch }}
|
||||
export CGO_ENABLED=0
|
||||
|
||||
# 构建可执行文件
|
||||
make frpc frps
|
||||
|
||||
if [ "${{ matrix.goos }}" = "windows" ]; then
|
||||
if [ -f "./bin/frpc" ]; then mv ./bin/frpc ./bin/frpc.exe; fi
|
||||
if [ -f "./bin/frps" ]; then mv ./bin/frps ./bin/frps.exe; fi
|
||||
fi
|
||||
|
||||
out_dir="release/packages/frp_${version}_${{ matrix.goos }}_${{ matrix.goarch }}"
|
||||
mkdir -p "$out_dir"
|
||||
|
||||
if [ "${{ matrix.goos }}" = "windows" ]; then
|
||||
mv ./bin/frpc.exe "$out_dir/frpc.exe"
|
||||
mv ./bin/frps.exe "$out_dir/frps.exe"
|
||||
else
|
||||
mv ./bin/frpc "$out_dir/frpc"
|
||||
mv ./bin/frps "$out_dir/frps"
|
||||
fi
|
||||
|
||||
cp LICENSE "$out_dir"
|
||||
cp -f conf/frpc.toml "$out_dir"
|
||||
cp -f conf/frps.toml "$out_dir"
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: LoliaFrp_${{ matrix.goos }}_${{ matrix.goarch }}
|
||||
path: |
|
||||
release/packages/frp_*
|
||||
retention-days: 7
|
||||
|
||||
build-android-arm:
|
||||
name: Build FRP android-arm
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout source
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: '1.22'
|
||||
|
||||
- name: Install dependencies and Android NDK
|
||||
run: |
|
||||
sudo apt-get update -y
|
||||
sudo apt-get install -y zip tar make gcc g++ upx wget unzip
|
||||
|
||||
# 下载并安装 Android NDK
|
||||
echo "Downloading Android NDK..."
|
||||
wget -q https://dl.google.com/android/repository/android-ndk-r26c-linux.zip
|
||||
echo "Extracting Android NDK..."
|
||||
unzip -q android-ndk-r26c-linux.zip
|
||||
echo "NDK installed at: $PWD/android-ndk-r26c"
|
||||
|
||||
- name: Build FRP for android-arm
|
||||
run: |
|
||||
mkdir -p release/packages
|
||||
mkdir -p bin
|
||||
|
||||
echo "Building for android-arm with CGO"
|
||||
|
||||
# 首先构建一次获取版本号
|
||||
CGO_ENABLED=0 make
|
||||
version=$(./bin/frps --version)
|
||||
echo "Detected version: $version"
|
||||
|
||||
# 清理之前的构建
|
||||
rm -rf ./bin/*
|
||||
|
||||
# 设置 Android ARM 交叉编译环境
|
||||
export GOOS=android
|
||||
export GOARCH=arm
|
||||
export GOARM=7
|
||||
export CGO_ENABLED=1
|
||||
export CC=$PWD/android-ndk-r26c/toolchains/llvm/prebuilt/linux-x86_64/bin/armv7a-linux-androideabi21-clang
|
||||
export CXX=$PWD/android-ndk-r26c/toolchains/llvm/prebuilt/linux-x86_64/bin/armv7a-linux-androideabi21-clang++
|
||||
|
||||
echo "Environment:"
|
||||
echo "GOOS=$GOOS"
|
||||
echo "GOARCH=$GOARCH"
|
||||
echo "GOARM=$GOARM"
|
||||
echo "CGO_ENABLED=$CGO_ENABLED"
|
||||
echo "CC=$CC"
|
||||
|
||||
# 直接使用 go build 命令,不通过 Makefile,防止 CGO_ENABLED 被覆盖
|
||||
echo "Building frps..."
|
||||
go build -trimpath -ldflags "-s -w" -tags frps -o bin/frps ./cmd/frps
|
||||
|
||||
echo "Building frpc..."
|
||||
go build -trimpath -ldflags "-s -w" -tags frpc -o bin/frpc ./cmd/frpc
|
||||
|
||||
# 验证文件已生成
|
||||
ls -lh ./bin/
|
||||
file ./bin/frpc
|
||||
file ./bin/frps
|
||||
|
||||
out_dir="release/packages/frp_${version}_android_arm"
|
||||
mkdir -p "$out_dir"
|
||||
|
||||
mv ./bin/frpc "$out_dir/frpc"
|
||||
mv ./bin/frps "$out_dir/frps"
|
||||
|
||||
cp LICENSE "$out_dir"
|
||||
cp -f conf/frpc.toml "$out_dir"
|
||||
cp -f conf/frps.toml "$out_dir"
|
||||
|
||||
echo "Build completed for android-arm"
|
||||
ls -lh "$out_dir"
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: LoliaFrp_android_arm
|
||||
path: |
|
||||
release/packages/frp_*
|
||||
retention-days: 7
|
||||
2
.github/workflows/golangci-lint.yml
vendored
2
.github/workflows/golangci-lint.yml
vendored
@@ -23,4 +23,4 @@ jobs:
|
||||
uses: golangci/golangci-lint-action@v8
|
||||
with:
|
||||
# Optional: version of golangci-lint to use in form of v1.2 or v1.2.3 or `latest` to use the latest version
|
||||
version: v2.3
|
||||
version: v2.3
|
||||
172
.github/workflows/release.yaml
vendored
Normal file
172
.github/workflows/release.yaml
vendored
Normal file
@@ -0,0 +1,172 @@
|
||||
name: Release FRP Binaries
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: 'Tag to release (e.g., v1.0.0)'
|
||||
required: true
|
||||
type: string
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
# 调用 build-all workflow
|
||||
build:
|
||||
uses: ./.github/workflows/build-all.yaml
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
# 创建 release
|
||||
release:
|
||||
name: Create Release
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout source
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Get tag name
|
||||
id: tag
|
||||
run: |
|
||||
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
|
||||
echo "tag=${{ github.event.inputs.tag }}" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "tag=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Download all artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
path: artifacts
|
||||
|
||||
- name: Display artifact structure
|
||||
run: |
|
||||
echo "Artifact structure:"
|
||||
ls -R artifacts/
|
||||
|
||||
- name: Organize release files
|
||||
run: |
|
||||
mkdir -p release_files
|
||||
|
||||
# 查找并复制所有压缩包
|
||||
find artifacts -type f \( -name "*.zip" -o -name "*.tar.gz" \) -exec cp {} release_files/ \;
|
||||
|
||||
# 如果没有压缩包,尝试查找二进制文件并打包
|
||||
if [ -z "$(ls -A release_files/)" ]; then
|
||||
echo "No archives found, looking for directories to package..."
|
||||
for dir in artifacts/*/; do
|
||||
if [ -d "$dir" ]; then
|
||||
artifact_name=$(basename "$dir")
|
||||
echo "Packaging $artifact_name"
|
||||
|
||||
# 检查是否是 Windows 构建
|
||||
if echo "$artifact_name" | grep -q "windows"; then
|
||||
(cd "$dir" && zip -r "../../release_files/${artifact_name}.zip" .)
|
||||
else
|
||||
tar -czf "release_files/${artifact_name}.tar.gz" -C "$dir" .
|
||||
fi
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
echo "Files in release_files:"
|
||||
ls -lh release_files/
|
||||
|
||||
- name: Generate checksums
|
||||
run: |
|
||||
cd release_files
|
||||
if [ -n "$(ls -A .)" ]; then
|
||||
sha256sum * > sha256sum.txt
|
||||
cat sha256sum.txt
|
||||
else
|
||||
echo "No files to generate checksums for!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Save checksums for changelog
|
||||
id: checksums
|
||||
run: |
|
||||
{
|
||||
echo 'content<<EOF'
|
||||
cat release_files/sha256sum.txt
|
||||
echo EOF
|
||||
} >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Build Changelog
|
||||
id: changelog
|
||||
uses: mikepenz/release-changelog-builder-action@v4
|
||||
with:
|
||||
configuration: |
|
||||
{
|
||||
"categories": [
|
||||
{
|
||||
"title": "## 🚀 Features",
|
||||
"labels": ["feature", "feat", "enhancement"]
|
||||
},
|
||||
{
|
||||
"title": "## 🐛 Bug Fixes",
|
||||
"labels": ["fix", "bug", "bugfix"]
|
||||
},
|
||||
{
|
||||
"title": "## 📝 Documentation",
|
||||
"labels": ["docs", "documentation"]
|
||||
},
|
||||
{
|
||||
"title": "## 🔧 Maintenance",
|
||||
"labels": ["chore", "refactor", "perf"]
|
||||
},
|
||||
{
|
||||
"title": "## 📦 Dependencies",
|
||||
"labels": ["dependencies", "deps"]
|
||||
},
|
||||
{
|
||||
"title": "## 🔀 Other Changes",
|
||||
"labels": []
|
||||
}
|
||||
],
|
||||
"template": "#{{CHANGELOG}}\n\n## 📥 Download\n\n### Checksums (SHA256)\n\n```\n${{ steps.checksums.outputs.content }}\n```\n\n**Full Changelog**: #{{RELEASE_DIFF}}",
|
||||
"pr_template": "- #{{TITLE}} by @#{{AUTHOR}} in ##{{NUMBER}}",
|
||||
"empty_template": "- No changes",
|
||||
"label_extractor": [
|
||||
{
|
||||
"pattern": "^(feat|feature)(\\(.+\\))?:",
|
||||
"target": "feature"
|
||||
},
|
||||
{
|
||||
"pattern": "^fix(\\(.+\\))?:",
|
||||
"target": "fix"
|
||||
},
|
||||
{
|
||||
"pattern": "^docs(\\(.+\\))?:",
|
||||
"target": "docs"
|
||||
},
|
||||
{
|
||||
"pattern": "^(chore|refactor|perf)(\\(.+\\))?:",
|
||||
"target": "chore"
|
||||
}
|
||||
]
|
||||
}
|
||||
toTag: ${{ steps.tag.outputs.tag }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Create Release
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
tag_name: ${{ steps.tag.outputs.tag }}
|
||||
name: Release ${{ steps.tag.outputs.tag }}
|
||||
body: ${{ steps.changelog.outputs.changelog }}
|
||||
draft: false
|
||||
prerelease: false
|
||||
files: |
|
||||
release_files/*
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
22
README.md
22
README.md
@@ -13,6 +13,14 @@ frp is an open source project with its ongoing development made possible entirel
|
||||
|
||||
<h3 align="center">Gold Sponsors</h3>
|
||||
<!--gold sponsors start-->
|
||||
<p align="center">
|
||||
<a href="https://jb.gg/frp" target="_blank">
|
||||
<img width="420px" src="https://raw.githubusercontent.com/fatedier/frp/dev/doc/pic/sponsor_jetbrains.jpg">
|
||||
<br>
|
||||
<b>The complete IDE crafted for professional Go developers</b>
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/beclab/Olares" target="_blank">
|
||||
<img width="420px" src="https://raw.githubusercontent.com/fatedier/frp/dev/doc/pic/sponsor_olares.jpeg">
|
||||
@@ -50,20 +58,6 @@ an API that records Zoom, Google Meet, Microsoft Teams, in-person meetings, and
|
||||
<sub>Available for macOS, Linux and Windows</sub>
|
||||
</a>
|
||||
</p>
|
||||
<p align="center">
|
||||
<a href="https://jb.gg/frp" target="_blank">
|
||||
<img width="420px" src="https://raw.githubusercontent.com/fatedier/frp/dev/doc/pic/sponsor_jetbrains.jpg">
|
||||
<br>
|
||||
<b>The complete IDE crafted for professional Go developers</b>
|
||||
</a>
|
||||
</p>
|
||||
<p align="center">
|
||||
<a href="https://github.com/daytonaio/daytona" target="_blank">
|
||||
<img width="420px" src="https://raw.githubusercontent.com/fatedier/frp/dev/doc/pic/sponsor_daytona.png">
|
||||
<br>
|
||||
<b>Secure and Elastic Infrastructure for Running Your AI-Generated Code</b>
|
||||
</a>
|
||||
</p>
|
||||
<!--gold sponsors end-->
|
||||
|
||||
## What is frp?
|
||||
|
||||
22
README_zh.md
22
README_zh.md
@@ -15,6 +15,14 @@ frp 是一个完全开源的项目,我们的开发工作完全依靠赞助者
|
||||
|
||||
<h3 align="center">Gold Sponsors</h3>
|
||||
<!--gold sponsors start-->
|
||||
<p align="center">
|
||||
<a href="https://jb.gg/frp" target="_blank">
|
||||
<img width="420px" src="https://raw.githubusercontent.com/fatedier/frp/dev/doc/pic/sponsor_jetbrains.jpg">
|
||||
<br>
|
||||
<b>The complete IDE crafted for professional Go developers</b>
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/beclab/Olares" target="_blank">
|
||||
<img width="420px" src="https://raw.githubusercontent.com/fatedier/frp/dev/doc/pic/sponsor_olares.jpeg">
|
||||
@@ -51,20 +59,6 @@ an API that records Zoom, Google Meet, Microsoft Teams, in-person meetings, and
|
||||
<sub>Available for macOS, Linux and Windows</sub>
|
||||
</a>
|
||||
</p>
|
||||
<p align="center">
|
||||
<a href="https://jb.gg/frp" target="_blank">
|
||||
<img width="420px" src="https://raw.githubusercontent.com/fatedier/frp/dev/doc/pic/sponsor_jetbrains.jpg">
|
||||
<br>
|
||||
<b>The complete IDE crafted for professional Go developers</b>
|
||||
</a>
|
||||
</p>
|
||||
<p align="center">
|
||||
<a href="https://github.com/daytonaio/daytona" target="_blank">
|
||||
<img width="420px" src="https://raw.githubusercontent.com/fatedier/frp/dev/doc/pic/sponsor_daytona.png">
|
||||
<br>
|
||||
<b>Secure and Elastic Infrastructure for Running Your AI-Generated Code</b>
|
||||
</a>
|
||||
</p>
|
||||
<!--gold sponsors end-->
|
||||
|
||||
## 为什么使用 frp ?
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
* HTTPS proxies now support load balancing groups. Multiple HTTPS proxies can be configured with the same `loadBalancer.group` and `loadBalancer.groupKey` to share the same custom domain and distribute traffic across multiple backend services, similar to the existing TCP and HTTP load balancing capabilities.
|
||||
* Individual frpc proxies and visitors now accept an `enabled` flag (defaults to true), letting you disable specific entries without relying on the global `start` list—disabled blocks are skipped when client configs load.
|
||||
* OIDC authentication now supports a `tokenSource` field to dynamically obtain tokens from external sources. You can use `type = "file"` to read a token from a file, or `type = "exec"` to run an external command (e.g., a cloud CLI or secrets manager) and capture its stdout as the token. The `exec` type requires the `--allow-unsafe=TokenSourceExec` CLI flag for security reasons.
|
||||
|
||||
## Improvements
|
||||
|
||||
|
||||
@@ -16,7 +16,9 @@ package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
@@ -43,8 +45,8 @@ type SessionContext struct {
|
||||
Conn net.Conn
|
||||
// Indicates whether the connection is encrypted.
|
||||
ConnEncrypted bool
|
||||
// Sets authentication based on selected method
|
||||
AuthSetter auth.Setter
|
||||
// Auth runtime used for login, heartbeats, and encryption.
|
||||
Auth *auth.ClientAuth
|
||||
// Connector is used to create new connections, which could be real TCP connections or virtual streams.
|
||||
Connector Connector
|
||||
// Virtual net controller
|
||||
@@ -91,7 +93,7 @@ func NewControl(ctx context.Context, sessionCtx *SessionContext) (*Control, erro
|
||||
ctl.lastPong.Store(time.Now())
|
||||
|
||||
if sessionCtx.ConnEncrypted {
|
||||
cryptoRW, err := netpkg.NewCryptoReadWriter(sessionCtx.Conn, []byte(sessionCtx.Common.Auth.Token))
|
||||
cryptoRW, err := netpkg.NewCryptoReadWriter(sessionCtx.Conn, sessionCtx.Auth.EncryptionKey())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -102,7 +104,7 @@ func NewControl(ctx context.Context, sessionCtx *SessionContext) (*Control, erro
|
||||
ctl.registerMsgHandlers()
|
||||
ctl.msgTransporter = transport.NewMessageTransporter(ctl.msgDispatcher)
|
||||
|
||||
ctl.pm = proxy.NewManager(ctl.ctx, sessionCtx.Common, ctl.msgTransporter, sessionCtx.VnetController)
|
||||
ctl.pm = proxy.NewManager(ctl.ctx, sessionCtx.Common, sessionCtx.Auth.EncryptionKey(), ctl.msgTransporter, sessionCtx.VnetController)
|
||||
ctl.vm = visitor.NewManager(ctl.ctx, sessionCtx.RunID, sessionCtx.Common,
|
||||
ctl.connectServer, ctl.msgTransporter, sessionCtx.VnetController)
|
||||
return ctl, nil
|
||||
@@ -133,7 +135,7 @@ func (ctl *Control) handleReqWorkConn(_ msg.Message) {
|
||||
m := &msg.NewWorkConn{
|
||||
RunID: ctl.sessionCtx.RunID,
|
||||
}
|
||||
if err = ctl.sessionCtx.AuthSetter.SetNewWorkConn(m); err != nil {
|
||||
if err = ctl.sessionCtx.Auth.Setter.SetNewWorkConn(m); err != nil {
|
||||
xl.Warnf("error during NewWorkConn authentication: %v", err)
|
||||
workConn.Close()
|
||||
return
|
||||
@@ -167,9 +169,44 @@ func (ctl *Control) handleNewProxyResp(m msg.Message) {
|
||||
// Start a new proxy handler if no error got
|
||||
err := ctl.pm.StartProxy(inMsg.ProxyName, inMsg.RemoteAddr, inMsg.Error)
|
||||
if err != nil {
|
||||
xl.Warnf("[%s] start error: %v", inMsg.ProxyName, err)
|
||||
xl.Warnf("[%s] 启动失败: %v", inMsg.ProxyName, err)
|
||||
} else {
|
||||
xl.Infof("[%s] start proxy success", inMsg.ProxyName)
|
||||
xl.Infof("[%s] 成功启动隧道", inMsg.ProxyName)
|
||||
if inMsg.RemoteAddr != "" {
|
||||
// Get proxy type to format access message
|
||||
if status, ok := ctl.pm.GetProxyStatus(inMsg.ProxyName); ok {
|
||||
proxyType := status.Type
|
||||
remoteAddr := inMsg.RemoteAddr
|
||||
var accessMsg string
|
||||
|
||||
switch proxyType {
|
||||
case "tcp", "udp", "stcp", "xtcp", "sudp", "tcpmux":
|
||||
// If remoteAddr only contains port (e.g., ":8080"), prepend server address
|
||||
if strings.HasPrefix(remoteAddr, ":") {
|
||||
serverAddr := ctl.sessionCtx.Common.ServerAddr
|
||||
remoteAddr = serverAddr + remoteAddr
|
||||
}
|
||||
accessMsg = fmt.Sprintf("您可通过 %s 访问您的服务", remoteAddr)
|
||||
case "http", "https":
|
||||
// Format as URL with protocol
|
||||
protocol := proxyType
|
||||
addr := remoteAddr
|
||||
// Remove standard ports for cleaner URL
|
||||
if proxyType == "http" && strings.HasSuffix(addr, ":80") {
|
||||
addr = strings.TrimSuffix(addr, ":80")
|
||||
} else if proxyType == "https" && strings.HasSuffix(addr, ":443") {
|
||||
addr = strings.TrimSuffix(addr, ":443")
|
||||
}
|
||||
accessMsg = fmt.Sprintf("您可通过 %s://%s 访问您的服务", protocol, addr)
|
||||
default:
|
||||
accessMsg = fmt.Sprintf("您可通过 %s 访问您的服务", remoteAddr)
|
||||
}
|
||||
|
||||
xl.Infof("[%s] %s", inMsg.ProxyName, accessMsg)
|
||||
} else {
|
||||
xl.Infof("[%s] 您可通过 %s 访问您的服务", inMsg.ProxyName, inMsg.RemoteAddr)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -243,7 +280,7 @@ func (ctl *Control) heartbeatWorker() {
|
||||
sendHeartBeat := func() (bool, error) {
|
||||
xl.Debugf("send heartbeat to server")
|
||||
pingMsg := &msg.Ping{}
|
||||
if err := ctl.sessionCtx.AuthSetter.SetPing(pingMsg); err != nil {
|
||||
if err := ctl.sessionCtx.Auth.Setter.SetPing(pingMsg); err != nil {
|
||||
xl.Warnf("error during ping authentication: %v, skip sending ping message", err)
|
||||
return false, err
|
||||
}
|
||||
|
||||
@@ -57,6 +57,7 @@ func NewProxy(
|
||||
ctx context.Context,
|
||||
pxyConf v1.ProxyConfigurer,
|
||||
clientCfg *v1.ClientCommonConfig,
|
||||
encryptionKey []byte,
|
||||
msgTransporter transport.MessageTransporter,
|
||||
vnetController *vnet.Controller,
|
||||
) (pxy Proxy) {
|
||||
@@ -69,6 +70,7 @@ func NewProxy(
|
||||
baseProxy := BaseProxy{
|
||||
baseCfg: pxyConf.GetBaseConfig(),
|
||||
clientCfg: clientCfg,
|
||||
encryptionKey: encryptionKey,
|
||||
limiter: limiter,
|
||||
msgTransporter: msgTransporter,
|
||||
vnetController: vnetController,
|
||||
@@ -86,6 +88,7 @@ func NewProxy(
|
||||
type BaseProxy struct {
|
||||
baseCfg *v1.ProxyBaseConfig
|
||||
clientCfg *v1.ClientCommonConfig
|
||||
encryptionKey []byte
|
||||
msgTransporter transport.MessageTransporter
|
||||
vnetController *vnet.Controller
|
||||
limiter *rate.Limiter
|
||||
@@ -129,7 +132,7 @@ func (pxy *BaseProxy) InWorkConn(conn net.Conn, m *msg.StartWorkConn) {
|
||||
return
|
||||
}
|
||||
}
|
||||
pxy.HandleTCPWorkConnection(conn, m, []byte(pxy.clientCfg.Auth.Token))
|
||||
pxy.HandleTCPWorkConnection(conn, m, pxy.encryptionKey)
|
||||
}
|
||||
|
||||
// Common handler for tcp work connections.
|
||||
|
||||
@@ -40,7 +40,8 @@ type Manager struct {
|
||||
closed bool
|
||||
mu sync.RWMutex
|
||||
|
||||
clientCfg *v1.ClientCommonConfig
|
||||
encryptionKey []byte
|
||||
clientCfg *v1.ClientCommonConfig
|
||||
|
||||
ctx context.Context
|
||||
}
|
||||
@@ -48,6 +49,7 @@ type Manager struct {
|
||||
func NewManager(
|
||||
ctx context.Context,
|
||||
clientCfg *v1.ClientCommonConfig,
|
||||
encryptionKey []byte,
|
||||
msgTransporter transport.MessageTransporter,
|
||||
vnetController *vnet.Controller,
|
||||
) *Manager {
|
||||
@@ -56,6 +58,7 @@ func NewManager(
|
||||
msgTransporter: msgTransporter,
|
||||
vnetController: vnetController,
|
||||
closed: false,
|
||||
encryptionKey: encryptionKey,
|
||||
clientCfg: clientCfg,
|
||||
ctx: ctx,
|
||||
}
|
||||
@@ -156,14 +159,14 @@ func (pm *Manager) UpdateAll(proxyCfgs []v1.ProxyConfigurer) {
|
||||
}
|
||||
}
|
||||
if len(delPxyNames) > 0 {
|
||||
xl.Infof("proxy removed: %s", delPxyNames)
|
||||
xl.Infof("隧道移除: %s", delPxyNames)
|
||||
}
|
||||
|
||||
addPxyNames := make([]string, 0)
|
||||
for _, cfg := range proxyCfgs {
|
||||
name := cfg.GetBaseConfig().Name
|
||||
if _, ok := pm.proxies[name]; !ok {
|
||||
pxy := NewWrapper(pm.ctx, cfg, pm.clientCfg, pm.HandleEvent, pm.msgTransporter, pm.vnetController)
|
||||
pxy := NewWrapper(pm.ctx, cfg, pm.clientCfg, pm.encryptionKey, pm.HandleEvent, pm.msgTransporter, pm.vnetController)
|
||||
if pm.inWorkConnCallback != nil {
|
||||
pxy.SetInWorkConnCallback(pm.inWorkConnCallback)
|
||||
}
|
||||
@@ -174,6 +177,6 @@ func (pm *Manager) UpdateAll(proxyCfgs []v1.ProxyConfigurer) {
|
||||
}
|
||||
}
|
||||
if len(addPxyNames) > 0 {
|
||||
xl.Infof("proxy added: %s", addPxyNames)
|
||||
xl.Infof("添加隧道: %s", addPxyNames)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,6 +92,7 @@ func NewWrapper(
|
||||
ctx context.Context,
|
||||
cfg v1.ProxyConfigurer,
|
||||
clientCfg *v1.ClientCommonConfig,
|
||||
encryptionKey []byte,
|
||||
eventHandler event.Handler,
|
||||
msgTransporter transport.MessageTransporter,
|
||||
vnetController *vnet.Controller,
|
||||
@@ -122,7 +123,7 @@ func NewWrapper(
|
||||
xl.Tracef("enable health check monitor")
|
||||
}
|
||||
|
||||
pw.pxy = NewProxy(pw.ctx, pw.Cfg, clientCfg, pw.msgTransporter, pw.vnetController)
|
||||
pw.pxy = NewProxy(pw.ctx, pw.Cfg, clientCfg, encryptionKey, pw.msgTransporter, pw.vnetController)
|
||||
return pw
|
||||
}
|
||||
|
||||
|
||||
@@ -91,7 +91,7 @@ func (pxy *SUDPProxy) InWorkConn(conn net.Conn, _ *msg.StartWorkConn) {
|
||||
})
|
||||
}
|
||||
if pxy.cfg.Transport.UseEncryption {
|
||||
rwc, err = libio.WithEncryption(rwc, []byte(pxy.clientCfg.Auth.Token))
|
||||
rwc, err = libio.WithEncryption(rwc, pxy.encryptionKey)
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
xl.Errorf("create encryption stream error: %v", err)
|
||||
|
||||
@@ -90,7 +90,7 @@ func (pxy *UDPProxy) Close() {
|
||||
|
||||
func (pxy *UDPProxy) InWorkConn(conn net.Conn, _ *msg.StartWorkConn) {
|
||||
xl := pxy.xl
|
||||
xl.Infof("incoming a new work connection for udp proxy, %s", conn.RemoteAddr().String())
|
||||
xl.Infof("收到一条新的 UDP 代理工作连接, %s", conn.RemoteAddr().String())
|
||||
// close resources related with old workConn
|
||||
pxy.Close()
|
||||
|
||||
@@ -102,7 +102,7 @@ func (pxy *UDPProxy) InWorkConn(conn net.Conn, _ *msg.StartWorkConn) {
|
||||
})
|
||||
}
|
||||
if pxy.cfg.Transport.UseEncryption {
|
||||
rwc, err = libio.WithEncryption(rwc, []byte(pxy.clientCfg.Auth.Token))
|
||||
rwc, err = libio.WithEncryption(rwc, pxy.encryptionKey)
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
xl.Errorf("create encryption stream error: %v", err)
|
||||
|
||||
@@ -111,8 +111,8 @@ type Service struct {
|
||||
// Uniq id got from frps, it will be attached to loginMsg.
|
||||
runID string
|
||||
|
||||
// Sets authentication based on selected method
|
||||
authSetter auth.Setter
|
||||
// Auth runtime and encryption materials
|
||||
auth *auth.ClientAuth
|
||||
|
||||
// web server for admin UI and apis
|
||||
webServer *httppkg.Server
|
||||
@@ -155,14 +155,14 @@ func NewService(options ServiceOptions) (*Service, error) {
|
||||
webServer = ws
|
||||
}
|
||||
|
||||
authSetter, err := auth.NewAuthSetter(options.Common.Auth)
|
||||
authRuntime, err := auth.BuildClientAuth(&options.Common.Auth)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s := &Service{
|
||||
ctx: context.Background(),
|
||||
authSetter: authSetter,
|
||||
auth: authRuntime,
|
||||
webServer: webServer,
|
||||
common: options.Common,
|
||||
configFilePath: options.ConfigFilePath,
|
||||
@@ -219,7 +219,7 @@ func (svr *Service) Run(ctx context.Context) error {
|
||||
if svr.ctl == nil {
|
||||
cancelCause := cancelErr{}
|
||||
_ = errors.As(context.Cause(svr.ctx), &cancelCause)
|
||||
return fmt.Errorf("login to the server failed: %v. With loginFailExit enabled, no additional retries will be attempted", cancelCause.Err)
|
||||
return fmt.Errorf("登录服务器失败: %v. 启用 loginFailExit 后,将不再尝试重试", cancelCause.Err)
|
||||
}
|
||||
|
||||
go svr.keepControllerWorking()
|
||||
@@ -296,7 +296,7 @@ func (svr *Service) login() (conn net.Conn, connector Connector, err error) {
|
||||
}
|
||||
|
||||
// Add auth
|
||||
if err = svr.authSetter.SetLogin(loginMsg); err != nil {
|
||||
if err = svr.auth.Setter.SetLogin(loginMsg); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -320,7 +320,7 @@ func (svr *Service) login() (conn net.Conn, connector Connector, err error) {
|
||||
svr.runID = loginRespMsg.RunID
|
||||
xl.AddPrefix(xlog.LogPrefix{Name: "runID", Value: svr.runID})
|
||||
|
||||
xl.Infof("login to server success, get run id [%s]", loginRespMsg.RunID)
|
||||
xl.Infof("登录服务器成功, 获取 run id [%s]", loginRespMsg.RunID)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -328,10 +328,10 @@ func (svr *Service) loopLoginUntilSuccess(maxInterval time.Duration, firstLoginE
|
||||
xl := xlog.FromContextSafe(svr.ctx)
|
||||
|
||||
loginFunc := func() (bool, error) {
|
||||
xl.Infof("try to connect to server...")
|
||||
xl.Infof("尝试连接到服务器...")
|
||||
conn, connector, err := svr.login()
|
||||
if err != nil {
|
||||
xl.Warnf("connect to server error: %v", err)
|
||||
xl.Warnf("连接服务器错误: %v", err)
|
||||
if firstLoginExit {
|
||||
svr.cancel(cancelErr{Err: err})
|
||||
}
|
||||
@@ -350,7 +350,7 @@ func (svr *Service) loopLoginUntilSuccess(maxInterval time.Duration, firstLoginE
|
||||
RunID: svr.runID,
|
||||
Conn: conn,
|
||||
ConnEncrypted: connEncrypted,
|
||||
AuthSetter: svr.authSetter,
|
||||
Auth: svr.auth,
|
||||
Connector: connector,
|
||||
VnetController: svr.vnetController,
|
||||
}
|
||||
|
||||
@@ -80,7 +80,8 @@ func NewProxyCommand(name string, c v1.ProxyConfigurer, clientCfg *v1.ClientComm
|
||||
}
|
||||
|
||||
unsafeFeatures := security.NewUnsafeFeatures(allowUnsafe)
|
||||
if _, err := validation.ValidateClientCommonConfig(clientCfg, unsafeFeatures); err != nil {
|
||||
validator := validation.NewConfigValidator(unsafeFeatures)
|
||||
if _, err := validator.ValidateClientCommonConfig(clientCfg); err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
@@ -91,7 +92,7 @@ func NewProxyCommand(name string, c v1.ProxyConfigurer, clientCfg *v1.ClientComm
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
err := startService(clientCfg, []v1.ProxyConfigurer{c}, nil, unsafeFeatures, "")
|
||||
err := startService(clientCfg, []v1.ProxyConfigurer{c}, nil, unsafeFeatures, "", "", "")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
@@ -110,7 +111,8 @@ func NewVisitorCommand(name string, c v1.VisitorConfigurer, clientCfg *v1.Client
|
||||
os.Exit(1)
|
||||
}
|
||||
unsafeFeatures := security.NewUnsafeFeatures(allowUnsafe)
|
||||
if _, err := validation.ValidateClientCommonConfig(clientCfg, unsafeFeatures); err != nil {
|
||||
validator := validation.NewConfigValidator(unsafeFeatures)
|
||||
if _, err := validator.ValidateClientCommonConfig(clientCfg); err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
@@ -121,7 +123,7 @@ func NewVisitorCommand(name string, c v1.VisitorConfigurer, clientCfg *v1.Client
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
err := startService(clientCfg, nil, []v1.VisitorConfigurer{c}, unsafeFeatures, "")
|
||||
err := startService(clientCfg, nil, []v1.VisitorConfigurer{c}, unsafeFeatures, "", "", "")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
|
||||
@@ -16,8 +16,11 @@ package sub
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
@@ -34,6 +37,7 @@ import (
|
||||
"github.com/fatedier/frp/pkg/config/v1/validation"
|
||||
"github.com/fatedier/frp/pkg/policy/featuregate"
|
||||
"github.com/fatedier/frp/pkg/policy/security"
|
||||
"github.com/fatedier/frp/pkg/util/banner"
|
||||
"github.com/fatedier/frp/pkg/util/log"
|
||||
"github.com/fatedier/frp/pkg/util/version"
|
||||
)
|
||||
@@ -44,6 +48,7 @@ var (
|
||||
showVersion bool
|
||||
strictConfigMode bool
|
||||
allowUnsafe []string
|
||||
authTokens []string
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -51,7 +56,7 @@ func init() {
|
||||
rootCmd.PersistentFlags().StringVarP(&cfgDir, "config_dir", "", "", "config directory, run one frpc service for each file in config directory")
|
||||
rootCmd.PersistentFlags().BoolVarP(&showVersion, "version", "v", false, "version of frpc")
|
||||
rootCmd.PersistentFlags().BoolVarP(&strictConfigMode, "strict_config", "", true, "strict config parsing mode, unknown fields will cause an errors")
|
||||
|
||||
rootCmd.PersistentFlags().StringSliceVarP(&authTokens, "token", "t", []string{}, "authentication tokens in format 'id:token' (LoliaFRP only)")
|
||||
rootCmd.PersistentFlags().StringSliceVarP(&allowUnsafe, "allow-unsafe", "", []string{},
|
||||
fmt.Sprintf("allowed unsafe features, one or more of: %s", strings.Join(security.ClientUnsafeFeatures, ", ")))
|
||||
}
|
||||
@@ -67,6 +72,16 @@ var rootCmd = &cobra.Command{
|
||||
|
||||
unsafeFeatures := security.NewUnsafeFeatures(allowUnsafe)
|
||||
|
||||
// If authTokens is provided, fetch config from API
|
||||
if len(authTokens) > 0 {
|
||||
err := runClientWithTokens(authTokens, unsafeFeatures)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// If cfgDir is not empty, run multiple frpc service for each config file in cfgDir.
|
||||
// Note that it's only designed for testing. It's not guaranteed to be stable.
|
||||
if cfgDir != "" {
|
||||
@@ -142,7 +157,8 @@ func runClient(cfgFilePath string, unsafeFeatures *security.UnsafeFeatures) erro
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return startService(cfg, proxyCfgs, visitorCfgs, unsafeFeatures, cfgFilePath)
|
||||
|
||||
return startService(cfg, proxyCfgs, visitorCfgs, unsafeFeatures, cfgFilePath, "", "")
|
||||
}
|
||||
|
||||
func startService(
|
||||
@@ -151,12 +167,24 @@ func startService(
|
||||
visitorCfgs []v1.VisitorConfigurer,
|
||||
unsafeFeatures *security.UnsafeFeatures,
|
||||
cfgFile string,
|
||||
nodeName string,
|
||||
tunnelRemark string,
|
||||
) error {
|
||||
log.InitLogger(cfg.Log.To, cfg.Log.Level, int(cfg.Log.MaxDays), cfg.Log.DisablePrintColor)
|
||||
|
||||
// Display banner before starting the service
|
||||
banner.DisplayBanner()
|
||||
|
||||
log.Infof("Nya! %s 启动中", version.Full())
|
||||
|
||||
// Display node information if available
|
||||
if nodeName != "" {
|
||||
log.Info("已获取到配置文件", "隧道名称", tunnelRemark, "使用节点", nodeName)
|
||||
}
|
||||
|
||||
if cfgFile != "" {
|
||||
log.Infof("start frpc service for config file [%s]", cfgFile)
|
||||
defer log.Infof("frpc service for config file [%s] stopped", cfgFile)
|
||||
log.Infof("启动 frpc 服务 [%s]", cfgFile)
|
||||
defer log.Infof("frpc 服务 [%s] 已停止", cfgFile)
|
||||
}
|
||||
svr, err := client.NewService(client.ServiceOptions{
|
||||
Common: cfg,
|
||||
@@ -176,3 +204,148 @@ func startService(
|
||||
}
|
||||
return svr.Run(context.Background())
|
||||
}
|
||||
|
||||
// APIResponse represents the response from LoliaFRP API
|
||||
type APIResponse struct {
|
||||
Code int `json:"code"`
|
||||
Msg string `json:"msg"`
|
||||
Data struct {
|
||||
Config string `json:"config"`
|
||||
NodeName string `json:"node_name"`
|
||||
TunnelRemark string `json:"tunnel_remark"`
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
// TokenInfo stores parsed id and token from the -t parameter
|
||||
type TokenInfo struct {
|
||||
ID string
|
||||
Token string
|
||||
}
|
||||
|
||||
func runClientWithTokens(tokens []string, unsafeFeatures *security.UnsafeFeatures) error {
|
||||
// Parse all tokens (format: id:token)
|
||||
tokenInfos := make([]TokenInfo, 0, len(tokens))
|
||||
for _, t := range tokens {
|
||||
parts := strings.SplitN(t, ":", 2)
|
||||
if len(parts) != 2 {
|
||||
return fmt.Errorf("invalid token format '%s', expected 'id:token'", t)
|
||||
}
|
||||
tokenInfos = append(tokenInfos, TokenInfo{
|
||||
ID: strings.TrimSpace(parts[0]),
|
||||
Token: strings.TrimSpace(parts[1]),
|
||||
})
|
||||
}
|
||||
|
||||
// Group tokens by token value (same token can have multiple IDs)
|
||||
tokenToIDs := make(map[string][]string)
|
||||
for _, ti := range tokenInfos {
|
||||
tokenToIDs[ti.Token] = append(tokenToIDs[ti.Token], ti.ID)
|
||||
}
|
||||
|
||||
// Verify all tokens use the same token value
|
||||
if len(tokenToIDs) > 1 {
|
||||
return fmt.Errorf("different tokens are not supported, all ids must use the same token")
|
||||
}
|
||||
|
||||
// Get the single token and all its IDs
|
||||
var token string
|
||||
var ids []string
|
||||
for t, idList := range tokenToIDs {
|
||||
token = t
|
||||
ids = idList
|
||||
break
|
||||
}
|
||||
|
||||
return runClientWithTokenAndIDs(token, ids, unsafeFeatures)
|
||||
}
|
||||
|
||||
func runClientWithTokenAndIDs(token string, ids []string, unsafeFeatures *security.UnsafeFeatures) error {
|
||||
// Get API server address from environment variable
|
||||
apiServer := os.Getenv("LOLIA_API")
|
||||
if apiServer == "" {
|
||||
apiServer = "https://api.lolia.link"
|
||||
}
|
||||
|
||||
// Build URL with query parameters
|
||||
url := fmt.Sprintf("%s/api/v1/tunnel/frpc/config?token=%s&id=%s", apiServer, token, strings.Join(ids, ","))
|
||||
// #nosec G107 -- URL is constructed from trusted source (environment variable or hardcoded)
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to fetch config from API: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return fmt.Errorf("API returned status code: %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
var apiResp APIResponse
|
||||
if err := json.NewDecoder(resp.Body).Decode(&apiResp); err != nil {
|
||||
return fmt.Errorf("failed to decode API response: %v", err)
|
||||
}
|
||||
|
||||
if apiResp.Code != 200 {
|
||||
return fmt.Errorf("API error: %s", apiResp.Msg)
|
||||
}
|
||||
|
||||
// Decode base64 config
|
||||
configBytes, err := base64.StdEncoding.DecodeString(apiResp.Data.Config)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to decode base64 config: %v", err)
|
||||
}
|
||||
|
||||
// Load config directly from bytes
|
||||
return runClientWithConfig(configBytes, unsafeFeatures, apiResp.Data.NodeName, apiResp.Data.TunnelRemark)
|
||||
}
|
||||
|
||||
func runClientWithConfig(configBytes []byte, unsafeFeatures *security.UnsafeFeatures, nodeName, tunnelRemark string) error {
|
||||
// Render template first
|
||||
renderedBytes, err := config.RenderWithTemplate(configBytes, config.GetValues())
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to render template: %v", err)
|
||||
}
|
||||
|
||||
var allCfg v1.ClientConfig
|
||||
if err := config.LoadConfigure(renderedBytes, &allCfg, strictConfigMode); err != nil {
|
||||
return fmt.Errorf("failed to parse config: %v", err)
|
||||
}
|
||||
|
||||
cfg := &allCfg.ClientCommonConfig
|
||||
proxyCfgs := make([]v1.ProxyConfigurer, 0, len(allCfg.Proxies))
|
||||
for _, c := range allCfg.Proxies {
|
||||
proxyCfgs = append(proxyCfgs, c.ProxyConfigurer)
|
||||
}
|
||||
visitorCfgs := make([]v1.VisitorConfigurer, 0, len(allCfg.Visitors))
|
||||
for _, c := range allCfg.Visitors {
|
||||
visitorCfgs = append(visitorCfgs, c.VisitorConfigurer)
|
||||
}
|
||||
|
||||
// Call Complete to fill in default values
|
||||
if err := cfg.Complete(); err != nil {
|
||||
return fmt.Errorf("failed to complete config: %v", err)
|
||||
}
|
||||
|
||||
// Call Complete for all proxies to add name prefix (e.g., user.tunnel_name)
|
||||
for _, c := range proxyCfgs {
|
||||
c.Complete(cfg.User)
|
||||
}
|
||||
for _, c := range visitorCfgs {
|
||||
c.Complete(cfg)
|
||||
}
|
||||
|
||||
if len(cfg.FeatureGates) > 0 {
|
||||
if err := featuregate.SetFromMap(cfg.FeatureGates); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
warning, err := validation.ValidateAllClientConfig(cfg, proxyCfgs, visitorCfgs, unsafeFeatures)
|
||||
if warning != nil {
|
||||
fmt.Printf("WARNING: %v\n", warning)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return startService(cfg, proxyCfgs, visitorCfgs, unsafeFeatures, "", nodeName, tunnelRemark)
|
||||
}
|
||||
|
||||
@@ -18,12 +18,14 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/fatedier/frp/pkg/config"
|
||||
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||
"github.com/fatedier/frp/pkg/config/v1/validation"
|
||||
"github.com/fatedier/frp/pkg/policy/security"
|
||||
"github.com/fatedier/frp/pkg/util/log"
|
||||
"github.com/fatedier/frp/pkg/util/version"
|
||||
"github.com/fatedier/frp/server"
|
||||
@@ -33,6 +35,7 @@ var (
|
||||
cfgFile string
|
||||
showVersion bool
|
||||
strictConfigMode bool
|
||||
allowUnsafe []string
|
||||
|
||||
serverCfg v1.ServerConfig
|
||||
)
|
||||
@@ -41,6 +44,8 @@ func init() {
|
||||
rootCmd.PersistentFlags().StringVarP(&cfgFile, "config", "c", "", "config file of frps")
|
||||
rootCmd.PersistentFlags().BoolVarP(&showVersion, "version", "v", false, "version of frps")
|
||||
rootCmd.PersistentFlags().BoolVarP(&strictConfigMode, "strict_config", "", true, "strict config parsing mode, unknown fields will cause errors")
|
||||
rootCmd.PersistentFlags().StringSliceVarP(&allowUnsafe, "allow-unsafe", "", []string{},
|
||||
fmt.Sprintf("allowed unsafe features, one or more of: %s", strings.Join(security.ServerUnsafeFeatures, ", ")))
|
||||
|
||||
config.RegisterServerConfigFlags(rootCmd, &serverCfg)
|
||||
}
|
||||
@@ -77,7 +82,9 @@ var rootCmd = &cobra.Command{
|
||||
svrCfg = &serverCfg
|
||||
}
|
||||
|
||||
warning, err := validation.ValidateServerConfig(svrCfg)
|
||||
unsafeFeatures := security.NewUnsafeFeatures(allowUnsafe)
|
||||
validator := validation.NewConfigValidator(unsafeFeatures)
|
||||
warning, err := validator.ValidateServerConfig(svrCfg)
|
||||
if warning != nil {
|
||||
fmt.Printf("WARNING: %v\n", warning)
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import (
|
||||
|
||||
"github.com/fatedier/frp/pkg/config"
|
||||
"github.com/fatedier/frp/pkg/config/v1/validation"
|
||||
"github.com/fatedier/frp/pkg/policy/security"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -42,7 +43,9 @@ var verifyCmd = &cobra.Command{
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
warning, err := validation.ValidateServerConfig(svrCfg)
|
||||
unsafeFeatures := security.NewUnsafeFeatures(allowUnsafe)
|
||||
validator := validation.NewConfigValidator(unsafeFeatures)
|
||||
warning, err := validator.ValidateServerConfig(svrCfg)
|
||||
if warning != nil {
|
||||
fmt.Printf("WARNING: %v\n", warning)
|
||||
}
|
||||
|
||||
15
go.mod
15
go.mod
@@ -39,10 +39,18 @@ require (
|
||||
|
||||
require (
|
||||
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect
|
||||
github.com/charmbracelet/lipgloss v1.1.0 // indirect
|
||||
github.com/charmbracelet/log v0.4.2 // indirect
|
||||
github.com/charmbracelet/x/ansi v0.8.0 // indirect
|
||||
github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd // indirect
|
||||
github.com/charmbracelet/x/term v0.2.1 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/go-jose/go-jose/v4 v4.0.5 // indirect
|
||||
github.com/go-logfmt/logfmt v0.6.0 // indirect
|
||||
github.com/go-logr/logr v1.4.2 // indirect
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
|
||||
github.com/golang/snappy v0.0.4 // indirect
|
||||
@@ -51,6 +59,10 @@ require (
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.6 // indirect
|
||||
github.com/klauspost/reedsolomon v1.12.0 // indirect
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.16 // indirect
|
||||
github.com/muesli/termenv v0.16.0 // indirect
|
||||
github.com/pion/dtls/v2 v2.2.7 // indirect
|
||||
github.com/pion/logging v0.2.2 // indirect
|
||||
github.com/pion/transport/v2 v2.2.1 // indirect
|
||||
@@ -60,13 +72,16 @@ require (
|
||||
github.com/prometheus/client_model v0.5.0 // indirect
|
||||
github.com/prometheus/common v0.48.0 // indirect
|
||||
github.com/prometheus/procfs v0.12.0 // indirect
|
||||
github.com/rivo/uniseg v0.4.7 // indirect
|
||||
github.com/templexxx/cpu v0.1.1 // indirect
|
||||
github.com/templexxx/xorsimd v0.4.3 // indirect
|
||||
github.com/tidwall/match v1.1.1 // indirect
|
||||
github.com/tidwall/pretty v1.2.0 // indirect
|
||||
github.com/tjfoc/gmsm v1.4.1 // indirect
|
||||
github.com/vishvananda/netns v0.0.4 // indirect
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
||||
go.uber.org/automaxprocs v1.6.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect
|
||||
golang.org/x/mod v0.27.0 // indirect
|
||||
golang.org/x/sys v0.35.0 // indirect
|
||||
golang.org/x/text v0.28.0 // indirect
|
||||
|
||||
31
go.sum
31
go.sum
@@ -4,11 +4,25 @@ github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod h1:chxPXzS
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
|
||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
||||
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc h1:4pZI35227imm7yK2bGPcfpFEmuY1gc2YSTShr4iJBfs=
|
||||
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc/go.mod h1:X4/0JoqgTIPSFcRA/P6INZzIuyqdFY5rm8tb41s9okk=
|
||||
github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY=
|
||||
github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30=
|
||||
github.com/charmbracelet/log v0.4.2 h1:hYt8Qj6a8yLnvR+h7MwsJv/XvmBJXiueUcI3cIxsyig=
|
||||
github.com/charmbracelet/log v0.4.2/go.mod h1:qifHGX/tc7eluv2R6pWIpyHDDrrb/AG71Pf2ysQu5nw=
|
||||
github.com/charmbracelet/x/ansi v0.8.0 h1:9GTq3xq9caJW8ZrBTe0LIe2fvfLR/bYXKTx2llXn7xE=
|
||||
github.com/charmbracelet/x/ansi v0.8.0/go.mod h1:wdYl/ONOLHLIVmQaxbIYEC/cRKOQyjTkowiI4blgS9Q=
|
||||
github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd h1:vy0GVL4jeHEwG5YOXDmi86oYw2yuYUGqz6a8sLwg0X8=
|
||||
github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs=
|
||||
github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ=
|
||||
github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/coreos/go-oidc/v3 v3.14.1 h1:9ePWwfdwC4QKRlCXsJGou56adA/owXczOzwKdOumLqk=
|
||||
@@ -26,6 +40,8 @@ github.com/fatedier/yamux v0.0.0-20250825093530-d0154be01cd6 h1:u92UUy6FURPmNsMB
|
||||
github.com/fatedier/yamux v0.0.0-20250825093530-d0154be01cd6/go.mod h1:c5/tk6G0dSpXGzJN7Wk1OEie8grdSJAmeawId9Zvd34=
|
||||
github.com/go-jose/go-jose/v4 v4.0.5 h1:M6T8+mKZl/+fNNuFHvGIzDz7BTLQPIounk/b9dw3AaE=
|
||||
github.com/go-jose/go-jose/v4 v4.0.5/go.mod h1:s3P1lRrkT8igV8D9OjyL4WRyHvjB6a4JSllnOrmmBOA=
|
||||
github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4=
|
||||
github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
|
||||
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
||||
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
|
||||
@@ -70,8 +86,16 @@ github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
|
||||
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
|
||||
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc=
|
||||
github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk=
|
||||
github.com/onsi/ginkgo/v2 v2.23.4 h1:ktYTpKJAVZnDT4VjxSbiBenUjmlL/5QkBEocaWXiQus=
|
||||
github.com/onsi/ginkgo/v2 v2.23.4/go.mod h1:Bt66ApGPBFzHyR+JO10Zbt0Gsp4uWxu5mIOTusL46e8=
|
||||
github.com/onsi/gomega v1.36.3 h1:hID7cr8t3Wp26+cYnfcjR6HpJ00fdogN6dqZ1t6IylU=
|
||||
@@ -109,6 +133,8 @@ github.com/quic-go/quic-go v0.55.0 h1:zccPQIqYCXDt5NmcEabyYvOnomjs8Tlwl7tISjJh9M
|
||||
github.com/quic-go/quic-go v0.55.0/go.mod h1:DR51ilwU1uE164KuWXhinFcKWGlEjzys2l8zUl5Ss1U=
|
||||
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
github.com/rodaine/table v1.2.0 h1:38HEnwK4mKSHQJIkavVj+bst1TEY7j9zhLMWu4QJrMA=
|
||||
github.com/rodaine/table v1.2.0/go.mod h1:wejb/q/Yd4T/SVmBSRMr7GCq3KlcZp3gyNYdLSBhkaE=
|
||||
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
||||
@@ -149,6 +175,8 @@ github.com/vishvananda/netlink v1.3.0 h1:X7l42GfcV4S6E4vHTsw48qbrV+9PVojNfIhZcwQ
|
||||
github.com/vishvananda/netlink v1.3.0/go.mod h1:i6NetklAujEcC6fK0JPjT8qSwWyO0HLn4UKG+hGqeJs=
|
||||
github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8=
|
||||
github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
|
||||
github.com/xtaci/kcp-go/v5 v5.6.13 h1:FEjtz9+D4p8t2x4WjciGt/jsIuhlWjjgPCCWjrVR4Hk=
|
||||
github.com/xtaci/kcp-go/v5 v5.6.13/go.mod h1:75S1AKYYzNUSXIv30h+jPKJYZUwqpfvLshu63nCNSOM=
|
||||
github.com/xtaci/lossyconn v0.0.0-20200209145036-adba10fffc37 h1:EWU6Pktpas0n8lLQwDsRyZfmkPeRbdgPtW609es+/9E=
|
||||
@@ -167,6 +195,8 @@ golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98y
|
||||
golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
|
||||
golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
|
||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
@@ -209,6 +239,7 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
|
||||
@@ -18,7 +18,7 @@ rm -rf ./release/packages
|
||||
mkdir -p ./release/packages
|
||||
|
||||
os_all='linux windows darwin freebsd openbsd android'
|
||||
arch_all='386 amd64 arm arm64 mips64 mips64le mips mipsle riscv64 loong64'
|
||||
arch_all='amd64 arm arm64'
|
||||
extra_all='_ hf'
|
||||
|
||||
cd ./release
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||
@@ -27,6 +28,39 @@ type Setter interface {
|
||||
SetNewWorkConn(*msg.NewWorkConn) error
|
||||
}
|
||||
|
||||
type ClientAuth struct {
|
||||
Setter Setter
|
||||
key []byte
|
||||
}
|
||||
|
||||
func (a *ClientAuth) EncryptionKey() []byte {
|
||||
return a.key
|
||||
}
|
||||
|
||||
// BuildClientAuth resolves any dynamic auth values and returns a prepared auth runtime.
|
||||
// Caller must run validation before calling this function.
|
||||
func BuildClientAuth(cfg *v1.AuthClientConfig) (*ClientAuth, error) {
|
||||
if cfg == nil {
|
||||
return nil, fmt.Errorf("auth config is nil")
|
||||
}
|
||||
resolved := *cfg
|
||||
if resolved.Method == v1.AuthMethodToken && resolved.TokenSource != nil {
|
||||
token, err := resolved.TokenSource.Resolve(context.Background())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to resolve auth.tokenSource: %w", err)
|
||||
}
|
||||
resolved.Token = token
|
||||
}
|
||||
setter, err := NewAuthSetter(resolved)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &ClientAuth{
|
||||
Setter: setter,
|
||||
key: []byte(resolved.Token),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func NewAuthSetter(cfg v1.AuthClientConfig) (authProvider Setter, err error) {
|
||||
switch cfg.Method {
|
||||
case v1.AuthMethodToken:
|
||||
@@ -52,6 +86,35 @@ type Verifier interface {
|
||||
VerifyNewWorkConn(*msg.NewWorkConn) error
|
||||
}
|
||||
|
||||
type ServerAuth struct {
|
||||
Verifier Verifier
|
||||
key []byte
|
||||
}
|
||||
|
||||
func (a *ServerAuth) EncryptionKey() []byte {
|
||||
return a.key
|
||||
}
|
||||
|
||||
// BuildServerAuth resolves any dynamic auth values and returns a prepared auth runtime.
|
||||
// Caller must run validation before calling this function.
|
||||
func BuildServerAuth(cfg *v1.AuthServerConfig) (*ServerAuth, error) {
|
||||
if cfg == nil {
|
||||
return nil, fmt.Errorf("auth config is nil")
|
||||
}
|
||||
resolved := *cfg
|
||||
if resolved.Method == v1.AuthMethodToken && resolved.TokenSource != nil {
|
||||
token, err := resolved.TokenSource.Resolve(context.Background())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to resolve auth.tokenSource: %w", err)
|
||||
}
|
||||
resolved.Token = token
|
||||
}
|
||||
return &ServerAuth{
|
||||
Verifier: NewAuthVerifier(resolved),
|
||||
key: []byte(resolved.Token),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func NewAuthVerifier(cfg v1.AuthServerConfig) (authVerifier Verifier) {
|
||||
switch cfg.Method {
|
||||
case v1.AuthMethodToken:
|
||||
|
||||
@@ -15,8 +15,6 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/samber/lo"
|
||||
@@ -198,17 +196,6 @@ type AuthClientConfig struct {
|
||||
|
||||
func (c *AuthClientConfig) Complete() error {
|
||||
c.Method = util.EmptyOr(c.Method, "token")
|
||||
|
||||
// Resolve tokenSource during configuration loading
|
||||
if c.Method == AuthMethodToken && c.TokenSource != nil {
|
||||
token, err := c.TokenSource.Resolve(context.Background())
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to resolve auth.tokenSource: %w", err)
|
||||
}
|
||||
// Move the resolved token to the Token field and clear TokenSource
|
||||
c.Token = token
|
||||
c.TokenSource = nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -15,8 +15,6 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/samber/lo"
|
||||
@@ -38,68 +36,9 @@ func TestClientConfigComplete(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAuthClientConfig_Complete(t *testing.T) {
|
||||
// Create a temporary file for testing
|
||||
tmpDir := t.TempDir()
|
||||
testFile := filepath.Join(tmpDir, "test_token")
|
||||
testContent := "client-token-value"
|
||||
err := os.WriteFile(testFile, []byte(testContent), 0o600)
|
||||
require.NoError(t, err)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
config AuthClientConfig
|
||||
expectToken string
|
||||
expectPanic bool
|
||||
}{
|
||||
{
|
||||
name: "tokenSource resolved to token",
|
||||
config: AuthClientConfig{
|
||||
Method: AuthMethodToken,
|
||||
TokenSource: &ValueSource{
|
||||
Type: "file",
|
||||
File: &FileSource{
|
||||
Path: testFile,
|
||||
},
|
||||
},
|
||||
},
|
||||
expectToken: testContent,
|
||||
expectPanic: false,
|
||||
},
|
||||
{
|
||||
name: "direct token unchanged",
|
||||
config: AuthClientConfig{
|
||||
Method: AuthMethodToken,
|
||||
Token: "direct-token",
|
||||
},
|
||||
expectToken: "direct-token",
|
||||
expectPanic: false,
|
||||
},
|
||||
{
|
||||
name: "invalid tokenSource should panic",
|
||||
config: AuthClientConfig{
|
||||
Method: AuthMethodToken,
|
||||
TokenSource: &ValueSource{
|
||||
Type: "file",
|
||||
File: &FileSource{
|
||||
Path: "/non/existent/file",
|
||||
},
|
||||
},
|
||||
},
|
||||
expectPanic: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if tt.expectPanic {
|
||||
err := tt.config.Complete()
|
||||
require.Error(t, err)
|
||||
} else {
|
||||
err := tt.config.Complete()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tt.expectToken, tt.config.Token)
|
||||
require.Nil(t, tt.config.TokenSource, "TokenSource should be cleared after resolution")
|
||||
}
|
||||
})
|
||||
}
|
||||
require := require.New(t)
|
||||
cfg := &AuthClientConfig{}
|
||||
err := cfg.Complete()
|
||||
require.NoError(err)
|
||||
require.EqualValues("token", cfg.Method)
|
||||
}
|
||||
|
||||
@@ -15,9 +15,6 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/samber/lo"
|
||||
|
||||
"github.com/fatedier/frp/pkg/config/types"
|
||||
@@ -138,17 +135,6 @@ type AuthServerConfig struct {
|
||||
|
||||
func (c *AuthServerConfig) Complete() error {
|
||||
c.Method = util.EmptyOr(c.Method, "token")
|
||||
|
||||
// Resolve tokenSource during configuration loading
|
||||
if c.Method == AuthMethodToken && c.TokenSource != nil {
|
||||
token, err := c.TokenSource.Resolve(context.Background())
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to resolve auth.tokenSource: %w", err)
|
||||
}
|
||||
// Move the resolved token to the Token field and clear TokenSource
|
||||
c.Token = token
|
||||
c.TokenSource = nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -15,8 +15,6 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/samber/lo"
|
||||
@@ -35,68 +33,9 @@ func TestServerConfigComplete(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAuthServerConfig_Complete(t *testing.T) {
|
||||
// Create a temporary file for testing
|
||||
tmpDir := t.TempDir()
|
||||
testFile := filepath.Join(tmpDir, "test_token")
|
||||
testContent := "file-token-value"
|
||||
err := os.WriteFile(testFile, []byte(testContent), 0o600)
|
||||
require.NoError(t, err)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
config AuthServerConfig
|
||||
expectToken string
|
||||
expectPanic bool
|
||||
}{
|
||||
{
|
||||
name: "tokenSource resolved to token",
|
||||
config: AuthServerConfig{
|
||||
Method: AuthMethodToken,
|
||||
TokenSource: &ValueSource{
|
||||
Type: "file",
|
||||
File: &FileSource{
|
||||
Path: testFile,
|
||||
},
|
||||
},
|
||||
},
|
||||
expectToken: testContent,
|
||||
expectPanic: false,
|
||||
},
|
||||
{
|
||||
name: "direct token unchanged",
|
||||
config: AuthServerConfig{
|
||||
Method: AuthMethodToken,
|
||||
Token: "direct-token",
|
||||
},
|
||||
expectToken: "direct-token",
|
||||
expectPanic: false,
|
||||
},
|
||||
{
|
||||
name: "invalid tokenSource should panic",
|
||||
config: AuthServerConfig{
|
||||
Method: AuthMethodToken,
|
||||
TokenSource: &ValueSource{
|
||||
Type: "file",
|
||||
File: &FileSource{
|
||||
Path: "/non/existent/file",
|
||||
},
|
||||
},
|
||||
},
|
||||
expectPanic: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if tt.expectPanic {
|
||||
err := tt.config.Complete()
|
||||
require.Error(t, err)
|
||||
} else {
|
||||
err := tt.config.Complete()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tt.expectToken, tt.config.Token)
|
||||
require.Nil(t, tt.config.TokenSource, "TokenSource should be cleared after resolution")
|
||||
}
|
||||
})
|
||||
}
|
||||
require := require.New(t)
|
||||
cfg := &AuthServerConfig{}
|
||||
err := cfg.Complete()
|
||||
require.NoError(err)
|
||||
require.EqualValues("token", cfg.Method)
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ import (
|
||||
"github.com/fatedier/frp/pkg/policy/security"
|
||||
)
|
||||
|
||||
func ValidateClientCommonConfig(c *v1.ClientCommonConfig, unsafeFeatures *security.UnsafeFeatures) (Warning, error) {
|
||||
func (v *ConfigValidator) ValidateClientCommonConfig(c *v1.ClientCommonConfig) (Warning, error) {
|
||||
var (
|
||||
warnings Warning
|
||||
errs error
|
||||
@@ -35,15 +35,15 @@ func ValidateClientCommonConfig(c *v1.ClientCommonConfig, unsafeFeatures *securi
|
||||
|
||||
validators := []func() (Warning, error){
|
||||
func() (Warning, error) { return validateFeatureGates(c) },
|
||||
func() (Warning, error) { return validateAuthConfig(&c.Auth, unsafeFeatures) },
|
||||
func() (Warning, error) { return v.validateAuthConfig(&c.Auth) },
|
||||
func() (Warning, error) { return nil, validateLogConfig(&c.Log) },
|
||||
func() (Warning, error) { return nil, validateWebServerConfig(&c.WebServer) },
|
||||
func() (Warning, error) { return validateTransportConfig(&c.Transport) },
|
||||
func() (Warning, error) { return validateIncludeFiles(c.IncludeConfigFiles) },
|
||||
}
|
||||
|
||||
for _, v := range validators {
|
||||
w, err := v()
|
||||
for _, validator := range validators {
|
||||
w, err := validator()
|
||||
warnings = AppendError(warnings, w)
|
||||
errs = AppendError(errs, err)
|
||||
}
|
||||
@@ -59,7 +59,7 @@ func validateFeatureGates(c *v1.ClientCommonConfig) (Warning, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func validateAuthConfig(c *v1.AuthClientConfig, unsafeFeatures *security.UnsafeFeatures) (Warning, error) {
|
||||
func (v *ConfigValidator) validateAuthConfig(c *v1.AuthClientConfig) (Warning, error) {
|
||||
var errs error
|
||||
if !slices.Contains(SupportedAuthMethods, c.Method) {
|
||||
errs = AppendError(errs, fmt.Errorf("invalid auth method, optional values are %v", SupportedAuthMethods))
|
||||
@@ -76,9 +76,8 @@ func validateAuthConfig(c *v1.AuthClientConfig, unsafeFeatures *security.UnsafeF
|
||||
// Validate tokenSource if specified
|
||||
if c.TokenSource != nil {
|
||||
if c.TokenSource.Type == "exec" {
|
||||
if !unsafeFeatures.IsEnabled(security.TokenSourceExec) {
|
||||
errs = AppendError(errs, fmt.Errorf("unsafe feature %q is not enabled. "+
|
||||
"To enable it, start frpc with '--allow-unsafe %s'", security.TokenSourceExec, security.TokenSourceExec))
|
||||
if err := v.ValidateUnsafeFeature(security.TokenSourceExec); err != nil {
|
||||
errs = AppendError(errs, err)
|
||||
}
|
||||
}
|
||||
if err := c.TokenSource.Validate(); err != nil {
|
||||
@@ -86,13 +85,13 @@ func validateAuthConfig(c *v1.AuthClientConfig, unsafeFeatures *security.UnsafeF
|
||||
}
|
||||
}
|
||||
|
||||
if err := validateOIDCConfig(&c.OIDC, unsafeFeatures); err != nil {
|
||||
if err := v.validateOIDCConfig(&c.OIDC); err != nil {
|
||||
errs = AppendError(errs, err)
|
||||
}
|
||||
return nil, errs
|
||||
}
|
||||
|
||||
func validateOIDCConfig(c *v1.AuthOIDCClientConfig, unsafeFeatures *security.UnsafeFeatures) error {
|
||||
func (v *ConfigValidator) validateOIDCConfig(c *v1.AuthOIDCClientConfig) error {
|
||||
if c.TokenSource == nil {
|
||||
return nil
|
||||
}
|
||||
@@ -104,9 +103,8 @@ func validateOIDCConfig(c *v1.AuthOIDCClientConfig, unsafeFeatures *security.Uns
|
||||
errs = AppendError(errs, fmt.Errorf("cannot specify both auth.oidc.tokenSource and any other field of auth.oidc"))
|
||||
}
|
||||
if c.TokenSource.Type == "exec" {
|
||||
if !unsafeFeatures.IsEnabled(security.TokenSourceExec) {
|
||||
errs = AppendError(errs, fmt.Errorf("unsafe feature %q is not enabled. "+
|
||||
"To enable it, start frpc with '--allow-unsafe %s'", security.TokenSourceExec, security.TokenSourceExec))
|
||||
if err := v.ValidateUnsafeFeature(security.TokenSourceExec); err != nil {
|
||||
errs = AppendError(errs, err)
|
||||
}
|
||||
}
|
||||
if err := c.TokenSource.Validate(); err != nil {
|
||||
@@ -167,9 +165,10 @@ func ValidateAllClientConfig(
|
||||
visitorCfgs []v1.VisitorConfigurer,
|
||||
unsafeFeatures *security.UnsafeFeatures,
|
||||
) (Warning, error) {
|
||||
validator := NewConfigValidator(unsafeFeatures)
|
||||
var warnings Warning
|
||||
if c != nil {
|
||||
warning, err := ValidateClientCommonConfig(c, unsafeFeatures)
|
||||
warning, err := validator.ValidateClientCommonConfig(c)
|
||||
warnings = AppendError(warnings, warning)
|
||||
if err != nil {
|
||||
return warnings, err
|
||||
|
||||
@@ -21,9 +21,10 @@ import (
|
||||
"github.com/samber/lo"
|
||||
|
||||
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||
"github.com/fatedier/frp/pkg/policy/security"
|
||||
)
|
||||
|
||||
func ValidateServerConfig(c *v1.ServerConfig) (Warning, error) {
|
||||
func (v *ConfigValidator) ValidateServerConfig(c *v1.ServerConfig) (Warning, error) {
|
||||
var (
|
||||
warnings Warning
|
||||
errs error
|
||||
@@ -42,6 +43,11 @@ func ValidateServerConfig(c *v1.ServerConfig) (Warning, error) {
|
||||
|
||||
// Validate tokenSource if specified
|
||||
if c.Auth.TokenSource != nil {
|
||||
if c.Auth.TokenSource.Type == "exec" {
|
||||
if err := v.ValidateUnsafeFeature(security.TokenSourceExec); err != nil {
|
||||
errs = AppendError(errs, err)
|
||||
}
|
||||
}
|
||||
if err := c.Auth.TokenSource.Validate(); err != nil {
|
||||
errs = AppendError(errs, fmt.Errorf("invalid auth.tokenSource: %v", err))
|
||||
}
|
||||
|
||||
28
pkg/config/v1/validation/validator.go
Normal file
28
pkg/config/v1/validation/validator.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package validation
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/fatedier/frp/pkg/policy/security"
|
||||
)
|
||||
|
||||
// ConfigValidator holds the context dependencies for configuration validation.
|
||||
type ConfigValidator struct {
|
||||
unsafeFeatures *security.UnsafeFeatures
|
||||
}
|
||||
|
||||
// NewConfigValidator creates a new ConfigValidator instance.
|
||||
func NewConfigValidator(unsafeFeatures *security.UnsafeFeatures) *ConfigValidator {
|
||||
return &ConfigValidator{
|
||||
unsafeFeatures: unsafeFeatures,
|
||||
}
|
||||
}
|
||||
|
||||
// ValidateUnsafeFeature checks if a specific unsafe feature is enabled.
|
||||
func (v *ConfigValidator) ValidateUnsafeFeature(feature string) error {
|
||||
if !v.unsafeFeatures.IsEnabled(feature) {
|
||||
return fmt.Errorf("unsafe feature %q is not enabled. "+
|
||||
"To enable it, ensure it is allowed in the configuration or command line flags", feature)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -206,8 +206,9 @@ func (m *serverMetrics) GetProxiesByType(proxyType string) []*ProxyStats {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
filterAll := proxyType == "" || proxyType == "all"
|
||||
for name, proxyStats := range m.info.ProxyStatistics {
|
||||
if proxyStats.ProxyType != proxyType {
|
||||
if !filterAll && proxyStats.ProxyType != proxyType {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -233,8 +234,9 @@ func (m *serverMetrics) GetProxiesByTypeAndName(proxyType string, proxyName stri
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
filterAll := proxyType == "" || proxyType == "all"
|
||||
for name, proxyStats := range m.info.ProxyStatistics {
|
||||
if proxyStats.ProxyType != proxyType {
|
||||
if !filterAll && proxyStats.ProxyType != proxyType {
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
@@ -220,7 +220,7 @@ func (c *Controller) HandleVisitor(m *msg.NatHoleVisitor, transporter transport.
|
||||
// Make hole-punching decisions based on the NAT information of the client and visitor.
|
||||
vResp, cResp, err := c.analysis(session)
|
||||
if err != nil {
|
||||
log.Debugf("sid [%s] analysis error: %v", err)
|
||||
log.Debugf("sid [%s] analysis error: %v", sid, err)
|
||||
vResp = c.GenNatHoleResponse(session.visitorMsg.TransactionID, nil, err.Error())
|
||||
cResp = c.GenNatHoleResponse(session.clientMsg.TransactionID, nil, err.Error())
|
||||
}
|
||||
|
||||
12
pkg/util/banner/banner.go
Normal file
12
pkg/util/banner/banner.go
Normal file
@@ -0,0 +1,12 @@
|
||||
package banner
|
||||
|
||||
import "fmt"
|
||||
|
||||
func DisplayBanner() {
|
||||
fmt.Println(" __ ___ __________ ____ ________ ____")
|
||||
fmt.Println(" / / ____ / (_)___ _/ ____/ __ \\/ __ \\ / ____/ / / _/")
|
||||
fmt.Println(" / / / __ \\/ / / __ `/ /_ / /_/ / /_/ /_____/ / / / / / ")
|
||||
fmt.Println(" / /___/ /_/ / / / /_/ / __/ / _, _/ ____/_____/ /___/ /____/ / ")
|
||||
fmt.Println("/_____/\\____/_/_/\\__,_/_/ /_/ |_/_/ \\____/_____/___/ ")
|
||||
fmt.Println(" ")
|
||||
}
|
||||
@@ -16,13 +16,18 @@ package log
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/fatedier/golib/log"
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
"github.com/charmbracelet/log"
|
||||
)
|
||||
|
||||
var (
|
||||
TraceLevel = log.TraceLevel
|
||||
TraceLevel = log.DebugLevel
|
||||
DebugLevel = log.DebugLevel
|
||||
InfoLevel = log.InfoLevel
|
||||
WarnLevel = log.WarnLevel
|
||||
@@ -32,39 +37,158 @@ var (
|
||||
var Logger *log.Logger
|
||||
|
||||
func init() {
|
||||
Logger = log.New(
|
||||
log.WithCaller(true),
|
||||
log.AddCallerSkip(1),
|
||||
log.WithLevel(log.InfoLevel),
|
||||
)
|
||||
Logger = log.NewWithOptions(os.Stderr, log.Options{
|
||||
ReportCaller: true,
|
||||
ReportTimestamp: true,
|
||||
TimeFormat: time.Kitchen,
|
||||
Prefix: "LoliaFRP-CLI",
|
||||
CallerOffset: 1,
|
||||
})
|
||||
// 设置自定义样式以支持 Trace 级别
|
||||
styles := log.DefaultStyles()
|
||||
styles.Levels[TraceLevel] = lipgloss.NewStyle().
|
||||
SetString("TRACE").
|
||||
Bold(true).
|
||||
MaxWidth(5).
|
||||
Foreground(lipgloss.Color("61"))
|
||||
Logger.SetStyles(styles)
|
||||
}
|
||||
|
||||
func InitLogger(logPath string, levelStr string, maxDays int, disableLogColor bool) {
|
||||
options := []log.Option{}
|
||||
var output io.Writer
|
||||
var err error
|
||||
|
||||
if logPath == "console" {
|
||||
if !disableLogColor {
|
||||
options = append(options,
|
||||
log.WithOutput(log.NewConsoleWriter(log.ConsoleConfig{
|
||||
Colorful: true,
|
||||
}, os.Stdout)),
|
||||
)
|
||||
}
|
||||
output = os.Stdout
|
||||
} else {
|
||||
writer := log.NewRotateFileWriter(log.RotateFileConfig{
|
||||
FileName: logPath,
|
||||
Mode: log.RotateFileModeDaily,
|
||||
MaxDays: maxDays,
|
||||
})
|
||||
writer.Init()
|
||||
options = append(options, log.WithOutput(writer))
|
||||
// Use rotating file writer
|
||||
output, err = NewRotateFileWriter(logPath, maxDays)
|
||||
if err != nil {
|
||||
// Fallback to console if file creation fails
|
||||
output = os.Stdout
|
||||
}
|
||||
}
|
||||
|
||||
level, err := log.ParseLevel(levelStr)
|
||||
if err != nil {
|
||||
level = log.InfoLevel
|
||||
}
|
||||
options = append(options, log.WithLevel(level))
|
||||
Logger = Logger.WithOptions(options...)
|
||||
|
||||
Logger = log.NewWithOptions(output, log.Options{
|
||||
ReportCaller: true,
|
||||
ReportTimestamp: true,
|
||||
TimeFormat: time.Kitchen,
|
||||
Prefix: "LoliaFRP-CLI",
|
||||
CallerOffset: 1,
|
||||
Level: level,
|
||||
})
|
||||
}
|
||||
|
||||
// NewRotateFileWriter creates a rotating file writer
|
||||
func NewRotateFileWriter(filePath string, maxDays int) (*RotateFileWriter, error) {
|
||||
w := &RotateFileWriter{
|
||||
filePath: filePath,
|
||||
maxDays: maxDays,
|
||||
lastRotate: time.Now(),
|
||||
currentDate: time.Now().Format("2006-01-02"),
|
||||
}
|
||||
|
||||
if err := w.openFile(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return w, nil
|
||||
}
|
||||
|
||||
// RotateFileWriter implements io.Writer with daily rotation
|
||||
type RotateFileWriter struct {
|
||||
filePath string
|
||||
maxDays int
|
||||
file *os.File
|
||||
lastRotate time.Time
|
||||
currentDate string
|
||||
}
|
||||
|
||||
func (w *RotateFileWriter) openFile() error {
|
||||
var err error
|
||||
w.file, err = os.OpenFile(w.filePath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o644)
|
||||
return err
|
||||
}
|
||||
|
||||
func (w *RotateFileWriter) checkRotate() error {
|
||||
now := time.Now()
|
||||
currentDate := now.Format("2006-01-02")
|
||||
|
||||
if currentDate != w.currentDate {
|
||||
// Close current file
|
||||
if w.file != nil {
|
||||
w.file.Close()
|
||||
}
|
||||
|
||||
// Rename current file with date suffix
|
||||
oldPath := w.filePath
|
||||
newPath := w.filePath + "." + w.currentDate
|
||||
if _, err := os.Stat(oldPath); err == nil {
|
||||
if err := os.Rename(oldPath, newPath); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Clean up old log files
|
||||
w.cleanupOldLogs(now)
|
||||
|
||||
// Update current date and open new file
|
||||
w.currentDate = currentDate
|
||||
w.lastRotate = now
|
||||
return w.openFile()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *RotateFileWriter) cleanupOldLogs(now time.Time) {
|
||||
if w.maxDays <= 0 {
|
||||
return
|
||||
}
|
||||
|
||||
cutoffDate := now.AddDate(0, 0, -w.maxDays)
|
||||
|
||||
// Find and remove old log files
|
||||
dir := filepath.Dir(w.filePath)
|
||||
base := filepath.Base(w.filePath)
|
||||
|
||||
files, _ := os.ReadDir(dir)
|
||||
for _, f := range files {
|
||||
if f.IsDir() {
|
||||
continue
|
||||
}
|
||||
|
||||
name := f.Name()
|
||||
if strings.HasPrefix(name, base+".") {
|
||||
// Extract date from filename (base.YYYY-MM-DD)
|
||||
dateStr := strings.TrimPrefix(name, base+".")
|
||||
if len(dateStr) == 10 {
|
||||
fileDate, err := time.Parse("2006-01-02", dateStr)
|
||||
if err == nil && fileDate.Before(cutoffDate) {
|
||||
os.Remove(filepath.Join(dir, name))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (w *RotateFileWriter) Write(p []byte) (n int, err error) {
|
||||
if err := w.checkRotate(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return w.file.Write(p)
|
||||
}
|
||||
|
||||
func (w *RotateFileWriter) Close() error {
|
||||
if w.file != nil {
|
||||
return w.file.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func Errorf(format string, v ...any) {
|
||||
@@ -75,6 +199,10 @@ func Warnf(format string, v ...any) {
|
||||
Logger.Warnf(format, v...)
|
||||
}
|
||||
|
||||
func Info(format string, v ...any) {
|
||||
Logger.Info(format, v...)
|
||||
}
|
||||
|
||||
func Infof(format string, v ...any) {
|
||||
Logger.Infof(format, v...)
|
||||
}
|
||||
@@ -84,11 +212,12 @@ func Debugf(format string, v ...any) {
|
||||
}
|
||||
|
||||
func Tracef(format string, v ...any) {
|
||||
Logger.Tracef(format, v...)
|
||||
Logger.Logf(TraceLevel, format, v...)
|
||||
}
|
||||
|
||||
func Logf(level log.Level, offset int, format string, v ...any) {
|
||||
Logger.Logf(level, offset, format, v...)
|
||||
// charmbracelet/log doesn't support offset, so we ignore it
|
||||
Logger.Logf(level, format, v...)
|
||||
}
|
||||
|
||||
type WriteLogger struct {
|
||||
@@ -104,6 +233,8 @@ func NewWriteLogger(level log.Level, offset int) *WriteLogger {
|
||||
}
|
||||
|
||||
func (w *WriteLogger) Write(p []byte) (n int, err error) {
|
||||
Logger.Log(w.level, w.offset, string(bytes.TrimRight(p, "\n")))
|
||||
// charmbracelet/log doesn't support offset in Log
|
||||
msg := string(bytes.TrimRight(p, "\n"))
|
||||
Logger.Log(w.level, msg)
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
package version
|
||||
|
||||
var version = "0.65.0"
|
||||
var version = "LoliaFRP-CLI 0.66.2"
|
||||
|
||||
func Full() string {
|
||||
return version
|
||||
|
||||
@@ -28,23 +28,70 @@ var NotFoundPagePath = ""
|
||||
|
||||
const (
|
||||
NotFound = `<!DOCTYPE html>
|
||||
<html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<title>Not Found</title>
|
||||
<style>
|
||||
body {
|
||||
width: 35em;
|
||||
margin: 0 auto;
|
||||
font-family: Tahoma, Verdana, Arial, sans-serif;
|
||||
}
|
||||
</style>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>404 - 未绑定域名</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: -apple-system, sans-serif;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 100vh;
|
||||
margin: 0;
|
||||
background: #fff;
|
||||
color: #333;
|
||||
}
|
||||
.container {
|
||||
max-width: 600px;
|
||||
padding: 40px 20px;
|
||||
text-align: center;
|
||||
}
|
||||
h1 {
|
||||
font-size: 32px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
p {
|
||||
line-height: 1.8;
|
||||
color: #666;
|
||||
margin: 10px 0;
|
||||
}
|
||||
ul {
|
||||
text-align: left;
|
||||
margin: 20px auto;
|
||||
max-width: 400px;
|
||||
}
|
||||
li {
|
||||
margin: 8px 0;
|
||||
color: #666;
|
||||
}
|
||||
a {
|
||||
color: #0066cc;
|
||||
text-decoration: none;
|
||||
}
|
||||
a:hover { text-decoration: underline; }
|
||||
.footer {
|
||||
margin-top: 40px;
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>The page you requested was not found.</h1>
|
||||
<p>Sorry, the page you are looking for is currently unavailable.<br/>
|
||||
Please try again later.</p>
|
||||
<p>The server is powered by <a href="https://github.com/fatedier/frp">frp</a>.</p>
|
||||
<p><em>Faithfully yours, frp.</em></p>
|
||||
<div class="container">
|
||||
<h1>域名未绑定</h1>
|
||||
<p>这个域名还没有绑定到任何隧道哦 (;д;)</p>
|
||||
<p><strong>可能是这些原因:</strong></p>
|
||||
<ul>
|
||||
<li>域名配置不对,或者没有正确解析</li>
|
||||
<li>隧道可能还没启动,或者已经停止</li>
|
||||
<li>自定义域名忘记在服务端配置了</li>
|
||||
</ul>
|
||||
<div class="footer">由 <a href="https://lolia.link/">LoliaFRP</a> 与捐赠者们用爱发电</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
`
|
||||
@@ -69,7 +116,7 @@ func getNotFoundPageContent() []byte {
|
||||
|
||||
func NotFoundResponse() *http.Response {
|
||||
header := make(http.Header)
|
||||
header.Set("server", "frp/"+version.Full())
|
||||
header.Set("server", version.Full())
|
||||
header.Set("Content-Type", "text/html")
|
||||
|
||||
content := getNotFoundPageContent()
|
||||
|
||||
@@ -111,5 +111,5 @@ func (l *Logger) Debugf(format string, v ...any) {
|
||||
}
|
||||
|
||||
func (l *Logger) Tracef(format string, v ...any) {
|
||||
log.Logger.Tracef(l.prefixString+format, v...)
|
||||
log.Logger.Logf(log.TraceLevel, l.prefixString+format, v...)
|
||||
}
|
||||
|
||||
@@ -94,6 +94,51 @@ func (cm *ControlManager) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// CloseAllProxyByName Finds the tunnel name and closes all tunnels on the same connection.
|
||||
func (cm *ControlManager) CloseAllProxyByName(proxyName string) error {
|
||||
cm.mu.RLock()
|
||||
var target *Control
|
||||
for _, ctl := range cm.ctlsByRunID {
|
||||
ctl.mu.RLock()
|
||||
_, ok := ctl.proxies[proxyName]
|
||||
ctl.mu.RUnlock()
|
||||
if ok {
|
||||
target = ctl
|
||||
break
|
||||
}
|
||||
}
|
||||
cm.mu.RUnlock()
|
||||
if target == nil {
|
||||
return fmt.Errorf("no proxy found with name [%s]", proxyName)
|
||||
}
|
||||
return target.Close()
|
||||
}
|
||||
|
||||
// KickByProxyName finds the Control that manages the given proxy (tunnel) name and closes
|
||||
// Bug: The client does not display the kickout message.
|
||||
func (cm *ControlManager) KickByProxyName(proxyName string) error {
|
||||
cm.mu.RLock()
|
||||
var target *Control
|
||||
for _, ctl := range cm.ctlsByRunID {
|
||||
ctl.mu.RLock()
|
||||
_, ok := ctl.proxies[proxyName]
|
||||
ctl.mu.RUnlock()
|
||||
if ok {
|
||||
target = ctl
|
||||
break
|
||||
}
|
||||
}
|
||||
cm.mu.RUnlock()
|
||||
|
||||
if target == nil {
|
||||
return fmt.Errorf("no proxy found with name [%s]", proxyName)
|
||||
}
|
||||
|
||||
xl := target.xl
|
||||
xl.Infof("kick client with proxy [%s] by server administrator request", proxyName)
|
||||
return target.Close()
|
||||
}
|
||||
|
||||
type Control struct {
|
||||
// all resource managers and controllers
|
||||
rc *controller.ResourceController
|
||||
@@ -106,6 +151,8 @@ type Control struct {
|
||||
|
||||
// verifies authentication based on selected method
|
||||
authVerifier auth.Verifier
|
||||
// key used for connection encryption
|
||||
encryptionKey []byte
|
||||
|
||||
// other components can use this to communicate with client
|
||||
msgTransporter transport.MessageTransporter
|
||||
@@ -157,6 +204,7 @@ func NewControl(
|
||||
pxyManager *proxy.Manager,
|
||||
pluginManager *plugin.Manager,
|
||||
authVerifier auth.Verifier,
|
||||
encryptionKey []byte,
|
||||
ctlConn net.Conn,
|
||||
ctlConnEncrypted bool,
|
||||
loginMsg *msg.Login,
|
||||
@@ -171,6 +219,7 @@ func NewControl(
|
||||
pxyManager: pxyManager,
|
||||
pluginManager: pluginManager,
|
||||
authVerifier: authVerifier,
|
||||
encryptionKey: encryptionKey,
|
||||
conn: ctlConn,
|
||||
loginMsg: loginMsg,
|
||||
workConnCh: make(chan net.Conn, poolCount+10),
|
||||
@@ -186,7 +235,7 @@ func NewControl(
|
||||
ctl.lastPing.Store(time.Now())
|
||||
|
||||
if ctlConnEncrypted {
|
||||
cryptoRW, err := netpkg.NewCryptoReadWriter(ctl.conn, []byte(ctl.serverCfg.Auth.Token))
|
||||
cryptoRW, err := netpkg.NewCryptoReadWriter(ctl.conn, ctl.encryptionKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -478,6 +527,7 @@ func (ctl *Control) RegisterProxy(pxyMsg *msg.NewProxy) (remoteAddr string, err
|
||||
GetWorkConnFn: ctl.GetWorkConn,
|
||||
Configurer: pxyConf,
|
||||
ServerCfg: ctl.serverCfg,
|
||||
EncryptionKey: ctl.encryptionKey,
|
||||
})
|
||||
if err != nil {
|
||||
return remoteAddr, err
|
||||
|
||||
@@ -52,8 +52,11 @@ func (svr *Service) registerRouteHandlers(helper *httppkg.RouterRegisterHelper)
|
||||
subRouter.HandleFunc("/api/serverinfo", svr.apiServerInfo).Methods("GET")
|
||||
subRouter.HandleFunc("/api/proxy/{type}", svr.apiProxyByType).Methods("GET")
|
||||
subRouter.HandleFunc("/api/proxy/{type}/{name}", svr.apiProxyByTypeAndName).Methods("GET")
|
||||
subRouter.HandleFunc("/api/proxy/{name}/close", svr.apiCloseProxyByName).Methods("POST")
|
||||
subRouter.HandleFunc("/api/proxy/{name}/kick", svr.apiKickProxyByName).Methods("POST")
|
||||
subRouter.HandleFunc("/api/traffic/{name}", svr.apiProxyTraffic).Methods("GET")
|
||||
subRouter.HandleFunc("/api/proxies", svr.deleteProxies).Methods("DELETE")
|
||||
subRouter.HandleFunc("/api/proxies", svr.apiProxiesAll).Methods("GET")
|
||||
|
||||
// view
|
||||
subRouter.Handle("/favicon.ico", http.FileServer(helper.AssetsFS)).Methods("GET")
|
||||
@@ -211,6 +214,29 @@ type GetProxyInfoResp struct {
|
||||
Proxies []*ProxyStatsInfo `json:"proxies"`
|
||||
}
|
||||
|
||||
// GET /api/proxies
|
||||
// Return all proxies across types (tcp, udp, http, https, stcp, xtcp, tcpmux)
|
||||
func (svr *Service) apiProxiesAll(w http.ResponseWriter, r *http.Request) {
|
||||
res := GeneralResponse{Code: 200}
|
||||
defer func() {
|
||||
log.Infof("http response [%s]: code [%d]", r.URL.Path, res.Code)
|
||||
w.WriteHeader(res.Code)
|
||||
if len(res.Msg) > 0 {
|
||||
_, _ = w.Write([]byte(res.Msg))
|
||||
}
|
||||
}()
|
||||
log.Infof("http request: [%s]", r.URL.Path)
|
||||
|
||||
proxyInfoResp := GetProxyInfoResp{}
|
||||
proxyInfoResp.Proxies = svr.getProxyStatsByType("all")
|
||||
slices.SortFunc(proxyInfoResp.Proxies, func(a, b *ProxyStatsInfo) int {
|
||||
return cmp.Compare(a.Name, b.Name)
|
||||
})
|
||||
|
||||
buf, _ := json.Marshal(&proxyInfoResp)
|
||||
res.Msg = string(buf)
|
||||
}
|
||||
|
||||
// /api/proxy/:type
|
||||
func (svr *Service) apiProxyByType(w http.ResponseWriter, r *http.Request) {
|
||||
res := GeneralResponse{Code: 200}
|
||||
@@ -237,6 +263,7 @@ func (svr *Service) apiProxyByType(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
func (svr *Service) getProxyStatsByType(proxyType string) (proxyInfos []*ProxyStatsInfo) {
|
||||
// mem.StatsCollector now supports proxyType=="all" or "" to return all proxies
|
||||
proxyStats := mem.StatsCollector.GetProxiesByType(proxyType)
|
||||
proxyInfos = make([]*ProxyStatsInfo, 0, len(proxyStats))
|
||||
for _, ps := range proxyStats {
|
||||
@@ -308,6 +335,69 @@ func (svr *Service) apiProxyByTypeAndName(w http.ResponseWriter, r *http.Request
|
||||
res.Msg = string(buf)
|
||||
}
|
||||
|
||||
// POST /api/proxy/:name/close
|
||||
// Close the proxy with given name only. The client connection remains active.
|
||||
func (svr *Service) apiCloseProxyByName(w http.ResponseWriter, r *http.Request) {
|
||||
res := GeneralResponse{Code: 200}
|
||||
params := mux.Vars(r)
|
||||
name := params["name"]
|
||||
|
||||
defer func() {
|
||||
log.Infof("http response [%s]: code [%d]", r.URL.Path, res.Code)
|
||||
w.WriteHeader(res.Code)
|
||||
if len(res.Msg) > 0 {
|
||||
_, _ = w.Write([]byte(res.Msg))
|
||||
}
|
||||
}()
|
||||
log.Infof("http request: [%s]", r.URL.Path)
|
||||
|
||||
if name == "" {
|
||||
res.Code = 400
|
||||
res.Msg = "proxy name required"
|
||||
return
|
||||
}
|
||||
|
||||
if err := svr.ctlManager.CloseAllProxyByName(name); err != nil {
|
||||
res.Code = 404
|
||||
res.Msg = err.Error()
|
||||
return
|
||||
}
|
||||
|
||||
res.Msg = "ok"
|
||||
}
|
||||
|
||||
// POST /api/proxy/:name/kick
|
||||
// Kick the client (frpc) that owns the proxy with given name.
|
||||
// This will disconnect the entire frpc client and close all its proxies.
|
||||
func (svr *Service) apiKickProxyByName(w http.ResponseWriter, r *http.Request) {
|
||||
res := GeneralResponse{Code: 200}
|
||||
params := mux.Vars(r)
|
||||
name := params["name"]
|
||||
|
||||
defer func() {
|
||||
log.Infof("http response [%s]: code [%d]", r.URL.Path, res.Code)
|
||||
w.WriteHeader(res.Code)
|
||||
if len(res.Msg) > 0 {
|
||||
_, _ = w.Write([]byte(res.Msg))
|
||||
}
|
||||
}()
|
||||
log.Infof("http request: [%s]", r.URL.Path)
|
||||
|
||||
if name == "" {
|
||||
res.Code = 400
|
||||
res.Msg = "proxy name required"
|
||||
return
|
||||
}
|
||||
|
||||
if err := svr.ctlManager.KickByProxyName(name); err != nil {
|
||||
res.Code = 404
|
||||
res.Msg = err.Error()
|
||||
return
|
||||
}
|
||||
|
||||
res.Msg = "ok"
|
||||
}
|
||||
|
||||
func (svr *Service) getProxyStatsByTypeAndName(proxyType string, proxyName string) (proxyInfo GetProxyStatsResp, code int, msg string) {
|
||||
proxyInfo.Name = proxyName
|
||||
ps := mem.StatsCollector.GetProxiesByTypeAndName(proxyType, proxyName)
|
||||
|
||||
@@ -165,7 +165,7 @@ func (pxy *HTTPProxy) GetRealConn(remoteAddr string) (workConn net.Conn, err err
|
||||
|
||||
var rwc io.ReadWriteCloser = tmpConn
|
||||
if pxy.cfg.Transport.UseEncryption {
|
||||
rwc, err = libio.WithEncryption(rwc, []byte(pxy.serverCfg.Auth.Token))
|
||||
rwc, err = libio.WithEncryption(rwc, pxy.encryptionKey)
|
||||
if err != nil {
|
||||
xl.Errorf("create encryption stream error: %v", err)
|
||||
return
|
||||
|
||||
@@ -68,6 +68,7 @@ type BaseProxy struct {
|
||||
poolCount int
|
||||
getWorkConnFn GetWorkConnFn
|
||||
serverCfg *v1.ServerConfig
|
||||
encryptionKey []byte
|
||||
limiter *rate.Limiter
|
||||
userInfo plugin.UserInfo
|
||||
loginMsg *msg.Login
|
||||
@@ -213,7 +214,6 @@ func (pxy *BaseProxy) handleUserTCPConnection(userConn net.Conn) {
|
||||
xl := xlog.FromContextSafe(pxy.Context())
|
||||
defer userConn.Close()
|
||||
|
||||
serverCfg := pxy.serverCfg
|
||||
cfg := pxy.configurer.GetBaseConfig()
|
||||
// server plugin hook
|
||||
rc := pxy.GetResourceController()
|
||||
@@ -240,7 +240,7 @@ func (pxy *BaseProxy) handleUserTCPConnection(userConn net.Conn) {
|
||||
xl.Tracef("handler user tcp connection, use_encryption: %t, use_compression: %t",
|
||||
cfg.Transport.UseEncryption, cfg.Transport.UseCompression)
|
||||
if cfg.Transport.UseEncryption {
|
||||
local, err = libio.WithEncryption(local, []byte(serverCfg.Auth.Token))
|
||||
local, err = libio.WithEncryption(local, pxy.encryptionKey)
|
||||
if err != nil {
|
||||
xl.Errorf("create encryption stream error: %v", err)
|
||||
return
|
||||
@@ -279,6 +279,7 @@ type Options struct {
|
||||
GetWorkConnFn GetWorkConnFn
|
||||
Configurer v1.ProxyConfigurer
|
||||
ServerCfg *v1.ServerConfig
|
||||
EncryptionKey []byte
|
||||
}
|
||||
|
||||
func NewProxy(ctx context.Context, options *Options) (pxy Proxy, err error) {
|
||||
@@ -298,6 +299,7 @@ func NewProxy(ctx context.Context, options *Options) (pxy Proxy, err error) {
|
||||
poolCount: options.PoolCount,
|
||||
getWorkConnFn: options.GetWorkConnFn,
|
||||
serverCfg: options.ServerCfg,
|
||||
encryptionKey: options.EncryptionKey,
|
||||
limiter: limiter,
|
||||
xl: xl,
|
||||
ctx: xlog.NewContext(ctx, xl),
|
||||
|
||||
@@ -205,7 +205,7 @@ func (pxy *UDPProxy) Run() (remoteAddr string, err error) {
|
||||
|
||||
var rwc io.ReadWriteCloser = workConn
|
||||
if pxy.cfg.Transport.UseEncryption {
|
||||
rwc, err = libio.WithEncryption(rwc, []byte(pxy.serverCfg.Auth.Token))
|
||||
rwc, err = libio.WithEncryption(rwc, pxy.encryptionKey)
|
||||
if err != nil {
|
||||
xl.Errorf("create encryption stream error: %v", err)
|
||||
workConn.Close()
|
||||
|
||||
@@ -113,8 +113,8 @@ type Service struct {
|
||||
|
||||
sshTunnelGateway *ssh.Gateway
|
||||
|
||||
// Verifies authentication based on selected method
|
||||
authVerifier auth.Verifier
|
||||
// Auth runtime and encryption materials
|
||||
auth *auth.ServerAuth
|
||||
|
||||
tlsConfig *tls.Config
|
||||
|
||||
@@ -149,6 +149,11 @@ func NewService(cfg *v1.ServerConfig) (*Service, error) {
|
||||
}
|
||||
}
|
||||
|
||||
authRuntime, err := auth.BuildServerAuth(&cfg.Auth)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
svr := &Service{
|
||||
ctlManager: NewControlManager(),
|
||||
pxyManager: proxy.NewManager(),
|
||||
@@ -160,7 +165,7 @@ func NewService(cfg *v1.ServerConfig) (*Service, error) {
|
||||
},
|
||||
sshTunnelListener: netpkg.NewInternalListener(),
|
||||
httpVhostRouter: vhost.NewRouters(),
|
||||
authVerifier: auth.NewAuthVerifier(cfg.Auth),
|
||||
auth: authRuntime,
|
||||
webServer: webServer,
|
||||
tlsConfig: tlsConfig,
|
||||
cfg: cfg,
|
||||
@@ -586,7 +591,7 @@ func (svr *Service) RegisterControl(ctlConn net.Conn, loginMsg *msg.Login, inter
|
||||
ctlConn.RemoteAddr().String(), loginMsg.Version, loginMsg.Hostname, loginMsg.Os, loginMsg.Arch)
|
||||
|
||||
// Check auth.
|
||||
authVerifier := svr.authVerifier
|
||||
authVerifier := svr.auth.Verifier
|
||||
if internal && loginMsg.ClientSpec.AlwaysAuthPass {
|
||||
authVerifier = auth.AlwaysPassVerifier
|
||||
}
|
||||
@@ -595,7 +600,7 @@ func (svr *Service) RegisterControl(ctlConn net.Conn, loginMsg *msg.Login, inter
|
||||
}
|
||||
|
||||
// TODO(fatedier): use SessionContext
|
||||
ctl, err := NewControl(ctx, svr.rc, svr.pxyManager, svr.pluginManager, authVerifier, ctlConn, !internal, loginMsg, svr.cfg)
|
||||
ctl, err := NewControl(ctx, svr.rc, svr.pxyManager, svr.pluginManager, authVerifier, svr.auth.EncryptionKey(), ctlConn, !internal, loginMsg, svr.cfg)
|
||||
if err != nil {
|
||||
xl.Warnf("create new controller error: %v", err)
|
||||
// don't return detailed errors to client
|
||||
|
||||
Reference in New Issue
Block a user