diff options
Diffstat (limited to 'search/search_index.json')
| -rw-r--r-- | search/search_index.json | 1 |
1 files changed, 1 insertions, 0 deletions
diff --git a/search/search_index.json b/search/search_index.json new file mode 100644 index 0000000..065d4fc --- /dev/null +++ b/search/search_index.json @@ -0,0 +1 @@ +{"config":{"indexing":"full","lang":["en"],"min_search_length":3,"prebuild_index":false,"separator":"[\\s\\-]+"},"docs":[{"location":"index.html","text":"About # This chapter contains background information for the library. We give a general introduction to eduVPN and explain what problems this library aims to solve. EduVPN introduction # eduVPN-common is a library for eduVPN , which is a VPN by SURF and a project by G\u00c9ANT , for research institutes and universities. Each institute that uses eduVPN has its own server. To discover these servers and establish a VPN connection with them, eduVPN clients are used. eduVPN has clients for each common platform: - Android - Linux - MacOS/iOS - Windows The problem # However, as these clients are rather similar in functionality, apart from platform specific differences, right now there is duplicate code between them. For example, the process to discover institution\u2019s servers, the authorization process (OAuth) and Wireguard key generation. This goal of this library is to provide the common functionality between these clients into one codebase. The library is written in the Go language and aims to have wrapper code for each of the languages that are used by the current clients. The main goal is thus the following: This library tries to remove non-platform specific common functionality. This way eduVPN clients have less duplicate code. The building blocks that are removed by the library is not just the four depicted in this figure. You can think of other building blocks, such as logging and local configuration file saving. As can be seen in the figure, no User Interface (UI) code will be implemented. This is left to the eduVPN clients, on top of platform-specific code. License # MIT Authors # This library is written by Steven Wallis de Vries and Jeroen Wijenbergh at the SURF and G\u00c9ANT organization.","title":"About"},{"location":"index.html#eduvpn-introduction","text":"eduVPN-common is a library for eduVPN , which is a VPN by SURF and a project by G\u00c9ANT , for research institutes and universities. Each institute that uses eduVPN has its own server. To discover these servers and establish a VPN connection with them, eduVPN clients are used. eduVPN has clients for each common platform: - Android - Linux - MacOS/iOS - Windows","title":"EduVPN introduction"},{"location":"index.html#the-problem","text":"However, as these clients are rather similar in functionality, apart from platform specific differences, right now there is duplicate code between them. For example, the process to discover institution\u2019s servers, the authorization process (OAuth) and Wireguard key generation. This goal of this library is to provide the common functionality between these clients into one codebase. The library is written in the Go language and aims to have wrapper code for each of the languages that are used by the current clients. The main goal is thus the following: This library tries to remove non-platform specific common functionality. This way eduVPN clients have less duplicate code. The building blocks that are removed by the library is not just the four depicted in this figure. You can think of other building blocks, such as logging and local configuration file saving. As can be seen in the figure, no User Interface (UI) code will be implemented. This is left to the eduVPN clients, on top of platform-specific code.","title":"The problem"},{"location":"index.html#license","text":"MIT","title":"License"},{"location":"index.html#authors","text":"This library is written by Steven Wallis de Vries and Jeroen Wijenbergh at the SURF and G\u00c9ANT organization.","title":"Authors"},{"location":"apidocs.html","text":"This document was automatically generated from the exports/exports.go file About the API # Some notes: Errors are returned as JSON c strings. The JSON type is defined in types/error/error.go Error . Free them using FreeString . Same is the case for other string types, you should also free them. The errors are always localized 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 , passing in the same cookie as argument Cookies must also be freed, by using the CookieDelete function if the cookie is no longer needed 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. The SetState and InState functions are useful for this Functions # AddServer # Signature: func AddServer(c C.uintptr_t, _type C.int, id *C.char, ot *C.longlong) *C.char 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 ni stands for non-interactive. If non-zero, any state transitions will not be run. This ot flag is useful for preprovisioned servers; set this to non-null to non-interactively add a server. This flag represents the Unix time OAuth was last triggered, if the server needs to be added non-interactively but there is no token structure, set this to zero (integer) or the current Unix time. This value will be overwritten once OAuth is triggered. If the server cannot be added it returns the error as types/error/error.go Error . 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. Note: For mobile platforms this returns a Cookie and data (json: {\"cookie\": x, \"data\": url} ). This url should also be opened in the browser like desktop platforms. But these platforms also need to reply to the library to give back the full authorization code URI with CookieReply(x, uri) . E.g. CookieReply(x, \"/callback?code=...&state=...&iss=...\") this is the path of the request that the apps get back when the user clicks approve. For this, apps need to register an app url or sorts. For the valid values for app URLs, see the redirect URIs for mobile platforms here https://git.sr.ht/~fkooman/vpn-user-portal/tree/v3/item/src/OAuth/VpnClientDb.php Example Input (3=custom server): AddServer(mycookie, 3, \"https://demo.eduvpn.nl\", 0) Example Output: { \"message\": { \"en\": \"failed to add server\" }, \"misc\": false } CalculateGateway # Signature: 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. The function is implemented according to: the eduVPN docs . Example Input: CalculateGateway(\"10.10.0.5/24\") Example Output: \"10.10.0.1\", null Cleanup # Signature: func Cleanup(c C.uintptr_t) *C.char Cleanup sends a /disconnect to cleanup the connection. This MUST be called when disconnecting, see the eduVPN docs . c is the Cookie that needs to be passed. Create a new Cookie using CookieNew . If it was unsuccessful, it returns an error. Example Input: Cleanup(myCookie) Example Output: { \"message\": { \"en\": \"cleanup was not successful\" }, \"misc\": false } CookieCancel # Signature: func CookieCancel(c C.uintptr_t) *C.char CookieCancel cancels the cookie. This means that functions which take this as first argument, return if they\u2019re still running. The error cause is always context.Canceled for that cancelled function: see the Go docs . This CookieCancel function can also return an error if cancelling was unsuccessful. Example Input: CookieCancel(myCookie) Example Output: null CookieDelete # Signature: func CookieDelete(c C.uintptr_t) *C.char 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. Example Input: CookieDelete(myCookie) Example Output: null CookieNew # Signature: func CookieNew() C.uintptr_t CookieNew creates a new cookie and returns it. Functions that take a cookie have it as the first argument. 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) Example Input: CookieNew() Example Output: 5 CookieReply # Signature: func CookieReply(c C.uintptr_t, data *C.char) *C.char CookieReply replies to a state transition using the cookie. c is the Cookie data is the data to send, e.g. a profile ID Example Input: CookieReply(myCookie, \"split-tunnel-profile\") Example Output: null CurrentServer # Signature: func CurrentServer() (*C.char, *C.char) CurrentServer gets the current server from eduvpn-common In eduvpn-common, a server is marked as \u2018current\u2019 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. Example Input: CurrentServer() Example Output: { \"institute_access_server\": { \"display_name\": { \"en\": \"Demo\" }, \"identifier\": \"https://demo.eduvpn.nl/\", \"profiles\": { \"map\": { \"internet\": { \"display_name\": { \"en\": \"Internet\" }, \"supported_protocols\": [ 1, 2 ] }, \"internet-split\": { \"display_name\": { \"en\": \"No rfc1918 routes\" }, \"supported_protocols\": [ 1, 2 ] } }, \"current\": \"internet\" }, \"support_contacts\": [ \"mailto:eduvpn@surf.nl\" ], \"delisted\": false }, \"server_type\": 1 }, null Deregister # Signature: func Deregister() *C.char Deregister cleans up the state for the client. 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. If no client is available or deregistering fails, it returns an error. Example Input: Deregister() Example Output: { \"message\": { \"en\": \"failed to deregister\" }, \"misc\": false } DiscoOrganizations # Signature: func DiscoOrganizations(c C.uintptr_t, cache C.int, search *C.char) (*C.char, *C.char) DiscoOrganizations 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 cache indicates whether or not the cache should only be used, meaning no network call search is the search string for filtering the list. If any of the words in the search query is not contained in any of the display names or keywords, the candidate is filtered. Otherwise they are ranked based on the levenshtein distance: Levenshtein Wikipedia . If search is empty it returns ALL organizations currently known in common 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, the error should then not be handled in a fatal way. E.g. show the returned cache list but log the error or show the error with a warning. Example Input: DiscoOrganizations(myCookie, \"\") Example Output: { \"organization_list\": [ { \"display_name\": { \"en\": \"Academic Network of Albania - RASH\" }, \"org_id\": \"https://idp.rash.al/simplesaml/saml2/idp/metadata.php\", }, { \"display_name\": { \"da\": \"Dansk Sprogn\u00e6vn\", \"en\": \"Danish Language Council\" }, \"org_id\": \"http://idp.dsn.dk/adfs/services/trust\", }, { \"display_name\": { \"da\": \"Erhvervsakademi Aarhus\", \"en\": \"Business Academy Aarhus\" }, \"org_id\": \"http://adfs.eaaa.dk/adfs/services/trust\", }, null Example Input: DiscoOrganizations(myCookie, \"rash\") Example Output: { \"organization_list\": [ { \"display_name\": { \"en\": \"Academic Network of Albania - RASH\" }, \"org_id\": \"https://idp.rash.al/simplesaml/saml2/idp/metadata.php\", }, ] }, null DiscoServers # Signature: func DiscoServers(c C.uintptr_t, cache C.int, search *C.char) (*C.char, *C.char) 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 cache indicates whether or not the cache should only be used, meaning no network call search is the search string for filtering the list. If any of the words in the search query is not contained in any of the display names or keywords, the candidate is filtered. Otherwise they are ranked based on the levenshtein distance: Levenshtein Wikipedia . If search is empty it returns ALL servers currently known in common 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, the error should then not be handled in a fatal way. E.g. show the returned cache list but log the error or show the error with a warning. Example Input: DiscoServers(myCookie, \"\") Example Output: { \"server_list\": [ { \"base_url\": \"https://eduvpn.rash.al/\", \"country_code\": \"AL\", \"server_type\": \"secure_internet\", }, { \"base_url\": \"https://eduvpn.deic.dk/\", \"country_code\": \"DK\", \"server_type\": \"secure_internet\", } , null Example Input: DiscoServers(myCookie, \"heanet\") Example Output: { \"server_list\": [ { \"base_url\": \"https://eduvpn.heanet.ie/\", \"display_name\": { \"en\": \"HEAnet Staff\" }, \"server_type\": \"institute_access\", }, ] } , null ExpiryTimes # Signature: func ExpiryTimes() (*C.char, *C.char) 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, it is returned as types/error/error.go Error Example Input: ExpiryTimes() Example Output (1\u20264 are unix timestamps): { \"start_time\": 1, \"end_time\": 2, \"button_time\": 3, \"countdown_time\": 4, \"notification_times\": [ 1, 2, ], }, null FreeString # Signature: func FreeString(addr *C.char) 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. Example Input: FreeString(strPtr) GetConfig # Signature: func GetConfig(c C.uintptr_t, _type C.int, id *C.char, pTCP C.int, startup C.int) (*C.char, *C.char) 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 pTCP is if we prefer TCP or not to get the configuration, non-zero means yes startup is if the client is just starting up, set this to true (non-zero) if you autoconnect to a server on startup. If this startup value is true (non-zero) then any authorization or other callacks (profile/location) are not triggered After getting a configuration, the FSM moves to the GOT_CONFIG state The return data is the configuration, marshalled as JSON and defined in types/server/server.go Configuration If the config cannot be retrieved it returns an error as types/error/error.go Error . 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, \u201cwireguard\u201d). CookieReply can be done in the background as the Go library waits for a reply The data for this transition is defined in types/server/server.go RequiredAskTransition with embedded data Profiles in types/server/server.go . Note that RequiredAskTransition 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 a location, reply with the choice using the CookieReply function and the location ID e.g. CookieReply(cookie, \u201cnl\u201d) CookieReply can be done in the background as the Go library waits for a reply The data for this transition is defined in types/server/server.go RequiredAskTransition with embedded data a list of strings ( []string ) Note that RequiredAskTransition contains the cookie to be used for the CookieReply function, 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 # 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. Note: For mobile platforms this returns a Cookie and data (json: {\"cookie\": x, \"data\": url} ). This url should also be opened in the browser like desktop platforms. But these platforms also need to reply to the library to give back the full authorization code URI with CookieReply(x, uri) . E.g. CookieReply(x, \"/callback?code=...&state=...&iss=...\") this is the path of the request that the apps get back when the user clicks approve. For this, apps need to register an app url or sorts. For the valid values for app URLs, see the redirect URIs for mobile platforms here https://git.sr.ht/~fkooman/vpn-user-portal/tree/v3/item/src/OAuth/VpnClientDb.php The client should open the webbrowser with this URL and continue the authorization process. This is only called if authorization needs to be retriggered Example Input (3=custom server): GetConfig(myCookie, 3, \"https://demo.eduvpn.nl/\", 0, 0) Example Output (2=WireGuard): { \"config\": \"[Interface]\\nPrivateKey = ...\\nAddress = ...\\nDNS = ...\\n\\n[Peer]\\nPublicKey = ...=\\nAllowedIPs = 0.0.0.0/0,::/0\\nEndpoint = ...\", \"protocol\": 2, \"default_gateway\": true, \"should_failover\": true, <- whether or not the failover procedure should happen } InState # Signature: func InState(fsmState C.int) (C.int, *C.char) InState checks if the FSM is in fsmState . Example Input: InState(5) Example Output: 1, null NewProxyguard # Signature: func NewProxyguard(c C.uintptr_t, lp C.int, tcpsp C.int, peer *C.char, proxySetup C.ProxySetup) (C.uintptr_t, *C.char) NewProxyguard creates the \u2018proxyguard\u2019 procedure in eduvpn-common. If the proxy cannot be created it returns an error. This function proxies WireGuard UDP connections over HTTP: ProxyGuard on Codeberg . These input variables can be gotten from the configuration that is retrieved using the proxy JSON key c is the cookie. Note that if you cancel/delete the cookie, ProxyGuard gets cleaned up. Common automatically cleans up ProxyGuard when Cleanup is called, but it is good to cleanup yourself too. lp is the port of the local udp ProxyGuard connection, this is what is set to the WireGuard endpoint tcpsp is the TCP source port. Pass 0 if you do not route based on source port, so far only the Linux client has to pass non-zero. peer is the ip:port of the remote server proxySetup is a callback which is called when the socket is setting up, this can be used for configuring routing in the client. It takes two arguments: the file descriptor (integer) and a JSON list of IPs the client connects to Example Input: NewProxyguard(myCookie, 1337, 0, \"5.5.5.5:51820\", proxySetupCB) Example Output: null ProxyguardPeerIPs # Signature: func ProxyguardPeerIPs(proxyH C.uintptr_t) (*C.char, *C.char) ProxyguardPeerIPs gets the Peer IPs configured by ProxyGuard Example Input: ProxyguardPeerIPs(handle) Example Output: [\"1.1.1.1\"], null ProxyguardRestart # Signature: func ProxyguardRestart(proxyH C.uintptr_t) *C.char ProxyguardRestart restarts ProxyGuard, call this when a network change happens Example Input: ProxyguardRestart(proxyHandle) Example Output: \"failed restarting ProxyGuard\" ProxyguardTunnel # Signature: func ProxyguardTunnel(c C.uintptr_t, proxyH C.uintptr_t, wglisten C.int) *C.char ProxyguardTunnel starts the tunneling for ProxyGuard c is the cookie proxyH is the proxy handle wglisten is the port WireGuard is listening on Register # Signature: func Register( name *C.char, version *C.char, configDirectory *C.char, cb C.StateCB, ) *C.char 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. Main (in Go: StateMain ), 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 After registering, the FSM is initialized and the state transition MAIN should have been completed If some error occurs during registering, it is returned as a types/error/error.go Error Example Input: Register(\"org.eduvpn.app.linux\", \"0.0.1\", \"/tmp/eduvpn-common\", myCallbackFunc, 1) Example Output: { \"message\": { \"en\": \"failed to register, a VPN state is already present\" }, \"misc\": false } RemoveServer # Signature: func RemoveServer(_type C.int, id *C.char) *C.char 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 types/error/error.go Error . Example Input (3=custom server): RemoveServer(3, \"bogus\") Example Output: { \"message\": { \"en\": \"failed to remove server\" }, \"misc\": false } RenewSession # Signature: func RenewSession(c C.uintptr_t) *C.char 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. So least you MUST handle the OAuth started transition It returns an error if unsuccessful. Example Input: RenewSession(myCookie) Example Output: { \"message\": { \"en\": \"could not renew session\" }, \"misc\": false } ServerList # Signature: func ServerList() (*C.char, *C.char) 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. Example Input: ServerList() Example Output (current profile here is empty as none has been chosen yet): { \"institute_access_servers\": [ { \"display_name\": { \"en\": \"Demo\" }, \"identifier\": \"https://demo.eduvpn.nl/\", \"profiles\": { \"current\": \"\" }, \"support_contacts\": [ \"mailto:eduvpn@surf.nl\" ], \"delisted\": false } ] }, null SetProfileID # Signature: func SetProfileID(data *C.char) *C.char 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. Example Input: SetProfileID(\"splittunnel\") Example Output: { \"message\": { \"en\": \"profile does not exist\" }, \"misc\": false } SetSecureLocation # Signature: func SetSecureLocation(orgID *C.char, cc *C.char) *C.char 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. orgID is the organisation ID for the secure internet server cc is the location ID/country code It returns an error if unsuccessful. Example Input: SetSecureLocation(\"http://idp.geant.org/\", \"nl\") Example Output: { \"message\": { \"en\": \"location does not exist\" }, \"misc\": false } SetState # Signature: func SetState(fsmState C.int) *C.char SetState sets the state of the state machine. Note: this transitions the FSM into the new state without passing any data to it. Example Input: SetState(5) Example Output: null SetTokenHandler # Signature: func SetTokenHandler(getter C.TokenGetter, setter C.TokenSetter) *C.char 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. The client must pass two callback arguments to this function: 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 \u2018output buffer\u2019 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. Example Input: SetTokenHandler(getterFunc, setterFunc) Example Output: null StartFailover # Signature: func StartFailover(c C.uintptr_t, gateway *C.char, mtu C.int, readRxBytes C.ReadRxBytes) (C.int, *C.char) StartFailover starts the \u2018failover\u2019 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 \u2018failover\u2019 to prefer TCP (if currently using UDP). 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. 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. 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. Example Input: StartFailover(myCookie, \"10.10.10.1\", 1400, myRxBytesReader) Example Output: 1, null","title":"API Docs"},{"location":"apidocs.html#addserver","text":"Signature: func AddServer(c C.uintptr_t, _type C.int, id *C.char, ot *C.longlong) *C.char 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 ni stands for non-interactive. If non-zero, any state transitions will not be run. This ot flag is useful for preprovisioned servers; set this to non-null to non-interactively add a server. This flag represents the Unix time OAuth was last triggered, if the server needs to be added non-interactively but there is no token structure, set this to zero (integer) or the current Unix time. This value will be overwritten once OAuth is triggered. If the server cannot be added it returns the error as types/error/error.go Error . 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. Note: For mobile platforms this returns a Cookie and data (json: {\"cookie\": x, \"data\": url} ). This url should also be opened in the browser like desktop platforms. But these platforms also need to reply to the library to give back the full authorization code URI with CookieReply(x, uri) . E.g. CookieReply(x, \"/callback?code=...&state=...&iss=...\") this is the path of the request that the apps get back when the user clicks approve. For this, apps need to register an app url or sorts. For the valid values for app URLs, see the redirect URIs for mobile platforms here https://git.sr.ht/~fkooman/vpn-user-portal/tree/v3/item/src/OAuth/VpnClientDb.php Example Input (3=custom server): AddServer(mycookie, 3, \"https://demo.eduvpn.nl\", 0) Example Output: { \"message\": { \"en\": \"failed to add server\" }, \"misc\": false }","title":"AddServer"},{"location":"apidocs.html#calculategateway","text":"Signature: 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. The function is implemented according to: the eduVPN docs . Example Input: CalculateGateway(\"10.10.0.5/24\") Example Output: \"10.10.0.1\", null","title":"CalculateGateway"},{"location":"apidocs.html#cleanup","text":"Signature: func Cleanup(c C.uintptr_t) *C.char Cleanup sends a /disconnect to cleanup the connection. This MUST be called when disconnecting, see the eduVPN docs . c is the Cookie that needs to be passed. Create a new Cookie using CookieNew . If it was unsuccessful, it returns an error. Example Input: Cleanup(myCookie) Example Output: { \"message\": { \"en\": \"cleanup was not successful\" }, \"misc\": false }","title":"Cleanup"},{"location":"apidocs.html#cookiecancel","text":"Signature: func CookieCancel(c C.uintptr_t) *C.char CookieCancel cancels the cookie. This means that functions which take this as first argument, return if they\u2019re still running. The error cause is always context.Canceled for that cancelled function: see the Go docs . This CookieCancel function can also return an error if cancelling was unsuccessful. Example Input: CookieCancel(myCookie) Example Output: null","title":"CookieCancel"},{"location":"apidocs.html#cookiedelete","text":"Signature: func CookieDelete(c C.uintptr_t) *C.char 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. Example Input: CookieDelete(myCookie) Example Output: null","title":"CookieDelete"},{"location":"apidocs.html#cookienew","text":"Signature: func CookieNew() C.uintptr_t CookieNew creates a new cookie and returns it. Functions that take a cookie have it as the first argument. 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) Example Input: CookieNew() Example Output: 5","title":"CookieNew"},{"location":"apidocs.html#cookiereply","text":"Signature: func CookieReply(c C.uintptr_t, data *C.char) *C.char CookieReply replies to a state transition using the cookie. c is the Cookie data is the data to send, e.g. a profile ID Example Input: CookieReply(myCookie, \"split-tunnel-profile\") Example Output: null","title":"CookieReply"},{"location":"apidocs.html#currentserver","text":"Signature: func CurrentServer() (*C.char, *C.char) CurrentServer gets the current server from eduvpn-common In eduvpn-common, a server is marked as \u2018current\u2019 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. Example Input: CurrentServer() Example Output: { \"institute_access_server\": { \"display_name\": { \"en\": \"Demo\" }, \"identifier\": \"https://demo.eduvpn.nl/\", \"profiles\": { \"map\": { \"internet\": { \"display_name\": { \"en\": \"Internet\" }, \"supported_protocols\": [ 1, 2 ] }, \"internet-split\": { \"display_name\": { \"en\": \"No rfc1918 routes\" }, \"supported_protocols\": [ 1, 2 ] } }, \"current\": \"internet\" }, \"support_contacts\": [ \"mailto:eduvpn@surf.nl\" ], \"delisted\": false }, \"server_type\": 1 }, null","title":"CurrentServer"},{"location":"apidocs.html#deregister","text":"Signature: func Deregister() *C.char Deregister cleans up the state for the client. 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. If no client is available or deregistering fails, it returns an error. Example Input: Deregister() Example Output: { \"message\": { \"en\": \"failed to deregister\" }, \"misc\": false }","title":"Deregister"},{"location":"apidocs.html#discoorganizations","text":"Signature: func DiscoOrganizations(c C.uintptr_t, cache C.int, search *C.char) (*C.char, *C.char) DiscoOrganizations 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 cache indicates whether or not the cache should only be used, meaning no network call search is the search string for filtering the list. If any of the words in the search query is not contained in any of the display names or keywords, the candidate is filtered. Otherwise they are ranked based on the levenshtein distance: Levenshtein Wikipedia . If search is empty it returns ALL organizations currently known in common 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, the error should then not be handled in a fatal way. E.g. show the returned cache list but log the error or show the error with a warning. Example Input: DiscoOrganizations(myCookie, \"\") Example Output: { \"organization_list\": [ { \"display_name\": { \"en\": \"Academic Network of Albania - RASH\" }, \"org_id\": \"https://idp.rash.al/simplesaml/saml2/idp/metadata.php\", }, { \"display_name\": { \"da\": \"Dansk Sprogn\u00e6vn\", \"en\": \"Danish Language Council\" }, \"org_id\": \"http://idp.dsn.dk/adfs/services/trust\", }, { \"display_name\": { \"da\": \"Erhvervsakademi Aarhus\", \"en\": \"Business Academy Aarhus\" }, \"org_id\": \"http://adfs.eaaa.dk/adfs/services/trust\", }, null Example Input: DiscoOrganizations(myCookie, \"rash\") Example Output: { \"organization_list\": [ { \"display_name\": { \"en\": \"Academic Network of Albania - RASH\" }, \"org_id\": \"https://idp.rash.al/simplesaml/saml2/idp/metadata.php\", }, ] }, null","title":"DiscoOrganizations"},{"location":"apidocs.html#discoservers","text":"Signature: func DiscoServers(c C.uintptr_t, cache C.int, search *C.char) (*C.char, *C.char) 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 cache indicates whether or not the cache should only be used, meaning no network call search is the search string for filtering the list. If any of the words in the search query is not contained in any of the display names or keywords, the candidate is filtered. Otherwise they are ranked based on the levenshtein distance: Levenshtein Wikipedia . If search is empty it returns ALL servers currently known in common 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, the error should then not be handled in a fatal way. E.g. show the returned cache list but log the error or show the error with a warning. Example Input: DiscoServers(myCookie, \"\") Example Output: { \"server_list\": [ { \"base_url\": \"https://eduvpn.rash.al/\", \"country_code\": \"AL\", \"server_type\": \"secure_internet\", }, { \"base_url\": \"https://eduvpn.deic.dk/\", \"country_code\": \"DK\", \"server_type\": \"secure_internet\", } , null Example Input: DiscoServers(myCookie, \"heanet\") Example Output: { \"server_list\": [ { \"base_url\": \"https://eduvpn.heanet.ie/\", \"display_name\": { \"en\": \"HEAnet Staff\" }, \"server_type\": \"institute_access\", }, ] } , null","title":"DiscoServers"},{"location":"apidocs.html#expirytimes","text":"Signature: func ExpiryTimes() (*C.char, *C.char) 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, it is returned as types/error/error.go Error Example Input: ExpiryTimes() Example Output (1\u20264 are unix timestamps): { \"start_time\": 1, \"end_time\": 2, \"button_time\": 3, \"countdown_time\": 4, \"notification_times\": [ 1, 2, ], }, null","title":"ExpiryTimes"},{"location":"apidocs.html#freestring","text":"Signature: func FreeString(addr *C.char) 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. Example Input: FreeString(strPtr)","title":"FreeString"},{"location":"apidocs.html#getconfig","text":"Signature: func GetConfig(c C.uintptr_t, _type C.int, id *C.char, pTCP C.int, startup C.int) (*C.char, *C.char) 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 pTCP is if we prefer TCP or not to get the configuration, non-zero means yes startup is if the client is just starting up, set this to true (non-zero) if you autoconnect to a server on startup. If this startup value is true (non-zero) then any authorization or other callacks (profile/location) are not triggered After getting a configuration, the FSM moves to the GOT_CONFIG state The return data is the configuration, marshalled as JSON and defined in types/server/server.go Configuration If the config cannot be retrieved it returns an error as types/error/error.go Error . The current state callbacks MUST be handled:","title":"GetConfig"},{"location":"apidocs.html#ask_profile","text":"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, \u201cwireguard\u201d). CookieReply can be done in the background as the Go library waits for a reply The data for this transition is defined in types/server/server.go RequiredAskTransition with embedded data Profiles in types/server/server.go . Note that RequiredAskTransition 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","title":"ASK_PROFILE"},{"location":"apidocs.html#ask_location","text":"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 a location, reply with the choice using the CookieReply function and the location ID e.g. CookieReply(cookie, \u201cnl\u201d) CookieReply can be done in the background as the Go library waits for a reply The data for this transition is defined in types/server/server.go RequiredAskTransition with embedded data a list of strings ( []string ) Note that RequiredAskTransition contains the cookie to be used for the CookieReply function, 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","title":"ASK_LOCATION"},{"location":"apidocs.html#oauth_started","text":"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. Note: For mobile platforms this returns a Cookie and data (json: {\"cookie\": x, \"data\": url} ). This url should also be opened in the browser like desktop platforms. But these platforms also need to reply to the library to give back the full authorization code URI with CookieReply(x, uri) . E.g. CookieReply(x, \"/callback?code=...&state=...&iss=...\") this is the path of the request that the apps get back when the user clicks approve. For this, apps need to register an app url or sorts. For the valid values for app URLs, see the redirect URIs for mobile platforms here https://git.sr.ht/~fkooman/vpn-user-portal/tree/v3/item/src/OAuth/VpnClientDb.php The client should open the webbrowser with this URL and continue the authorization process. This is only called if authorization needs to be retriggered Example Input (3=custom server): GetConfig(myCookie, 3, \"https://demo.eduvpn.nl/\", 0, 0) Example Output (2=WireGuard): { \"config\": \"[Interface]\\nPrivateKey = ...\\nAddress = ...\\nDNS = ...\\n\\n[Peer]\\nPublicKey = ...=\\nAllowedIPs = 0.0.0.0/0,::/0\\nEndpoint = ...\", \"protocol\": 2, \"default_gateway\": true, \"should_failover\": true, <- whether or not the failover procedure should happen }","title":"OAUTH_STARTED"},{"location":"apidocs.html#instate","text":"Signature: func InState(fsmState C.int) (C.int, *C.char) InState checks if the FSM is in fsmState . Example Input: InState(5) Example Output: 1, null","title":"InState"},{"location":"apidocs.html#newproxyguard","text":"Signature: func NewProxyguard(c C.uintptr_t, lp C.int, tcpsp C.int, peer *C.char, proxySetup C.ProxySetup) (C.uintptr_t, *C.char) NewProxyguard creates the \u2018proxyguard\u2019 procedure in eduvpn-common. If the proxy cannot be created it returns an error. This function proxies WireGuard UDP connections over HTTP: ProxyGuard on Codeberg . These input variables can be gotten from the configuration that is retrieved using the proxy JSON key c is the cookie. Note that if you cancel/delete the cookie, ProxyGuard gets cleaned up. Common automatically cleans up ProxyGuard when Cleanup is called, but it is good to cleanup yourself too. lp is the port of the local udp ProxyGuard connection, this is what is set to the WireGuard endpoint tcpsp is the TCP source port. Pass 0 if you do not route based on source port, so far only the Linux client has to pass non-zero. peer is the ip:port of the remote server proxySetup is a callback which is called when the socket is setting up, this can be used for configuring routing in the client. It takes two arguments: the file descriptor (integer) and a JSON list of IPs the client connects to Example Input: NewProxyguard(myCookie, 1337, 0, \"5.5.5.5:51820\", proxySetupCB) Example Output: null","title":"NewProxyguard"},{"location":"apidocs.html#proxyguardpeerips","text":"Signature: func ProxyguardPeerIPs(proxyH C.uintptr_t) (*C.char, *C.char) ProxyguardPeerIPs gets the Peer IPs configured by ProxyGuard Example Input: ProxyguardPeerIPs(handle) Example Output: [\"1.1.1.1\"], null","title":"ProxyguardPeerIPs"},{"location":"apidocs.html#proxyguardrestart","text":"Signature: func ProxyguardRestart(proxyH C.uintptr_t) *C.char ProxyguardRestart restarts ProxyGuard, call this when a network change happens Example Input: ProxyguardRestart(proxyHandle) Example Output: \"failed restarting ProxyGuard\"","title":"ProxyguardRestart"},{"location":"apidocs.html#proxyguardtunnel","text":"Signature: func ProxyguardTunnel(c C.uintptr_t, proxyH C.uintptr_t, wglisten C.int) *C.char ProxyguardTunnel starts the tunneling for ProxyGuard c is the cookie proxyH is the proxy handle wglisten is the port WireGuard is listening on","title":"ProxyguardTunnel"},{"location":"apidocs.html#register","text":"Signature: func Register( name *C.char, version *C.char, configDirectory *C.char, cb C.StateCB, ) *C.char 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. Main (in Go: StateMain ), 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 After registering, the FSM is initialized and the state transition MAIN should have been completed If some error occurs during registering, it is returned as a types/error/error.go Error Example Input: Register(\"org.eduvpn.app.linux\", \"0.0.1\", \"/tmp/eduvpn-common\", myCallbackFunc, 1) Example Output: { \"message\": { \"en\": \"failed to register, a VPN state is already present\" }, \"misc\": false }","title":"Register"},{"location":"apidocs.html#removeserver","text":"Signature: func RemoveServer(_type C.int, id *C.char) *C.char 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 types/error/error.go Error . Example Input (3=custom server): RemoveServer(3, \"bogus\") Example Output: { \"message\": { \"en\": \"failed to remove server\" }, \"misc\": false }","title":"RemoveServer"},{"location":"apidocs.html#renewsession","text":"Signature: func RenewSession(c C.uintptr_t) *C.char 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. So least you MUST handle the OAuth started transition It returns an error if unsuccessful. Example Input: RenewSession(myCookie) Example Output: { \"message\": { \"en\": \"could not renew session\" }, \"misc\": false }","title":"RenewSession"},{"location":"apidocs.html#serverlist","text":"Signature: func ServerList() (*C.char, *C.char) 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. Example Input: ServerList() Example Output (current profile here is empty as none has been chosen yet): { \"institute_access_servers\": [ { \"display_name\": { \"en\": \"Demo\" }, \"identifier\": \"https://demo.eduvpn.nl/\", \"profiles\": { \"current\": \"\" }, \"support_contacts\": [ \"mailto:eduvpn@surf.nl\" ], \"delisted\": false } ] }, null","title":"ServerList"},{"location":"apidocs.html#setprofileid","text":"Signature: func SetProfileID(data *C.char) *C.char 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. Example Input: SetProfileID(\"splittunnel\") Example Output: { \"message\": { \"en\": \"profile does not exist\" }, \"misc\": false }","title":"SetProfileID"},{"location":"apidocs.html#setsecurelocation","text":"Signature: func SetSecureLocation(orgID *C.char, cc *C.char) *C.char 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. orgID is the organisation ID for the secure internet server cc is the location ID/country code It returns an error if unsuccessful. Example Input: SetSecureLocation(\"http://idp.geant.org/\", \"nl\") Example Output: { \"message\": { \"en\": \"location does not exist\" }, \"misc\": false }","title":"SetSecureLocation"},{"location":"apidocs.html#setstate","text":"Signature: func SetState(fsmState C.int) *C.char SetState sets the state of the state machine. Note: this transitions the FSM into the new state without passing any data to it. Example Input: SetState(5) Example Output: null","title":"SetState"},{"location":"apidocs.html#settokenhandler","text":"Signature: func SetTokenHandler(getter C.TokenGetter, setter C.TokenSetter) *C.char 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. The client must pass two callback arguments to this function: 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 \u2018output buffer\u2019 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. Example Input: SetTokenHandler(getterFunc, setterFunc) Example Output: null","title":"SetTokenHandler"},{"location":"apidocs.html#startfailover","text":"Signature: func StartFailover(c C.uintptr_t, gateway *C.char, mtu C.int, readRxBytes C.ReadRxBytes) (C.int, *C.char) StartFailover starts the \u2018failover\u2019 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 \u2018failover\u2019 to prefer TCP (if currently using UDP). 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. 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. 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. Example Input: StartFailover(myCookie, \"10.10.10.1\", 1400, myRxBytesReader) Example Output: 1, null","title":"StartFailover"},{"location":"building-client.html","text":"Building a client # This chapter is a high-level overview on how to use eduvpn-common and build your own eduVPN/Let\u2019s Connect! client. In this chapter, we go over the basics of how the interop between Go and language x works, say something about the architecture, explain where to find detailed API documentation, explain the state machine, give a typical flow for a client and give a follow along tutorial on building an eduVPN client using Python code. At last, we will also have a few code examples that can be used as a short reference. Go <-> language X interop # Because this library is meant to be a general library for other clients to use that are written in different programming languages, we need to find a way to make this Go library available on each platform and codebase. The approach that we take is to build a C library from the Go library using Cgo. Cgo can have its disadvantages with performance and the constant conversion between Go and C types. To overcome those barriers, this library has the following goals (with some others noted here): Be high-level . Functions should do as much as possible in Go. The exported API should fit in one file. Lots of low-level functions would be a constant conversion between C and Go which adds overhead Move as much state to Go as possible . For example, Go keeps track of the servers you have configured and discovery. This makes the arguments to functions simple, clients should pass simple identifiers that Go can look up in the state Easy type conversion : to convert between C and Go types, JSON is used. Whereas Protobuf, Cap\u2019n\u2019proto or flatbuffers are more performant, they are harder to debug, add thousands of lines of autogenerated code and are not human friendly. Using JSON, the clients can approach it the same way they would use with a server using a REST API. Another approach is to just convert from Go -> C types -> language types. This was tried in version 1 of the library, but this ended up being too much work and manual memory management Make it as easy as possible for clients to manage UI and internal state : we use a state machine that gives the clients information in which state the Go library is in, e.g. we\u2019re selecting a server profile, we\u2019re loading the server endpoints. This library is not only a layer to talk to eduVPN servers, but the whole engine for a client Implement features currently not present in existing clients : WireGuard to OpenVPN failover, WireGuard over TCP Follow the official eduVPN specification and also contribute changes when needed Secure : We aim to follow the latest OAuth recommendations, to not store secret data and e.g. disable OpenVPN scripts from being ran by default And finally the most important goal: The advantages that this library brings for clients should outweigh the cost of incorporating it into the codebase . Initial versions would take more work than we get out of it. However, when each eduVPN/Let\u2019s Connect! client uses this library we should expect a net gain. New features should be easier to implement for clients by simply requiring a new eduvpn-common version and using the necessary functions Architecture # In the previous section, we have already hinted a bit on the exact architecture. This section will expand upon it by giving a figure of the basic structure As can be seen by this architecture, there is an intermediate layer between the client and the shared library. This wrapper eases the way of loading this library and then defining a more language specific API for it. In the eduvpn-common repo, we currently only support a Python wrapper. Clients themselves can define their own wrapper Typical flow for a client # NOTE: This uses the function names that are defined in the exports file in Go. For your own wrapper/the Python wrapper they are different. But the general flow is the same The client starts up. It calls the Register function that communicates with the library that it has initialized It gets the list of servers using ServerList When the user selects a server to connect to in the UI, it calls the GetConfig to get a VPN configuration for this server. This function transitions the state machine multiple times. The client uses these state transitions for logging or even updating the UI. The client then connects New feature in eduvpn-common: Check if the VPN can reach the gateway after the client is connected by calling StartFailover If the client has no servers, or it wants to add a new server, the client calls DiscoOrganizations and DiscoServers to get the discovery files from the library. This even returns cached copies if the organizations or servers should not have been updated according to the documentation From this discovery list, it calls AddServer to add the server to the internal server list of eduvpn-common. This also calls necessary state transitions, e.g. for authorizing the server. The next call to ServerList then has this server included It can then get a configuration for this server like we have explained in step 3 When a configuration has been obtained, the internal state has changed and the client can get the current server that was configured using CurrentServer . CurrentServer can also be called after startup if a server was previously set as the current server When the VPN disconnects, the client calls Cleanup so that the server resources are cleaned up by calling the /disconnect endpoint A server can be removed with the RemoveServer function When the client is done, it calls Deregister such that the most up to date internal state is saved to disk. Note that eduvpn-common also saves the internal state .e.g. after obtaining a VPN configuration Finite state machine # The eduvpn-common library uses a finite state machine internally to keep track of which state the client is in and to communicate data callbacks (e.g. to communicate the Authorization URL in the OAuth process to the client). FSM example # The following is an example of the FSM when the client has obtained a Wireguard/OpenVPN configuration from an eduVPN server The current state is highlighted in the cyan color. State explanation # For the explanation of what all the different states mean, see the client documentation States that ask data # In eduvpn-common, there are certain states that require attention from the client. OAuth Started: A state that must be handled by the client. How a client can \u2018handle\u2019 this state, we will see in the next section. In this state, the client must open the webbrowser with the authorization URL to complete to OAuth process. Note that on mobile platforms, you also need to reply with the authorization URI as these platforms do not support a local callback server using 127.0.0.1 Ask Profile: The state that asks for a profile selection to the client. Reply to this state by using a \u201ccookie\u201d and the CookieReply function. What this means will be discussed in the Python client example too Ask Location: Same for ask profile but for selecting a secure internet location. Only called if one must be chosen, e.g. due to a selection that is no longer valid The rest of the states are miscellaneous states, meaning that the client can handle them however it wants to. However, it can be useful to handle most state transitions to e.g. show loading screens or for logging and debugging purposes. Code examples # This chapter contains code examples that use the API Go command line client # The following is an example in the repository . It is a command line client. {// Package main implements an example CLI client package main import ( \"context\" \"flag\" \"fmt\" \"os\" \"reflect\" \"strings\" \"codeberg.org/eduVPN/eduvpn-common/client\" \"codeberg.org/eduVPN/eduvpn-common/i18n\" \"codeberg.org/eduVPN/eduvpn-common/internal/commonver\" \"codeberg.org/eduVPN/eduvpn-common/types/cookie\" srvtypes \"codeberg.org/eduVPN/eduvpn-common/types/server\" \"github.com/pkg/browser\" ) // Open a browser with xdg-open. func openBrowser(data any) { str, ok := data.(string) if !ok { return } go func() { err := browser.OpenURL(str) if err != nil { fmt.Fprintln(os.Stderr, \"failed to open browser with error:\", err) fmt.Println(\"Please open your browser manually\") } }() } func getProfileInteractive(profiles *srvtypes.Profiles, data any) (string, error) { fmt.Printf(\"Multiple VPN profiles found. Please select a profile by entering e.g. 1\") var ps strings.Builder var options []string i := 0 for k, v := range profiles.Map { fmt.Fprintf(&ps, \"\\n%d - %s\", i+1, i18n.GetLanguageMatched(v.DisplayName, \"en\")) options = append(options, k) i++ } // Show the profiles fmt.Println(ps.String()) var idx int if _, err := fmt.Scanf(\"%d\", &idx); err != nil || idx <= 0 || idx > len(profiles.Map) { fmt.Fprintln(os.Stderr, \"invalid profile chosen, please retry\") return getProfileInteractive(profiles, data) } p := options[idx-1] fmt.Println(\"Sending profile ID\", p) return p, nil } func sendProfile(profile string, data any) { d, ok := data.(*srvtypes.RequiredAskTransition) if !ok { fmt.Fprintf(os.Stderr, \"\\ninvalid data type: %v\\n\", reflect.TypeOf(data)) os.Exit(1) } sps, ok := d.Data.(*srvtypes.Profiles) if !ok { fmt.Fprintf(os.Stderr, \"\\ninvalid data type for profiles: %v\\n\", reflect.TypeOf(d.Data)) os.Exit(1) } if profile == \"\" { gprof, err := getProfileInteractive(sps, data) if err != nil { fmt.Fprintf(os.Stderr, \"failed getting profile interactively: %v\\n\", err) os.Exit(1) } profile = gprof } if err := d.C.Send(profile); err != nil { fmt.Fprintf(os.Stderr, \"failed setting profile with error: %v\\n\", err) os.Exit(1) } } // The callback function // If OAuth is started we open the browser with the Auth URL // If we ask for a profile, we send the profile using command line input // Note that this has an additional argument, the vpn state which was wrapped into this callback function below. func stateCallback(_ client.FSMStateID, newState client.FSMStateID, data any, prof string, dir string) { if newState == client.StateOAuthStarted { openBrowser(data) } if newState == client.StateAskProfile { sendProfile(prof, data) } if newState == client.StateAskLocation { // removing is best effort _ = os.RemoveAll(dir) fmt.Fprint(os.Stderr, \"An invalid secure location is stored. This CLI doesn't support interactively choosing a location yet. Give a correct location with the -country-code flag\") os.Exit(1) } } // Get a config for Institute Access or Secure Internet Server. func getConfig(state *client.Client, url string, srvType srvtypes.Type, cc string, prof string) (*srvtypes.Configuration, error) { if !strings.HasPrefix(url, \"http\") { url = \"https://\" + url } ck := cookie.NewWithContext(context.Background()) defer ck.Cancel() //nolint:errcheck err := state.AddServer(ck, url, srvType, nil) if err != nil { // TODO: This is quite hacky :^) if !strings.Contains(err.Error(), \"a secure internet server already exists.\") { return nil, err } } if cc != \"\" { err = state.SetSecureLocation(url, cc) if err != nil { return nil, err } } if prof != \"\" { // this is best effort, e.g. if no server was chosen before this fails _ = state.SetProfileID(prof) //nolint:errcheck } return state.GetConfig(ck, url, srvType, false, false) } // Get a config for a single server, Institute Access or Secure Internet. func printConfig(url string, cc string, srvType srvtypes.Type, prof string) error { var c *client.Client var err error var dir string dir, err = os.MkdirTemp(\"\", \"eduvpn-common\") if err != nil { return err } // removing is best effort defer os.RemoveAll(dir) //nolint:errcheck c, err = client.New( \"org.eduvpn.app.linux\", fmt.Sprintf(\"%s-cli\", commonver.Version), dir, func(oldState client.FSMStateID, newState client.FSMStateID, data any) bool { stateCallback(oldState, newState, data, prof, dir) return true }, nil, ) if err != nil { return err } _ = c.Register() ck := cookie.NewWithContext(context.Background()) _, err = c.DiscoOrganizations(ck, false, \"\") if err != nil { return err } _, err = c.DiscoServers(ck, false, \"\") if err != nil { return err } defer c.Deregister() cfg, err := getConfig(c, url, srvType, cc, prof) if err != nil { return err } fmt.Println(cfg.VPNConfig) return nil } // The main function // It parses the arguments and executes the correct functions. func main() { cu := flag.String(\"get-custom\", \"\", \"The url of a custom server to connect to\") u := flag.String(\"get-institute\", \"\", \"The url of an institute to connect to\") sec := flag.String(\"get-secure\", \"\", \"Gets secure internet servers\") cc := flag.String(\"country-code\", \"\", \"The country code to use in case of a secure internet server\") prof := flag.String(\"profile\", \"\", \"The profile ID to choose\") flag.Parse() // Connect to a VPN by getting an Institute Access config var err error switch { case *cu != \"\": err = printConfig(*cu, \"\", srvtypes.TypeCustom, *prof) case *u != \"\": err = printConfig(*u, \"\", srvtypes.TypeInstituteAccess, *prof) case *sec != \"\": err = printConfig(*sec, *cc, srvtypes.TypeSecureInternet, *prof) default: flag.PrintDefaults() } if err != nil { fmt.Fprintf(os.Stderr, \"failed to get a VPN config: %v\\n\", err) } }}","title":"Building a client"},{"location":"building-client.html#go-language-x-interop","text":"Because this library is meant to be a general library for other clients to use that are written in different programming languages, we need to find a way to make this Go library available on each platform and codebase. The approach that we take is to build a C library from the Go library using Cgo. Cgo can have its disadvantages with performance and the constant conversion between Go and C types. To overcome those barriers, this library has the following goals (with some others noted here): Be high-level . Functions should do as much as possible in Go. The exported API should fit in one file. Lots of low-level functions would be a constant conversion between C and Go which adds overhead Move as much state to Go as possible . For example, Go keeps track of the servers you have configured and discovery. This makes the arguments to functions simple, clients should pass simple identifiers that Go can look up in the state Easy type conversion : to convert between C and Go types, JSON is used. Whereas Protobuf, Cap\u2019n\u2019proto or flatbuffers are more performant, they are harder to debug, add thousands of lines of autogenerated code and are not human friendly. Using JSON, the clients can approach it the same way they would use with a server using a REST API. Another approach is to just convert from Go -> C types -> language types. This was tried in version 1 of the library, but this ended up being too much work and manual memory management Make it as easy as possible for clients to manage UI and internal state : we use a state machine that gives the clients information in which state the Go library is in, e.g. we\u2019re selecting a server profile, we\u2019re loading the server endpoints. This library is not only a layer to talk to eduVPN servers, but the whole engine for a client Implement features currently not present in existing clients : WireGuard to OpenVPN failover, WireGuard over TCP Follow the official eduVPN specification and also contribute changes when needed Secure : We aim to follow the latest OAuth recommendations, to not store secret data and e.g. disable OpenVPN scripts from being ran by default And finally the most important goal: The advantages that this library brings for clients should outweigh the cost of incorporating it into the codebase . Initial versions would take more work than we get out of it. However, when each eduVPN/Let\u2019s Connect! client uses this library we should expect a net gain. New features should be easier to implement for clients by simply requiring a new eduvpn-common version and using the necessary functions","title":"Go <-> language X interop"},{"location":"building-client.html#architecture","text":"In the previous section, we have already hinted a bit on the exact architecture. This section will expand upon it by giving a figure of the basic structure As can be seen by this architecture, there is an intermediate layer between the client and the shared library. This wrapper eases the way of loading this library and then defining a more language specific API for it. In the eduvpn-common repo, we currently only support a Python wrapper. Clients themselves can define their own wrapper","title":"Architecture"},{"location":"building-client.html#typical-flow-for-a-client","text":"NOTE: This uses the function names that are defined in the exports file in Go. For your own wrapper/the Python wrapper they are different. But the general flow is the same The client starts up. It calls the Register function that communicates with the library that it has initialized It gets the list of servers using ServerList When the user selects a server to connect to in the UI, it calls the GetConfig to get a VPN configuration for this server. This function transitions the state machine multiple times. The client uses these state transitions for logging or even updating the UI. The client then connects New feature in eduvpn-common: Check if the VPN can reach the gateway after the client is connected by calling StartFailover If the client has no servers, or it wants to add a new server, the client calls DiscoOrganizations and DiscoServers to get the discovery files from the library. This even returns cached copies if the organizations or servers should not have been updated according to the documentation From this discovery list, it calls AddServer to add the server to the internal server list of eduvpn-common. This also calls necessary state transitions, e.g. for authorizing the server. The next call to ServerList then has this server included It can then get a configuration for this server like we have explained in step 3 When a configuration has been obtained, the internal state has changed and the client can get the current server that was configured using CurrentServer . CurrentServer can also be called after startup if a server was previously set as the current server When the VPN disconnects, the client calls Cleanup so that the server resources are cleaned up by calling the /disconnect endpoint A server can be removed with the RemoveServer function When the client is done, it calls Deregister such that the most up to date internal state is saved to disk. Note that eduvpn-common also saves the internal state .e.g. after obtaining a VPN configuration","title":"Typical flow for a client"},{"location":"building-client.html#finite-state-machine","text":"The eduvpn-common library uses a finite state machine internally to keep track of which state the client is in and to communicate data callbacks (e.g. to communicate the Authorization URL in the OAuth process to the client).","title":"Finite state machine"},{"location":"building-client.html#fsm-example","text":"The following is an example of the FSM when the client has obtained a Wireguard/OpenVPN configuration from an eduVPN server The current state is highlighted in the cyan color.","title":"FSM example"},{"location":"building-client.html#state-explanation","text":"For the explanation of what all the different states mean, see the client documentation","title":"State explanation"},{"location":"building-client.html#states-that-ask-data","text":"In eduvpn-common, there are certain states that require attention from the client. OAuth Started: A state that must be handled by the client. How a client can \u2018handle\u2019 this state, we will see in the next section. In this state, the client must open the webbrowser with the authorization URL to complete to OAuth process. Note that on mobile platforms, you also need to reply with the authorization URI as these platforms do not support a local callback server using 127.0.0.1 Ask Profile: The state that asks for a profile selection to the client. Reply to this state by using a \u201ccookie\u201d and the CookieReply function. What this means will be discussed in the Python client example too Ask Location: Same for ask profile but for selecting a secure internet location. Only called if one must be chosen, e.g. due to a selection that is no longer valid The rest of the states are miscellaneous states, meaning that the client can handle them however it wants to. However, it can be useful to handle most state transitions to e.g. show loading screens or for logging and debugging purposes.","title":"States that ask data"},{"location":"building-client.html#code-examples","text":"This chapter contains code examples that use the API","title":"Code examples"},{"location":"building-client.html#go-command-line-client","text":"The following is an example in the repository . It is a command line client. {// Package main implements an example CLI client package main import ( \"context\" \"flag\" \"fmt\" \"os\" \"reflect\" \"strings\" \"codeberg.org/eduVPN/eduvpn-common/client\" \"codeberg.org/eduVPN/eduvpn-common/i18n\" \"codeberg.org/eduVPN/eduvpn-common/internal/commonver\" \"codeberg.org/eduVPN/eduvpn-common/types/cookie\" srvtypes \"codeberg.org/eduVPN/eduvpn-common/types/server\" \"github.com/pkg/browser\" ) // Open a browser with xdg-open. func openBrowser(data any) { str, ok := data.(string) if !ok { return } go func() { err := browser.OpenURL(str) if err != nil { fmt.Fprintln(os.Stderr, \"failed to open browser with error:\", err) fmt.Println(\"Please open your browser manually\") } }() } func getProfileInteractive(profiles *srvtypes.Profiles, data any) (string, error) { fmt.Printf(\"Multiple VPN profiles found. Please select a profile by entering e.g. 1\") var ps strings.Builder var options []string i := 0 for k, v := range profiles.Map { fmt.Fprintf(&ps, \"\\n%d - %s\", i+1, i18n.GetLanguageMatched(v.DisplayName, \"en\")) options = append(options, k) i++ } // Show the profiles fmt.Println(ps.String()) var idx int if _, err := fmt.Scanf(\"%d\", &idx); err != nil || idx <= 0 || idx > len(profiles.Map) { fmt.Fprintln(os.Stderr, \"invalid profile chosen, please retry\") return getProfileInteractive(profiles, data) } p := options[idx-1] fmt.Println(\"Sending profile ID\", p) return p, nil } func sendProfile(profile string, data any) { d, ok := data.(*srvtypes.RequiredAskTransition) if !ok { fmt.Fprintf(os.Stderr, \"\\ninvalid data type: %v\\n\", reflect.TypeOf(data)) os.Exit(1) } sps, ok := d.Data.(*srvtypes.Profiles) if !ok { fmt.Fprintf(os.Stderr, \"\\ninvalid data type for profiles: %v\\n\", reflect.TypeOf(d.Data)) os.Exit(1) } if profile == \"\" { gprof, err := getProfileInteractive(sps, data) if err != nil { fmt.Fprintf(os.Stderr, \"failed getting profile interactively: %v\\n\", err) os.Exit(1) } profile = gprof } if err := d.C.Send(profile); err != nil { fmt.Fprintf(os.Stderr, \"failed setting profile with error: %v\\n\", err) os.Exit(1) } } // The callback function // If OAuth is started we open the browser with the Auth URL // If we ask for a profile, we send the profile using command line input // Note that this has an additional argument, the vpn state which was wrapped into this callback function below. func stateCallback(_ client.FSMStateID, newState client.FSMStateID, data any, prof string, dir string) { if newState == client.StateOAuthStarted { openBrowser(data) } if newState == client.StateAskProfile { sendProfile(prof, data) } if newState == client.StateAskLocation { // removing is best effort _ = os.RemoveAll(dir) fmt.Fprint(os.Stderr, \"An invalid secure location is stored. This CLI doesn't support interactively choosing a location yet. Give a correct location with the -country-code flag\") os.Exit(1) } } // Get a config for Institute Access or Secure Internet Server. func getConfig(state *client.Client, url string, srvType srvtypes.Type, cc string, prof string) (*srvtypes.Configuration, error) { if !strings.HasPrefix(url, \"http\") { url = \"https://\" + url } ck := cookie.NewWithContext(context.Background()) defer ck.Cancel() //nolint:errcheck err := state.AddServer(ck, url, srvType, nil) if err != nil { // TODO: This is quite hacky :^) if !strings.Contains(err.Error(), \"a secure internet server already exists.\") { return nil, err } } if cc != \"\" { err = state.SetSecureLocation(url, cc) if err != nil { return nil, err } } if prof != \"\" { // this is best effort, e.g. if no server was chosen before this fails _ = state.SetProfileID(prof) //nolint:errcheck } return state.GetConfig(ck, url, srvType, false, false) } // Get a config for a single server, Institute Access or Secure Internet. func printConfig(url string, cc string, srvType srvtypes.Type, prof string) error { var c *client.Client var err error var dir string dir, err = os.MkdirTemp(\"\", \"eduvpn-common\") if err != nil { return err } // removing is best effort defer os.RemoveAll(dir) //nolint:errcheck c, err = client.New( \"org.eduvpn.app.linux\", fmt.Sprintf(\"%s-cli\", commonver.Version), dir, func(oldState client.FSMStateID, newState client.FSMStateID, data any) bool { stateCallback(oldState, newState, data, prof, dir) return true }, nil, ) if err != nil { return err } _ = c.Register() ck := cookie.NewWithContext(context.Background()) _, err = c.DiscoOrganizations(ck, false, \"\") if err != nil { return err } _, err = c.DiscoServers(ck, false, \"\") if err != nil { return err } defer c.Deregister() cfg, err := getConfig(c, url, srvType, cc, prof) if err != nil { return err } fmt.Println(cfg.VPNConfig) return nil } // The main function // It parses the arguments and executes the correct functions. func main() { cu := flag.String(\"get-custom\", \"\", \"The url of a custom server to connect to\") u := flag.String(\"get-institute\", \"\", \"The url of an institute to connect to\") sec := flag.String(\"get-secure\", \"\", \"Gets secure internet servers\") cc := flag.String(\"country-code\", \"\", \"The country code to use in case of a secure internet server\") prof := flag.String(\"profile\", \"\", \"The profile ID to choose\") flag.Parse() // Connect to a VPN by getting an Institute Access config var err error switch { case *cu != \"\": err = printConfig(*cu, \"\", srvtypes.TypeCustom, *prof) case *u != \"\": err = printConfig(*u, \"\", srvtypes.TypeInstituteAccess, *prof) case *sec != \"\": err = printConfig(*sec, *cc, srvtypes.TypeSecureInternet, *prof) default: flag.PrintDefaults() } if err != nil { fmt.Fprintf(os.Stderr, \"failed to get a VPN config: %v\\n\", err) } }}","title":"Go command line client"},{"location":"building.html","text":"Building # This section contains the instruction on how to build the library and associated wrappers. We first explain how to build the Go library and then further explain the wrapper specific building process. As the Python wrapper is the only wrapper at the moment, this only consists of this wrapper language for now. Building the Go library # To build the Go library, you need the dependencies for your system installed. We will go over the needed dependencies for Linux. Afterwards, we explain the basic commands to build the library. Dependencies # Linux # To build the Go shared library using Linux you need the following dependencies: Go 1.18 or later Gcc GNU Make Dependencies for the Python wrapper if you want to build that as well Commands # Before we can begin building the wrapper code, we need to build the Go code as a shared library. This section will tell you how to do so. To build the shared library for the current platform issue the following command in the root directory: make The shared library will be output in lib/ . Cleaning # To clean build the library and wrapper, issue the following command in the root directory: make clean Python wrapper # To build the python wrapper issue the following command (in the root directory of the eduvpn-common project): make -C wrappers/python This uses the makefile in wrappers/python/Makefile to build the python file into a wheel placed in wrappers/python/dist/eduvpncommon-[version]-py3-none-[platform].whl . Where version is the version of the library and platform is your current platform. The wheel can be installed with pip : pip install ./wrappers/python/dist/eduvpncommon-[version]-py3-none-[platform].whl Notes on building for release # To build for release, make sure you obtain the tarball artifacts in the release (ending with .tar.xz ) at https://codeberg.org/eduVPN/eduvpn-common/releases . These are signed with minisign and gpg keys, make sure to verify these signatures using the public keys available here: https://codeberg.org/eduVPN/eduvpn-common/src/branch/main/keys , they are also available externally: - https://app.eduvpn.org/linux/v4/deb/app+linux@eduvpn.org.asc - https://git.sr.ht/~jwijenbergh/python3-eduvpn-common.rpm/tree/main/item/SOURCES/minisign-CA9409316AC93C07.pub To build for release, make sure to extract the tarball, and then build: make To upload the releases to Codeberg, run: ./make_release.sh For pre-releases: ./make_release.sh -p Package formats # We support the following additional package formats: RPM (Linux, Fedora), Deb (Linux, Debian derivatives) and Pip. These are build in the CI and for production build on a separate non-public host.","title":"Building"},{"location":"building.html#building-the-go-library","text":"To build the Go library, you need the dependencies for your system installed. We will go over the needed dependencies for Linux. Afterwards, we explain the basic commands to build the library.","title":"Building the Go library"},{"location":"building.html#dependencies","text":"","title":"Dependencies"},{"location":"building.html#commands","text":"Before we can begin building the wrapper code, we need to build the Go code as a shared library. This section will tell you how to do so. To build the shared library for the current platform issue the following command in the root directory: make The shared library will be output in lib/ .","title":"Commands"},{"location":"building.html#python-wrapper","text":"To build the python wrapper issue the following command (in the root directory of the eduvpn-common project): make -C wrappers/python This uses the makefile in wrappers/python/Makefile to build the python file into a wheel placed in wrappers/python/dist/eduvpncommon-[version]-py3-none-[platform].whl . Where version is the version of the library and platform is your current platform. The wheel can be installed with pip : pip install ./wrappers/python/dist/eduvpncommon-[version]-py3-none-[platform].whl","title":"Python wrapper"},{"location":"building.html#notes-on-building-for-release","text":"To build for release, make sure you obtain the tarball artifacts in the release (ending with .tar.xz ) at https://codeberg.org/eduVPN/eduvpn-common/releases . These are signed with minisign and gpg keys, make sure to verify these signatures using the public keys available here: https://codeberg.org/eduVPN/eduvpn-common/src/branch/main/keys , they are also available externally: - https://app.eduvpn.org/linux/v4/deb/app+linux@eduvpn.org.asc - https://git.sr.ht/~jwijenbergh/python3-eduvpn-common.rpm/tree/main/item/SOURCES/minisign-CA9409316AC93C07.pub To build for release, make sure to extract the tarball, and then build: make To upload the releases to Codeberg, run: ./make_release.sh For pre-releases: ./make_release.sh -p","title":"Notes on building for release"},{"location":"building.html#package-formats","text":"We support the following additional package formats: RPM (Linux, Fedora), Deb (Linux, Debian derivatives) and Pip. These are build in the CI and for production build on a separate non-public host.","title":"Package formats"},{"location":"testing.html","text":"Testing # The Go library right now has various tests defined. E.g. server interaction, oauth, discovery and signature verification tests. To run the Go test suite, issue the following command in a shell make test Note that this runs the tests without any server interaction (so for now only the signature verification tests). To run the tests with an eduVPN server you need to specify environment variables: SERVER_URI=\"eduvpn.example.com\" PORTAL_USER=\"example\" PORTAL_PASS=\"example\" make test This needs python3-selenium and geckodriver (extract and put in your $PATH ). Note that testing with a server assumes it uses a default portal, due to it needing to click on buttons on the web page. You can add your own portal by customizing the called Selenium script . There are other environment variables that can be used: OAUTH_EXPIRED_TTL : Use this for a server which has a low OAuth access token expiry time, e.g. 10 seconds. You would then set this variable to \"10\" so that a test is ran which waits for 10 seconds for the OAuth tokens to expire Testing the Python code # To test the Python code, issue the following command in a shell (you will need dependencies for all wrappers if you do this[^1]): make -C wrappers/python test","title":"Testing"},{"location":"testing.html#testing-the-python-code","text":"To test the Python code, issue the following command in a shell (you will need dependencies for all wrappers if you do this[^1]): make -C wrappers/python test","title":"Testing the Python code"}]}
\ No newline at end of file |
