]> git.basschouten.com Git - openhab-addons.git/blob
dc7d881603ac643aa4cd9e42e15f610e239e478c
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2024 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
7  * This program and the accompanying materials are made available under the
8  * terms of the Eclipse Public License 2.0 which is available at
9  * http://www.eclipse.org/legal/epl-2.0
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.mikrotik.internal.model;
14
15 import java.util.ArrayList;
16 import java.util.HashMap;
17 import java.util.HashSet;
18 import java.util.List;
19 import java.util.Map;
20 import java.util.Optional;
21 import java.util.Set;
22
23 import javax.net.SocketFactory;
24
25 import org.eclipse.jdt.annotation.NonNullByDefault;
26 import org.eclipse.jdt.annotation.Nullable;
27 import org.slf4j.Logger;
28 import org.slf4j.LoggerFactory;
29
30 import me.legrange.mikrotik.ApiConnection;
31 import me.legrange.mikrotik.ApiConnectionException;
32 import me.legrange.mikrotik.MikrotikApiException;
33
34 /**
35  * The {@link RouterosDevice} class is wrapped inside a bridge thing and responsible for communication with
36  * Mikrotik device, data fetching, caching and aggregation.
37  *
38  * @author Oleg Vivtash - Initial contribution
39  */
40 @NonNullByDefault
41 public class RouterosDevice {
42     private final Logger logger = LoggerFactory.getLogger(RouterosDevice.class);
43
44     private final String host;
45     private final int port;
46     private final int connectionTimeout;
47     private final String login;
48     private final String password;
49     private @Nullable ApiConnection connection;
50
51     public static final String PROP_ID_KEY = ".id";
52     public static final String PROP_TYPE_KEY = "type";
53     public static final String PROP_NAME_KEY = "name";
54     public static final String PROP_SSID_KEY = "ssid";
55
56     private static final String CMD_PRINT_IFACES = "/interface/print";
57     private static final String CMD_PRINT_IFACE_TYPE_TPL = "/interface/%s/print";
58     private static final String CMD_MONTOR_IFACE_MONITOR_TPL = "/interface/%s/monitor numbers=%s once";
59     private static final String CMD_PRINT_CAPS_IFACES = "/caps-man/interface/print";
60     private static final String CMD_PRINT_CAPSMAN_REGS = "/caps-man/registration-table/print";
61     private static final String CMD_PRINT_WIRELESS_REGS = "/interface/wireless/registration-table/print";
62     private static final String CMD_PRINT_RESOURCE = "/system/resource/print";
63     private static final String CMD_PRINT_RB_INFO = "/system/routerboard/print";
64
65     private final List<RouterosInterfaceBase> interfaceCache = new ArrayList<>();
66     private final List<RouterosCapsmanRegistration> capsmanRegistrationCache = new ArrayList<>();
67     private final List<RouterosWirelessRegistration> wirelessRegistrationCache = new ArrayList<>();
68     private final Set<String> monitoredInterfaces = new HashSet<>();
69     private final Map<String, String> wlanSsid = new HashMap<>();
70
71     private @Nullable RouterosSystemResources resourcesCache;
72     private @Nullable RouterosRouterboardInfo rbInfo;
73
74     private static Optional<RouterosInterfaceBase> createTypedInterface(Map<String, String> interfaceProps) {
75         RouterosInterfaceType ifaceType = RouterosInterfaceType.resolve(interfaceProps.get(PROP_TYPE_KEY));
76         if (ifaceType == null) {
77             return Optional.empty();
78         }
79         switch (ifaceType) {
80             case ETHERNET:
81                 return Optional.of(new RouterosEthernetInterface(interfaceProps));
82             case BRIDGE:
83                 return Optional.of(new RouterosBridgeInterface(interfaceProps));
84             case CAP:
85                 return Optional.of(new RouterosCapInterface(interfaceProps));
86             case WLAN:
87                 return Optional.of(new RouterosWlanInterface(interfaceProps));
88             case PPPOE_CLIENT:
89                 return Optional.of(new RouterosPPPoECliInterface(interfaceProps));
90             case PPP_CLIENT:
91                 return Optional.of(new RouterosPPPCliInterface(interfaceProps));
92             case L2TP_SERVER:
93                 return Optional.of(new RouterosL2TPSrvInterface(interfaceProps));
94             case L2TP_CLIENT:
95                 return Optional.of(new RouterosL2TPCliInterface(interfaceProps));
96             case LTE:
97                 return Optional.of(new RouterosLTEInterface(interfaceProps));
98             default:
99                 return Optional.empty();
100         }
101     }
102
103     public RouterosDevice(String host, int port, String login, String password) {
104         this.host = host;
105         this.port = port;
106         this.login = login;
107         this.password = password;
108         this.connectionTimeout = ApiConnection.DEFAULT_CONNECTION_TIMEOUT;
109     }
110
111     public boolean isConnected() {
112         ApiConnection conn = this.connection;
113         return conn != null && conn.isConnected();
114     }
115
116     public void start() throws MikrotikApiException {
117         login();
118         updateRouterboardInfo();
119     }
120
121     public void stop() {
122         ApiConnection conn = this.connection;
123         if (conn != null && conn.isConnected()) {
124             logout();
125         }
126     }
127
128     public void login() throws MikrotikApiException {
129         logger.debug("Attempting login to {} ...", host);
130         ApiConnection conn = ApiConnection.connect(SocketFactory.getDefault(), host, port, connectionTimeout);
131         conn.login(login, password);
132         logger.debug("Logged in to RouterOS at {} !", host);
133         this.connection = conn;
134     }
135
136     public void logout() {
137         ApiConnection conn = this.connection;
138         logger.debug("Logging out of {}", host);
139         if (conn != null) {
140             logger.debug("Closing connection to {}", host);
141             try {
142                 conn.close();
143             } catch (ApiConnectionException e) {
144                 logger.debug("Logout error", e);
145             } finally {
146                 this.connection = null;
147             }
148         }
149     }
150
151     public boolean registerForMonitoring(String interfaceName) {
152         return monitoredInterfaces.add(interfaceName);
153     }
154
155     public boolean unregisterForMonitoring(String interfaceName) {
156         return monitoredInterfaces.remove(interfaceName);
157     }
158
159     public void refresh() throws MikrotikApiException {
160         synchronized (this) {
161             updateResources();
162             updateInterfaceData();
163             updateCapsmanRegistrations();
164             updateWirelessRegistrations();
165         }
166     }
167
168     public @Nullable RouterosRouterboardInfo getRouterboardInfo() {
169         return rbInfo;
170     }
171
172     public @Nullable RouterosSystemResources getSysResources() {
173         return resourcesCache;
174     }
175
176     public @Nullable RouterosCapsmanRegistration findCapsmanRegistration(String macAddress) {
177         Optional<RouterosCapsmanRegistration> searchResult = capsmanRegistrationCache.stream()
178                 .filter(registration -> macAddress.equalsIgnoreCase(registration.getMacAddress())).findFirst();
179         return searchResult.orElse(null);
180     }
181
182     public @Nullable RouterosWirelessRegistration findWirelessRegistration(String macAddress) {
183         Optional<RouterosWirelessRegistration> searchResult = wirelessRegistrationCache.stream()
184                 .filter(registration -> macAddress.equalsIgnoreCase(registration.getMacAddress())).findFirst();
185         return searchResult.orElse(null);
186     }
187
188     @SuppressWarnings("null")
189     public @Nullable RouterosInterfaceBase findInterface(String name) {
190         Optional<RouterosInterfaceBase> searchResult = interfaceCache.stream()
191                 .filter(iface -> iface.getName() != null && iface.getName().equalsIgnoreCase(name)).findFirst();
192         return searchResult.orElse(null);
193     }
194
195     @SuppressWarnings("null")
196     private void updateInterfaceData() throws MikrotikApiException {
197         ApiConnection conn = this.connection;
198         if (conn == null) {
199             return;
200         }
201
202         List<Map<String, String>> ifaceResponse = conn.execute(CMD_PRINT_IFACES);
203
204         Set<String> interfaceTypesToPoll = new HashSet<>();
205         this.wlanSsid.clear();
206         this.interfaceCache.clear();
207         ifaceResponse.forEach(props -> {
208             Optional<RouterosInterfaceBase> ifaceOpt = createTypedInterface(props);
209             if (ifaceOpt.isPresent()) {
210                 RouterosInterfaceBase iface = ifaceOpt.get();
211                 if (iface.hasDetailedReport()) {
212                     interfaceTypesToPoll.add(iface.getApiType());
213                 }
214                 this.interfaceCache.add(iface);
215             }
216         });
217
218         Map<String, Map<String, String>> typedIfaceResponse = new HashMap<>();
219         for (String ifaceApiType : interfaceTypesToPoll) {
220             String cmd = String.format(CMD_PRINT_IFACE_TYPE_TPL, ifaceApiType);
221             if (ifaceApiType.compareTo("cap") == 0) {
222                 cmd = CMD_PRINT_CAPS_IFACES;
223             }
224             connection.execute(cmd).forEach(propMap -> {
225                 String ifaceName = propMap.get(PROP_NAME_KEY);
226                 if (ifaceName != null) {
227                     if (typedIfaceResponse.containsKey(ifaceName)) {
228                         typedIfaceResponse.get(ifaceName).putAll(propMap);
229                     } else {
230                         typedIfaceResponse.put(ifaceName, propMap);
231                     }
232                 }
233             });
234         }
235
236         for (RouterosInterfaceBase ifaceModel : interfaceCache) {
237             // Enrich with detailed data
238
239             Map<String, String> additionalIfaceProps = typedIfaceResponse.get(ifaceModel.getName());
240             if (additionalIfaceProps != null) {
241                 ifaceModel.mergeProps(additionalIfaceProps);
242             }
243             // Get monitor data
244             if (ifaceModel.hasMonitor() && monitoredInterfaces.contains(ifaceModel.getName())) {
245                 String cmd = String.format(CMD_MONTOR_IFACE_MONITOR_TPL, ifaceModel.getApiType(), ifaceModel.getName());
246                 List<Map<String, String>> monitorProps = connection.execute(cmd);
247                 ifaceModel.mergeProps(monitorProps.get(0));
248             }
249             // Note SSIDs for non-CAPsMAN wireless clients
250             String ifaceName = ifaceModel.getName();
251             String ifaceSsid = ifaceModel.getProperty(PROP_SSID_KEY);
252             if (ifaceName != null && ifaceSsid != null && !ifaceName.isBlank() && !ifaceSsid.isBlank()) {
253                 this.wlanSsid.put(ifaceName, ifaceSsid);
254             }
255         }
256     }
257
258     private void updateCapsmanRegistrations() throws MikrotikApiException {
259         ApiConnection conn = this.connection;
260         if (conn == null) {
261             return;
262         }
263         List<Map<String, String>> response = conn.execute(CMD_PRINT_CAPSMAN_REGS);
264         if (response != null) {
265             capsmanRegistrationCache.clear();
266             response.forEach(reg -> capsmanRegistrationCache.add(new RouterosCapsmanRegistration(reg)));
267         }
268     }
269
270     private void updateWirelessRegistrations() throws MikrotikApiException {
271         ApiConnection conn = this.connection;
272         if (conn == null) {
273             return;
274         }
275         List<Map<String, String>> response = conn.execute(CMD_PRINT_WIRELESS_REGS);
276         wirelessRegistrationCache.clear();
277         response.forEach(props -> {
278             String wlanIfaceName = props.get("interface");
279             String wlanSsidName = wlanSsid.get(wlanIfaceName);
280
281             if (wlanSsidName != null && wlanIfaceName != null && !wlanIfaceName.isBlank() && !wlanSsidName.isBlank()) {
282                 props.put(PROP_SSID_KEY, wlanSsidName);
283             }
284             wirelessRegistrationCache.add(new RouterosWirelessRegistration(props));
285         });
286     }
287
288     private void updateResources() throws MikrotikApiException {
289         ApiConnection conn = this.connection;
290         if (conn == null) {
291             return;
292         }
293         List<Map<String, String>> response = conn.execute(CMD_PRINT_RESOURCE);
294         this.resourcesCache = new RouterosSystemResources(response.get(0));
295     }
296
297     private void updateRouterboardInfo() throws MikrotikApiException {
298         ApiConnection conn = this.connection;
299         if (conn == null) {
300             return;
301         }
302         List<Map<String, String>> response = conn.execute(CMD_PRINT_RB_INFO);
303         this.rbInfo = new RouterosRouterboardInfo(response.get(0));
304     }
305 }