diff --git a/handler/routes.go b/handler/routes.go index 41207b1..f27e441 100644 --- a/handler/routes.go +++ b/handler/routes.go @@ -3,6 +3,7 @@ package handler import ( "encoding/base64" "encoding/json" + "fmt" "net/http" "time" @@ -26,11 +27,6 @@ func WireGuardClients() echo.HandlerFunc { log.Error("Cannot initialize the database: ", err) } - records, err := db.ReadAll("clients") - if err != nil { - log.Error("Cannot fetch clients from database: ", err) - } - // read server information serverInterface := model.ServerInterface{} if err := db.Read("server", "interfaces", &serverInterface); err != nil { @@ -53,6 +49,11 @@ func WireGuardClients() echo.HandlerFunc { server.KeyPair = &serverKeyPair // read client information and build a client list + records, err := db.ReadAll("clients") + if err != nil { + log.Error("Cannot fetch clients from database: ", err) + } + clientDataList := []model.ClientData{} for _, f := range records { client := model.Client{} @@ -76,7 +77,7 @@ func WireGuardClients() echo.HandlerFunc { } return c.Render(http.StatusOK, "clients.html", map[string]interface{}{ - "baseData": model.BaseData{""}, + "baseData": model.BaseData{Active: ""}, "clientDataList": clientDataList, }) } @@ -88,6 +89,13 @@ func NewClient() echo.HandlerFunc { client := new(model.Client) c.Bind(client) + // validate the input Allocation IPs + // TODO: validate if they are really available + if util.ValidateCIDRList(client.AllocatedIPs) == false { + log.Warnf("Invalid allocation ip input from user: %v", client.AllocatedIPs) + return c.JSON(http.StatusBadRequest, jsonHTTPResponse{false, "IP allocation is not valid"}) + } + // validate the input AllowedIPs if util.ValidateAllowedIPs(client.AllowedIPs) == false { log.Warnf("Invalid Allowed IPs input from user: %v", client.AllowedIPs) @@ -167,7 +175,7 @@ func WireGuardServer() echo.HandlerFunc { } return c.Render(http.StatusOK, "server.html", map[string]interface{}{ - "baseData": model.BaseData{"wg-server"}, + "baseData": model.BaseData{Active: "wg-server"}, "serverInterface": serverInterface, "serverKeyPair": serverKeyPair, }) @@ -247,7 +255,7 @@ func GlobalSettings() echo.HandlerFunc { } return c.Render(http.StatusOK, "global_settings.html", map[string]interface{}{ - "baseData": model.BaseData{"global-settings"}, + "baseData": model.BaseData{Active: "global-settings"}, "globalSettings": globalSettings, }) } @@ -302,3 +310,41 @@ func MachineIPAddresses() echo.HandlerFunc { return c.JSON(http.StatusOK, interfaceList) } } + +// SuggestIPAllocation handler to get the list of ip address for client +func SuggestIPAllocation() echo.HandlerFunc { + return func(c echo.Context) error { + // initialize database directory + dir := "./db" + db, err := scribble.New(dir, nil) + if err != nil { + log.Error("Cannot initialize the database: ", err) + } + + // read server information + serverInterface := model.ServerInterface{} + if err := db.Read("server", "interfaces", &serverInterface); err != nil { + log.Error("Cannot fetch server interface config from database: ", err) + } + + // return the list of suggestedIPs + // we take the first available ip address from + // each server's network addresses. + suggestedIPs := make([]string, 0) + allocatedIPs, err := util.GetAllocatedIPs() + if err != nil { + log.Error("Cannot suggest ip allocation. Failed to get list of allocated ip addresses: ", err) + return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{false, "Cannot suggest ip allocation: failed to get list of allocated ip addresses"}) + } + for _, cidr := range serverInterface.Addresses { + ip, err := util.GetAvailableIP(cidr, allocatedIPs) + if err != nil { + log.Error("Failed to get available ip from a CIDR: ", err) + return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{false, fmt.Sprintf("Cannot suggest ip allocation: failed to get available ip from network %s", cidr)}) + } + suggestedIPs = append(suggestedIPs, fmt.Sprintf("%s/32", ip)) + } + + return c.JSON(http.StatusOK, suggestedIPs) + } +} diff --git a/main.go b/main.go index 9553c7f..df6f7b1 100644 --- a/main.go +++ b/main.go @@ -17,5 +17,6 @@ func main() { app.GET("/global-settings", handler.GlobalSettings()) app.POST("/global-settings", handler.GlobalSettingSubmit()) app.GET("/api/machine-ips", handler.MachineIPAddresses()) + app.GET("/api/suggest-client-ips", handler.SuggestIPAllocation()) app.Logger.Fatal(app.Start("127.0.0.1:5000")) } diff --git a/templates/base.html b/templates/base.html index d9af34e..5b1e923 100644 --- a/templates/base.html +++ b/templates/base.html @@ -137,16 +137,7 @@
- +
@@ -228,10 +219,9 @@ diff --git a/util/util.go b/util/util.go index 343fa10..c355f42 100644 --- a/util/util.go +++ b/util/util.go @@ -1,6 +1,8 @@ package util import ( + "encoding/json" + "errors" "fmt" "net" "strings" @@ -8,6 +10,7 @@ import ( externalip "github.com/glendc/go-external-ip" "github.com/ngoduykhanh/wireguard-ui/model" + "github.com/sdomino/scribble" ) // BuildClientConfig to create wireguard client config string @@ -154,3 +157,114 @@ func GetPublicIP() (model.Interface, error) { return publicInterface, err } + +// GetIPFromCIDR get ip from CIDR +func GetIPFromCIDR(cidr string) (string, error) { + ip, _, err := net.ParseCIDR(cidr) + if err != nil { + return "", err + } + return ip.String(), nil +} + +// GetAllocatedIPs to get all ip addresses allocated to clients and server +func GetAllocatedIPs() ([]string, error) { + allocatedIPs := make([]string, 0) + + // initialize database directory + dir := "./db" + db, err := scribble.New(dir, nil) + if err != nil { + return nil, err + } + + // read server information + serverInterface := model.ServerInterface{} + if err := db.Read("server", "interfaces", &serverInterface); err != nil { + return nil, err + } + + // append server's addresses to the result + for _, cidr := range serverInterface.Addresses { + ip, err := GetIPFromCIDR(cidr) + if err != nil { + return nil, err + } + allocatedIPs = append(allocatedIPs, ip) + } + + // read client information + records, err := db.ReadAll("clients") + if err != nil { + return nil, err + } + + // append client's addresses to the result + for _, f := range records { + client := model.Client{} + if err := json.Unmarshal([]byte(f), &client); err != nil { + return nil, err + } + + for _, cidr := range client.AllocatedIPs { + ip, err := GetIPFromCIDR(cidr) + if err != nil { + return nil, err + } + allocatedIPs = append(allocatedIPs, ip) + } + } + + return allocatedIPs, nil +} + +// inc from https://play.golang.org/p/m8TNTtygK0 +func inc(ip net.IP) { + for j := len(ip) - 1; j >= 0; j-- { + ip[j]++ + if ip[j] > 0 { + break + } + } +} + +// GetBroadcastIP func to get the broadcast ip address of a network +func GetBroadcastIP(n *net.IPNet) net.IP { + var broadcast net.IP + if len(n.IP) == 4 { + broadcast = net.ParseIP("0.0.0.0").To4() + } else { + broadcast = net.ParseIP("::") + } + for i := 0; i < len(n.IP); i++ { + broadcast[i] = n.IP[i] | ^n.Mask[i] + } + return broadcast +} + +// GetAvailableIP get the ip address that can be allocated from an CIDR +func GetAvailableIP(cidr string, allocatedList []string) (string, error) { + ip, net, err := net.ParseCIDR(cidr) + if err != nil { + return "", err + } + + broadcastAddr := GetBroadcastIP(net).String() + networkAddr := net.IP.String() + + for ip := ip.Mask(net.Mask); net.Contains(ip); inc(ip) { + available := true + suggestedAddr := ip.String() + for _, allocatedAddr := range allocatedList { + if suggestedAddr == allocatedAddr { + available = false + break + } + } + if available && suggestedAddr != networkAddr && suggestedAddr != broadcastAddr { + return suggestedAddr, nil + } + } + + return "", errors.New("No more available ip address") +}