Skip to content

Commit 6079d4d

Browse files
committed
feat: define client manager interface for wireguard
1 parent 90594b2 commit 6079d4d

File tree

1 file changed

+132
-0
lines changed

1 file changed

+132
-0
lines changed
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
import { AddClientResponse, RemoveClientResponse } from '../types/responses';
2+
import { PeerClient } from "../types/peerClient";
3+
import { SSHClient } from "./MikrotikSSHClient";
4+
import {
5+
CLIENT_ADD_FAILED,
6+
CLIENT_ADD_SUCCESS,
7+
CLIENT_REMOVE_FAILED,
8+
CLIENT_REMOVE_SUCCESS,
9+
LIST_CLIENTS_ERROR, NO_CLIENTS_FOUND,
10+
PARSE_LISTEN_PORT_ERROR,
11+
PARSE_PUBLIC_KEY_ERROR,
12+
WIREGUARD_COMMANDS,
13+
WIREGUARD_FIELDS
14+
} from "../config/constants";
15+
16+
17+
export class MikroTikWireGuardClientManager {
18+
private sshClient: SSHClient;
19+
20+
constructor(sshClient: SSHClient) {
21+
this.sshClient = sshClient;
22+
}
23+
24+
public async addClient(publicKey: string, comment: string): Promise<AddClientResponse> {
25+
try {
26+
const clientAddress = await this.getNextAvailableIp();
27+
const command = WIREGUARD_COMMANDS.ADD_PEER(comment, clientAddress, publicKey);
28+
await this.sshClient.executeCommand(command);
29+
30+
const wireGuardInfo = await this.getWireGuardInterfaceInfo();
31+
return {
32+
success: true,
33+
message: CLIENT_ADD_SUCCESS,
34+
config: {
35+
clientAddress,
36+
routerPublicKey: wireGuardInfo.publicKey,
37+
listenPort: wireGuardInfo.listenPort
38+
}
39+
};
40+
} catch (error) {
41+
return {success: false, message: CLIENT_ADD_FAILED};
42+
}
43+
}
44+
45+
public async removeClient(publicKey: string): Promise<RemoveClientResponse> {
46+
try {
47+
const command = WIREGUARD_COMMANDS.REMOVE_PEER(publicKey);
48+
await this.sshClient.executeCommand(command);
49+
return {success: true, message: CLIENT_REMOVE_SUCCESS};
50+
} catch (error) {
51+
return {success: false, message: CLIENT_REMOVE_FAILED};
52+
}
53+
}
54+
55+
public async listClients(): Promise<PeerClient[]> {
56+
try {
57+
const command = WIREGUARD_COMMANDS.LIST_PEERS;
58+
const output = await this.sshClient.executeCommand(command);
59+
60+
return this.parseWireGuardPeers(output);
61+
} catch (error) {
62+
throw new Error(LIST_CLIENTS_ERROR);
63+
}
64+
}
65+
66+
private parseWireGuardPeers(output: string): PeerClient[] {
67+
const clients: PeerClient[] = [];
68+
const lines = output.split('\n');
69+
let currentComment = '';
70+
lines.forEach((line) => {
71+
const trimmedLine = line.trim();
72+
if (trimmedLine.startsWith(';;;')) {
73+
currentComment = trimmedLine.slice(4).trim();
74+
} else if (trimmedLine.length > 0) {
75+
const parts = trimmedLine.split(/\s+/);
76+
if (parts.length >= 4) {
77+
clients.push({
78+
index: parts[0],
79+
interface: parts[1],
80+
publicKey: parts[2],
81+
allowedIp: parts[4],
82+
comment: currentComment,
83+
});
84+
}
85+
}
86+
});
87+
return clients;
88+
}
89+
90+
private async getWireGuardInterfaceInfo(): Promise<{ publicKey: string; listenPort: number }> {
91+
const command = WIREGUARD_COMMANDS.GET_INTERFACE_INFO;
92+
const output = await this.sshClient.executeCommand(command);
93+
const lines = output.split('\n');
94+
let publicKey = '';
95+
let listenPort = 0;
96+
97+
lines.forEach(line => {
98+
const trimmedLine = line.trim();
99+
if (trimmedLine.includes(WIREGUARD_FIELDS.PUBLIC_KEY)) {
100+
publicKey = trimmedLine.split(WIREGUARD_FIELDS.PUBLIC_KEY)[1].replace(/"/g, '');
101+
}
102+
if (trimmedLine.includes(WIREGUARD_FIELDS.LISTEN_PORT)) {
103+
listenPort = parseInt(trimmedLine.split(WIREGUARD_FIELDS.LISTEN_PORT)[1], 10);
104+
}
105+
});
106+
107+
if (!publicKey) {
108+
throw new Error(PARSE_PUBLIC_KEY_ERROR);
109+
}
110+
if (!listenPort) {
111+
throw new Error(PARSE_LISTEN_PORT_ERROR);
112+
}
113+
114+
return {publicKey, listenPort};
115+
}
116+
117+
private async getNextAvailableIp(): Promise<string> {
118+
const clients = await this.listClients();
119+
const validClients = clients.filter(client => client.allowedIp.includes('/'));
120+
if (validClients.length === 0) {
121+
throw new Error(NO_CLIENTS_FOUND);
122+
}
123+
124+
const firstClientIp = validClients[0].allowedIp.split('/')[0];
125+
const subnet = validClients[0].allowedIp.split('/')[1];
126+
const baseIp = firstClientIp.split('.').slice(0, 3).join('.');
127+
const lastOctets = validClients.map(client => parseInt(client.allowedIp.split('.')[3])).sort((a, b) => a - b);
128+
const nextOctet = (lastOctets[lastOctets.length - 1] || 1) + 1;
129+
130+
return `${baseIp}.${nextOctet}/${subnet}`;
131+
}
132+
}

0 commit comments

Comments
 (0)