summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGES.md4
-rw-r--r--docs/src/api/functiondocs.md18
-rw-r--r--exports/exports.go20
-rw-r--r--util/util.go33
-rw-r--r--util/util_test.go82
-rw-r--r--wrappers/python/eduvpn_common/loader.py1
-rw-r--r--wrappers/python/eduvpn_common/main.py17
7 files changed, 173 insertions, 2 deletions
diff --git a/CHANGES.md b/CHANGES.md
index d9613ce..9d8ea05 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -1,3 +1,7 @@
+# UNRELEASED
+* Util:
+ - Add a function to calculate the gateway address for a given IPv4/IPv6 subnet
+
# 2.1.0 (2024-07-25)
* Discovery:
- Fetch on startup in the background with a function "DiscoveryStartup"
diff --git a/docs/src/api/functiondocs.md b/docs/src/api/functiondocs.md
index dccb547..c1b1122 100644
--- a/docs/src/api/functiondocs.md
+++ b/docs/src/api/functiondocs.md
@@ -4,6 +4,7 @@ This document was automatically generated from the exports/exports.go file
- [About the API](#about-the-api)
- [Functions](#functions)
* [AddServer](#addserver)
+ * [CalculateGateway](#calculategateway)
* [Cleanup](#cleanup)
* [CookieCancel](#cookiecancel)
* [CookieDelete](#cookiedelete)
@@ -113,6 +114,20 @@ Example Output:
"misc": false
}
+## CalculateGateway
+Signature:
+ ```go
+func CalculateGateway(subnet *C.char) (*C.char, *C.char)
+```
+CalculateGateway calculates the gateway for a subnet, it can take IPv4 or
+IPv6 networks with CIDR notation as inputs and returns the gateway address
+This is useful to pass to `StartFailover`. It returns an error if it fails
+to calculate a gateway.
+
+Example Input: ```CalculateGateway("10.10.0.5/24")```
+
+Example Output: ```"10.10.0.1", null```
+
## Cleanup
Signature:
```go
@@ -854,7 +869,8 @@ WireGuard connection to OpenVPN over TCP
- `c` is the cookie that is passed for cancellation. To create a cookie,
use the `CookieNew` function
- - `gateway` is the gateway IP of the VPN
+ - `gateway` is the gateway IP of the VPN. You MAY calculate this with the
+ `CalculateGateway` function
- `readRxBytes` is a function that returns the current rx bytes of the VPN
interface, this should return a `long long int` in c
diff --git a/exports/exports.go b/exports/exports.go
index f1b55cd..a569b8e 100644
--- a/exports/exports.go
+++ b/exports/exports.go
@@ -72,6 +72,7 @@ import (
"github.com/eduvpn/eduvpn-common/types/cookie"
errtypes "github.com/eduvpn/eduvpn-common/types/error"
srvtypes "github.com/eduvpn/eduvpn-common/types/server"
+ "github.com/eduvpn/eduvpn-common/util"
)
// VPNState is the current state of the library
@@ -885,7 +886,7 @@ func RenewSession(c C.uintptr_t) *C.char {
// Which is useful to go from a broken WireGuard connection to OpenVPN over TCP
//
// - `c` is the cookie that is passed for cancellation. To create a cookie, use the `CookieNew` function
-// - `gateway` is the gateway IP of the VPN
+// - `gateway` is the gateway IP of the VPN. You MAY calculate this with the `CalculateGateway` function
// - `readRxBytes` is a function that returns the current rx bytes of the VPN interface, this should return a `long long int` in c
//
// It returns a boolean whether or not the common lib has determined that it cannot reach the gateway. Non-zero=dropped, zero=not dropped.
@@ -1129,6 +1130,23 @@ func SetTokenHandler(getter C.TokenGetter, setter C.TokenSetter) *C.char {
return nil
}
+// CalculateGateway calculates the gateway for a subnet, it can take IPv4 or IPv6 networks with CIDR notation as inputs and returns the gateway address
+// This is useful to pass to `StartFailover`. It returns an error if it fails to calculate a gateway.
+// This is implemented according to: https://docs.eduvpn.org/server/v3/client-implementation-notes.html#fail-over
+//
+// Example Input: ```CalculateGateway("10.10.0.5/24")```
+//
+// Example Output: ```"10.10.0.1", null```
+//
+//export CalculateGateway
+func CalculateGateway(subnet *C.char) (*C.char, *C.char) {
+ gw, err := util.CalculateGateway(C.GoString(subnet))
+ if err != nil {
+ return nil, getCError(err)
+ }
+ return C.CString(gw), nil
+}
+
// CookieNew creates a new cookie and returns it
//
// This value should not be parsed or converted somehow by the client
diff --git a/util/util.go b/util/util.go
new file mode 100644
index 0000000..c7816df
--- /dev/null
+++ b/util/util.go
@@ -0,0 +1,33 @@
+// package util defines public utility functions to be used by applications
+// these are outside of the client package as they can be used even if a client hasn't been created yet
+package util
+
+import (
+ "net"
+
+ "github.com/eduvpn/eduvpn-common/i18nerr"
+)
+
+// CalculateGateway takes a CIDR encoded subnet `cidr` and returns the gateway and an error
+func CalculateGateway(cidr string) (string, error) {
+ _, ipn, err := net.ParseCIDR(cidr)
+ if err != nil {
+ return "", i18nerr.WrapInternalf(err, "failed to parse CIDR for calculating gateway: %v", cidr)
+ }
+
+ ret := make(net.IP, len(ipn.IP))
+ copy(ret, ipn.IP)
+
+ for i := len(ret) - 1; i >= 0; i-- {
+ ret[i]++
+ if ret[i] > 0 {
+ break
+ }
+ }
+
+ if !ipn.Contains(ret) {
+ return "", i18nerr.Newf("IP network does not contain incremented IP: %v", ret)
+ }
+
+ return ret.String(), nil
+}
diff --git a/util/util_test.go b/util/util_test.go
new file mode 100644
index 0000000..0f4888d
--- /dev/null
+++ b/util/util_test.go
@@ -0,0 +1,82 @@
+package util
+
+import (
+ "testing"
+
+ "github.com/eduvpn/eduvpn-common/internal/test"
+)
+
+func TestCalculateGateway(t *testing.T) {
+ cases := []struct {
+ in string
+ want string
+ err string
+ }{
+ // normal cases
+ {
+ in: "10.10.10.5/24",
+ want: "10.10.10.1",
+ err: "",
+ },
+ {
+ in: "10.10.10.130/25",
+ want: "10.10.10.129",
+ err: "",
+ },
+ {
+ in: "fd42::5/112",
+ want: "fd42::1",
+ err: "",
+ },
+ {
+ in: "5502:df9::/64",
+ want: "5502:df9::1",
+ err: "",
+ },
+ // unrealistic scenario but we have to handle these!
+ {
+ in: "5502:df9::0/128",
+ want: "",
+ err: "IP network does not contain incremented IP: 5502:df9::1",
+ },
+ {
+ in: "5502:df9::ffff/128",
+ want: "",
+ err: "IP network does not contain incremented IP: 5502:df9::1:0",
+ },
+ {
+ in: "10.0.0.0/32",
+ want: "",
+ err: "IP network does not contain incremented IP: 10.0.0.1",
+ },
+ {
+ in: "10.0.0.255/32",
+ want: "",
+ err: "IP network does not contain incremented IP: 10.0.1.0",
+ },
+ // parsing errors
+ {
+ in: "10.0.0.1",
+ want: "",
+ err: "An internal error occurred. The cause of the error is: invalid CIDR address: 10.0.0.1.",
+ },
+ {
+ in: "bla",
+ want: "",
+ err: "An internal error occurred. The cause of the error is: invalid CIDR address: bla.",
+ },
+ {
+ in: "5502:df9::ffff",
+ want: "",
+ err: "An internal error occurred. The cause of the error is: invalid CIDR address: 5502:df9::ffff.",
+ },
+ }
+
+ for _, c := range cases {
+ got, err := CalculateGateway(c.in)
+ test.AssertError(t, err, c.err)
+ if got != c.want {
+ t.Fatalf("got: %v not equal to want: %v", got, c.want)
+ }
+ }
+}
diff --git a/wrappers/python/eduvpn_common/loader.py b/wrappers/python/eduvpn_common/loader.py
index 4bfc55f..e163148 100644
--- a/wrappers/python/eduvpn_common/loader.py
+++ b/wrappers/python/eduvpn_common/loader.py
@@ -99,6 +99,7 @@ def initialize_functions(lib: CDLL) -> None:
],
c_void_p,
)
+ lib.CalculateGateway.argtypes, lib.CalculateGateway.restype = [c_char_p], DataError
lib.Cleanup.argtypes, lib.Cleanup.restype = [c_int], c_void_p
lib.SetProfileID.argtypes, lib.SetProfileID.restype = [c_char_p], c_void_p
lib.CookieNew.argtypes, lib.CookieNew.restype = [], c_int
diff --git a/wrappers/python/eduvpn_common/main.py b/wrappers/python/eduvpn_common/main.py
index 8c556e9..ce52024 100644
--- a/wrappers/python/eduvpn_common/main.py
+++ b/wrappers/python/eduvpn_common/main.py
@@ -152,6 +152,23 @@ class EduVPN(object):
if register_err:
forwardError(register_err)
+ def calculate_gateway(self, subnet: str) -> str:
+ """Calculate the gateway
+
+ :param subnet: str: The IPv4/IPv6 subnet in CIDR notation
+
+ :raises WrappedError: An error by the Go library
+ """
+ gw, gw_err = self.go_function(
+ self.lib.CalculateGateway,
+ subnet,
+ )
+
+ if gw_err:
+ forwardError(gw_err)
+
+ return gw
+
def add_server(self, _type: ServerType, _id: str, ot: Optional[int] = None) -> None:
"""Add a server