diff options
| -rw-r--r-- | CHANGES.md | 4 | ||||
| -rw-r--r-- | docs/src/api/functiondocs.md | 18 | ||||
| -rw-r--r-- | exports/exports.go | 20 | ||||
| -rw-r--r-- | util/util.go | 33 | ||||
| -rw-r--r-- | util/util_test.go | 82 | ||||
| -rw-r--r-- | wrappers/python/eduvpn_common/loader.py | 1 | ||||
| -rw-r--r-- | wrappers/python/eduvpn_common/main.py | 17 |
7 files changed, 173 insertions, 2 deletions
@@ -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 |
