Merge branch 'dev'

This commit is contained in:
alex 2023-04-20 10:09:33 +08:00
commit a68dedf3b9
48 changed files with 5024 additions and 202 deletions

10
.github/pull_request_template.md vendored Normal file
View File

@ -0,0 +1,10 @@
### Summary
copilot:summary
### WHY
<!-- author to complete -->
### Walkthrough
copilot:walkthrough

View File

@ -6,6 +6,23 @@
[README](README.md) | [中文文档](README_zh.md)
> This version adds a navigation page that automatically reads TCP proxy on the basis of the original Dashboard. The page uses the rarely used empty attributes `group_key` and `group` to pass the direct connection address and navigation image. For non-HTTP links, this content does not need to be configured (such as SSH), in which case the navigation page will ignore the proxy item. The navigation icon can be configured using the group field and can be in BASE64 format or a linkable URL. Here is an example configuration:
```
[Nextcloud]
type = tcp
local_ip = 127.0.0.1
local_port = 3000
remote_port = 6000
group = data:image/svg+xml;base64,PHN2ZyB0PSIxNjgxOTA4ODAyODYxIiBjbGFzcz0iaWNvbiIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHAtaWQ9IjEyMDgiIHdpZHRoPSIyMDAiIGhlaWdodD0iMjAwIj48cGF0aCBkPSJNMTAyLjQgMTAyLjRtMzA3LjIgMGwyMDQuOCAwcTMwNy4yIDAgMzA3LjIgMzA3LjJsMCAyMDQuOHEwIDMwNy4yLTMwNy4yIDMwNy4ybC0yMDQuOCAwcS0zMDcuMiAwLTMwNy4yLTMwNy4ybDAtMjA0LjhxMC0zMDcuMiAzMDcuMi0zMDcuMloiIGZpbGw9IiM1MENDQ0MiIHAtaWQ9IjEyMDkiPjwvcGF0aD48cGF0aCBkPSJNNTEzLjcwNDk2IDMwNy4yQzU4OS4xMTIzMiAzMDcuMiA2NTAuMjQgMzY4LjMzMjggNjUwLjI0IDQ0My43MzUwNGM3NS40MDczNiAwIDEzNi41MzUwNCA2MS4xMjc2OCAxMzYuNTM1MDQgMTM2LjUyOTkyIDAgNzUuNDA3MzYtNjEuMTMyOCAxMzYuNTM1MDQtMTM2LjUzNTA0IDEzNi41MzUwNEgzNzcuMTc1MDRDMzAxLjc2NzY4IDcxNi44IDI0MC42NCA2NTUuNjY3MiAyNDAuNjQgNTgwLjI2NDk2YzAtNzUuNDAyMjQgNjEuMTMyOC0xMzYuNTI5OTIgMTM2LjUzNTA0LTEzNi41Mjk5MmwwLjAxNTM2LTIuMjU3OTJDMzc4LjM5ODcyIDM2Ny4xMDQgNDM5LjA1NTM2IDMwNy4yIDUxMy43MDQ5NiAzMDcuMnoiIGZpbGw9IiNGRkZGRkYiIGZpbGwtb3BhY2l0eT0iLjgiIHAtaWQ9IjEyMTAiPjwvcGF0aD48L3N2Zz4=
group_key = https://nextcloud.domain.com
```
And it looks like:
![image](https://raw.githubusercontent.com/synebula/frp-with-navigation-page/dev/doc/pic/navigation.png)
<h3 align="center">Gold Sponsors</h3>
<!--gold sponsors start-->
<p align="center">

View File

@ -7,6 +7,22 @@
frp 是一个专注于内网穿透的高性能的反向代理应用,支持 TCP、UDP、HTTP、HTTPS 等多种协议。可以将内网服务以安全、便捷的方式通过具有公网 IP 节点的中转暴露到公网。
> 该版本在原版Dashboard基础上增加了一个自动读取TCP代理的导航页面。 该页面利用了不常用的空置属性`group_key`和`group`来传递直连地址和导航图片对于非HTTP链接可以可以不用配置这部分内容(如SSH),这种情况下导航页面会忽略该代理项。使用`group`字段配置导航图标可以是BASE64格式也可以是可链接地URL配置示例
```
[Nextcloud]
type = tcp
local_ip = 127.0.0.1
local_port = 3000
remote_port = 6000
group = data:image/svg+xml;base64,PHN2ZyB0PSIxNjgxOTA4ODAyODYxIiBjbGFzcz0iaWNvbiIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHAtaWQ9IjEyMDgiIHdpZHRoPSIyMDAiIGhlaWdodD0iMjAwIj48cGF0aCBkPSJNMTAyLjQgMTAyLjRtMzA3LjIgMGwyMDQuOCAwcTMwNy4yIDAgMzA3LjIgMzA3LjJsMCAyMDQuOHEwIDMwNy4yLTMwNy4yIDMwNy4ybC0yMDQuOCAwcS0zMDcuMiAwLTMwNy4yLTMwNy4ybDAtMjA0LjhxMC0zMDcuMiAzMDcuMi0zMDcuMloiIGZpbGw9IiM1MENDQ0MiIHAtaWQ9IjEyMDkiPjwvcGF0aD48cGF0aCBkPSJNNTEzLjcwNDk2IDMwNy4yQzU4OS4xMTIzMiAzMDcuMiA2NTAuMjQgMzY4LjMzMjggNjUwLjI0IDQ0My43MzUwNGM3NS40MDczNiAwIDEzNi41MzUwNCA2MS4xMjc2OCAxMzYuNTM1MDQgMTM2LjUyOTkyIDAgNzUuNDA3MzYtNjEuMTMyOCAxMzYuNTM1MDQtMTM2LjUzNTA0IDEzNi41MzUwNEgzNzcuMTc1MDRDMzAxLjc2NzY4IDcxNi44IDI0MC42NCA2NTUuNjY3MiAyNDAuNjQgNTgwLjI2NDk2YzAtNzUuNDAyMjQgNjEuMTMyOC0xMzYuNTI5OTIgMTM2LjUzNTA0LTEzNi41Mjk5MmwwLjAxNTM2LTIuMjU3OTJDMzc4LjM5ODcyIDM2Ny4xMDQgNDM5LjA1NTM2IDMwNy4yIDUxMy43MDQ5NiAzMDcuMnoiIGZpbGw9IiNGRkZGRkYiIGZpbGwtb3BhY2l0eT0iLjgiIHAtaWQ9IjEyMTAiPjwvcGF0aD48L3N2Zz4=
group_key = https://nextcloud.domain.com
```
导航效果图:
![image](https://raw.githubusercontent.com/synebula/frp-with-navigation-page/dev/doc/pic/navigation.png)
<h3 align="center">Gold Sponsors</h3>
<!--gold sponsors start-->
<p align="center">

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -4,8 +4,8 @@
<head>
<meta charset="utf-8">
<title>frp client admin UI</title>
<script type="module" crossorigin src="./index-7dd223da.js"></script>
<link rel="stylesheet" href="./index-aa3c7267.css">
<script type="module" crossorigin src="./index-1c7ed8b0.js"></script>
<link rel="stylesheet" href="./index-1e2a7ce0.css">
</head>
<body>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -4,8 +4,8 @@
<head>
<meta charset="utf-8">
<title>frps dashboard</title>
<script type="module" crossorigin src="./index-b8250b3f.js"></script>
<link rel="stylesheet" href="./index-7b4711f8.css">
<script type="module" crossorigin src="./index-93e38bbf.js"></script>
<link rel="stylesheet" href="./index-1e0c7400.css">
</head>
<body>

View File

@ -305,8 +305,12 @@ func (pxy *XTCPProxy) InWorkConn(conn net.Conn, m *msg.StartWorkConn) {
ProxyName: pxy.cfg.ProxyName,
Sid: natHoleSidMsg.Sid,
}
serverAddr := pxy.clientCfg.NatHoleServerAddr
if serverAddr == "" {
serverAddr = pxy.clientCfg.ServerAddr
}
raddr, _ := net.ResolveUDPAddr("udp",
net.JoinHostPort(pxy.clientCfg.ServerAddr, strconv.Itoa(pxy.serverUDPPort)))
net.JoinHostPort(serverAddr, strconv.Itoa(pxy.serverUDPPort)))
clientConn, err := net.DialUDP("udp", nil, raddr)
if err != nil {
xl.Error("dial server udp addr error: %v", err)
@ -815,6 +819,9 @@ func HandleTCPWorkConnection(ctx context.Context, localInfo *config.LocalSvrConf
}
}
frpIo.Join(localConn, remote)
_, _, errs := frpIo.Join(localConn, remote)
xl.Debug("join connections closed")
if len(errs) > 0 {
xl.Trace("join connections errors: %v", errs)
}
}

View File

@ -210,8 +210,12 @@ func (sv *XTCPVisitor) handleConn(userConn net.Conn) {
return
}
serverAddr := sv.ctl.clientCfg.NatHoleServerAddr
if serverAddr == "" {
serverAddr = sv.ctl.clientCfg.ServerAddr
}
raddr, err := net.ResolveUDPAddr("udp",
net.JoinHostPort(sv.ctl.clientCfg.ServerAddr, strconv.Itoa(sv.ctl.serverUDPPort)))
net.JoinHostPort(serverAddr, strconv.Itoa(sv.ctl.serverUDPPort)))
if err != nil {
xl.Error("resolve server UDP addr error")
return
@ -335,8 +339,11 @@ func (sv *XTCPVisitor) handleConn(userConn net.Conn) {
muxConnRWCloser = frpIo.WithCompression(muxConnRWCloser)
}
frpIo.Join(userConn, muxConnRWCloser)
_, _, errs := frpIo.Join(userConn, muxConnRWCloser)
xl.Debug("join connections closed")
if len(errs) > 0 {
xl.Trace("join connections errors: %v", errs)
}
}
type SUDPVisitor struct {

102
cmd/frpc/sub/nathole.go Normal file
View File

@ -0,0 +1,102 @@
// Copyright 2023 The frp Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package sub
import (
"fmt"
"net"
"os"
"strconv"
"github.com/spf13/cobra"
"github.com/fatedier/frp/pkg/config"
"github.com/fatedier/frp/pkg/nathole"
)
var (
natHoleSTUNServer string
serverUDPPort int
)
func init() {
RegisterCommonFlags(natholeCmd)
rootCmd.AddCommand(natholeCmd)
natholeCmd.AddCommand(natholeDiscoveryCmd)
natholeCmd.PersistentFlags().StringVarP(&natHoleSTUNServer, "nat_hole_stun_server", "", "stun.easyvoip.com:3478", "STUN server address for nathole")
natholeCmd.PersistentFlags().IntVarP(&serverUDPPort, "server_udp_port", "", 0, "UDP port of frps for nathole")
}
var natholeCmd = &cobra.Command{
Use: "nathole",
Short: "Actions about nathole",
}
var natholeDiscoveryCmd = &cobra.Command{
Use: "discover",
Short: "Discover nathole information by frps and stun server",
RunE: func(cmd *cobra.Command, args []string) error {
// ignore error here, because we can use command line pameters
cfg, _, _, _ := config.ParseClientConfig(cfgFile)
if natHoleSTUNServer != "" {
cfg.NatHoleSTUNServer = natHoleSTUNServer
}
if serverUDPPort != 0 {
cfg.ServerUDPPort = serverUDPPort
}
if err := validateForNatHoleDiscovery(cfg); err != nil {
fmt.Println(err)
os.Exit(1)
}
serverAddr := ""
if cfg.ServerUDPPort != 0 {
serverAddr = net.JoinHostPort(cfg.ServerAddr, strconv.Itoa(cfg.ServerUDPPort))
}
addresses, err := nathole.Discover(
serverAddr,
[]string{cfg.NatHoleSTUNServer},
[]byte(cfg.Token),
)
if err != nil {
fmt.Println("discover error:", err)
os.Exit(1)
}
if len(addresses) < 2 {
fmt.Printf("discover error: can not get enough addresses, need 2, got: %v\n", addresses)
os.Exit(1)
}
natType, behavior, err := nathole.ClassifyNATType(addresses)
if err != nil {
fmt.Println("classify nat type error:", err)
os.Exit(1)
}
fmt.Println("Your NAT type is:", natType)
fmt.Println("Behavior is:", behavior)
fmt.Println("External address is:", addresses)
return nil
},
}
func validateForNatHoleDiscovery(cfg config.ClientCommonConf) error {
if cfg.NatHoleSTUNServer == "" {
return fmt.Errorf("nat_hole_stun_server can not be empty")
}
return nil
}

View File

@ -76,7 +76,7 @@ func init() {
rootCmd.PersistentFlags().IntVarP(&vhostHTTPPort, "vhost_http_port", "", 0, "vhost http port")
rootCmd.PersistentFlags().IntVarP(&vhostHTTPSPort, "vhost_https_port", "", 0, "vhost https port")
rootCmd.PersistentFlags().Int64VarP(&vhostHTTPTimeout, "vhost_http_timeout", "", 60, "vhost http response header timeout")
rootCmd.PersistentFlags().StringVarP(&dashboardAddr, "dashboard_addr", "", "0.0.0.0", "dasboard address")
rootCmd.PersistentFlags().StringVarP(&dashboardAddr, "dashboard_addr", "", "0.0.0.0", "dashboard address")
rootCmd.PersistentFlags().IntVarP(&dashboardPort, "dashboard_port", "", 0, "dashboard port")
rootCmd.PersistentFlags().StringVarP(&dashboardUser, "dashboard_user", "", "admin", "dashboard user")
rootCmd.PersistentFlags().StringVarP(&dashboardPwd, "dashboard_pwd", "", "admin", "dashboard password")

View File

@ -6,6 +6,17 @@
server_addr = 0.0.0.0
server_port = 7000
# Specify another address of the server to connect for nat hole. By default, it's same with
# server_addr.
# nat_hole_server_addr = 0.0.0.0
# ServerUDPPort specifies the server port to help penetrate NAT hole. By default, this value is 0.
# This parameter is only used when executing "nathole discover" in the command line.
# server_udp_port = 0
# STUN server to help penetrate NAT hole.
# nat_hole_stun_server = stun.easyvoip.com:3478
# The maximum amount of time a dial to server will wait for a connect to complete. Default value is 10 seconds.
# dial_server_timeout = 10
@ -247,7 +258,7 @@ local_ip = 127.0.0.1
local_port = 8000
use_encryption = false
use_compression = false
subdomain = web01
subdomain = web02
custom_domains = web02.yourdomain.com
# if not empty, frpc will use proxy protocol to transfer connection info to your local service
# v1 or v2 or empty

BIN
doc/pic/navigation.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 424 KiB

6
go.mod
View File

@ -6,7 +6,7 @@ require (
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5
github.com/coreos/go-oidc/v3 v3.4.0
github.com/fatedier/beego v0.0.0-20171024143340-6c6a4f5bd5eb
github.com/fatedier/golib v0.1.1-0.20220321042308-c306138b83ac
github.com/fatedier/golib v0.1.1-0.20230320133937-a7edcc8c793d
github.com/fatedier/kcp-go v2.0.4-0.20190803094908-fe8645b0a904+incompatible
github.com/go-playground/validator/v10 v10.11.0
github.com/google/uuid v1.3.0
@ -15,12 +15,13 @@ require (
github.com/hashicorp/yamux v0.1.1
github.com/onsi/ginkgo/v2 v2.8.3
github.com/onsi/gomega v1.27.0
github.com/pion/stun v0.4.0
github.com/pires/go-proxyproto v0.6.2
github.com/prometheus/client_golang v1.13.0
github.com/quic-go/quic-go v0.32.0
github.com/rodaine/table v1.0.1
github.com/spf13/cobra v1.1.3
github.com/stretchr/testify v1.8.0
github.com/stretchr/testify v1.8.1
golang.org/x/net v0.7.0
golang.org/x/oauth2 v0.3.0
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8
@ -48,6 +49,7 @@ require (
github.com/klauspost/reedsolomon v1.9.15 // indirect
github.com/leodido/go-urn v1.2.1 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
github.com/pion/transport/v2 v2.0.0 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_model v0.2.0 // indirect

25
go.sum
View File

@ -121,8 +121,8 @@ github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fatedier/beego v0.0.0-20171024143340-6c6a4f5bd5eb h1:wCrNShQidLmvVWn/0PikGmpdP0vtQmnvyRg3ZBEhczw=
github.com/fatedier/beego v0.0.0-20171024143340-6c6a4f5bd5eb/go.mod h1:wx3gB6dbIfBRcucp94PI9Bt3I0F2c/MyNEWuhzpWiwk=
github.com/fatedier/golib v0.1.1-0.20220321042308-c306138b83ac h1:td1FJwN/oz8+9GldeEm3YdBX0Husc0FSPywLesZxi4w=
github.com/fatedier/golib v0.1.1-0.20220321042308-c306138b83ac/go.mod h1:fLV0TLwHqrnB/L3jbNl67Gn6PCLggDGHniX1wLrA2Qo=
github.com/fatedier/golib v0.1.1-0.20230320133937-a7edcc8c793d h1:/m9Atycn9uKRwwOkxv4c+zaugxRgkdSG/Eg3IJWOpNs=
github.com/fatedier/golib v0.1.1-0.20230320133937-a7edcc8c793d/go.mod h1:Wdn1pJ0dHB1lah6FPYwt4AO9NEmWI0OzW13dpzC9g4E=
github.com/fatedier/kcp-go v2.0.4-0.20190803094908-fe8645b0a904+incompatible h1:ssXat9YXFvigNge/IkkZvFMn8yeYKFX+uI6wn2mLJ74=
github.com/fatedier/kcp-go v2.0.4-0.20190803094908-fe8645b0a904+incompatible/go.mod h1:YpCOaxj7vvMThhIQ9AfTOPW2sfztQR5WDfs7AflSy4s=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
@ -336,6 +336,11 @@ github.com/onsi/gomega v1.27.0 h1:QLidEla4bXUuZVFa4KX6JHCsuGgbi85LC/pCHrt/O08=
github.com/onsi/gomega v1.27.0/go.mod h1:i189pavgK95OSIipFBa74gC2V4qrQuvjuyGEr3GmbXA=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms=
github.com/pion/stun v0.4.0 h1:vgRrbBE2htWHy7l3Zsxckk7rkjnjOsSM7PHZnBwo8rk=
github.com/pion/stun v0.4.0/go.mod h1:QPsh1/SbXASntw3zkkrIk3ZJVKz4saBY2G7S10P3wCw=
github.com/pion/transport/v2 v2.0.0 h1:bsMYyqHCbkvHwj+eNCFBuxtlKndKfyGI2vaQmM3fIE4=
github.com/pion/transport/v2 v2.0.0/go.mod h1:HS2MEBJTwD+1ZI2eSXSvHJx/HnzQqRy2/LXxt6eVMHc=
github.com/pires/go-proxyproto v0.6.2 h1:KAZ7UteSOt6urjme6ZldyFm4wDe/z0ZUP0Yv0Dos0d8=
github.com/pires/go-proxyproto v0.6.2/go.mod h1:Odh9VFOZJCf9G8cLW5o435Xf1J95Jw9Gw5rnCjcwzAY=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
@ -415,6 +420,7 @@ github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5q
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
@ -422,8 +428,9 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161 h1:89CEmDvlq/F7SJEOqkIdNDGJXrQIhuIx9D2DBXjavSU=
github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161/go.mod h1:wM7WEvslTq+iOEAMDLSzhVuOt5BRZ05WirO+b09GHQU=
@ -439,6 +446,7 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
@ -459,6 +467,7 @@ golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.4.0 h1:UVQgzMY87xqpKNgb+kDsll2Igd33HszWHFLmpaRMq/8=
golang.org/x/crypto v0.4.0/go.mod h1:3quD/ATkf6oY+rnes5c3ExXTbLc8mueNue5/DoinL80=
@ -499,6 +508,7 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -551,7 +561,9 @@ golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@ -589,6 +601,7 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@ -659,11 +672,15 @@ golang.org/x/sys v0.0.0-20220502124256-b6088ccd6cba/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@ -673,6 +690,7 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@ -734,6 +752,7 @@ golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

View File

@ -35,9 +35,17 @@ type ClientCommonConf struct {
// ServerAddr specifies the address of the server to connect to. By
// default, this value is "0.0.0.0".
ServerAddr string `ini:"server_addr" json:"server_addr"`
// Specify another address of the server to connect for nat hole. By default, it's same with
// ServerAddr.
NatHoleServerAddr string `ini:"nat_hole_server_addr" json:"nat_hole_server_addr"`
// ServerPort specifies the port to connect to the server on. By default,
// this value is 7000.
ServerPort int `ini:"server_port" json:"server_port"`
// ServerUDPPort specifies the server port to help penetrate NAT hole. By default, this value is 0.
// This parameter is only used when executing "nathole discover" in the command line.
ServerUDPPort int `ini:"server_udp_port" json:"server_udp_port"`
// STUN server to help penetrate NAT hole.
NatHoleSTUNServer string `ini:"nat_hole_stun_server" json:"nat_hole_stun_server"`
// The maximum amount of time a dial to server will wait for a connect to complete.
DialServerTimeout int64 `ini:"dial_server_timeout" json:"dial_server_timeout"`
// DialServerKeepAlive specifies the interval between keep-alive probes for an active network connection between frpc and frps.
@ -169,6 +177,7 @@ func GetDefaultClientConf() ClientCommonConf {
ClientConfig: auth.GetDefaultClientConf(),
ServerAddr: "0.0.0.0",
ServerPort: 7000,
NatHoleSTUNServer: "stun.easyvoip.com:3478",
DialServerTimeout: 10,
DialServerKeepAlive: 7200,
HTTPProxy: os.Getenv("http_proxy"),

View File

@ -260,6 +260,7 @@ func Test_LoadClientCommonConf(t *testing.T) {
},
ServerAddr: "0.0.0.9",
ServerPort: 7009,
NatHoleSTUNServer: "stun.easyvoip.com:3478",
DialServerTimeout: 10,
DialServerKeepAlive: 7200,
HTTPProxy: "http://user:passwd@192.168.1.128:8080",

View File

@ -42,3 +42,7 @@ func ReadMsgInto(c io.Reader, msg Message) (err error) {
func WriteMsg(c io.Writer, msg interface{}) (err error) {
return msgCtl.WriteMsg(c, msg)
}
func Pack(msg interface{}) (data []byte, err error) {
return msgCtl.Pack(msg)
}

View File

@ -37,6 +37,8 @@ const (
TypeNatHoleResp = 'm'
TypeNatHoleClientDetectOK = 'd'
TypeNatHoleSid = '5'
TypeNatHoleBinding = 'b'
TypeNatHoleBindingResp = '6'
)
var msgTypeMap = map[byte]interface{}{
@ -58,6 +60,8 @@ var msgTypeMap = map[byte]interface{}{
TypeNatHoleResp: NatHoleResp{},
TypeNatHoleClientDetectOK: NatHoleClientDetectOK{},
TypeNatHoleSid: NatHoleSid{},
TypeNatHoleBinding: NatHoleBinding{},
TypeNatHoleBindingResp: NatHoleBindingResp{},
}
// When frpc start, client send this message to login to server.
@ -193,3 +197,13 @@ type NatHoleClientDetectOK struct{}
type NatHoleSid struct {
Sid string `json:"sid,omitempty"`
}
type NatHoleBinding struct {
TransactionID string `json:"transaction_id,omitempty"`
}
type NatHoleBindingResp struct {
TransactionID string `json:"transaction_id,omitempty"`
Address string `json:"address,omitempty"`
Error string `json:"error,omitempty"`
}

74
pkg/nathole/classify.go Normal file
View File

@ -0,0 +1,74 @@
// Copyright 2023 The frp Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package nathole
import (
"fmt"
"net"
)
const (
EasyNAT = "EasyNAT"
HardNAT = "HardNAT"
BehaviorNoChange = "BehaviorNoChange"
BehaviorIPChanged = "BehaviorIPChanged"
BehaviorPortChanged = "BehaviorPortChanged"
BehaviorBothChanged = "BehaviorBothChanged"
)
// ClassifyNATType classify NAT type by given addresses.
func ClassifyNATType(addresses []string) (string, string, error) {
if len(addresses) <= 1 {
return "", "", fmt.Errorf("not enough addresses")
}
ipChanged := false
portChanged := false
var baseIP, basePort string
for _, addr := range addresses {
ip, port, err := net.SplitHostPort(addr)
if err != nil {
return "", "", err
}
if baseIP == "" {
baseIP = ip
basePort = port
continue
}
if baseIP != ip {
ipChanged = true
}
if basePort != port {
portChanged = true
}
if ipChanged && portChanged {
break
}
}
switch {
case ipChanged && portChanged:
return HardNAT, BehaviorBothChanged, nil
case ipChanged:
return HardNAT, BehaviorIPChanged, nil
case portChanged:
return HardNAT, BehaviorPortChanged, nil
default:
return EasyNAT, BehaviorNoChange, nil
}
}

224
pkg/nathole/discovery.go Normal file
View File

@ -0,0 +1,224 @@
// Copyright 2023 The frp Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package nathole
import (
"fmt"
"net"
"time"
"github.com/pion/stun"
"github.com/fatedier/frp/pkg/msg"
)
var responseTimeout = 3 * time.Second
type Message struct {
Body []byte
Addr string
}
func Discover(serverAddress string, stunServers []string, key []byte) ([]string, error) {
// create a discoverConn and get response from messageChan
discoverConn, err := listen()
if err != nil {
return nil, err
}
defer discoverConn.Close()
go discoverConn.readLoop()
addresses := make([]string, 0, len(stunServers)+1)
if serverAddress != "" {
// get external address from frp server
externalAddr, err := discoverConn.discoverFromServer(serverAddress, key)
if err != nil {
return nil, err
}
addresses = append(addresses, externalAddr)
}
for _, addr := range stunServers {
// get external address from stun server
externalAddrs, err := discoverConn.discoverFromStunServer(addr)
if err != nil {
return nil, err
}
addresses = append(addresses, externalAddrs...)
}
return addresses, nil
}
type stunResponse struct {
externalAddr string
otherAddr string
}
type discoverConn struct {
conn *net.UDPConn
localAddr net.Addr
messageChan chan *Message
}
func listen() (*discoverConn, error) {
conn, err := net.ListenUDP("udp4", nil)
if err != nil {
return nil, err
}
return &discoverConn{
conn: conn,
localAddr: conn.LocalAddr(),
messageChan: make(chan *Message, 10),
}, nil
}
func (c *discoverConn) Close() error {
if c.messageChan != nil {
close(c.messageChan)
c.messageChan = nil
}
return c.conn.Close()
}
func (c *discoverConn) readLoop() {
for {
buf := make([]byte, 1024)
n, addr, err := c.conn.ReadFromUDP(buf)
if err != nil {
return
}
buf = buf[:n]
c.messageChan <- &Message{
Body: buf,
Addr: addr.String(),
}
}
}
func (c *discoverConn) doSTUNRequest(addr string) (*stunResponse, error) {
serverAddr, err := net.ResolveUDPAddr("udp4", addr)
if err != nil {
return nil, err
}
request, err := stun.Build(stun.TransactionID, stun.BindingRequest)
if err != nil {
return nil, err
}
if err = request.NewTransactionID(); err != nil {
return nil, err
}
if _, err := c.conn.WriteTo(request.Raw, serverAddr); err != nil {
return nil, err
}
var m stun.Message
select {
case msg := <-c.messageChan:
m.Raw = msg.Body
if err := m.Decode(); err != nil {
return nil, err
}
case <-time.After(responseTimeout):
return nil, fmt.Errorf("wait response from stun server timeout")
}
xorAddrGetter := &stun.XORMappedAddress{}
mappedAddrGetter := &stun.MappedAddress{}
changedAddrGetter := ChangedAddress{}
otherAddrGetter := &stun.OtherAddress{}
resp := &stunResponse{}
if err := mappedAddrGetter.GetFrom(&m); err == nil {
resp.externalAddr = mappedAddrGetter.String()
}
if err := xorAddrGetter.GetFrom(&m); err == nil {
resp.externalAddr = xorAddrGetter.String()
}
if err := changedAddrGetter.GetFrom(&m); err == nil {
resp.otherAddr = changedAddrGetter.String()
}
if err := otherAddrGetter.GetFrom(&m); err == nil {
resp.otherAddr = otherAddrGetter.String()
}
return resp, nil
}
func (c *discoverConn) discoverFromServer(serverAddress string, key []byte) (string, error) {
addr, err := net.ResolveUDPAddr("udp4", serverAddress)
if err != nil {
return "", err
}
m := &msg.NatHoleBinding{
TransactionID: NewTransactionID(),
}
buf, err := EncodeMessage(m, key)
if err != nil {
return "", err
}
if _, err := c.conn.WriteTo(buf, addr); err != nil {
return "", err
}
var respMsg msg.NatHoleBindingResp
select {
case rawMsg := <-c.messageChan:
if err := DecodeMessageInto(rawMsg.Body, key, &respMsg); err != nil {
return "", err
}
case <-time.After(responseTimeout):
return "", fmt.Errorf("wait response from frp server timeout")
}
if respMsg.TransactionID == "" {
return "", fmt.Errorf("error format: no transaction id found")
}
if respMsg.Error != "" {
return "", fmt.Errorf("get externalAddr from frp server error: %s", respMsg.Error)
}
return respMsg.Address, nil
}
func (c *discoverConn) discoverFromStunServer(addr string) ([]string, error) {
resp, err := c.doSTUNRequest(addr)
if err != nil {
return nil, err
}
if resp.externalAddr == "" {
return nil, fmt.Errorf("no external address found")
}
externalAddrs := make([]string, 0, 2)
externalAddrs = append(externalAddrs, resp.externalAddr)
if resp.otherAddr == "" {
return externalAddrs, nil
}
// find external address from changed address
resp, err = c.doSTUNRequest(resp.otherAddr)
if err != nil {
return nil, err
}
if resp.externalAddr != "" {
externalAddrs = append(externalAddrs, resp.externalAddr)
}
return externalAddrs, nil
}

View File

@ -1,3 +1,17 @@
// Copyright 2023 The frp Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package nathole
import (
@ -7,6 +21,7 @@ import (
"sync"
"time"
"github.com/fatedier/golib/crypto"
"github.com/fatedier/golib/errors"
"github.com/fatedier/golib/pool"
@ -18,6 +33,11 @@ import (
// NatHoleTimeout seconds.
var NatHoleTimeout int64 = 10
func NewTransactionID() string {
id, _ := util.RandID()
return fmt.Sprintf("%d%s", time.Now().Unix(), id)
}
type SidRequest struct {
Sid string
NotifyCh chan struct{}
@ -29,10 +49,11 @@ type Controller struct {
clientCfgs map[string]*ClientCfg
sessions map[string]*Session
mu sync.RWMutex
encryptionKey []byte
mu sync.RWMutex
}
func NewController(udpBindAddr string) (nc *Controller, err error) {
func NewController(udpBindAddr string, encryptionKey []byte) (nc *Controller, err error) {
addr, err := net.ResolveUDPAddr("udp", udpBindAddr)
if err != nil {
return nil, err
@ -42,9 +63,10 @@ func NewController(udpBindAddr string) (nc *Controller, err error) {
return nil, err
}
nc = &Controller{
listener: lconn,
clientCfgs: make(map[string]*ClientCfg),
sessions: make(map[string]*Session),
listener: lconn,
clientCfgs: make(map[string]*ClientCfg),
sessions: make(map[string]*Session),
encryptionKey: encryptionKey,
}
return nc, nil
}
@ -72,24 +94,30 @@ func (nc *Controller) Run() {
buf := pool.GetBuf(1024)
n, raddr, err := nc.listener.ReadFromUDP(buf)
if err != nil {
log.Trace("nat hole listener read from udp error: %v", err)
log.Warn("nat hole listener read from udp error: %v", err)
return
}
rd := bytes.NewReader(buf[:n])
rawMsg, err := msg.ReadMsg(rd)
plain, err := crypto.Decode(buf[:n], nc.encryptionKey)
if err != nil {
log.Trace("read nat hole message error: %v", err)
log.Warn("nathole listener decode from %s error: %v", raddr.String(), err)
continue
}
rawMsg, err := msg.ReadMsg(bytes.NewReader(plain))
if err != nil {
log.Warn("read nat hole message error: %v", err)
continue
}
switch m := rawMsg.(type) {
case *msg.NatHoleBinding:
go nc.HandleBinding(m, raddr)
case *msg.NatHoleVisitor:
go nc.HandleVisitor(m, raddr)
case *msg.NatHoleClient:
go nc.HandleClient(m, raddr)
default:
log.Trace("error nat hole message type")
log.Trace("unknown nat hole message type")
continue
}
pool.PutBuf(buf)
@ -102,6 +130,29 @@ func (nc *Controller) GenSid() string {
return fmt.Sprintf("%d%s", t, id)
}
func (nc *Controller) HandleBinding(m *msg.NatHoleBinding, raddr *net.UDPAddr) {
log.Trace("handle binding message from %s", raddr.String())
resp := &msg.NatHoleBindingResp{
TransactionID: m.TransactionID,
Address: raddr.String(),
}
plain, err := msg.Pack(resp)
if err != nil {
log.Error("pack nat hole binding response error: %v", err)
return
}
buf, err := crypto.Encode(plain, nc.encryptionKey)
if err != nil {
log.Error("encode nat hole binding response error: %v", err)
return
}
_, err = nc.listener.WriteToUDP(buf, raddr)
if err != nil {
log.Error("write nat hole binding response to %s error: %v", raddr.String(), err)
return
}
}
func (nc *Controller) HandleVisitor(m *msg.NatHoleVisitor, raddr *net.UDPAddr) {
sid := nc.GenSid()
session := &Session{

65
pkg/nathole/utils.go Normal file
View File

@ -0,0 +1,65 @@
// Copyright 2023 The frp Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package nathole
import (
"bytes"
"net"
"strconv"
"github.com/fatedier/golib/crypto"
"github.com/pion/stun"
"github.com/fatedier/frp/pkg/msg"
)
func EncodeMessage(m msg.Message, key []byte) ([]byte, error) {
buffer := bytes.NewBuffer(nil)
if err := msg.WriteMsg(buffer, m); err != nil {
return nil, err
}
buf, err := crypto.Encode(buffer.Bytes(), key)
if err != nil {
return nil, err
}
return buf, nil
}
func DecodeMessageInto(data, key []byte, m msg.Message) error {
buf, err := crypto.Decode(data, key)
if err != nil {
return err
}
if err := msg.ReadMsgInto(bytes.NewReader(buf), m); err != nil {
return err
}
return nil
}
type ChangedAddress struct {
IP net.IP
Port int
}
func (s *ChangedAddress) GetFrom(m *stun.Message) error {
a := (*stun.MappedAddress)(s)
return a.GetFromAs(m, stun.AttrChangedAddress)
}
func (s *ChangedAddress) String() string {
return net.JoinHostPort(s.IP.String(), strconv.Itoa(s.Port))
}

View File

@ -319,7 +319,7 @@ func HandleUserTCPConnection(pxy Proxy, userConn net.Conn, serverCfg config.Serv
name := pxy.GetName()
proxyType := pxy.GetConf().GetBaseInfo().ProxyType
metrics.Server.OpenConnection(name, proxyType)
inCount, outCount := frpIo.Join(local, userConn)
inCount, outCount, _ := frpIo.Join(local, userConn)
metrics.Server.CloseConnection(name, proxyType)
metrics.Server.AddTrafficIn(name, proxyType, inCount)
metrics.Server.AddTrafficOut(name, proxyType, outCount)

View File

@ -293,7 +293,7 @@ func NewService(cfg config.ServerCommonConf) (svr *Service, err error) {
if cfg.BindUDPPort > 0 {
var nc *nathole.Controller
address := net.JoinHostPort(cfg.BindAddr, strconv.Itoa(cfg.BindUDPPort))
nc, err = nathole.NewController(address)
nc, err = nathole.NewController(address, []byte(cfg.Token))
if err != nil {
err = fmt.Errorf("create nat hole controller error, %v", err)
return

View File

@ -11,8 +11,8 @@
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore"
},
"dependencies": {
"element-plus": "^2.2.28",
"vue": "^3.2.45",
"element-plus": "^2.3.3",
"vue": "^3.2.47",
"vue-router": "^4.1.6"
},
"devDependencies": {

View File

@ -1136,10 +1136,10 @@ electron-to-chromium@^1.4.284:
resolved "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.302.tgz"
integrity sha512-Uk7C+7aPBryUR1Fwvk9VmipBcN9fVsqBO57jV2ZjTm+IZ6BMNqu7EDVEg2HxCNufk6QcWlFsBkhQyQroB2VWKw==
element-plus@^2.2.28:
version "2.2.32"
resolved "https://registry.npmjs.org/element-plus/-/element-plus-2.2.32.tgz"
integrity sha512-DTJMhYOy6MApbmh6z/95hPTK5WrBiNHGzV4IN+uEkup1WoimQ+Qyt8RxKdTe/X1LWEJ8YgWv/Cl8P4ocrt5z5g==
element-plus@^2.3.3:
version "2.3.3"
resolved "https://registry.yarnpkg.com/element-plus/-/element-plus-2.3.3.tgz#33173bbbe84ada40f4d796fe5043c44781198ea4"
integrity sha512-Zy61OXrG6b4FF3h29A9ZOUkaEQXjCuFwNa7DlpB3Vo+42Tw5zBbHe5a4BY7i56TVJG5xTbS9UQyA726J91pDqg==
dependencies:
"@ctrl/tinycolor" "^3.4.1"
"@element-plus/icons-vue" "^2.0.6"
@ -3018,9 +3018,9 @@ vue-tsc@^1.0.12:
"@volar/vue-language-core" "1.1.4"
"@volar/vue-typescript" "1.1.4"
vue@^3.2.45:
vue@^3.2.47:
version "3.2.47"
resolved "https://registry.npmjs.org/vue/-/vue-3.2.47.tgz"
resolved "https://registry.yarnpkg.com/vue/-/vue-3.2.47.tgz#3eb736cbc606fc87038dbba6a154707c8a34cff0"
integrity sha512-60188y/9Dc9WVrAZeUVSDxRQOZ+z+y5nO2ts9jWXSTkMvayiWxCWOWtBQoYjLeccfXkiiPZWAHcV+WTPhkqJHQ==
dependencies:
"@vue/compiler-dom" "3.2.47"

View File

@ -3,6 +3,7 @@
<head>
<meta charset="utf-8">
<meta name="viewport" content="user-scalable=0,width=device-width, initial-scale=1.0">
<title>frps dashboard</title>
</head>

View File

@ -13,9 +13,9 @@
"dependencies": {
"@types/humanize-plus": "^1.8.0",
"echarts": "^5.4.1",
"element-plus": "^2.2.28",
"element-plus": "^2.3.3",
"humanize-plus": "^1.8.2",
"vue": "^3.2.45",
"vue": "^3.2.47",
"vue-router": "^4.1.6"
},
"devDependencies": {

108
web/frps/src/Home.vue Normal file
View File

@ -0,0 +1,108 @@
<template>
<div id="container">
<div id="wrap">
<div id="top">
<div id="logo">
<!--<img class="logo"-->
<!-- src="img/logo.png"-->
<!--/>-->
<!--如果需要自定义logo图片,使用上面这段代码,并删除下面这段代码,图片位置应放在img下的logo.png-->
<router-link to="/overview">
<img
class="logo"
src="data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBzdGFuZGFsb25lPSJubyI/PjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+PHN2ZyB0PSIxNjgxODk0Mjk2MzM3IiBjbGFzcz0iaWNvbiIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHAtaWQ9IjI2NjEiIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB3aWR0aD0iMjAwIiBoZWlnaHQ9IjIwMCI+PHBhdGggZD0iTTc4NS4wNjY2NjcgODUzLjMzMzMzM0gyMzguOTMzMzMzYy0xMzMuMTIgMC0yMzguOTMzMzMzLTEwNS44MTMzMzMtMjM4LjkzMzMzMy0yMzguOTMzMzMzIDAtMTA1LjgxMzMzMyA3MS42OC0xOTcuOTczMzMzIDE3MC42NjY2NjctMjI4LjY5MzMzMyA2LjgyNjY2Ny03MS42OCA3MS42OC0xMjkuNzA2NjY3IDE0My4zNi0xMjkuNzA2NjY3aDYuODI2NjY2YzIwLjQ4IDAgNDAuOTYgMy40MTMzMzMgNjEuNDQgMTMuNjUzMzMzQzQyNi42NjY2NjcgMjA4LjIxMzMzMyA0OTguMzQ2NjY3IDE3MC42NjY2NjcgNTczLjQ0IDE3MC42NjY2NjdoNi44MjY2NjdjMTE5LjQ2NjY2NyAwIDIyMS44NjY2NjcgODguNzQ2NjY3IDIzNS41MiAyMDguMjEzMzMzIDExNi4wNTMzMzMgMTcuMDY2NjY3IDIwOC4yMTMzMzMgMTE2LjA1MzMzMyAyMDguMjEzMzMzIDIzNS41MiAwIDEzMy4xMi0xMDUuODEzMzMzIDIzOC45MzMzMzMtMjM4LjkzMzMzMyAyMzguOTMzMzMzek0zMTcuNDQgMjkwLjEzMzMzM0MyNTYgMjkwLjEzMzMzMyAyMDQuOCAzMzcuOTIgMjA0LjggMzk5LjM2YzAgNi44MjY2NjctNi44MjY2NjcgMTMuNjUzMzMzLTEzLjY1MzMzMyAxNy4wNjY2NjctOTIuMTYgMjAuNDgtMTU3LjAxMzMzMyAxMDIuNC0xNTcuMDEzMzM0IDE5Ny45NzMzMzMgMCAxMTIuNjQgOTIuMTYgMjA0LjggMjA0LjggMjA0LjhoNTQ2LjEzMzMzNGMxMTIuNjQgMCAyMDQuOC05Mi4xNiAyMDQuOC0yMDQuOCAwLTEwNS44MTMzMzMtODEuOTItMTk3Ljk3MzMzMy0xOTEuMTQ2NjY3LTIwNC44LTYuODI2NjY3IDAtMTMuNjUzMzMzLTYuODI2NjY3LTE3LjA2NjY2Ny0xNy4wNjY2NjdDNzc4LjI0IDI4Ni43MiA2ODYuMDggMjA0LjggNTgwLjI2NjY2NyAyMDQuOGgtMy40MTMzMzRsLTMuNDEzMzMzLTE3LjA2NjY2N1YyMDQuOGMtNjguMjY2NjY3IDAtMTI5LjcwNjY2NyAzNC4xMzMzMzMtMTY3LjI1MzMzMyA5NS41NzMzMzMtNi44MjY2NjcgNi44MjY2NjctMTcuMDY2NjY3IDEwLjI0LTIzLjg5MzMzNCA2LjgyNjY2Ny0xNy4wNjY2NjctMTAuMjQtMzcuNTQ2NjY3LTE3LjA2NjY2Ny01OC4wMjY2NjYtMTcuMDY2NjY3aC0zLjQxMzMzNC0zLjQxMzMzM3oiIGZpbGw9IiNmZmZmZmYiIHAtaWQ9IjI2NjIiPjwvcGF0aD48L3N2Zz4="
/>
</router-link>
<p></p>
<div class="headline">
<h1>Welcome</h1>
</div>
<div class="" id="kg-btn">
<input class="tgl tgl-flip" id="qieh" type="checkbox" />
<label
class="tgl-btn"
data-tg-off="PROXY"
data-tg-on="DIRECT"
for="qieh"
@click="toggleProxy"
></label>
</div>
</div>
</div>
<div id="main">
<div class="app animated fadeInLeft" id="app">
<ul>
<li v-for="p in proxies">
<a :href="isProxy ? p.proxyUrl : p.directUrl" target="_blank">
<img class="shake" width="128" height="128" :src="p.image" />
<strong>{{ p.name }}</strong></a
>
</li>
</ul>
</div>
</div>
</div>
<div id="footer">
<div class="footer-contents">
<div class="links">
<div class="line">
<a href="/">FRP Home</a>&nbsp;&nbsp;&nbsp;
<span class="footer-link-separator"></span>&nbsp;&nbsp;&nbsp;
<span class="copyright">@</span>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
class Item {
name: string
image: string
directUrl: string
proxyUrl: string
/**
*
*/
constructor(proxy: any, host: string) {
this.name = proxy.name
this.image = proxy.conf.group
this.directUrl = proxy.conf.group_key
this.proxyUrl = `${host}:${proxy.conf.remote_port}`
}
}
let proxies = ref<Item[]>([])
let isProxy = ref<Boolean>(true)
const protocal = window.location.protocol
const host = window.location.hostname
const fetchData = () => {
fetch('../api/proxy/tcp', { credentials: 'include' })
.then((res) => {
return res.json()
})
.then((json) => {
const result = json.proxies.sort(
(a: any, b: any) =>
parseInt(a.conf.remote_port) - parseInt(b.conf.remote_port)
)
for (let proxy of result) {
if (proxy != null && proxy.conf.group != '')
proxies.value.push(new Item(proxy, `${protocal}//${host}`))
}
})
}
fetchData()
const toggleProxy = () => {
isProxy.value = !isProxy.value
}
</script>
<style scoped>
@import url('assets/style.css');
</style>

31
web/frps/src/Preload.vue Normal file
View File

@ -0,0 +1,31 @@
<template>
<div v-if="isHome">
<router-view />
</div>
<div v-else>
<App />
</div>
</template>
<script lang="ts">
import App from './App.vue'
export default {
components: {
App,
},
data() {
return {
isHome: false,
}
},
mounted() {
this.isHome = document.location.hash == '#/'
},
watch: {
$route(to, from) {
this.isHome = to.path == '/'
},
},
}
</script>

File diff suppressed because it is too large Load Diff

View File

@ -8,26 +8,25 @@
<el-table-column type="expand">
<template #default="props">
<el-popover
ref="popoverTraffic"
:virtual-ref="buttonTraffic"
placement="right"
width="600"
style="margin-left: 0px"
trigger="click"
virtual-triggering
>
<Traffic :proxy_name="props.row.name" />
</el-popover>
<template #default>
<Traffic :proxy_name="props.row.name" />
</template>
<el-button
ref="buttonTraffic"
type="primary"
size="large"
:name="props.row.name"
style="margin-bottom: 10px"
v-click-outside="onClickTrafficStats"
>Traffic Statistics
</el-button>
<template #reference>
<el-button
type="primary"
size="large"
:name="props.row.name"
style="margin-bottom: 10px"
>Traffic Statistics
</el-button>
</template>
</el-popover>
<ProxyViewExpand :row="props.row" :proxyType="proxyType" />
</template>
@ -65,7 +64,6 @@
</template>
<script setup lang="ts">
import { ref, unref } from 'vue'
import * as Humanize from 'humanize-plus'
import type { TableColumnCtx } from 'element-plus'
import type { BaseProxy } from '../utils/proxy.js'
@ -83,11 +81,4 @@ const formatTrafficIn = (row: BaseProxy, _: TableColumnCtx<BaseProxy>) => {
const formatTrafficOut = (row: BaseProxy, _: TableColumnCtx<BaseProxy>) => {
return Humanize.fileSize(row.traffic_out)
}
const buttonTraffic = ref()
const popoverTraffic = ref()
const onClickTrafficStats = () => {
unref(popoverTraffic).popoverTraffic?.delayHide?.()
}
</script>

View File

@ -1,7 +1,7 @@
import { createApp } from 'vue'
import 'element-plus/dist/index.css'
import 'element-plus/theme-chalk/dark/css-vars.css'
import App from './App.vue'
import App from './Preload.vue'
import router from './router'
import './assets/custom.css'

View File

@ -1,4 +1,5 @@
import { createRouter, createWebHashHistory } from 'vue-router'
import Home from '../Home.vue'
import ServerOverview from '../components/ServerOverview.vue'
import ProxiesTCP from '../components/ProxiesTCP.vue'
import ProxiesUDP from '../components/ProxiesUDP.vue'
@ -12,6 +13,11 @@ const router = createRouter({
routes: [
{
path: '/',
name: 'Home',
component: Home,
},
{
path: '/overview',
name: 'ServerOverview',
component: ServerOverview,
},

View File

@ -674,6 +674,16 @@
estree-walker "^2.0.2"
source-map "^0.6.1"
"@vue/compiler-core@3.2.47":
version "3.2.47"
resolved "https://registry.yarnpkg.com/@vue/compiler-core/-/compiler-core-3.2.47.tgz#3e07c684d74897ac9aa5922c520741f3029267f8"
integrity sha512-p4D7FDnQb7+YJmO2iPEv0SQNeNzcbHdGByJDsT4lynf63AFkOTFN07HsiRSvjGo0QrxR/o3d0hUyNCUnBU2Tig==
dependencies:
"@babel/parser" "^7.16.4"
"@vue/shared" "3.2.47"
estree-walker "^2.0.2"
source-map "^0.6.1"
"@vue/compiler-dom@3.2.45", "@vue/compiler-dom@^3.2.45":
version "3.2.45"
resolved "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.2.45.tgz"
@ -682,7 +692,31 @@
"@vue/compiler-core" "3.2.45"
"@vue/shared" "3.2.45"
"@vue/compiler-sfc@3.2.45", "@vue/compiler-sfc@^3.2.45":
"@vue/compiler-dom@3.2.47":
version "3.2.47"
resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.2.47.tgz#a0b06caf7ef7056939e563dcaa9cbde30794f305"
integrity sha512-dBBnEHEPoftUiS03a4ggEig74J2YBZ2UIeyfpcRM2tavgMWo4bsEfgCGsu+uJIL/vax9S+JztH8NmQerUo7shQ==
dependencies:
"@vue/compiler-core" "3.2.47"
"@vue/shared" "3.2.47"
"@vue/compiler-sfc@3.2.47":
version "3.2.47"
resolved "https://registry.yarnpkg.com/@vue/compiler-sfc/-/compiler-sfc-3.2.47.tgz#1bdc36f6cdc1643f72e2c397eb1a398f5004ad3d"
integrity sha512-rog05W+2IFfxjMcFw10tM9+f7i/+FFpZJJ5XHX72NP9eC2uRD+42M3pYcQqDXVYoj74kHMSEdQ/WmCjt8JFksQ==
dependencies:
"@babel/parser" "^7.16.4"
"@vue/compiler-core" "3.2.47"
"@vue/compiler-dom" "3.2.47"
"@vue/compiler-ssr" "3.2.47"
"@vue/reactivity-transform" "3.2.47"
"@vue/shared" "3.2.47"
estree-walker "^2.0.2"
magic-string "^0.25.7"
postcss "^8.1.10"
source-map "^0.6.1"
"@vue/compiler-sfc@^3.2.45":
version "3.2.45"
resolved "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.2.45.tgz"
integrity sha512-1jXDuWah1ggsnSAOGsec8cFjT/K6TMZ0sPL3o3d84Ft2AYZi2jWJgRMjw4iaK0rBfA89L5gw427H4n1RZQBu6Q==
@ -706,6 +740,14 @@
"@vue/compiler-dom" "3.2.45"
"@vue/shared" "3.2.45"
"@vue/compiler-ssr@3.2.47":
version "3.2.47"
resolved "https://registry.yarnpkg.com/@vue/compiler-ssr/-/compiler-ssr-3.2.47.tgz#35872c01a273aac4d6070ab9d8da918ab13057ee"
integrity sha512-wVXC+gszhulcMD8wpxMsqSOpvDZ6xKXSVWkf50Guf/S+28hTAXPDYRTbLQ3EDkOP5Xz/+SY37YiwDquKbJOgZw==
dependencies:
"@vue/compiler-dom" "3.2.47"
"@vue/shared" "3.2.47"
"@vue/devtools-api@^6.4.5":
version "6.5.0"
resolved "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.5.0.tgz"
@ -739,43 +781,66 @@
estree-walker "^2.0.2"
magic-string "^0.25.7"
"@vue/reactivity@3.2.45", "@vue/reactivity@^3.2.45":
"@vue/reactivity-transform@3.2.47":
version "3.2.47"
resolved "https://registry.yarnpkg.com/@vue/reactivity-transform/-/reactivity-transform-3.2.47.tgz#e45df4d06370f8abf29081a16afd25cffba6d84e"
integrity sha512-m8lGXw8rdnPVVIdIFhf0LeQ/ixyHkH5plYuS83yop5n7ggVJU+z5v0zecwEnX7fa7HNLBhh2qngJJkxpwEEmYA==
dependencies:
"@babel/parser" "^7.16.4"
"@vue/compiler-core" "3.2.47"
"@vue/shared" "3.2.47"
estree-walker "^2.0.2"
magic-string "^0.25.7"
"@vue/reactivity@3.2.47":
version "3.2.47"
resolved "https://registry.yarnpkg.com/@vue/reactivity/-/reactivity-3.2.47.tgz#1d6399074eadfc3ed35c727e2fd707d6881140b6"
integrity sha512-7khqQ/75oyyg+N/e+iwV6lpy1f5wq759NdlS1fpAhFXa8VeAIKGgk2E/C4VF59lx5b+Ezs5fpp/5WsRYXQiKxQ==
dependencies:
"@vue/shared" "3.2.47"
"@vue/reactivity@^3.2.45":
version "3.2.45"
resolved "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.2.45.tgz"
integrity sha512-PRvhCcQcyEVohW0P8iQ7HDcIOXRjZfAsOds3N99X/Dzewy8TVhTCT4uXpAHfoKjVTJRA0O0K+6QNkDIZAxNi3A==
dependencies:
"@vue/shared" "3.2.45"
"@vue/runtime-core@3.2.45":
version "3.2.45"
resolved "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.2.45.tgz"
integrity sha512-gzJiTA3f74cgARptqzYswmoQx0fIA+gGYBfokYVhF8YSXjWTUA2SngRzZRku2HbGbjzB6LBYSbKGIaK8IW+s0A==
"@vue/runtime-core@3.2.47":
version "3.2.47"
resolved "https://registry.yarnpkg.com/@vue/runtime-core/-/runtime-core-3.2.47.tgz#406ebade3d5551c00fc6409bbc1eeb10f32e121d"
integrity sha512-RZxbLQIRB/K0ev0K9FXhNbBzT32H9iRtYbaXb0ZIz2usLms/D55dJR2t6cIEUn6vyhS3ALNvNthI+Q95C+NOpA==
dependencies:
"@vue/reactivity" "3.2.45"
"@vue/shared" "3.2.45"
"@vue/reactivity" "3.2.47"
"@vue/shared" "3.2.47"
"@vue/runtime-dom@3.2.45":
version "3.2.45"
resolved "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.2.45.tgz"
integrity sha512-cy88YpfP5Ue2bDBbj75Cb4bIEZUMM/mAkDMfqDTpUYVgTf/kuQ2VQ8LebuZ8k6EudgH8pYhsGWHlY0lcxlvTwA==
"@vue/runtime-dom@3.2.47":
version "3.2.47"
resolved "https://registry.yarnpkg.com/@vue/runtime-dom/-/runtime-dom-3.2.47.tgz#93e760eeaeab84dedfb7c3eaf3ed58d776299382"
integrity sha512-ArXrFTjS6TsDei4qwNvgrdmHtD930KgSKGhS5M+j8QxXrDJYLqYw4RRcDy1bz1m1wMmb6j+zGLifdVHtkXA7gA==
dependencies:
"@vue/runtime-core" "3.2.45"
"@vue/shared" "3.2.45"
"@vue/runtime-core" "3.2.47"
"@vue/shared" "3.2.47"
csstype "^2.6.8"
"@vue/server-renderer@3.2.45":
version "3.2.45"
resolved "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.2.45.tgz"
integrity sha512-ebiMq7q24WBU1D6uhPK//2OTR1iRIyxjF5iVq/1a5I1SDMDyDu4Ts6fJaMnjrvD3MqnaiFkKQj+LKAgz5WIK3g==
"@vue/server-renderer@3.2.47":
version "3.2.47"
resolved "https://registry.yarnpkg.com/@vue/server-renderer/-/server-renderer-3.2.47.tgz#8aa1d1871fc4eb5a7851aa7f741f8f700e6de3c0"
integrity sha512-dN9gc1i8EvmP9RCzvneONXsKfBRgqFeFZLurmHOveL7oH6HiFXJw5OGu294n1nHc/HMgTy6LulU/tv5/A7f/LA==
dependencies:
"@vue/compiler-ssr" "3.2.45"
"@vue/shared" "3.2.45"
"@vue/compiler-ssr" "3.2.47"
"@vue/shared" "3.2.47"
"@vue/shared@3.2.45", "@vue/shared@^3.2.45":
version "3.2.45"
resolved "https://registry.npmjs.org/@vue/shared/-/shared-3.2.45.tgz"
integrity sha512-Ewzq5Yhimg7pSztDV+RH1UDKBzmtqieXQlpTVm2AwraoRL/Rks96mvd8Vgi7Lj+h+TH8dv7mXD3FRZR3TUvbSg==
"@vue/shared@3.2.47":
version "3.2.47"
resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.2.47.tgz#e597ef75086c6e896ff5478a6bfc0a7aa4bbd14c"
integrity sha512-BHGyyGN3Q97EZx0taMQ+OLNuZcW3d37ZEVmEAyeoA9ERdGvm9Irc/0Fua8SNyOtV1w6BS4q25wbMzJujO9HIfQ==
"@vue/tsconfig@^0.1.3":
version "0.1.3"
resolved "https://registry.npmjs.org/@vue/tsconfig/-/tsconfig-0.1.3.tgz"
@ -1149,10 +1214,10 @@ electron-to-chromium@^1.4.284:
resolved "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.286.tgz"
integrity sha512-Vp3CVhmYpgf4iXNKAucoQUDcCrBQX3XLBtwgFqP9BUXuucgvAV9zWp1kYU7LL9j4++s9O+12cb3wMtN4SJy6UQ==
element-plus@^2.2.28:
version "2.2.28"
resolved "https://registry.npmjs.org/element-plus/-/element-plus-2.2.28.tgz"
integrity sha512-BsxF7iEaBydmRfw1Tt++EO9jRBjbtJr7ZRIrnEwz4J3Cwa1IzHCNCcx3ZwcYTlJq9CYFxv94JnbNr1EbkTou3A==
element-plus@^2.3.3:
version "2.3.3"
resolved "https://registry.yarnpkg.com/element-plus/-/element-plus-2.3.3.tgz#33173bbbe84ada40f4d796fe5043c44781198ea4"
integrity sha512-Zy61OXrG6b4FF3h29A9ZOUkaEQXjCuFwNa7DlpB3Vo+42Tw5zBbHe5a4BY7i56TVJG5xTbS9UQyA726J91pDqg==
dependencies:
"@ctrl/tinycolor" "^3.4.1"
"@element-plus/icons-vue" "^2.0.6"
@ -3047,16 +3112,16 @@ vue-tsc@^1.0.12:
"@volar/vue-language-core" "1.0.24"
"@volar/vue-typescript" "1.0.24"
vue@^3.2.45:
version "3.2.45"
resolved "https://registry.npmjs.org/vue/-/vue-3.2.45.tgz"
integrity sha512-9Nx/Mg2b2xWlXykmCwiTUCWHbWIj53bnkizBxKai1g61f2Xit700A1ljowpTIM11e3uipOeiPcSqnmBg6gyiaA==
vue@^3.2.47:
version "3.2.47"
resolved "https://registry.yarnpkg.com/vue/-/vue-3.2.47.tgz#3eb736cbc606fc87038dbba6a154707c8a34cff0"
integrity sha512-60188y/9Dc9WVrAZeUVSDxRQOZ+z+y5nO2ts9jWXSTkMvayiWxCWOWtBQoYjLeccfXkiiPZWAHcV+WTPhkqJHQ==
dependencies:
"@vue/compiler-dom" "3.2.45"
"@vue/compiler-sfc" "3.2.45"
"@vue/runtime-dom" "3.2.45"
"@vue/server-renderer" "3.2.45"
"@vue/shared" "3.2.45"
"@vue/compiler-dom" "3.2.47"
"@vue/compiler-sfc" "3.2.47"
"@vue/runtime-dom" "3.2.47"
"@vue/server-renderer" "3.2.47"
"@vue/shared" "3.2.47"
webpack-sources@^3.2.3:
version "3.2.3"