diff options
| -rw-r--r-- | exports/exports.go | 166 |
1 files changed, 165 insertions, 1 deletions
diff --git a/exports/exports.go b/exports/exports.go index 709e7ba..e400189 100644 --- a/exports/exports.go +++ b/exports/exports.go @@ -1,3 +1,9 @@ +// package main implements the main exported API to be used by other languages +// Some notes: +// - Errors are returned as c strings, free them using FreeString. Same is the case for other string types, you should also free them +// - Types are converted from the Go representation to C using JSON strings +// - Cookies are used for cancellation, just fancy contexts. Create a cookie using `CookieNew`, pass it to the function that needs one as the first argument. To cancel the function, call `CookieCancel` +// - The state machine is used to track the state of a client. It is mainly used for asking for certain data from the client, e.g. asking for profiles and locations. But a client may also wish to build upon this state machine to build the whole UI around it package main /* @@ -94,7 +100,16 @@ func getVPNState() (*client.Client, error) { } // Register creates a new client and also registers the FSM to go to the initial state -// +// Name is the name of the client, must be a valid client ID +// Version is the version of the client. This version field is used for the user agent in all HTTP requests +// cb is the state callback. It takes three arguments: The old state, the new state and the data for the state as JSON +// - Note that the states are defined in client/fsm.go, e.g. NO_SERVER (in Go: StateNoServer), ASK_PROFILE (in Go: StateAskProfile) +// - This callback returns non-zero if the state transition is handled. This is used to check if the client handles the needed transitions +// debug, if non-zero, enables debugging mode for the library, this means: +// - Log everything in debug mode, so you can get more detail of what is going on +// - Write the state graph to a file in the configDirectory. This can be used to create a FSM png file with mermaid https://mermaid.js.org/ +// After registering, the FSM is initialized and the state transition NO_SERVER should have been completed +// If some error occurs during registering, it is returned as a C string //export Register func Register( name *C.char, @@ -134,6 +149,11 @@ func Register( return getCError(err) } +// ExpiryTimes gets the expiry times for the current server +// Expiry times are just fields that represent unix timestamps at which to do certain events regarding expiry, +// e.g. when to show the renew button, when to show expiry notifications +// The expiry times structure is defined in types/server/server.go `Expiry` +// If some error occurs during registering, it is returned as a C string //export ExpiryTimes func ExpiryTimes() (*C.char, *C.char) { state, stateErr := getVPNState() @@ -151,6 +171,11 @@ func ExpiryTimes() (*C.char, *C.char) { return C.CString(ret), nil } +// Deregister cleans up the state for the client. +// If no client is available or deregistering fails, it returns an error +// This function SHOULD be called when the application exits such that the configuration file is saved correctly +// Note that saving of the configuration file also happens in other cases, such as after getting a VPN configuration +// Thus it is often not problematic if this function cannot be called due to a client crash //export Deregister func Deregister() *C.char { state, stateErr := getVPNState() @@ -162,6 +187,18 @@ func Deregister() *C.char { return nil } +// AddServer adds a server to the eduvpn-common server list +// c is the cookie that is used for cancellation. Create a cookie first with CookieNew. This same cookie is also used for replying to state transitions +// _type is the type of server that needs to be added. This type is defined in types/server/server.go Type +// id is the identifier of the string +// - In case of secure internet: The organization ID +// - In case of custom server: The base URL +// - In case of institute access: The base URL +// If the server cannot be added it returns the error as a string +// Note that the server is removed when an error has occured +// The following state callbacks are mandatory to handle: +// - OAUTH_STARTED: This indicates that the OAuth procedure has been started, it returns the URL as the data. +// The client should open the webbrowser with this URL and continue the authorization process. //export AddServer func AddServer(c C.uintptr_t, _type C.int, id *C.char, ni C.int) *C.char { // TODO: type @@ -177,6 +214,14 @@ func AddServer(c C.uintptr_t, _type C.int, id *C.char, ni C.int) *C.char { return getCError(err) } +// RemoveServer removes a server from the eduvpn-common server list +// _type is the type of server that needs to be added. This type is defined in types/server/server.go Type +// id is the identifier of the string +// - In case of secure internet: The organization ID +// - In case of custom server: The base URL +// - In case of institute access: The base URL +// If the server cannot be removed it returns the error as a string +// Note that the server is not removed when an error has occured //export RemoveServer func RemoveServer(_type C.int, id *C.char) *C.char { state, stateErr := getVPNState() @@ -187,6 +232,10 @@ func RemoveServer(_type C.int, id *C.char) *C.char { return getCError(err) } +// CurrentServer gets the current server from eduvpn-common +// In eduvpn-common, a server is marked as 'current' if you have gotten a VPN configuration for it +// It returns the server as JSON, defined in types/server/server.go Current +// If there is no current server or some other, e.g. there is no current state, an error is returned with a nil string //export CurrentServer func CurrentServer() (*C.char, *C.char) { state, stateErr := getVPNState() @@ -204,6 +253,10 @@ func CurrentServer() (*C.char, *C.char) { return C.CString(ret), nil } +// ServerList gets the list of servers that are currently added +// This is NOT the discovery list, but the servers that have previously been added with `AddServer` +// It returns the server list as a JSON string defined in types/server/server.go List +// If the server list cannot be retrieved it returns a nil string and an error //export ServerList func ServerList() (*C.char, *C.char) { state, stateErr := getVPNState() @@ -221,6 +274,43 @@ func ServerList() (*C.char, *C.char) { return C.CString(ret), nil } +// GetConfig gets a configuration for the server +// c is the cookie that is used for cancellation. Create a cookie first with CookieNew, this same cookie is also used for replying to state transitions +// _type is the type of server that needs to be added. This type is defined in types/server/server.go Type +// id is the identifier of the string +// - In case of secure internet: The organization ID +// - In case of custom server: The base URL +// - In case of institute access: The base URL +// If the server cannot be added it returns the error as a string +// Note that the server is removed when an error has occured +// The current state callbacks MUST be handled +// - ASK_PROFILE: This asks the client for profile. +// This is called when the user/client has not set a profile for this server before, or the current profile is invalid +// when the user has selected a profile. Reply with the choice using the `CookieReply` function and the profile ID +// e.g. CookieReply(cookie, "wireguard") +// The data for this transition is defined in types/server/server.go RequiredAskTransition with embedded data Profiles in types/server/server.go +// Note that RequiredTransition contains the cookie to be used for the CookieReply +// so a client would: +// - Parse the data to get the cookie and data +// - get the cookie, +// - get the profiles from the data +// - show it in the UI and then reply with CookieReply using the choice +// - ASK_LOCATION: This asks the client for a location. Note that under normal circumstances, +// this callback is not actually called as the home organization for the secure internet server is set as the current +// if for some reason, an invalid location has been configured, the library will ask the client for a new one +// when the user has selected al ocation. Reply with the choice using the `CookieReply` function and the location ID +// e.g. CookieReply(cookie, "nl") +// The data for this transition is defined in types/server/server.go RequiredAskTransition with embedded data a list of strings ([]string) +// Note that RequiredTransition contains the cookie to be used for the CookieReply, +// so a client would: +// - Parse the data to get the cookie and data +// - get the cookie, +// - get the list of locations from the data +// - show it in the UI and then reply with CookieReply using the choice +// - OAUTH_STARTED: This indicates that the OAuth procedure has been started, it returns the URL as the data. +// The client should open the webbrowser with this URL and continue the authorization process. +// This is only called if authorization needs to be retriggered +// After getting a configuration, the FSM moves to the GOT_CONFIG state //export GetConfig func GetConfig(c C.uintptr_t, _type C.int, id *C.char, pTCP C.int) (*C.char, *C.char) { state, stateErr := getVPNState() @@ -242,6 +332,10 @@ func GetConfig(c C.uintptr_t, _type C.int, id *C.char, pTCP C.int) (*C.char, *C. return nil, getCError(err) } +// SetProfileID sets the profile ID of the current serrver +// This MUST only be called if the user/client wishes to manually set a profile instead of the common lib asking for one using a transition +// Data is the profile ID +// It returns an error if unsuccessful //export SetProfileID func SetProfileID(data *C.char) *C.char { state, stateErr := getVPNState() @@ -252,6 +346,12 @@ func SetProfileID(data *C.char) *C.char { return getCError(profileErr) } +// SetSecureLocation sets the location for the secure internet server if it exists +// This MUST only be called if the user/client wishes to manually set a location instead of the common lib asking for one using a transition +// Because this does network requests to initialize the location, there is a cookie again :) +// c is the Cookie that needs to be passed. To create a cookie, first call `CookieNew` +// Data is the location ID +// It returns an error if unsuccessful //export SetSecureLocation func SetSecureLocation(c C.uintptr_t, data *C.char) *C.char { state, stateErr := getVPNState() @@ -266,6 +366,10 @@ func SetSecureLocation(c C.uintptr_t, data *C.char) *C.char { return getCError(locationErr) } +// DiscoServers gets the servers from discovery, returned as types/discovery/discovery.go Servers marshalled as JSON +// c is the Cookie that needs to be passed. Create a new Cookie using `CookieNew` +// If it was unsuccessful, it returns an error. Note that when the lib was built in release mode the data is almost always non-nil, even when an error has occurred +// This means it has just returned the cached list //export DiscoServers func DiscoServers(c C.uintptr_t) (*C.char, *C.char) { state, stateErr := getVPNState() @@ -287,6 +391,10 @@ func DiscoServers(c C.uintptr_t) (*C.char, *C.char) { return C.CString(s), getCError(err) } +// DiscoServers gets the organizations from discovery, returned as types/discovery/discovery.go Organizations marshalled as JSON +// c is the Cookie that needs to be passed. Create a new Cookie using `CookieNew` +// If it was unsuccessful, it returns an error. Note that when the lib was built in release mode the data is almost always non-nil, even when an error has occurred +// This means it has just returned the cached list //export DiscoOrganizations func DiscoOrganizations(c C.uintptr_t) (*C.char, *C.char) { state, stateErr := getVPNState() @@ -308,6 +416,10 @@ func DiscoOrganizations(c C.uintptr_t) (*C.char, *C.char) { return C.CString(s), getCError(err) } +// Cleanup sends a /disconnect to cleanup the connection +// This MUST be called when disconnecting, see https://docs.eduvpn.org/server/v3/api.html#application-flow +// c is the Cookie that needs to be passed. Create a new Cookie using `CookieNew` +// If it was unsuccessful, it returns an error //export Cleanup func Cleanup(c C.uintptr_t) *C.char { state, stateErr := getVPNState() @@ -322,6 +434,11 @@ func Cleanup(c C.uintptr_t) *C.char { return getCError(err) } +// RenewSession renews the session of the VPN +// This essentially means that the OAuth tokens are deleted +// And it also possibly re-runs every state callback you need when getting a config +// At least you MUST handle the OAuth started transition +// It returns an error if unsuccessful //export RenewSession func RenewSession(c C.uintptr_t) *C.char { state, stateErr := getVPNState() @@ -336,6 +453,11 @@ func RenewSession(c C.uintptr_t) *C.char { return getCError(renewSessionErr) } +// SetSupportWireguard enables or disables WireGuard for the client +// By defualt WireGuard support is enabled +// To disable it you can pass a 0 int to this +// support thus indicates whether or not to enable WireGuard +// An error is returned if this is not possible //export SetSupportWireguard func SetSupportWireguard(support C.int) *C.char { state, stateErr := getVPNState() @@ -346,6 +468,15 @@ func SetSupportWireguard(support C.int) *C.char { return nil } +// StartFailover starts the 'failover' procedure in eduvpn-common +// Failover has one primary goal: check if the VPN can reach the gateway +// This can be used to check whether or not the client needs to 'failover' to prefer TCP (if currently using UDP) +// This 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 +// 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 +// It also returns an error, if it fails to indicate if it has dropped or not. In this case, dropped is also set to zero //export StartFailover func StartFailover(c C.uintptr_t, gateway *C.char, mtu C.int, readRxBytes C.ReadRxBytes) (C.int, *C.char) { state, stateErr := getVPNState() @@ -373,6 +504,10 @@ func StartFailover(c C.uintptr_t, gateway *C.char, mtu C.int, readRxBytes C.Read return droppedC, nil } +// FreeString frees a string that was allocated by the eduvpn-common Go library +// This happens when we return strings, such as errors from the Go lib back to the client +// The client MUST thus ensure that this memory is freed using this function +// Simply pass the pointer to the string in here //export FreeString func FreeString(addr *C.char) { C.free(unsafe.Pointer(addr)) @@ -394,6 +529,19 @@ func getCookie(c C.uintptr_t) (*cookie.Cookie, error) { return v, nil } +// SetTokenHandler sets the token getters and token setters for OAuth +// Because the data that is saved does not contain OAuth tokens for server, the common lib asks and sets the tokens using these callback functions +// The client can thus pass callbacks to this function so that the tokens can be securely stored in a keyring +// Client must pass two arguments to these functions +// getter is the void function that gets tokens from the client. It takes three arguments: +// - The server for which to get the tokens for, marshalled as JSON and defined in types/server/server.go `Current` +// - The output buffer +// - The length of the output buffer +// This 'output buffer' must contain the tokens, marshalled as JSON that is defined in types/server/server.go `Tokens` +// setter is the void function that sets tokens. It takes two arguments: +// - The server for which to get the tokens for, marshalled as JSON and defined in types/server/server.go `Current` +// - The tokens, defined in types/server/server.go `Tokens` marshalled as JSON +// It returns an error when the tokens cannot be set //export SetTokenHandler func SetTokenHandler(getter C.TokenGetter, setter C.TokenSetter) *C.char { state, stateErr := getVPNState() @@ -452,12 +600,23 @@ func SetTokenHandler(getter C.TokenGetter, setter C.TokenSetter) *C.char { return nil } +// CookieNew creates a new cookie and returns it +// This value should not be parsed or converted somehow by the client +// This value is simply to pass back to the Go library +// This value has two purposes: +// - Cancel a long running function +// - Send a reply to a state transition (ASK_PROFILE and ASK_LOCATION) +// Functions that take a cookie have it as the first argument //export CookieNew func CookieNew() C.uintptr_t { c := cookie.NewWithContext(context.Background()) return C.uintptr_t(cgo.NewHandle(&c)) } +// CookieReply replies to a state transition using the cookie +// The data that is sent to the Go library is the second argument of this function +// c is the Cookie +// data is the data to send //export CookieReply func CookieReply(c C.uintptr_t, data *C.char) *C.char { v, err := getCookie(c) @@ -468,6 +627,8 @@ func CookieReply(c C.uintptr_t, data *C.char) *C.char { return getCError(err) } +// CookieDelete deletes the cookie by cancelling it and deleting the underlying cgo handle +// This function MUST be called when the cookie that is created using `CookieNew` is no longer needed //export CookieDelete func CookieDelete(c C.uintptr_t) *C.char { v, err := getCookie(c) @@ -480,6 +641,9 @@ func CookieDelete(c C.uintptr_t) *C.char { return getCError(err) } +// CookieCancel cancels the cookie +// This means that functions which take this as first argument, return if they're still running +// The error cause is always context.Canceled: https://pkg.go.dev/context#pkg-variables //export CookieCancel func CookieCancel(c C.uintptr_t) *C.char { v, err := getCookie(c) |
