mirror of
https://github.com/ngoduykhanh/wireguard-ui.git
synced 2025-05-23 00:15:19 +03:00
Subnet range selector, interface fixes (#481)
This commit is contained in:
parent
e73047b14f
commit
a9be53899c
12 changed files with 473 additions and 44 deletions
3
util/cache.go
Normal file
3
util/cache.go
Normal file
|
@ -0,0 +1,3 @@
|
|||
package util
|
||||
|
||||
var IPToSubnetRange = map[string]uint16{}
|
|
@ -1,24 +1,31 @@
|
|||
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
|
||||
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
|
||||
SendgridApiKey string
|
||||
EmailFrom string
|
||||
EmailFromName string
|
||||
SessionSecret []byte
|
||||
WgConfTemplate string
|
||||
BasePath string
|
||||
SubnetRanges map[string]([]*net.IPNet)
|
||||
SubnetRangesOrder []string
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -30,7 +37,7 @@ 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"
|
||||
|
@ -66,3 +73,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
|
||||
}
|
||||
|
|
154
util/util.go
154
util/util.go
|
@ -95,6 +95,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)
|
||||
|
@ -317,15 +326,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
|
||||
|
@ -336,7 +362,7 @@ func GetAvailableIP(cidr string, allocatedList []string) (string, error) {
|
|||
break
|
||||
}
|
||||
}
|
||||
if available && suggestedAddr != networkAddr && suggestedAddr != broadcastAddr {
|
||||
if available && !unavailableIPs[suggestedAddr] {
|
||||
return suggestedAddr, nil
|
||||
}
|
||||
}
|
||||
|
@ -384,6 +410,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
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue