Skip to main content

Husarnet Daemon HTTP API

This manual describes in detail the Husarnet Daemon HTTP API, which is the interface that our CLI uses to communicate with the Husarnet Daemon service. SInce the Daemon API uses standardized protocol (HTTP) users can easily integrate the Husarnet Daemon with their own solutions.

info

Note that the API described here is suitable for communicating and issuing commands to the locally running Husarnet Daemon, such as:

  • joining
  • manipulating whitelist
  • enabling/disabling hooks
  • fetching Daemon status and others. If you are looking for a way to manage your Husarnet networks remotely our Dashboard API is the tool you are looking for.

Overall API description

The Daemon API utilizes standard HTTP protocol. We currently make use of two HTTP methods:

  • GET - for operations that don't change the Daemon configuration such as fetching status or checking configuration flags
  • POST - for operations that make change to the current configuration

All methods return JSON objects, while the GET methods can return different JSON objects, many2 methods return a standard reply. Note that the standard reply is also returned as a field in the status object. The standard reply looks like this:

       {
"is_dirty": false,
"notifications": [...],
"notifications_enabled": true,
"notifications_to_display": false
}

Daemon binds to the 127.0.0.1 or as it is sometimes called the loopback interface. The default port is: 16216. The port is configurable, and can be changed by adding the following line to the husarnet.service config file and restarting the service:

Environment=HUSARNET_DAEMON_API_PORT=<DESIRED PORT NUMBER>

API methods

GET methods

The daemon api exposes following GET entrypoints:

Hello world

This is a test entrypoint that returns the message 'Hello World!' in plain text.

/hi

Returns

Hello World!

Fetch Daemon status

This entry point returns the Daemon status which contains all kinds of information about current running Daemon.

/api/status

Returns

{
"result": {
"base_connection": {...},
"connection_status": {...},
"dashboard_fqdn": "app.husarnet.com",
"hooks_enabled": true,
"host_table": {...},
"is_joined": true,
"is_ready": true,
"is_ready_to_join": true,
"local_hostname": <DEVICE NAME>,
"local_ip": <DEVICE ADDRESS>,
"peers": [],
"standard_result": {...},
"user_settings": {
"daemonApiPort": "16216",
"dashboardFqdn": "app.husarnet.com",
"enableCompression": "false",
"enableHooks": "true",
"enableMulticast": "true",
"enableNotifications": "true",
"enableTcpTunelling": "true",
"enableUdp": "true",
"enableUdpTunelling": "true",
"enableWhitelist": "true",
"extraAdvertisedAddress": "",
"hostname": "",
"interfaceName": "hnet0",
"joinCode": "",
"logVerbosity": "1",
"overrideBaseAddress": "",
"overrideSourcePort": ""
},
"version": "2.0.133",
"websetup_address": <WEBSETUP ADDRESS>,
"whitelist": [...]
},
"status": "success"
}

Fetch whitelist

Returns current contents

/api/whitelist/ls

Returns

{
"result": [...],
"status": "success"
}

Fetch logs

Returns currently stored in memory logs

/api/logs/get

Returns

{
"result": {
"logs": [...],
"standard_result": {...}
},
"status": "success"
}

Fetch logs settings

Returns current logs settings

/api/logs/settings

Returns

{
"result": {
"current_size": 100,
"size": 100,
"standard_result": {...},
"verbosity": 1
},
"status": "success"
}

POST methods

Important fact about all API entrypoints that use the POST HTTP method is that they all require a special request body parameter to be passed to them, containing a secret generated by the Husarnet Daemon, which the Daemon saves in a write protected file which can be found in the Husarnet configuration directory under filename daemon_api_token.

Join network

/api/join

Parameters

secret = <DAEMON API SECRET>
code = <JOINCODE>
hostname = <DEVICE NAME> #Optional argument if not given the hostname will be set to empty string

Returns

{
"result": "success",
"status": "success"
}

Change setup server

/api/change-server

Parameters

secret = <DAEMON API SECRET>
domain = <FQDN FOR HUSARNET DASHBOARD INSTANCE>

Returns

{
"result": "success",
"status": "success"
}

Add entry to host table

/api/host/add"

Parameters

secret = <DAEMON API SECRET>
hostname = <HOSTNAME>
address = <ADDRESS>

Returns

{
"result": "success",
"status": "success"
}

Remove entry form host table

/api/host/rm

Parameters

secret = <DAEMON API SECRET>
hostname = <HOSTNAME>

Returns

{
"result": "success",
"status": "success"
}

Add whitelist entry

/api/whitelist/add

Parameters

secret = <DAEMON API SECRET>
address = <ADDRESS>

Returns

{
"result": {
"standard_result": {...},
},
"status": "success"
}

Remove whitelist entry

/api/whitelist/rm

Parameters

secret = <DAEMON API SECRET>
address = <ADDRESS>

Returns

{
"result": {
"standard_result": {...},
},
"status": "success"
}

Enable whitelist

/api/whitelist/enable

Parameters

secret = <DAEMON API SECRET>

Returns

{
"result": {
"standard_result": {...},
},
"status": "success"
}

Disable whitelist

/api/whitelist/disable

Parameters

secret = <DAEMON API SECRET>

Returns

{
"result": {
"standard_result": {...},
},
"status": "success"
}

Enable Daemon hooks

/api/hooks/enable

Parameters

secret = <DAEMON API SECRET>

Returns

{
"result": {
"standard_result": {...},
},
"status": "success"
}

Disable Daemon hooks

/api/hooks/disable

Parameters

secret = <DAEMON API SECRET>

Returns

{
"result": {
"standard_result": {...},
},
"status": "success"
}

Enable notifications

/api/notifications/enable

Parameters

secret = <DAEMON API SECRET>

Returns

{
"result": {
"standard_result": {...},
},
"status": "success"
}

Disable notifications

/api/notifications/disable

Parameters

secret = <DAEMON API SECRET>

Returns

{
"result": {
"standard_result": {...},
},
"status": "success"
}

Change logs settings

/api/logs/settings

Parameters

secret = <DAEMON API SECRET>
verbosity = <INTEGER FROM RANGE '0' TO '4'> # Optional if size present
size = <INTEGER FROM RANGE '10' TO '1000'> # Optional if verbosity present

Returns

{
"result": {
"standard_result": {...},
},
"status": "success"
}

Usage example

This section showcases how simply the HTTP Daemon API can be used to issue commands to Husarnet Daemon.

Curl example

Following usage of curl would result in joining a Husarnet network using a given joincode.

curl -d 'secret=wgjeznpzbpddnfujdvylczfguwksvlua&code=<JOINCODE>&hostname=test_device'  -X POST 127.0.0.1:16216/api/join

Which can be seen as a partial equivalent to running a following Husarnet CLI command:

husarnet join <JOINCODE> test_device

This is a partial equivalent since Husarnet CLI waits for joining to be successful (i.e. waits for base server and websetup connectivity to be established), while the API request simply registers a join request. However this waiting happens on the CLI side and in terms of Daemon actions those two calls are synonymous.

Python example

Example of API usage with Python.

import requests


url = 'http://127.0.0.1:16216/api/join'
data = {'secret': 'wgjeznpzbpddnfujdvylczfguwksvlua', 'code': '<JOINCODE>', 'hostname': 'test_device'}
response = requests.post(url, data=data)


print(response.text)

Golang example

Example of API usage with Golang.

package main


import (
"fmt"
"net/http"
"net/url"
)


func main() {
apiUrl := "http://127.0.0.1:16216/api/join"
data := url.Values{"secret": {"wgjeznpzbpddnfujdvylczfguwksvlua"}, "code": {"<JOINCODE>"}, "hostname": {"test_device"}}
resp, err := http.PostForm(apiUrl, data)
if err != nil {
panic(err)
}
defer resp.Body.Close()
fmt.Println(resp.Status)
}

C++ example

Example of API usage with C++.

#include <iostream>
#include <sstream>
#include <fstream>
#include <iomanip>
#include <memory>
#include <cstring>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <unistd.h>


using namespace std;


const string API_URL = "127.0.0.1";
const string SECRET = "secret=wgjeznpzbpddnfujdvylczfguwksvlua";
const string CODE = "code=<JOINCODE>";
const string HOSTNAME = "hostname=test_device";


string http_post_request(const string& url, const string& data) {
// Create a TCP socket
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0) {
cerr << "Error: Failed to create socket" << endl;
return "";
}


// Get the IP address of the server
struct hostent* server = gethostbyname(url.c_str());
if (server == NULL) {
cerr << "Error: No such host " << url << endl;
return "";
}


// Create a socket address structure
struct sockaddr_in serverAddr;
memset(&serverAddr, 0, sizeof(serverAddr));
serverAddr.sin_family = AF_INET;
serverAddr.sin_port = htons(16216);
memcpy(&serverAddr.sin_addr.s_addr, server->h_addr, server->h_length);


// Connect to the server
if (connect(sockfd, (struct sockaddr*) &serverAddr, sizeof(serverAddr)) < 0) {
cerr << "Error: Failed to connect to server" << endl;
close(sockfd);
return "";
}


// Send the HTTP POST request
ostringstream request;
request << "POST /api/join" << " HTTP/1.1\r\n";
request << "Host: " << url << "\r\n";
request << "Content-Type: application/x-www-form-urlencoded\r\n";
request << "Content-Length: " << data.size() << "\r\n";
request << "Connection: close\r\n";
request << "\r\n";
request << data;


string httpRequest = request.str();
if (write(sockfd, httpRequest.c_str(), httpRequest.size()) < 0) {
cerr << "Error: Failed to send HTTP request" << endl;
close(sockfd);
return "";
}


// Read the response from the server
const int bufferSize = 4096;
unique_ptr<char[]> buffer(new char[bufferSize]);
ostringstream response;
ssize_t bytesRead;
while ((bytesRead = read(sockfd, buffer.get(), bufferSize)) > 0) {
response.write(buffer.get(), bytesRead);
}


// Close the socket
close(sockfd);


return response.str();
}


int main() {
string postData = SECRET + "&" + CODE + "&" + HOSTNAME;
string response = http_post_request(API_URL, postData);
cout << "HTTP Response: " << response << endl;


return 0;
}