mirror of
https://github.com/ngoduykhanh/wireguard-ui.git
synced 2025-04-19 19:59:13 +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
|
@ -42,6 +42,7 @@ docker-compose up
|
||||||
| `BIND_ADDRESS` | The addresses that can access to the web interface and the port, use unix:///abspath/to/file.socket for unix domain socket. | 0.0.0.0:80 |
|
| `BIND_ADDRESS` | The addresses that can access to the web interface and the port, use unix:///abspath/to/file.socket for unix domain socket. | 0.0.0.0:80 |
|
||||||
| `SESSION_SECRET` | The secret key used to encrypt the session cookies. Set this to a random value | N/A |
|
| `SESSION_SECRET` | The secret key used to encrypt the session cookies. Set this to a random value | N/A |
|
||||||
| `SESSION_SECRET_FILE` | Optional filepath for the secret key used to encrypt the session cookies. Leave `SESSION_SECRET` blank to take effect | N/A |
|
| `SESSION_SECRET_FILE` | Optional filepath for the secret key used to encrypt the session cookies. Leave `SESSION_SECRET` blank to take effect | N/A |
|
||||||
|
| `SUBNET_RANGES` | The list of address subdivision ranges. Format: `SR Name:10.0.1.0/24; SR2:10.0.2.0/24,10.0.3.0/24` Each CIDR must be inside one of the server interfaces. | N/A |
|
||||||
| `WGUI_USERNAME` | The username for the login page. Used for db initialization only | `admin` |
|
| `WGUI_USERNAME` | The username for the login page. Used for db initialization only | `admin` |
|
||||||
| `WGUI_PASSWORD` | The password for the user on the login page. Will be hashed automatically. Used for db initialization only | `admin` |
|
| `WGUI_PASSWORD` | The password for the user on the login page. Will be hashed automatically. Used for db initialization only | `admin` |
|
||||||
| `WGUI_PASSWORD_FILE` | Optional filepath for the user login password. Will be hashed automatically. Used for db initialization only. Leave `WGUI_PASSWORD` blank to take effect | N/A |
|
| `WGUI_PASSWORD_FILE` | Optional filepath for the user login password. Will be hashed automatically. Used for db initialization only. Leave `WGUI_PASSWORD` blank to take effect | N/A |
|
||||||
|
|
|
@ -18,6 +18,11 @@ function renderClientList(data) {
|
||||||
allowedIpsHtml += `<small class="badge badge-secondary">${obj}</small> `;
|
allowedIpsHtml += `<small class="badge badge-secondary">${obj}</small> `;
|
||||||
})
|
})
|
||||||
|
|
||||||
|
let subnetRangesString = "";
|
||||||
|
if (obj.Client.subnet_ranges && obj.Client.subnet_ranges.length > 0) {
|
||||||
|
subnetRangesString = obj.Client.subnet_ranges.join(',')
|
||||||
|
}
|
||||||
|
|
||||||
// render client html content
|
// render client html content
|
||||||
let html = `<div class="col-sm-6 col-md-6 col-lg-4" id="client_${obj.Client.id}">
|
let html = `<div class="col-sm-6 col-md-6 col-lg-4" id="client_${obj.Client.id}">
|
||||||
<div class="info-box">
|
<div class="info-box">
|
||||||
|
@ -59,6 +64,7 @@ function renderClientList(data) {
|
||||||
<hr>
|
<hr>
|
||||||
<span class="info-box-text"><i class="fas fa-user"></i> ${obj.Client.name}</span>
|
<span class="info-box-text"><i class="fas fa-user"></i> ${obj.Client.name}</span>
|
||||||
<span class="info-box-text" style="display: none"><i class="fas fa-key"></i> ${obj.Client.public_key}</span>
|
<span class="info-box-text" style="display: none"><i class="fas fa-key"></i> ${obj.Client.public_key}</span>
|
||||||
|
<span class="info-box-text" style="display: none"><i class="fas fa-subnetrange"></i>${subnetRangesString}</span>
|
||||||
<span class="info-box-text"><i class="fas fa-envelope"></i> ${obj.Client.email}</span>
|
<span class="info-box-text"><i class="fas fa-envelope"></i> ${obj.Client.email}</span>
|
||||||
<span class="info-box-text"><i class="fas fa-clock"></i>
|
<span class="info-box-text"><i class="fas fa-clock"></i>
|
||||||
${prettyDateTime(obj.Client.created_at)}</span>
|
${prettyDateTime(obj.Client.created_at)}</span>
|
||||||
|
|
|
@ -366,6 +366,10 @@ func GetClients(db store.IStore) echo.HandlerFunc {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for i, clientData := range clientDataList {
|
||||||
|
clientDataList[i] = util.FillClientSubnetRange(clientData)
|
||||||
|
}
|
||||||
|
|
||||||
return c.JSON(http.StatusOK, clientDataList)
|
return c.JSON(http.StatusOK, clientDataList)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -391,7 +395,7 @@ func GetClient(db store.IStore) echo.HandlerFunc {
|
||||||
return c.JSON(http.StatusNotFound, jsonHTTPResponse{false, "Client not found"})
|
return c.JSON(http.StatusNotFound, jsonHTTPResponse{false, "Client not found"})
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.JSON(http.StatusOK, clientData)
|
return c.JSON(http.StatusOK, util.FillClientSubnetRange(clientData))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -988,6 +992,13 @@ func MachineIPAddresses() echo.HandlerFunc {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetOrderedSubnetRanges handler to get the ordered list of subnet ranges
|
||||||
|
func GetOrderedSubnetRanges() echo.HandlerFunc {
|
||||||
|
return func(c echo.Context) error {
|
||||||
|
return c.JSON(http.StatusOK, util.SubnetRangesOrder)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// SuggestIPAllocation handler to get the list of ip address for client
|
// SuggestIPAllocation handler to get the list of ip address for client
|
||||||
func SuggestIPAllocation(db store.IStore) echo.HandlerFunc {
|
func SuggestIPAllocation(db store.IStore) echo.HandlerFunc {
|
||||||
return func(c echo.Context) error {
|
return func(c echo.Context) error {
|
||||||
|
@ -1009,20 +1020,46 @@ func SuggestIPAllocation(db store.IStore) echo.HandlerFunc {
|
||||||
false, "Cannot suggest ip allocation: failed to get list of allocated ip addresses",
|
false, "Cannot suggest ip allocation: failed to get list of allocated ip addresses",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
for _, cidr := range server.Interface.Addresses {
|
|
||||||
ip, err := util.GetAvailableIP(cidr, allocatedIPs)
|
sr := c.QueryParam("sr")
|
||||||
|
searchCIDRList := make([]string, 0)
|
||||||
|
found := false
|
||||||
|
|
||||||
|
// Use subnet range or default to interface addresses
|
||||||
|
if util.SubnetRanges[sr] != nil {
|
||||||
|
for _, cidr := range util.SubnetRanges[sr] {
|
||||||
|
searchCIDRList = append(searchCIDRList, cidr.String())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
searchCIDRList = append(searchCIDRList, server.Interface.Addresses...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save only unique IPs
|
||||||
|
ipSet := make(map[string]struct{})
|
||||||
|
|
||||||
|
for _, cidr := range searchCIDRList {
|
||||||
|
ip, err := util.GetAvailableIP(cidr, allocatedIPs, server.Interface.Addresses)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Failed to get available ip from a CIDR: ", err)
|
log.Error("Failed to get available ip from a CIDR: ", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
found = true
|
||||||
|
if strings.Contains(ip, ":") {
|
||||||
|
ipSet[fmt.Sprintf("%s/128", ip)] = struct{}{}
|
||||||
|
} else {
|
||||||
|
ipSet[fmt.Sprintf("%s/32", ip)] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !found {
|
||||||
return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{
|
return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{
|
||||||
false,
|
false,
|
||||||
fmt.Sprintf("Cannot suggest ip allocation: failed to get available ip from network %s", cidr),
|
"Cannot suggest ip allocation: failed to get available ip. Try a different subnet or deallocate some ips.",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if strings.Contains(ip, ":") {
|
|
||||||
suggestedIPs = append(suggestedIPs, fmt.Sprintf("%s/128", ip))
|
for ip := range ipSet {
|
||||||
} else {
|
suggestedIPs = append(suggestedIPs, ip)
|
||||||
suggestedIPs = append(suggestedIPs, fmt.Sprintf("%s/32", ip))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.JSON(http.StatusOK, suggestedIPs)
|
return c.JSON(http.StatusOK, suggestedIPs)
|
||||||
|
|
16
main.go
16
main.go
|
@ -45,6 +45,7 @@ var (
|
||||||
flagSessionSecret string = util.RandomString(32)
|
flagSessionSecret string = util.RandomString(32)
|
||||||
flagWgConfTemplate string
|
flagWgConfTemplate string
|
||||||
flagBasePath string
|
flagBasePath string
|
||||||
|
flagSubnetRanges string
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -81,6 +82,7 @@ func init() {
|
||||||
flag.StringVar(&flagEmailFromName, "email-from-name", util.LookupEnvOrString("EMAIL_FROM_NAME", flagEmailFromName), "'From' email name.")
|
flag.StringVar(&flagEmailFromName, "email-from-name", util.LookupEnvOrString("EMAIL_FROM_NAME", flagEmailFromName), "'From' email name.")
|
||||||
flag.StringVar(&flagWgConfTemplate, "wg-conf-template", util.LookupEnvOrString("WG_CONF_TEMPLATE", flagWgConfTemplate), "Path to custom wg.conf template.")
|
flag.StringVar(&flagWgConfTemplate, "wg-conf-template", util.LookupEnvOrString("WG_CONF_TEMPLATE", flagWgConfTemplate), "Path to custom wg.conf template.")
|
||||||
flag.StringVar(&flagBasePath, "base-path", util.LookupEnvOrString("BASE_PATH", flagBasePath), "The base path of the URL")
|
flag.StringVar(&flagBasePath, "base-path", util.LookupEnvOrString("BASE_PATH", flagBasePath), "The base path of the URL")
|
||||||
|
flag.StringVar(&flagSubnetRanges, "subnet-ranges", util.LookupEnvOrString("SUBNET_RANGES", flagSubnetRanges), "IP ranges to choose from when assigning an IP for a client.")
|
||||||
|
|
||||||
var (
|
var (
|
||||||
smtpPasswordLookup = util.LookupEnvOrString("SMTP_PASSWORD", flagSmtpPassword)
|
smtpPasswordLookup = util.LookupEnvOrString("SMTP_PASSWORD", flagSmtpPassword)
|
||||||
|
@ -127,6 +129,7 @@ func init() {
|
||||||
util.SessionSecret = []byte(flagSessionSecret)
|
util.SessionSecret = []byte(flagSessionSecret)
|
||||||
util.WgConfTemplate = flagWgConfTemplate
|
util.WgConfTemplate = flagWgConfTemplate
|
||||||
util.BasePath = util.ParseBasePath(flagBasePath)
|
util.BasePath = util.ParseBasePath(flagBasePath)
|
||||||
|
util.SubnetRanges = util.ParseSubnetRanges(flagSubnetRanges)
|
||||||
|
|
||||||
// print only if log level is INFO or lower
|
// print only if log level is INFO or lower
|
||||||
if lvl, _ := util.ParseLogLevel(util.LookupEnvOrString(util.LogLevel, "INFO")); lvl <= log.INFO {
|
if lvl, _ := util.ParseLogLevel(util.LookupEnvOrString(util.LogLevel, "INFO")); lvl <= log.INFO {
|
||||||
|
@ -145,6 +148,7 @@ func init() {
|
||||||
//fmt.Println("Session secret\t:", util.SessionSecret)
|
//fmt.Println("Session secret\t:", util.SessionSecret)
|
||||||
fmt.Println("Custom wg.conf\t:", util.WgConfTemplate)
|
fmt.Println("Custom wg.conf\t:", util.WgConfTemplate)
|
||||||
fmt.Println("Base path\t:", util.BasePath+"/")
|
fmt.Println("Base path\t:", util.BasePath+"/")
|
||||||
|
fmt.Println("Subnet ranges\t:", util.GetSubnetRangesString())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -170,6 +174,17 @@ func main() {
|
||||||
// create the wireguard config on start, if it doesn't exist
|
// create the wireguard config on start, if it doesn't exist
|
||||||
initServerConfig(db, tmplDir)
|
initServerConfig(db, tmplDir)
|
||||||
|
|
||||||
|
// Check if subnet ranges are valid for the server configuration
|
||||||
|
// Remove any non-valid CIDRs
|
||||||
|
if err := util.ValidateAndFixSubnetRanges(db); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print valid ranges
|
||||||
|
if lvl, _ := util.ParseLogLevel(util.LookupEnvOrString(util.LogLevel, "INFO")); lvl <= log.INFO {
|
||||||
|
fmt.Println("Valid subnet ranges:", util.GetSubnetRangesString())
|
||||||
|
}
|
||||||
|
|
||||||
// register routes
|
// register routes
|
||||||
app := router.New(tmplDir, extraData, util.SessionSecret)
|
app := router.New(tmplDir, extraData, util.SessionSecret)
|
||||||
|
|
||||||
|
@ -218,6 +233,7 @@ func main() {
|
||||||
app.GET(util.BasePath+"/api/clients", handler.GetClients(db), handler.ValidSession)
|
app.GET(util.BasePath+"/api/clients", handler.GetClients(db), handler.ValidSession)
|
||||||
app.GET(util.BasePath+"/api/client/:id", handler.GetClient(db), handler.ValidSession)
|
app.GET(util.BasePath+"/api/client/:id", handler.GetClient(db), handler.ValidSession)
|
||||||
app.GET(util.BasePath+"/api/machine-ips", handler.MachineIPAddresses(), handler.ValidSession)
|
app.GET(util.BasePath+"/api/machine-ips", handler.MachineIPAddresses(), handler.ValidSession)
|
||||||
|
app.GET(util.BasePath+"/api/subnet-ranges", handler.GetOrderedSubnetRanges(), handler.ValidSession)
|
||||||
app.GET(util.BasePath+"/api/suggest-client-ips", handler.SuggestIPAllocation(db), handler.ValidSession)
|
app.GET(util.BasePath+"/api/suggest-client-ips", handler.SuggestIPAllocation(db), handler.ValidSession)
|
||||||
app.POST(util.BasePath+"/api/apply-wg-config", handler.ApplyServerConfig(db, tmplDir), handler.ValidSession, handler.ContentTypeJson)
|
app.POST(util.BasePath+"/api/apply-wg-config", handler.ApplyServerConfig(db, tmplDir), handler.ValidSession, handler.ContentTypeJson)
|
||||||
app.GET(util.BasePath+"/wake_on_lan_hosts", handler.GetWakeOnLanHosts(db), handler.ValidSession)
|
app.GET(util.BasePath+"/wake_on_lan_hosts", handler.GetWakeOnLanHosts(db), handler.ValidSession)
|
||||||
|
|
|
@ -12,6 +12,7 @@ type Client struct {
|
||||||
PresharedKey string `json:"preshared_key"`
|
PresharedKey string `json:"preshared_key"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Email string `json:"email"`
|
Email string `json:"email"`
|
||||||
|
SubnetRanges []string `json:"subnet_ranges,omitempty"`
|
||||||
AllocatedIPs []string `json:"allocated_ips"`
|
AllocatedIPs []string `json:"allocated_ips"`
|
||||||
AllowedIPs []string `json:"allowed_ips"`
|
AllowedIPs []string `json:"allowed_ips"`
|
||||||
ExtraAllowedIPs []string `json:"extra_allowed_ips"`
|
ExtraAllowedIPs []string `json:"extra_allowed_ips"`
|
||||||
|
|
|
@ -58,11 +58,13 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group form-group-sm">
|
<div class="form-group form-group-sm">
|
||||||
<select name="status-selector" id="status-selector" class="custom-select form-control-navbar" style="margin-left: 0.5em; height: 90%; font-size: 14px;">
|
<select name="status-selector" id="status-selector" class="custom-select form-control-navbar" style="margin-left: 0.5em; height: 90%; font-size: 14px;">
|
||||||
|
<!-- THIS SECTION IS OVERRIDDEN BY JS. SEE updateSearchList() function in clients.html BEFORE EDITING -->
|
||||||
<option value="All">All</option>
|
<option value="All">All</option>
|
||||||
<option value="Enabled">Enabled</option>
|
<option value="Enabled">Enabled</option>
|
||||||
<option value="Disabled">Disabled</option>
|
<option value="Disabled">Disabled</option>
|
||||||
<option value="Connected">Connected</option>
|
<option value="Connected">Connected</option>
|
||||||
<option value="Disconnected">Disconnected</option>
|
<option value="Disconnected">Disconnected</option>
|
||||||
|
<!-- THIS SECTION IS OVERRIDDEN BY JS. SEE updateSearchList() function in clients.html BEFORE EDITING -->
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
@ -209,6 +211,12 @@
|
||||||
<label for="client_email" class="control-label">Email</label>
|
<label for="client_email" class="control-label">Email</label>
|
||||||
<input type="text" class="form-control" id="client_email" name="client_email">
|
<input type="text" class="form-control" id="client_email" name="client_email">
|
||||||
</div>
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="subnet_ranges" class="control-label">Subnet range</label>
|
||||||
|
<select id="subnet_ranges" class="select2"
|
||||||
|
data-placeholder="Select a subnet range" style="width: 100%;">
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="client_allocated_ips" class="control-label">IP Allocation</label>
|
<label for="client_allocated_ips" class="control-label">IP Allocation</label>
|
||||||
<input type="text" data-role="tagsinput" class="form-control" id="client_allocated_ips">
|
<input type="text" data-role="tagsinput" class="form-control" id="client_allocated_ips">
|
||||||
|
@ -368,6 +376,36 @@
|
||||||
|
|
||||||
$(document).ready(function () {
|
$(document).ready(function () {
|
||||||
|
|
||||||
|
addGlobalStyle(`
|
||||||
|
.toast-top-right-fix {
|
||||||
|
top: 67px;
|
||||||
|
right: 12px;
|
||||||
|
}
|
||||||
|
`, 'toastrToastStyleFix')
|
||||||
|
|
||||||
|
toastr.options.closeDuration = 100;
|
||||||
|
// toastr.options.timeOut = 10000;
|
||||||
|
toastr.options.positionClass = 'toast-top-right-fix';
|
||||||
|
|
||||||
|
updateApplyConfigVisibility()
|
||||||
|
// from clients.html
|
||||||
|
updateSearchList()
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
function addGlobalStyle(css, id) {
|
||||||
|
if (!document.querySelector('#' + id)) {
|
||||||
|
let head = document.head
|
||||||
|
if (!head) { return }
|
||||||
|
let style = document.createElement('style')
|
||||||
|
style.type = 'text/css'
|
||||||
|
style.id = id
|
||||||
|
style.innerHTML = css
|
||||||
|
head.appendChild(style)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateApplyConfigVisibility() {
|
||||||
$.ajax({
|
$.ajax({
|
||||||
cache: false,
|
cache: false,
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
|
@ -388,8 +426,7 @@
|
||||||
toastr.error(responseJson['message']);
|
toastr.error(responseJson['message']);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
}
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
// populateClient function for render new client info
|
// populateClient function for render new client info
|
||||||
|
@ -456,6 +493,7 @@
|
||||||
if (window.location.pathname === "{{.basePath}}/") {
|
if (window.location.pathname === "{{.basePath}}/") {
|
||||||
populateClient(resp.id);
|
populateClient(resp.id);
|
||||||
}
|
}
|
||||||
|
updateApplyConfigVisibility()
|
||||||
},
|
},
|
||||||
error: function(jqXHR, exception) {
|
error: function(jqXHR, exception) {
|
||||||
const responseJson = jQuery.parseJSON(jqXHR.responseText);
|
const responseJson = jQuery.parseJSON(jqXHR.responseText);
|
||||||
|
@ -466,19 +504,32 @@
|
||||||
|
|
||||||
// updateIPAllocationSuggestion function for automatically fill
|
// updateIPAllocationSuggestion function for automatically fill
|
||||||
// the IP Allocation input with suggested ip addresses
|
// the IP Allocation input with suggested ip addresses
|
||||||
function updateIPAllocationSuggestion() {
|
function updateIPAllocationSuggestion(forceDefault = false) {
|
||||||
|
let subnetRange = $("#subnet_ranges").select2('val');
|
||||||
|
|
||||||
|
if (forceDefault || !subnetRange || subnetRange.length === 0) {
|
||||||
|
subnetRange = '__default_any__'
|
||||||
|
}
|
||||||
$.ajax({
|
$.ajax({
|
||||||
cache: false,
|
cache: false,
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
url: '{{.basePath}}/api/suggest-client-ips',
|
url: `{{.basePath}}/api/suggest-client-ips?sr=${subnetRange}`,
|
||||||
dataType: 'json',
|
dataType: 'json',
|
||||||
contentType: "application/json",
|
contentType: "application/json",
|
||||||
success: function(data) {
|
success: function(data) {
|
||||||
|
const allocated_ips = $("#client_allocated_ips").val().split(",");
|
||||||
|
allocated_ips.forEach(function (item, index) {
|
||||||
|
$('#client_allocated_ips').removeTag(escape(item));
|
||||||
|
})
|
||||||
data.forEach(function (item, index) {
|
data.forEach(function (item, index) {
|
||||||
$('#client_allocated_ips').addTag(item);
|
$('#client_allocated_ips').addTag(item);
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
error: function(jqXHR, exception) {
|
error: function(jqXHR, exception) {
|
||||||
|
const allocated_ips = $("#client_allocated_ips").val().split(",");
|
||||||
|
allocated_ips.forEach(function (item, index) {
|
||||||
|
$('#client_allocated_ips').removeTag(escape(item));
|
||||||
|
})
|
||||||
const responseJson = jQuery.parseJSON(jqXHR.responseText);
|
const responseJson = jQuery.parseJSON(jqXHR.responseText);
|
||||||
toastr.error(responseJson['message']);
|
toastr.error(responseJson['message']);
|
||||||
}
|
}
|
||||||
|
@ -497,7 +548,6 @@
|
||||||
'defaultText': 'Add More',
|
'defaultText': 'Add More',
|
||||||
'removeWithBackspace': true,
|
'removeWithBackspace': true,
|
||||||
'minChars': 0,
|
'minChars': 0,
|
||||||
'minInputWidth': '100%',
|
|
||||||
'placeholderColor': '#666666'
|
'placeholderColor': '#666666'
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -509,7 +559,6 @@
|
||||||
'defaultText': 'Add More',
|
'defaultText': 'Add More',
|
||||||
'removeWithBackspace': true,
|
'removeWithBackspace': true,
|
||||||
'minChars': 0,
|
'minChars': 0,
|
||||||
'minInputWidth': '100%',
|
|
||||||
'placeholderColor': '#666666'
|
'placeholderColor': '#666666'
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -520,7 +569,6 @@
|
||||||
'defaultText': 'Add More',
|
'defaultText': 'Add More',
|
||||||
'removeWithBackspace': true,
|
'removeWithBackspace': true,
|
||||||
'minChars': 0,
|
'minChars': 0,
|
||||||
'minInputWidth': '100%',
|
|
||||||
'placeholderColor': '#666666'
|
'placeholderColor': '#666666'
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -565,10 +613,17 @@
|
||||||
$("#client_preshared_key").val("");
|
$("#client_preshared_key").val("");
|
||||||
$("#client_allocated_ips").importTags('');
|
$("#client_allocated_ips").importTags('');
|
||||||
$("#client_extra_allowed_ips").importTags('');
|
$("#client_extra_allowed_ips").importTags('');
|
||||||
updateIPAllocationSuggestion();
|
updateSubnetRangesList("#subnet_ranges");
|
||||||
|
updateIPAllocationSuggestion(true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// handle subnet range select
|
||||||
|
$('#subnet_ranges').on('select2:select', function (e) {
|
||||||
|
// console.log('Selected Option: ', $("#subnet_ranges").select2('val'));
|
||||||
|
updateIPAllocationSuggestion();
|
||||||
|
});
|
||||||
|
|
||||||
// apply_config_confirm button event
|
// apply_config_confirm button event
|
||||||
$(document).ready(function () {
|
$(document).ready(function () {
|
||||||
$("#apply_config_confirm").click(function () {
|
$("#apply_config_confirm").click(function () {
|
||||||
|
@ -579,6 +634,7 @@
|
||||||
dataType: 'json',
|
dataType: 'json',
|
||||||
contentType: "application/json",
|
contentType: "application/json",
|
||||||
success: function(data) {
|
success: function(data) {
|
||||||
|
updateApplyConfigVisibility()
|
||||||
$("#modal_apply_config").modal('hide');
|
$("#modal_apply_config").modal('hide');
|
||||||
toastr.success('Applied config successfully');
|
toastr.success('Applied config successfully');
|
||||||
},
|
},
|
||||||
|
|
|
@ -100,6 +100,12 @@ Wireguard Clients
|
||||||
<label for="_client_email" class="control-label">Email</label>
|
<label for="_client_email" class="control-label">Email</label>
|
||||||
<input type="text" class="form-control" id="_client_email" name="client_email">
|
<input type="text" class="form-control" id="_client_email" name="client_email">
|
||||||
</div>
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="_subnet_ranges" class="control-label">Subnet range</label>
|
||||||
|
<select id="_subnet_ranges" class="select2"
|
||||||
|
data-placeholder="Select a subnet range" style="width: 100%;">
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="_client_allocated_ips" class="control-label">IP Allocation</label>
|
<label for="_client_allocated_ips" class="control-label">IP Allocation</label>
|
||||||
<input type="text" data-role="tagsinput" class="form-control" id="_client_allocated_ips">
|
<input type="text" data-role="tagsinput" class="form-control" id="_client_allocated_ips">
|
||||||
|
@ -253,12 +259,101 @@ Wireguard Clients
|
||||||
setClientStatus(clientID, true);
|
setClientStatus(clientID, true);
|
||||||
const divElement = document.getElementById("paused_" + clientID);
|
const divElement = document.getElementById("paused_" + clientID);
|
||||||
divElement.style.visibility = "hidden";
|
divElement.style.visibility = "hidden";
|
||||||
|
updateApplyConfigVisibility()
|
||||||
}
|
}
|
||||||
|
|
||||||
function pauseClient(clientID) {
|
function pauseClient(clientID) {
|
||||||
setClientStatus(clientID, false);
|
setClientStatus(clientID, false);
|
||||||
const divElement = document.getElementById("paused_" + clientID);
|
const divElement = document.getElementById("paused_" + clientID);
|
||||||
divElement.style.visibility = "visible";
|
divElement.style.visibility = "visible";
|
||||||
|
updateApplyConfigVisibility()
|
||||||
|
}
|
||||||
|
|
||||||
|
// updateIPAllocationSuggestion function for automatically fill
|
||||||
|
// the IP Allocation input with suggested ip addresses
|
||||||
|
// FOR CHANGING A SUBNET OF AN EXISTING CLIENT
|
||||||
|
function updateIPAllocationSuggestionExisting() {
|
||||||
|
let subnetRange = $("#_subnet_ranges").select2('val');
|
||||||
|
|
||||||
|
if (!subnetRange || subnetRange.length === 0) {
|
||||||
|
subnetRange = '__default_any__'
|
||||||
|
}
|
||||||
|
$.ajax({
|
||||||
|
cache: false,
|
||||||
|
method: 'GET',
|
||||||
|
url: `{{.basePath}}/api/suggest-client-ips?sr=${subnetRange}`,
|
||||||
|
dataType: 'json',
|
||||||
|
contentType: "application/json",
|
||||||
|
success: function(data) {
|
||||||
|
const allocated_ips = $("#_client_allocated_ips").val().split(",");
|
||||||
|
allocated_ips.forEach(function (item, index) {
|
||||||
|
$('#_client_allocated_ips').removeTag(escape(item));
|
||||||
|
})
|
||||||
|
data.forEach(function (item, index) {
|
||||||
|
$('#_client_allocated_ips').addTag(item);
|
||||||
|
})
|
||||||
|
},
|
||||||
|
error: function(jqXHR, exception) {
|
||||||
|
const allocated_ips = $("#_client_allocated_ips").val().split(",");
|
||||||
|
allocated_ips.forEach(function (item, index) {
|
||||||
|
$('#_client_allocated_ips').removeTag(escape(item));
|
||||||
|
})
|
||||||
|
const responseJson = jQuery.parseJSON(jqXHR.responseText);
|
||||||
|
toastr.error(responseJson['message']);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateSubnetRangesList(elementID, preselectedVal) {
|
||||||
|
$.getJSON("{{.basePath}}/api/subnet-ranges", null, function(data) {
|
||||||
|
$(`${elementID} option`).remove();
|
||||||
|
$(elementID).append(
|
||||||
|
$("<option></option>")
|
||||||
|
.text("Any")
|
||||||
|
.val("__default_any__")
|
||||||
|
);
|
||||||
|
$.each(data, function(index, item) {
|
||||||
|
$(elementID).append(
|
||||||
|
$("<option></option>")
|
||||||
|
.text(item)
|
||||||
|
.val(item)
|
||||||
|
);
|
||||||
|
if (item === preselectedVal) {
|
||||||
|
console.log(preselectedVal);
|
||||||
|
$(elementID).val(preselectedVal).trigger('change')
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateSearchList() {
|
||||||
|
$.getJSON("{{.basePath}}/api/subnet-ranges", null, function(data) {
|
||||||
|
$("#status-selector option").remove();
|
||||||
|
$("#status-selector").append(
|
||||||
|
$("<option></option>")
|
||||||
|
.text("All")
|
||||||
|
.val("All"),
|
||||||
|
$("<option></option>")
|
||||||
|
.text("Enabled")
|
||||||
|
.val("Enabled"),
|
||||||
|
$("<option></option>")
|
||||||
|
.text("Disabled")
|
||||||
|
.val("Disabled"),
|
||||||
|
$("<option></option>")
|
||||||
|
.text("Connected")
|
||||||
|
.val("Connected"),
|
||||||
|
$("<option></option>")
|
||||||
|
.text("Disconnected")
|
||||||
|
.val("Disconnected")
|
||||||
|
);
|
||||||
|
$.each(data, function(index, item) {
|
||||||
|
$("#status-selector").append(
|
||||||
|
$("<option></option>")
|
||||||
|
.text(item)
|
||||||
|
.val(item)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<script>
|
<script>
|
||||||
|
@ -349,7 +444,18 @@ Wireguard Clients
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
$('.col-lg-4').show();
|
$('.col-lg-4').hide();
|
||||||
|
const selectedSR = $("#status-selector").val()
|
||||||
|
$(".fa-subnetrange").each(function () {
|
||||||
|
const srs = $(this).parent().text().trim().split(',')
|
||||||
|
for (const sr of srs) {
|
||||||
|
if (sr === selectedSR) {
|
||||||
|
$(this).closest('.col-lg-4').show();
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
// $('.col-lg-4').show();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -400,6 +506,7 @@ Wireguard Clients
|
||||||
toastr.success('Removed client successfully');
|
toastr.success('Removed client successfully');
|
||||||
const divElement = document.getElementById('client_' + client_id);
|
const divElement = document.getElementById('client_' + client_id);
|
||||||
divElement.style.display = "none";
|
divElement.style.display = "none";
|
||||||
|
updateApplyConfigVisibility()
|
||||||
},
|
},
|
||||||
error: function(jqXHR, exception) {
|
error: function(jqXHR, exception) {
|
||||||
const responseJson = jQuery.parseJSON(jqXHR.responseText);
|
const responseJson = jQuery.parseJSON(jqXHR.responseText);
|
||||||
|
@ -427,7 +534,6 @@ Wireguard Clients
|
||||||
'defaultText': 'Add More',
|
'defaultText': 'Add More',
|
||||||
'removeWithBackspace': true,
|
'removeWithBackspace': true,
|
||||||
'minChars': 0,
|
'minChars': 0,
|
||||||
'minInputWidth': '100%',
|
|
||||||
'placeholderColor': '#666666'
|
'placeholderColor': '#666666'
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -439,7 +545,6 @@ Wireguard Clients
|
||||||
'defaultText': 'Add More',
|
'defaultText': 'Add More',
|
||||||
'removeWithBackspace': true,
|
'removeWithBackspace': true,
|
||||||
'minChars': 0,
|
'minChars': 0,
|
||||||
'minInputWidth': '100%',
|
|
||||||
'placeholderColor': '#666666'
|
'placeholderColor': '#666666'
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -450,7 +555,6 @@ Wireguard Clients
|
||||||
'defaultText': 'Add More',
|
'defaultText': 'Add More',
|
||||||
'removeWithBackspace' : true,
|
'removeWithBackspace' : true,
|
||||||
'minChars': 0,
|
'minChars': 0,
|
||||||
'minInputWidth': '100%',
|
|
||||||
'placeholderColor': '#666666'
|
'placeholderColor': '#666666'
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -469,6 +573,13 @@ Wireguard Clients
|
||||||
modal.find("#_client_name").val(client.name);
|
modal.find("#_client_name").val(client.name);
|
||||||
modal.find("#_client_email").val(client.email);
|
modal.find("#_client_email").val(client.email);
|
||||||
|
|
||||||
|
let preselectedEl
|
||||||
|
if (client.subnet_ranges && client.subnet_ranges.length > 0) {
|
||||||
|
preselectedEl = client.subnet_ranges[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
updateSubnetRangesList("#_subnet_ranges", preselectedEl);
|
||||||
|
|
||||||
modal.find("#_client_allocated_ips").importTags('');
|
modal.find("#_client_allocated_ips").importTags('');
|
||||||
client.allocated_ips.forEach(function (obj) {
|
client.allocated_ips.forEach(function (obj) {
|
||||||
modal.find("#_client_allocated_ips").addTag(obj);
|
modal.find("#_client_allocated_ips").addTag(obj);
|
||||||
|
@ -491,6 +602,11 @@ Wireguard Clients
|
||||||
|
|
||||||
modal.find("#_client_public_key").val(client.public_key);
|
modal.find("#_client_public_key").val(client.public_key);
|
||||||
modal.find("#_client_preshared_key").val(client.preshared_key);
|
modal.find("#_client_preshared_key").val(client.preshared_key);
|
||||||
|
|
||||||
|
// handle subnet range select
|
||||||
|
$('#_subnet_ranges').on('select2:select', function (e) {
|
||||||
|
updateIPAllocationSuggestionExisting();
|
||||||
|
});
|
||||||
},
|
},
|
||||||
error: function (jqXHR, exception) {
|
error: function (jqXHR, exception) {
|
||||||
const responseJson = jQuery.parseJSON(jqXHR.responseText);
|
const responseJson = jQuery.parseJSON(jqXHR.responseText);
|
||||||
|
|
|
@ -203,7 +203,6 @@ Global Settings
|
||||||
'defaultText': 'Add More',
|
'defaultText': 'Add More',
|
||||||
'removeWithBackspace': true,
|
'removeWithBackspace': true,
|
||||||
'minChars': 0,
|
'minChars': 0,
|
||||||
'minInputWidth': '100%',
|
|
||||||
'placeholderColor': '#666666'
|
'placeholderColor': '#666666'
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -167,7 +167,6 @@ Wireguard Server Settings
|
||||||
'defaultText': 'Add More',
|
'defaultText': 'Add More',
|
||||||
'removeWithBackspace': true,
|
'removeWithBackspace': true,
|
||||||
'minChars': 0,
|
'minChars': 0,
|
||||||
'minInputWidth': '100%',
|
|
||||||
'placeholderColor': '#666666'
|
'placeholderColor': '#666666'
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
3
util/cache.go
Normal file
3
util/cache.go
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
package util
|
||||||
|
|
||||||
|
var IPToSubnetRange = map[string]uint16{}
|
|
@ -1,6 +1,11 @@
|
||||||
package util
|
package util
|
||||||
|
|
||||||
import "strings"
|
import (
|
||||||
|
"net"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/labstack/gommon/log"
|
||||||
|
)
|
||||||
|
|
||||||
// Runtime config
|
// Runtime config
|
||||||
var (
|
var (
|
||||||
|
@ -19,6 +24,8 @@ var (
|
||||||
SessionSecret []byte
|
SessionSecret []byte
|
||||||
WgConfTemplate string
|
WgConfTemplate string
|
||||||
BasePath string
|
BasePath string
|
||||||
|
SubnetRanges map[string]([]*net.IPNet)
|
||||||
|
SubnetRangesOrder []string
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -66,3 +73,45 @@ func ParseBasePath(basePath string) string {
|
||||||
}
|
}
|
||||||
return basePath
|
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
|
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
|
// ValidateCIDR to validate a network CIDR
|
||||||
func ValidateCIDR(cidr string) bool {
|
func ValidateCIDR(cidr string) bool {
|
||||||
_, _, err := net.ParseCIDR(cidr)
|
_, _, err := net.ParseCIDR(cidr)
|
||||||
|
@ -317,15 +326,32 @@ func GetBroadcastIP(n *net.IPNet) net.IP {
|
||||||
return broadcast
|
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
|
// 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)
|
ip, net, err := net.ParseCIDR(cidr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
broadcastAddr := GetBroadcastIP(net).String()
|
unavailableIPs := GetBroadcastAndNetworkAddrsLookup(interfaceAddresses)
|
||||||
networkAddr := net.IP.String()
|
|
||||||
|
|
||||||
for ip := ip.Mask(net.Mask); net.Contains(ip); inc(ip) {
|
for ip := ip.Mask(net.Mask); net.Contains(ip); inc(ip) {
|
||||||
available := true
|
available := true
|
||||||
|
@ -336,7 +362,7 @@ func GetAvailableIP(cidr string, allocatedList []string) (string, error) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if available && suggestedAddr != networkAddr && suggestedAddr != broadcastAddr {
|
if available && !unavailableIPs[suggestedAddr] {
|
||||||
return suggestedAddr, nil
|
return suggestedAddr, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -384,6 +410,126 @@ func ValidateIPAllocation(serverAddresses []string, ipAllocatedList []string, ip
|
||||||
return true, nil
|
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
|
// 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 {
|
func WriteWireGuardServerConfig(tmplDir fs.FS, serverConfig model.Server, clientDataList []model.ClientData, usersList []model.User, globalSettings model.GlobalSetting) error {
|
||||||
var tmplWireguardConf string
|
var tmplWireguardConf string
|
||||||
|
|
Loading…
Add table
Reference in a new issue