python-glinet

GitHub Workflow Status (event) GitHub Workflow Status PyPI - Python Version PyPI Code Cov

python-glinet - A Python3 Client for GL.Inet Router

  • This python client provides full access to the GL.Inet Luci API.

  • Supported firmware versions: 4.0 onwards

  • Dynamic method creation including docstring from the gl.inet online documentation

  • Api responses are represented recursively as objects, such that you can access all properties via ‘.’

  • Cache for api description and hashed login

  • Configurable background thread to keep connection alive

https://github.com/tomtana/python-glinet/raw/main/ressources/python_glinet_demo.gif

About

The original use case was to automatically generate and write nordvpn wireguard configs to my slate axt-1800 router, but then I found an elegant way to autogenerate methods for the whole api and python-glinet was born.

It should be noted that GL.Inet changed the api mechanism from REST to JSON-RPC with the introduction of the firmware 4.0. Therefore, older versions are not supported.

Also, there is no official documentation in English yet. The client parses the Chinese documentation from here and dynamically creates the api methods. Once it is available, the repo will be updated.

The best way to navigate and explore the api is within an ipython shell. A wrapper for ipython and terminal is on the roadmap.

Installation

PiP

pip install python-glinet

From Repo

#clone repository
git clone https://github.com/tomtana/python-glinet.git
cd python-glinet

Install package directly. The -e parameter lets you edit the files. If this is not needed to can also install without the -e parameter.

pip install -e .

Alternatively install it in an Python virtual environment (see here for more infos)

python3 -m venv venv
source venv/bin/activate
pip install -e .

Getting Started

Login

Login is as easy as shown below. Only in case you modified the router default settings such as ip-address or username you need to pass them as parameter (see the documentation of the GlInet class for more details). The login method will ask you to input the password first time you call it.

from pyglinet import GlInet
glinet = GlInet()
glinet.login()

Info: With the default settings, the following tasks will be executed during init and login:

  • try to load api reference from persistence, otherwise load it from the gl.inet online documentation

  • if no password is passed as parameter in the constructor

  • try to load from persistence (password stored as hash)

  • if no success ask via prompt

  • persist settings

  • start background thread to keep connection alive

API Access Via Dynamically Created Client

First generate an api object.

client = glinet.get_api_client()
General
  • The api structure is as follow: client.<functionial_group>.<method>

  • Due to python naming rules for variables, all “-” are replaced with “_” for the api method construction. e.g. wg-client becomes wg_client.

  • Use code completion and docstring to intuitively navigate the api

Functional Groups

Just call your client to see all available api function groups.

client
Out[11]:
Function
------------------
repeater
rs485
qos
acl
modem
logread
igmp
custom_dns
dns
dlna
nas_web
adguardhome
s2s
samba
switch_button
diag
rtty
network
upgrade
reboot
wg_server
firewall
ovpn_server
vpn_policy
fan
system
wg_client
cable
led
ui
netmode
ddns
ipv6
ovpn_client
plugins
tethering
macclone
lan
edgerouter
clients
wifi
cloud
cloud_batch_manage
Methods

To explore the methods of a function group, just select it and hit enter.

client.wg_client
Out[6]:
Function
--------------------
get_recommend_config
get_third_config
add_config
set_config
remove_config
clear_config_list
get_config_list
start
stop
get_status
check_config
confirm_config
add_group
remove_group
set_group
get_group_list
get_all_config_list
set_proxy
add_route
set_route
get_route_list
remove_route
Parameters

Select your method and press enter. A list for all possible parameters are printed. If a parameter is prepended with ?, it means it is optional.

api.wg_client.set_config
Out[8]:
Parameter              Type    Description
---------------------  ------  ------------------
name                   string  节点名
address_v4             string  节点IPv4子网
?address_v6            string  节点IPv6子网
private_key            string  节点私钥
allowed_ips            string  节点的allowedips
end_point              string  节点的endpoint
public_key             string  节点公钥
?dns                   string  节点的dns
?preshared_key         string  预分享密钥
?ipv6_enable           bool    是否启用IPv6
presharedkey_enable    bool    是否使用预分享密钥
group_id               number  组ID
peer_id                number  配置ID
?listen_port           number  监听端口
?persistent_keepalive  number  节点保活
?mtu                   number  节点的mtu
Docstring

You can also show the docstring by appending a ? to the method. It will show all the parameter and usage examples.

api.wg_client.set_config?
Signature: api.wg_client.set_config(params=None)
Type:      GlInetApiCall
File:      ~/.local/lib/python3.10/site-packages/pyglinet/api_helper.py
Docstring:
Available parameters (?=optional):
Parameter              Type    Description
---------------------  ------  ------------------
name                   string  节点名
address_v4             string  节点IPv4子网
?address_v6            string  节点IPv6子网
private_key            string  节点私钥
allowed_ips            string  节点的allowedips
end_point              string  节点的endpoint
public_key             string  节点公钥
?dns                   string  节点的dns
?preshared_key         string  预分享密钥
?ipv6_enable           bool    是否启用IPv6
presharedkey_enable    bool    是否使用预分享密钥
group_id               number  组ID
peer_id                number  配置ID
?listen_port           number  监听端口
?persistent_keepalive  number  节点保活
?mtu                   number  节点的mtu

Example request:
{\"jsonrpc\":\"2.0\",\"method\":\"call\",\"params\":[\"\",\"wg-client\",\"set_config\",{\"group_id\":3212,\"peer_id\":1254,\"name\":\"test\",\"address_v4\":\"10.8.0.0/24\",\"address_v6\":\"fd00:db8:0:123::/64\",\"private_key\":\"XVpIdr+oYjTcgDwzSZmNa1nSsk8JO+tx1NBo17LDBAI=\",\"allowed_ips\":\"0.0.0.0/0,::/0\",\"end_point\":\"103.231.88.18:3102\",\"public_key\":\"zv0p34WZN7p2vIgehwe33QF27ExjChrPUisk481JHU0=\",\"dns\":\"193.138.219.228\",\"presharedkey_enable\":false,\"listen_port\":22536,\"persistent_keepalive\":25,\"mtu\":1420,\"ipv6_enable\":true}],\"id\":1}

Example response:
{\"jsonrpc\": \"2.0\", \"id\": 1, \"result\": {}}
Method call

Just call the method as usual. Check the usage examples to understand how parameters need to be passed.

client.wg_client.get_all_config_list()
Out[12]: {'name': 'wg_client__get_all_config_list', 'config_list': [{'name': 'wg_client__get_all_config_list', 'username': '', 'group_name': 'AzireVPN', 'peers': [], 'password': '', 'auth_type': 1, 'group_id': 9690}]}
API Response Processing

The API json responses are recursively converted into objects. This provides convenient access with code completion and point access to the data.

API Access Via Manual Requests

Instead of using the dynamically created api_client, it is also possible to use the GlInet instance to make api requests. In fact, the api_client uses the same mechanism.

Once logged in, you simply can use the glinet.request(method, params) method to access or retrieve data from the api. Information about the method and the parameters can either be found in the documentation or via the api_client.

e.g.

glinet.request("call", ["adguardhome", "get_config"])
Out[12]: {'name': 'adguardhome__get_config', 'id': 13, 'jsonrpc': '2.0', 'result': {'name': 'adguardhome__get_config', 'enabled': False}}

is equivalent to

api_client.adguardhome.get_config()
Out[13]: {'name': 'adguardhome__get_config', 'enabled': False}

Note: the output of the request method returns the whole response body whereas the api_client just returns the result.

Roadmap

V1.0.0

  • ☒ Add dynamically docstring for API calls

  • ☒ Create pip compliant package

  • ☒ Publish pip package

  • ☒ Add tests

  • ☒ Improve documentation

  • ☐ Increase test coverage

  • ☐ replace crypt dependency to allow also Windows execution

V2.0.0

  • ☐ Add wrapper for execution via terminal

  • ☐ …

GlInet API

class pyglinet.GlInet(url: str = 'https://192.168.8.1/rpc', username: str = 'root', password: Optional[str] = None, protocol_version: str = '2.0', keep_alive: bool = True, keep_alive_intervall: float = 30, verify_ssl_certificate: bool = False, update_api_reference_cache: bool = False, api_reference_url: str = 'https://dev.gl-inet.cn/docs/api_docs_api/', cache_folder: Optional[str] = None)

This class manages the connection to a GL-Inet router and provides basic routines to send and receive data.

Important: Only works with firmware version >=4.0. The api has changed from REST api to json-rpc with the 4.0, so older firmware versions won’t work.

Before you can start making requests, you need to call the login() method

The api calls can either be made via the GlInetApi object, which can be constructed via the get_api_client method, or via the request method directly.

Parameters
  • url – url to router rpc api

  • username – username, default is root.

  • password – password, if left empty, a prompt will ask you when login() is called. For security reasons, you should never pass your password here.

  • protocol_version – default 2.0

  • keep_alive – if set to True, a background thread will be started to keep the connection alive

  • keep_alive_intervall – intervall in which the background thread sends requests to the router

  • verify_ssl_certificate – either True/False or path to certificate.

  • update_api_reference_cache – if True, data is loaded from the web, otherwise application tries first to load data from cache.

  • api_reference_url – url to api description

  • cache_folder – folder where data is persisted. If left empty, default is $home/.python-pyglinet

__challenge_login()

Request cryptographic parameters to compute login hash. This is the first step in the login sequence.

Returns

challence

__create_object(json_data, method, params)

Create recursive object from json api response

Json data is stored in a convenience container, such that elements can be accessed as class attributes via ‘.’

Parameters
  • json_data – json data

  • method – api method call

  • params – params

Returns

ResultContainer

__dump_to_file(obj, file)

Dump pickle data to file.

Parameters
  • obj – object to dump

  • file – path to file

Returns

None

__generate_login_hash(challenge)

Generate final authentication hash

Parameters

challenge – dict with nonce, salt and algo type

Returns

authentication hash

__generate_query_id() int

Generate json-rpc query id

Returns

query id

__generate_request(method: str, params: Union[Dict, List[str], str]) dict

Generate json for rpc api call

Parameters
  • method – rpc method

  • params – params

Returns

json

__generate_unix_passwd_hash(password: str, alg: str, salt: str) str

Generate unix style hash with given algo and salt

Parameters
  • alg – algorithm

  • salt – salt

Returns

hash

__keep_alive() None

Keep connection alive

Function is started in background thread (see login() for more details). Send in fixed intervall requests to api. If not successful, try to connect again.

Returns

None

__load_api_description(update: bool = False)

Load api description in json format

Parameters

update – if true, the api description is loaded from the web. If false, the program first tries to load the data from the cache and in case this fails from the web.

Returns

api description

__load_if_exist(file: str)

Load pickle file if it exists.

Parameters

file – path to file

Returns

None if file doesn’t exist, else Data

__request(method: str, params: Union[Dict, List[str], str]) ResultContainer

Send request to router without considering the current login state. This may lead to misleading error messages.

Parameters
  • method – rpc method

  • params – parameter

Returns

ResultContainer

__request_with_sid(method: str, params: Union[Dict, List[str], str]) ResultContainer

Request which requires prior login

Parameters
  • method – api method call

  • params – params

Returns

ResultContainer

__request_without_sid(method: str, params: Union[Dict, List[str], str]) ResultContainer

Request which requires to be logged out

Parameters
  • method – api method call

  • params – params

Returns

ResultContainer

__update_login_and_cache(challenge, update_password=False)

Generates the login struct containing username, hash, salt and alg type. If data is diverging from persisted set, old data will be deleted and new data will be persisted to file.

Parameters
  • challenge – challenge as received containing nonce, salt and algo

  • update_password – if True, the user will be requested to enter the password

_start_keep_alive_thread()

Starts keep alive background thread which calls __keep_alive() in the configured interval.

Returns

_stop_keep_alive_thread()

Stop keep alive thread

get_api_client() GlInetApi

Create GlInetApi object client to access api functions

Returns

api client

is_alive() bool

Check if connection is alive.

Returns

True if alive, else False

login()

Login and start background thread for keep_alive is configured. If password was set in constructor, cached values will be ignored. If password was not set (default) in GlInet(), you will be asked to enter the password the first time this function is called. If login was successful, the password hash is cashed.

Returns

True

logout() bool

Logout and stop keep alive thread

Returns

True

request(method: str, params: Union[Dict, List[str], str]) ResultContainer

Send request. Function checks if method requires login and chooses the respective request wrapper. see __request_with_sid() and __request()

Parameters
  • method – api method call

  • params – params

Returns

ResultContainer