wireguard-dashboard/http/api/client.go

426 lines
11 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package api
import (
"encoding/json"
"fmt"
"gitee.ltd/lxh/logger/log"
"github.com/gin-gonic/gin"
"github.com/spf13/cast"
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
"os"
"strings"
"time"
"wireguard-dashboard/client"
"wireguard-dashboard/http/param"
"wireguard-dashboard/model/entity"
"wireguard-dashboard/model/template_data"
"wireguard-dashboard/model/vo"
"wireguard-dashboard/queues"
"wireguard-dashboard/repository"
"wireguard-dashboard/utils"
)
type clients struct{}
func Client() clients {
return clients{}
}
// List
// @description: 客户端列表
// @receiver clients
// @param c
func (clients) List(c *gin.Context) {
var p param.ClientList
if err := c.ShouldBind(&p); err != nil {
utils.GinResponse(c).FailedWithErr("参数错误", err)
return
}
data, total, err := repository.Client().List(p)
if err != nil {
utils.GinResponse(c).FailedWithMsg("获取失败")
return
}
utils.GinResponse(c).OkWithPage(data, total, p.Current, p.Size)
}
// Save
// @description: 新增/更新客户端
// @receiver clients
// @param c
func (clients) Save(c *gin.Context) {
var p param.SaveClient
if err := c.ShouldBind(&p); err != nil {
utils.GinResponse(c).FailedWithErr("参数错误", err)
return
}
info, ok := c.Get("user")
if !ok {
utils.GinResponse(c).FailedWithMsg("获取信息失败")
return
}
_, err := repository.Client().Save(p, info.(*entity.User).Id)
if err != nil {
utils.GinResponse(c).FailedWithErr("操作失败", err)
return
}
go func() {
if err = queues.PutAsyncWireguardConfigFile(p.ServerId); err != nil {
log.Errorf("[新增/编辑客户端]同步配置文件失败: %v", err.Error())
}
}()
utils.GinResponse(c).OK()
}
// AssignIPAndAllowedIP
// @description: 分配客户端IP和允许访问的IP段
// @receiver clients
// @param c
func (clients) AssignIPAndAllowedIP(c *gin.Context) {
var p param.AssignIPAndAllowedIP
if err := c.ShouldBind(&p); err != nil {
utils.GinResponse(c).FailedWithErr("参数错误", err)
return
}
// 获取一下服务端信息因为IP分配需要根据服务端的IP制定
serverInfo, err := repository.Server().GetServer()
if err != nil {
utils.GinResponse(c).FailedWithErr("获取服务端信息失败", err)
return
}
var assignIPS []string
assignIPS = append(assignIPS, serverInfo.IpScope)
switch p.Rule {
case "AUTO":
// 只获取最新的一个
var clientInfo *entity.Client
if err = repository.Client().Order("created_at DESC").Take(&clientInfo).Error; err == nil {
if cast.ToInt64(utils.Wireguard().GetIPSuffix(clientInfo.IpAllocation)) >= 255 {
utils.GinResponse(c).FailedWithMsg("当前IP分配错误请手动进行分配")
return
}
assignIPS = append(assignIPS, clientInfo.IpAllocation)
}
case "RANDOM":
// 查询全部客户端不管是禁用还是没禁用的
var clientsInfo []entity.Client
if err = repository.Client().Find(&clientsInfo).Error; err != nil {
utils.GinResponse(c).FailedWithErr("获取失败", err)
return
}
for _, v := range clientsInfo {
assignIPS = append(assignIPS, v.IpAllocation)
}
}
clientIP := utils.Wireguard().GenerateClientIP(serverInfo.IpScope, p.Rule, assignIPS...)
utils.GinResponse(c).OKWithData(map[string]any{
"clientIP": []string{fmt.Sprintf("%s/32", clientIP)},
"serverIP": []string{serverInfo.IpScope},
})
}
// GenerateKeys
// @description: 生成密钥对
// @receiver clients
// @param c
func (clients) GenerateKeys(c *gin.Context) {
// 为空,新增
privateKey, err := wgtypes.GeneratePrivateKey()
if err != nil {
utils.GinResponse(c).FailedWithErr("生成密钥对失败", err)
return
}
publicKey := privateKey.PublicKey().String()
presharedKey, err := wgtypes.GenerateKey()
if err != nil {
return
}
keys := template_data.Keys{
PrivateKey: privateKey.String(),
PublicKey: publicKey,
PresharedKey: presharedKey.String(),
}
utils.GinResponse(c).OKWithData(keys)
}
// Delete
// @description: 删除客户端
// @receiver clients
// @param c
func (clients) Delete(c *gin.Context) {
var id = c.Param("id")
if id == "" || id == "undefined" {
utils.GinResponse(c).FailedWithMsg("参数错误")
return
}
if err := repository.Client().Delete(id); err != nil {
utils.GinResponse(c).FailedWithMsg("操作失败")
return
}
// 再同步一下配置文件
go func() {
if err := queues.PutAsyncWireguardConfigFile(""); err != nil {
log.Errorf("[下线客户端]同步配置文件失败: %v", err.Error())
}
}()
utils.GinResponse(c).OK()
}
// Download
// @description: 下载配置文件
// @receiver clients
// @param c
func (clients) Download(c *gin.Context) {
var id = c.Param("id")
if id == "" || id == "undefined" {
utils.GinResponse(c).FailedWithMsg("参数错误")
return
}
data, err := repository.Client().GetById(id)
if err != nil {
utils.GinResponse(c).FailedWithMsg("获取失败")
return
}
var keys template_data.Keys
_ = json.Unmarshal([]byte(data.Keys), &keys)
serverSetting, err := repository.System().GetServerSetting()
if err != nil {
utils.GinResponse(c).FailedWithMsg("获取设置失败")
return
}
outPath, err := utils.Wireguard().GenerateClientFile(&data, serverSetting)
if err != nil {
utils.GinResponse(c).FailedWithErr("生成失败", err)
return
}
// 输出文件流
c.Header("Content-Type", "application/octet-stream")
c.Header("Content-Disposition", "attachment; filename="+outPath)
c.Header("Content-Transfer-Encoding", "binary")
c.Header("Connection", "keep-alive")
c.File(outPath)
if err = os.Remove(outPath); err != nil {
log.Errorf("删除临时文件失败: %s", err.Error())
}
}
// GenerateQrCode
// @description: 生成客户端信息二维码
// @receiver clients
// @param c
func (clients) GenerateQrCode(c *gin.Context) {
var id = c.Param("id")
if id == "" || id == "undefined" {
utils.GinResponse(c).FailedWithMsg("参数错误")
return
}
data, err := repository.Client().GetById(id)
if err != nil {
utils.GinResponse(c).FailedWithMsg("获取失败")
return
}
var keys template_data.Keys
_ = json.Unmarshal([]byte(data.Keys), &keys)
serverSetting, err := repository.System().GetServerSetting()
if err != nil {
utils.GinResponse(c).FailedWithMsg("获取设置失败")
return
}
outPath, err := utils.Wireguard().GenerateClientFile(&data, serverSetting)
if err != nil {
utils.GinResponse(c).FailedWithErr("生成失败", err)
return
}
// 读取文件内容
fileContent, err := os.ReadFile(outPath)
if err != nil {
utils.GinResponse(c).FailedWithMsg("读取文件失败")
return
}
png, err := utils.QRCode().GenerateQrCodeBase64(fileContent, 256)
if err != nil {
utils.GinResponse(c).FailedWithErr("生成二维码失败", err)
return
}
if err = os.Remove(outPath); err != nil {
log.Errorf("删除临时文件失败: %s", err.Error())
}
utils.GinResponse(c).OKWithData(map[string]interface{}{
"qrCode": png,
})
}
// SendEmail
// @description: 发送邮件
// @receiver clients
// @param c
func (clients) SendEmail(c *gin.Context) {
var id = c.Param("id")
if id == "" || id == "undefined" {
utils.GinResponse(c).FailedWithMsg("id不能为空")
return
}
// 先校验一下邮箱发送是否可用
if err := utils.Mail().VerifyConfig(); err != nil {
utils.GinResponse(c).FailedWithMsg(err.Error())
return
}
// 获取该客户端信息
clientInfo, err := repository.Client().GetById(id)
if err != nil {
utils.GinResponse(c).FailedWithErr("获取失败", err)
return
}
if clientInfo.Email == "" {
utils.GinResponse(c).FailedWithMsg("当前客户端未配置联系邮箱!")
return
}
serverSetting, err := repository.System().GetServerSetting()
if err != nil {
utils.GinResponse(c).FailedWithMsg("获取设置失败")
return
}
outPath, err := utils.Wireguard().GenerateClientFile(&clientInfo, serverSetting)
if err != nil {
utils.GinResponse(c).FailedWithErr("生成失败", err)
return
}
err = utils.Mail().SendMail(clientInfo.Email, fmt.Sprintf("客户端: %s", clientInfo.Name), "请查收附件", outPath)
if err != nil {
utils.GinResponse(c).FailedWithErr("发送邮件失败", err)
return
}
if err = os.Remove(outPath); err != nil {
log.Errorf("删除临时文件失败: %s", err.Error())
}
utils.GinResponse(c).OK()
}
// Status
// @description: 获取客户端状态信息,链接状态等
// @receiver clients
// @param c
func (clients) Status(c *gin.Context) {
// 使用sdk拉取一下客户端信息
devices, err := client.WireguardClient.Devices()
if err != nil {
utils.GinResponse(c).FailedWithErr("获取客户端信息失败", err)
return
}
var data []vo.ClientStatus
// 遍历客户端数据,并渲染数据信息
for _, d := range devices {
for _, p := range d.Peers {
clientInfo, err := repository.Client().GetByPublicKey(p.PublicKey.String())
if err != nil {
log.Errorf("没有找到公钥匹配的客户端: %s", p.PublicKey.String())
continue
}
var ipAllocation string
for _, iaip := range p.AllowedIPs {
ipAllocation += iaip.String() + ","
}
ipAllocation = strings.TrimRight(ipAllocation, ",")
isOnline := time.Since(p.LastHandshakeTime).Minutes() < 3
data = append(data, vo.ClientStatus{
ID: clientInfo.Id,
Name: clientInfo.Name,
Email: clientInfo.Email,
IpAllocation: ipAllocation,
Endpoint: p.Endpoint.String(),
Received: utils.FlowCalculation().Parse(p.ReceiveBytes),
Transmitted: utils.FlowCalculation().Parse(p.TransmitBytes),
IsOnline: isOnline,
LastHandShake: p.LastHandshakeTime.Format("2006-01-02 15:04:05"),
})
}
}
utils.GinResponse(c).OKWithData(data)
}
// Offline
// @description: 强制下线指定客户端
// @receiver clients
// @param c
func (clients) Offline(c *gin.Context) {
id := c.Param("id")
if id == "" || id == "undefined" {
utils.GinResponse(c).FailedWithMsg("参数错误")
return
}
// 查询一下客户端信息
clientInfo, err := repository.Client().GetById(id)
if err != nil {
utils.GinResponse(c).FailedWithMsg("获取信息失败")
return
}
keys := template_data.Keys{}
_ = json.Unmarshal([]byte(clientInfo.Keys), &keys)
connectInfo, err := utils.Wireguard().GetSpecClient(keys.PublicKey)
if err != nil {
utils.GinResponse(c).FailedWithMsg("获取客户端信息失败")
return
}
if connectInfo == nil {
utils.GinResponse(c).FailedWithMsg("未获取到该客户端链接信息")
return
}
// 获取到了,执行踢下线操作。此处踢下线就是禁用该客户端
if err = repository.Client().Disabled(clientInfo.Id); err != nil {
utils.GinResponse(c).FailedWithErr("客户端下线失败: %v", err)
return
}
// 再同步一下配置文件
go func() {
if err = queues.PutAsyncWireguardConfigFile(clientInfo.ServerId); err != nil {
log.Errorf("[下线客户端]同步配置文件失败: %v", err.Error())
}
}()
utils.GinResponse(c).OK()
}