mirror of
https://github.com/ngoduykhanh/wireguard-ui.git
synced 2025-05-23 00:15:19 +03:00
Resolve conflict
This commit is contained in:
commit
8cfe9a3d5b
25 changed files with 1550 additions and 405 deletions
7
util/cache.go
Normal file
7
util/cache.go
Normal file
|
@ -0,0 +1,7 @@
|
|||
package util
|
||||
|
||||
import "sync"
|
||||
|
||||
var IPToSubnetRange = map[string]uint16{}
|
||||
var TgUseridToClientID = map[int64]([]string){}
|
||||
var TgUseridToClientIDMutex sync.RWMutex
|
|
@ -1,25 +1,32 @@
|
|||
package util
|
||||
|
||||
import "strings"
|
||||
import (
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
"github.com/labstack/gommon/log"
|
||||
)
|
||||
|
||||
// Runtime config
|
||||
var (
|
||||
DisableLogin bool
|
||||
BindAddress string
|
||||
SmtpHostname string
|
||||
SmtpPort int
|
||||
SmtpUsername string
|
||||
SmtpPassword string
|
||||
SmtpHelo string
|
||||
SmtpNoTLSCheck bool
|
||||
SmtpEncryption string
|
||||
SmtpAuthType string
|
||||
SendgridApiKey string
|
||||
EmailFrom string
|
||||
EmailFromName string
|
||||
SessionSecret []byte
|
||||
WgConfTemplate string
|
||||
BasePath string
|
||||
DisableLogin bool
|
||||
BindAddress string
|
||||
SmtpHostname string
|
||||
SmtpPort int
|
||||
SmtpUsername string
|
||||
SmtpPassword string
|
||||
SmtpNoTLSCheck bool
|
||||
SmtpEncryption string
|
||||
SmtpAuthType string
|
||||
SmtpHelo string
|
||||
SendgridApiKey string
|
||||
EmailFrom string
|
||||
EmailFromName string
|
||||
SessionSecret []byte
|
||||
WgConfTemplate string
|
||||
BasePath string
|
||||
SubnetRanges map[string]([]*net.IPNet)
|
||||
SubnetRangesOrder []string
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -31,12 +38,14 @@ const (
|
|||
DefaultDNS = "1.1.1.1"
|
||||
DefaultMTU = 1450
|
||||
DefaultPersistentKeepalive = 15
|
||||
DefaultFirewallMark = "0xca6c" // i.e. 51820
|
||||
DefaultFirewallMark = "0xca6c" // i.e. 51820
|
||||
DefaultTable = "auto"
|
||||
DefaultConfigFilePath = "/etc/wireguard/wg0.conf"
|
||||
UsernameEnvVar = "WGUI_USERNAME"
|
||||
PasswordEnvVar = "WGUI_PASSWORD"
|
||||
PasswordFileEnvVar = "WGUI_PASSWORD_FILE"
|
||||
PasswordHashEnvVar = "WGUI_PASSWORD_HASH"
|
||||
PasswordHashFileEnvVar = "WGUI_PASSWORD_HASH_FILE"
|
||||
FaviconFilePathEnvVar = "WGUI_FAVICON_FILE_PATH"
|
||||
EndpointAddressEnvVar = "WGUI_ENDPOINT_ADDRESS"
|
||||
DNSEnvVar = "WGUI_DNS"
|
||||
|
@ -65,3 +74,45 @@ func ParseBasePath(basePath string) string {
|
|||
}
|
||||
return basePath
|
||||
}
|
||||
|
||||
func ParseSubnetRanges(subnetRangesStr string) map[string]([]*net.IPNet) {
|
||||
subnetRanges := map[string]([]*net.IPNet){}
|
||||
if subnetRangesStr == "" {
|
||||
return subnetRanges
|
||||
}
|
||||
cidrSet := map[string]bool{}
|
||||
subnetRangesStr = strings.TrimSpace(subnetRangesStr)
|
||||
subnetRangesStr = strings.Trim(subnetRangesStr, ";:,")
|
||||
ranges := strings.Split(subnetRangesStr, ";")
|
||||
for _, rng := range ranges {
|
||||
rng = strings.TrimSpace(rng)
|
||||
rngSpl := strings.Split(rng, ":")
|
||||
if len(rngSpl) != 2 {
|
||||
log.Warnf("Unable to parse subnet range: %v. Skipped.", rng)
|
||||
continue
|
||||
}
|
||||
rngName := strings.TrimSpace(rngSpl[0])
|
||||
subnetRanges[rngName] = make([]*net.IPNet, 0)
|
||||
cidrs := strings.Split(rngSpl[1], ",")
|
||||
for _, cidr := range cidrs {
|
||||
cidr = strings.TrimSpace(cidr)
|
||||
_, net, err := net.ParseCIDR(cidr)
|
||||
if err != nil {
|
||||
log.Warnf("[%v] Unable to parse CIDR: %v. Skipped.", rngName, cidr)
|
||||
continue
|
||||
}
|
||||
if cidrSet[net.String()] {
|
||||
log.Warnf("[%v] CIDR already exists: %v. Skipped.", rngName, net.String())
|
||||
continue
|
||||
}
|
||||
cidrSet[net.String()] = true
|
||||
subnetRanges[rngName] = append(subnetRanges[rngName], net)
|
||||
}
|
||||
if len(subnetRanges[rngName]) == 0 {
|
||||
delete(subnetRanges, rngName)
|
||||
} else {
|
||||
SubnetRangesOrder = append(SubnetRangesOrder, rngName)
|
||||
}
|
||||
}
|
||||
return subnetRanges
|
||||
}
|
||||
|
|
300
util/util.go
300
util/util.go
|
@ -1,11 +1,10 @@
|
|||
package util
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/ngoduykhanh/wireguard-ui/store"
|
||||
"golang.org/x/mod/sumdb/dirhash"
|
||||
"io"
|
||||
"io/fs"
|
||||
"io/ioutil"
|
||||
|
@ -19,12 +18,23 @@ import (
|
|||
"text/template"
|
||||
"time"
|
||||
|
||||
"github.com/ngoduykhanh/wireguard-ui/store"
|
||||
"github.com/ngoduykhanh/wireguard-ui/telegram"
|
||||
"github.com/skip2/go-qrcode"
|
||||
"golang.org/x/mod/sumdb/dirhash"
|
||||
|
||||
externalip "github.com/glendc/go-external-ip"
|
||||
"github.com/labstack/gommon/log"
|
||||
"github.com/ngoduykhanh/wireguard-ui/model"
|
||||
"github.com/sdomino/scribble"
|
||||
)
|
||||
|
||||
var qrCodeSettings = model.QRCodeSettings{
|
||||
Enabled: true,
|
||||
IncludeDNS: true,
|
||||
IncludeMTU: true,
|
||||
}
|
||||
|
||||
// BuildClientConfig to create wireguard client config string
|
||||
func BuildClientConfig(client model.Client, server model.Server, setting model.GlobalSetting) string {
|
||||
// Interface section
|
||||
|
@ -93,6 +103,15 @@ func ClientDefaultsFromEnv() model.ClientDefaults {
|
|||
return clientDefaults
|
||||
}
|
||||
|
||||
// ContainsCIDR to check if ipnet1 contains ipnet2
|
||||
// https://stackoverflow.com/a/40406619/6111641
|
||||
// https://go.dev/play/p/Q4J-JEN3sF
|
||||
func ContainsCIDR(ipnet1, ipnet2 *net.IPNet) bool {
|
||||
ones1, _ := ipnet1.Mask.Size()
|
||||
ones2, _ := ipnet2.Mask.Size()
|
||||
return ones1 <= ones2 && ipnet1.Contains(ipnet2.IP)
|
||||
}
|
||||
|
||||
// ValidateCIDR to validate a network CIDR
|
||||
func ValidateCIDR(cidr string) bool {
|
||||
_, _, err := net.ParseCIDR(cidr)
|
||||
|
@ -315,15 +334,32 @@ func GetBroadcastIP(n *net.IPNet) net.IP {
|
|||
return broadcast
|
||||
}
|
||||
|
||||
// GetBroadcastAndNetworkAddrsLookup get the ip address that can't be used with current server interfaces
|
||||
func GetBroadcastAndNetworkAddrsLookup(interfaceAddresses []string) map[string]bool {
|
||||
list := make(map[string]bool, 0)
|
||||
for _, ifa := range interfaceAddresses {
|
||||
_, net, err := net.ParseCIDR(ifa)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
broadcastAddr := GetBroadcastIP(net).String()
|
||||
networkAddr := net.IP.String()
|
||||
list[broadcastAddr] = true
|
||||
list[networkAddr] = true
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
// GetAvailableIP get the ip address that can be allocated from an CIDR
|
||||
func GetAvailableIP(cidr string, allocatedList []string) (string, error) {
|
||||
// We need interfaceAddresses to find real broadcast and network addresses
|
||||
func GetAvailableIP(cidr string, allocatedList, interfaceAddresses []string) (string, error) {
|
||||
ip, net, err := net.ParseCIDR(cidr)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
broadcastAddr := GetBroadcastIP(net).String()
|
||||
networkAddr := net.IP.String()
|
||||
unavailableIPs := GetBroadcastAndNetworkAddrsLookup(interfaceAddresses)
|
||||
|
||||
for ip := ip.Mask(net.Mask); net.Contains(ip); inc(ip) {
|
||||
available := true
|
||||
|
@ -334,7 +370,7 @@ func GetAvailableIP(cidr string, allocatedList []string) (string, error) {
|
|||
break
|
||||
}
|
||||
}
|
||||
if available && suggestedAddr != networkAddr && suggestedAddr != broadcastAddr {
|
||||
if available && !unavailableIPs[suggestedAddr] {
|
||||
return suggestedAddr, nil
|
||||
}
|
||||
}
|
||||
|
@ -382,6 +418,126 @@ func ValidateIPAllocation(serverAddresses []string, ipAllocatedList []string, ip
|
|||
return true, nil
|
||||
}
|
||||
|
||||
// findSubnetRangeForIP to find first SR for IP, and cache the match
|
||||
func findSubnetRangeForIP(cidr string) (uint16, error) {
|
||||
ip, _, err := net.ParseCIDR(cidr)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if srName, ok := IPToSubnetRange[ip.String()]; ok {
|
||||
return srName, nil
|
||||
}
|
||||
|
||||
for srIndex, sr := range SubnetRangesOrder {
|
||||
for _, srCIDR := range SubnetRanges[sr] {
|
||||
if srCIDR.Contains(ip) {
|
||||
IPToSubnetRange[ip.String()] = uint16(srIndex)
|
||||
return uint16(srIndex), nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0, fmt.Errorf("Subnet range not found for this IP")
|
||||
}
|
||||
|
||||
// FillClientSubnetRange to fill subnet ranges client belongs to, does nothing if SRs are not found
|
||||
func FillClientSubnetRange(client model.ClientData) model.ClientData {
|
||||
cl := *client.Client
|
||||
for _, ip := range cl.AllocatedIPs {
|
||||
sr, err := findSubnetRangeForIP(ip)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
cl.SubnetRanges = append(cl.SubnetRanges, SubnetRangesOrder[sr])
|
||||
}
|
||||
return model.ClientData{
|
||||
Client: &cl,
|
||||
QRCode: client.QRCode,
|
||||
}
|
||||
}
|
||||
|
||||
// ValidateAndFixSubnetRanges to check if subnet ranges are valid for the server configuration
|
||||
// Removes all non-valid CIDRs
|
||||
func ValidateAndFixSubnetRanges(db store.IStore) error {
|
||||
if len(SubnetRangesOrder) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
server, err := db.GetServer()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var serverSubnets []*net.IPNet
|
||||
for _, addr := range server.Interface.Addresses {
|
||||
addr = strings.TrimSpace(addr)
|
||||
_, net, err := net.ParseCIDR(addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
serverSubnets = append(serverSubnets, net)
|
||||
}
|
||||
|
||||
for _, rng := range SubnetRangesOrder {
|
||||
cidrs := SubnetRanges[rng]
|
||||
if len(cidrs) > 0 {
|
||||
newCIDRs := make([]*net.IPNet, 0)
|
||||
for _, cidr := range cidrs {
|
||||
valid := false
|
||||
|
||||
for _, serverSubnet := range serverSubnets {
|
||||
if ContainsCIDR(serverSubnet, cidr) {
|
||||
valid = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if valid {
|
||||
newCIDRs = append(newCIDRs, cidr)
|
||||
} else {
|
||||
log.Warnf("[%v] CIDR is outside of all server subnets: %v. Removed.", rng, cidr)
|
||||
}
|
||||
}
|
||||
|
||||
if len(newCIDRs) > 0 {
|
||||
SubnetRanges[rng] = newCIDRs
|
||||
} else {
|
||||
delete(SubnetRanges, rng)
|
||||
log.Warnf("[%v] No valid CIDRs in this subnet range. Removed.", rng)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetSubnetRangesString to get a formatted string, representing active subnet ranges
|
||||
func GetSubnetRangesString() string {
|
||||
if len(SubnetRangesOrder) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
strB := strings.Builder{}
|
||||
|
||||
for _, rng := range SubnetRangesOrder {
|
||||
cidrs := SubnetRanges[rng]
|
||||
if len(cidrs) > 0 {
|
||||
strB.WriteString(rng)
|
||||
strB.WriteString(":[")
|
||||
first := true
|
||||
for _, cidr := range cidrs {
|
||||
if !first {
|
||||
strB.WriteString(", ")
|
||||
}
|
||||
strB.WriteString(cidr.String())
|
||||
first = false
|
||||
}
|
||||
strB.WriteString("] ")
|
||||
}
|
||||
}
|
||||
|
||||
return strings.TrimSpace(strB.String())
|
||||
}
|
||||
|
||||
// WriteWireGuardServerConfig to write Wireguard server config. e.g. wg0.conf
|
||||
func WriteWireGuardServerConfig(tmplDir fs.FS, serverConfig model.Server, clientDataList []model.ClientData, usersList []model.User, globalSettings model.GlobalSetting) error {
|
||||
var tmplWireguardConf string
|
||||
|
@ -430,6 +586,57 @@ func WriteWireGuardServerConfig(tmplDir fs.FS, serverConfig model.Server, client
|
|||
return nil
|
||||
}
|
||||
|
||||
// SendRequestedConfigsToTelegram to send client all their configs. Returns failed configs list.
|
||||
func SendRequestedConfigsToTelegram(db store.IStore, userid int64) []string {
|
||||
failedList := make([]string, 0)
|
||||
TgUseridToClientIDMutex.RLock()
|
||||
if clids, found := TgUseridToClientID[userid]; found && len(clids) > 0 {
|
||||
TgUseridToClientIDMutex.RUnlock()
|
||||
|
||||
for _, clid := range clids {
|
||||
clientData, err := db.GetClientByID(clid, qrCodeSettings)
|
||||
if err != nil {
|
||||
// return fmt.Errorf("unable to get client")
|
||||
failedList = append(failedList, clid)
|
||||
continue
|
||||
}
|
||||
|
||||
// build config
|
||||
server, _ := db.GetServer()
|
||||
globalSettings, _ := db.GetGlobalSettings()
|
||||
config := BuildClientConfig(*clientData.Client, server, globalSettings)
|
||||
configData := []byte(config)
|
||||
var qrData []byte
|
||||
|
||||
if clientData.Client.PrivateKey != "" {
|
||||
qrData, err = qrcode.Encode(config, qrcode.Medium, 512)
|
||||
if err != nil {
|
||||
// return fmt.Errorf("unable to encode qr")
|
||||
failedList = append(failedList, clientData.Client.Name)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
userid, err := strconv.ParseInt(clientData.Client.TgUserid, 10, 64)
|
||||
if err != nil {
|
||||
// return fmt.Errorf("tg usrid is unreadable")
|
||||
failedList = append(failedList, clientData.Client.Name)
|
||||
continue
|
||||
}
|
||||
|
||||
err = telegram.SendConfig(userid, clientData.Client.Name, configData, qrData, true)
|
||||
if err != nil {
|
||||
failedList = append(failedList, clientData.Client.Name)
|
||||
continue
|
||||
}
|
||||
time.Sleep(2 * time.Second)
|
||||
}
|
||||
} else {
|
||||
TgUseridToClientIDMutex.RUnlock()
|
||||
}
|
||||
return failedList
|
||||
}
|
||||
|
||||
func LookupEnvOrString(key string, defaultVal string) string {
|
||||
if val, ok := os.LookupEnv(key); ok {
|
||||
return val
|
||||
|
@ -466,6 +673,20 @@ func LookupEnvOrStrings(key string, defaultVal []string) []string {
|
|||
return defaultVal
|
||||
}
|
||||
|
||||
func LookupEnvOrFile(key string, defaultVal string) string {
|
||||
if val, ok := os.LookupEnv(key); ok {
|
||||
if file, err := os.Open(val); err == nil {
|
||||
var content string
|
||||
scanner := bufio.NewScanner(file)
|
||||
for scanner.Scan() {
|
||||
content += scanner.Text()
|
||||
}
|
||||
return content
|
||||
}
|
||||
}
|
||||
return defaultVal
|
||||
}
|
||||
|
||||
func StringFromEmbedFile(embed fs.FS, filename string) (string, error) {
|
||||
file, err := embed.Open(filename)
|
||||
if err != nil {
|
||||
|
@ -540,3 +761,70 @@ func RandomString(length int) string {
|
|||
}
|
||||
return string(b)
|
||||
}
|
||||
|
||||
func ManagePerms(path string) error {
|
||||
err := os.Chmod(path, 0600)
|
||||
return err
|
||||
}
|
||||
|
||||
func AddTgToClientID(userid int64, clientID string) {
|
||||
TgUseridToClientIDMutex.Lock()
|
||||
defer TgUseridToClientIDMutex.Unlock()
|
||||
|
||||
if _, ok := TgUseridToClientID[userid]; ok && TgUseridToClientID[userid] != nil {
|
||||
TgUseridToClientID[userid] = append(TgUseridToClientID[userid], clientID)
|
||||
} else {
|
||||
TgUseridToClientID[userid] = []string{clientID}
|
||||
}
|
||||
}
|
||||
|
||||
func UpdateTgToClientID(userid int64, clientID string) {
|
||||
TgUseridToClientIDMutex.Lock()
|
||||
defer TgUseridToClientIDMutex.Unlock()
|
||||
|
||||
// Detach clientID from any existing userid
|
||||
for uid, cls := range TgUseridToClientID {
|
||||
if cls != nil {
|
||||
filtered := filterStringSlice(cls, clientID)
|
||||
if len(filtered) > 0 {
|
||||
TgUseridToClientID[uid] = filtered
|
||||
} else {
|
||||
delete(TgUseridToClientID, uid)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Attach it to the new one
|
||||
if _, ok := TgUseridToClientID[userid]; ok && TgUseridToClientID[userid] != nil {
|
||||
TgUseridToClientID[userid] = append(TgUseridToClientID[userid], clientID)
|
||||
} else {
|
||||
TgUseridToClientID[userid] = []string{clientID}
|
||||
}
|
||||
}
|
||||
|
||||
func RemoveTgToClientID(clientID string) {
|
||||
TgUseridToClientIDMutex.Lock()
|
||||
defer TgUseridToClientIDMutex.Unlock()
|
||||
|
||||
// Detach clientID from any existing userid
|
||||
for uid, cls := range TgUseridToClientID {
|
||||
if cls != nil {
|
||||
filtered := filterStringSlice(cls, clientID)
|
||||
if len(filtered) > 0 {
|
||||
TgUseridToClientID[uid] = filtered
|
||||
} else {
|
||||
delete(TgUseridToClientID, uid)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func filterStringSlice(s []string, excludedStr string) []string {
|
||||
filtered := s[:0]
|
||||
for _, v := range s {
|
||||
if v != excludedStr {
|
||||
filtered = append(filtered, v)
|
||||
}
|
||||
}
|
||||
return filtered
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue