Full implementation of subnet ranges for a new client, fixes

fix: free ip search was stopping after no free ip on the first interface
fix: toast obstructing the buttons
fix: stuck apply config button
This commit is contained in:
0xCA 2023-11-05 23:45:12 +05:00
parent 92333a08d8
commit 53eaab0079
5 changed files with 134 additions and 16 deletions

View file

@ -988,6 +988,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,15 +1016,27 @@ 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...)
}
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)
return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{ continue
false,
fmt.Sprintf("Cannot suggest ip allocation: failed to get available ip from network %s", cidr),
})
} }
found = true
if strings.Contains(ip, ":") { if strings.Contains(ip, ":") {
suggestedIPs = append(suggestedIPs, fmt.Sprintf("%s/128", ip)) suggestedIPs = append(suggestedIPs, fmt.Sprintf("%s/128", ip))
} else { } else {
@ -1025,6 +1044,13 @@ func SuggestIPAllocation(db store.IStore) echo.HandlerFunc {
} }
} }
if !found {
return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{
false,
"Cannot suggest ip allocation: failed to get available ip. Try a different subnet or deallocate some ips.",
})
}
return c.JSON(http.StatusOK, suggestedIPs) return c.JSON(http.StatusOK, suggestedIPs)
} }
} }

View file

@ -233,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)

View file

@ -209,6 +209,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 ranges</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 +374,35 @@
$(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()
});
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 +423,7 @@
toastr.error(responseJson['message']); toastr.error(responseJson['message']);
} }
}); });
}
});
// populateClient function for render new client info // populateClient function for render new client info
@ -466,19 +500,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']);
} }
@ -565,10 +612,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();
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 +633,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');
}, },

View file

@ -260,6 +260,25 @@ Wireguard Clients
const divElement = document.getElementById("paused_" + clientID); const divElement = document.getElementById("paused_" + clientID);
divElement.style.visibility = "visible"; divElement.style.visibility = "visible";
} }
function updateSubnetRangesList() {
$.getJSON("{{.basePath}}/api/subnet-ranges", null, function(data) {
console.log(data);
$("#subnet_ranges option").remove();
$("#subnet_ranges").append(
$("<option></option>")
.text("Any")
.val("__default_any__")
);
$.each(data, function(index, item) {
$("#subnet_ranges").append(
$("<option></option>")
.text(item)
.val(item)
);
});
});
}
</script> </script>
<script> <script>
// load client list // load client list

View file

@ -326,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
@ -345,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
} }
} }