blob: b10b0b0f13319a64ff8e3a7622b2347614b851e0 [file] [log] [blame]
Garrick Evans08843932019-09-17 14:41:08 +09001// Copyright 2016 The Chromium OS Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
Jason Jeremy Imanadffbcb2020-08-31 13:21:36 +09005#include "patchpanel/dbus/client.h"
Garrick Evans08843932019-09-17 14:41:08 +09006
Hugo Benichicc6850f2020-01-17 13:26:06 +09007#include <fcntl.h>
8
Jie Jiang0a70acf2020-10-02 11:57:32 +09009#include <base/bind.h>
Garrick Evans08843932019-09-17 14:41:08 +090010#include <base/logging.h>
11#include <chromeos/dbus/service_constants.h>
12#include <dbus/message.h>
13#include <dbus/object_path.h>
14
Garrick Evans3388a032020-03-24 11:25:55 +090015#include "patchpanel/net_util.h"
Hugo Benichicc6850f2020-01-17 13:26:06 +090016
Garrick Evans08843932019-09-17 14:41:08 +090017namespace patchpanel {
18
Jason Jeremy Iman6f1f3e72020-07-06 13:04:03 +090019namespace {
20
21std::ostream& operator<<(std::ostream& stream,
22 const ModifyPortRuleRequest& request) {
23 stream << "{ operation: "
24 << ModifyPortRuleRequest::Operation_Name(request.op())
25 << ", rule type: "
26 << ModifyPortRuleRequest::RuleType_Name(request.type())
27 << ", protocol: "
28 << ModifyPortRuleRequest::Protocol_Name(request.proto());
29 if (!request.input_ifname().empty()) {
30 stream << ", input interface name: " << request.input_ifname();
31 }
32 if (!request.input_dst_ip().empty()) {
33 stream << ", input destination IP: " << request.input_dst_ip();
34 }
35 stream << ", input destination port: " << request.input_dst_port();
36 if (!request.dst_ip().empty()) {
37 stream << ", destination IP: " << request.dst_ip();
38 }
39 if (request.dst_port() != 0) {
40 stream << ", destination port: " << request.dst_port();
41 }
42 stream << " }";
43 return stream;
44}
45
Jie Jiang0a70acf2020-10-02 11:57:32 +090046void OnGetTrafficCountersDBusResponse(
47 Client::GetTrafficCountersCallback callback,
48 dbus::Response* dbus_response) {
49 if (!dbus_response) {
50 LOG(ERROR) << "Failed to send TrafficCountersRequest message to patchpanel "
51 "service";
52 std::move(callback).Run({});
53 return;
54 }
55
56 TrafficCountersResponse response;
57 dbus::MessageReader reader(dbus_response);
58 if (!reader.PopArrayOfBytesAsProto(&response)) {
59 LOG(ERROR) << "Failed to parse TrafficCountersResponse proto";
60 std::move(callback).Run({});
61 return;
62 }
63
64 std::move(callback).Run(
65 {response.counters().begin(), response.counters().end()});
66}
67
Jie Jiange2e4c0b2020-09-16 18:48:43 +090068void OnNeighborConnectedStateChangedSignal(
69 const Client::NeighborConnectedStateChangedHandler& handler,
70 dbus::Signal* signal) {
71 dbus::MessageReader reader(signal);
72 NeighborConnectedStateChangedSignal proto;
73 if (!reader.PopArrayOfBytesAsProto(&proto)) {
74 LOG(ERROR) << "Failed to parse NeighborConnectedStateChangedSignal proto";
75 return;
76 }
77
78 handler.Run(proto);
79}
80
81void OnSignalConnectedCallback(const std::string& interface_name,
82 const std::string& signal_name,
83 bool success) {
84 if (!success)
85 LOG(ERROR) << "Failed to connect to " << signal_name;
86}
87
Jason Jeremy Iman6f1f3e72020-07-06 13:04:03 +090088} // namespace
89
Garrick Evans08843932019-09-17 14:41:08 +090090// static
91std::unique_ptr<Client> Client::New() {
92 dbus::Bus::Options opts;
93 opts.bus_type = dbus::Bus::SYSTEM;
94 scoped_refptr<dbus::Bus> bus(new dbus::Bus(std::move(opts)));
95
96 if (!bus->Connect()) {
97 LOG(ERROR) << "Failed to connect to system bus";
98 return nullptr;
99 }
100
101 dbus::ObjectProxy* proxy = bus->GetObjectProxy(
102 kPatchPanelServiceName, dbus::ObjectPath(kPatchPanelServicePath));
103 if (!proxy) {
104 LOG(ERROR) << "Unable to get dbus proxy for " << kPatchPanelServiceName;
105 return nullptr;
106 }
107
Garrick Evans93a83fc2020-03-31 15:16:55 +0900108 return std::make_unique<Client>(std::move(bus), proxy);
109}
110
111Client::~Client() {
112 if (bus_)
113 bus_->ShutdownAndBlock();
Garrick Evans08843932019-09-17 14:41:08 +0900114}
115
116bool Client::NotifyArcStartup(pid_t pid) {
117 dbus::MethodCall method_call(kPatchPanelInterface, kArcStartupMethod);
118 dbus::MessageWriter writer(&method_call);
119
120 ArcStartupRequest request;
121 request.set_pid(static_cast<uint32_t>(pid));
122
123 if (!writer.AppendProtoAsArrayOfBytes(request)) {
124 LOG(ERROR) << "Failed to encode ArcStartupRequest proto";
125 return false;
126 }
127
128 std::unique_ptr<dbus::Response> dbus_response = proxy_->CallMethodAndBlock(
129 &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT);
130 if (!dbus_response) {
131 LOG(ERROR) << "Failed to send dbus message to patchpanel service";
132 return false;
133 }
134
135 dbus::MessageReader reader(dbus_response.get());
136 ArcStartupResponse response;
137 if (!reader.PopArrayOfBytesAsProto(&response)) {
138 LOG(ERROR) << "Failed to parse response proto";
139 return false;
140 }
141
142 return true;
143}
144
Garrick Evansca2b41b2019-12-02 09:06:11 +0900145bool Client::NotifyArcShutdown() {
Garrick Evans08843932019-09-17 14:41:08 +0900146 dbus::MethodCall method_call(kPatchPanelInterface, kArcShutdownMethod);
147 dbus::MessageWriter writer(&method_call);
148
149 ArcShutdownRequest request;
Garrick Evans08843932019-09-17 14:41:08 +0900150 if (!writer.AppendProtoAsArrayOfBytes(request)) {
151 LOG(ERROR) << "Failed to encode ArcShutdownRequest proto";
152 return false;
153 }
154
155 std::unique_ptr<dbus::Response> dbus_response = proxy_->CallMethodAndBlock(
156 &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT);
157 if (!dbus_response) {
158 LOG(ERROR) << "Failed to send dbus message to patchpanel service";
159 return false;
160 }
161
162 dbus::MessageReader reader(dbus_response.get());
163 ArcShutdownResponse response;
164 if (!reader.PopArrayOfBytesAsProto(&response)) {
165 LOG(ERROR) << "Failed to parse response proto";
166 return false;
167 }
168
169 return true;
170}
171
Garrick Evans3388a032020-03-24 11:25:55 +0900172std::vector<NetworkDevice> Client::NotifyArcVmStartup(uint32_t cid) {
Garrick Evans08843932019-09-17 14:41:08 +0900173 dbus::MethodCall method_call(kPatchPanelInterface, kArcVmStartupMethod);
174 dbus::MessageWriter writer(&method_call);
175
176 ArcVmStartupRequest request;
Garrick Evans0a189372020-02-07 08:55:27 +0900177 request.set_cid(cid);
Garrick Evans08843932019-09-17 14:41:08 +0900178
179 if (!writer.AppendProtoAsArrayOfBytes(request)) {
180 LOG(ERROR) << "Failed to encode ArcVmStartupRequest proto";
181 return {};
182 }
183
184 std::unique_ptr<dbus::Response> dbus_response = proxy_->CallMethodAndBlock(
185 &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT);
186 if (!dbus_response) {
187 LOG(ERROR) << "Failed to send dbus message to patchpanel service";
188 return {};
189 }
190
191 dbus::MessageReader reader(dbus_response.get());
192 ArcVmStartupResponse response;
193 if (!reader.PopArrayOfBytesAsProto(&response)) {
194 LOG(ERROR) << "Failed to parse response proto";
195 return {};
196 }
197
Garrick Evans3388a032020-03-24 11:25:55 +0900198 std::vector<NetworkDevice> devices;
Garrick Evans08843932019-09-17 14:41:08 +0900199 for (const auto& d : response.devices()) {
200 devices.emplace_back(d);
201 }
202 return devices;
203}
204
Garrick Evans0a189372020-02-07 08:55:27 +0900205bool Client::NotifyArcVmShutdown(uint32_t cid) {
Garrick Evans08843932019-09-17 14:41:08 +0900206 dbus::MethodCall method_call(kPatchPanelInterface, kArcVmShutdownMethod);
207 dbus::MessageWriter writer(&method_call);
208
209 ArcVmShutdownRequest request;
Garrick Evans0a189372020-02-07 08:55:27 +0900210 request.set_cid(cid);
Garrick Evans08843932019-09-17 14:41:08 +0900211
212 if (!writer.AppendProtoAsArrayOfBytes(request)) {
213 LOG(ERROR) << "Failed to encode ArcVmShutdownRequest proto";
214 return false;
215 }
216
217 std::unique_ptr<dbus::Response> dbus_response = proxy_->CallMethodAndBlock(
218 &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT);
219 if (!dbus_response) {
220 LOG(ERROR) << "Failed to send dbus message to patchpanel service";
221 return false;
222 }
223
224 dbus::MessageReader reader(dbus_response.get());
225 ArcVmShutdownResponse response;
226 if (!reader.PopArrayOfBytesAsProto(&response)) {
227 LOG(ERROR) << "Failed to parse response proto";
228 return false;
229 }
230
231 return true;
232}
233
Garrick Evans0a189372020-02-07 08:55:27 +0900234bool Client::NotifyTerminaVmStartup(uint32_t cid,
Garrick Evans3388a032020-03-24 11:25:55 +0900235 NetworkDevice* device,
236 IPv4Subnet* container_subnet) {
Garrick Evans27b74032019-11-19 13:33:47 +0900237 dbus::MethodCall method_call(kPatchPanelInterface, kTerminaVmStartupMethod);
238 dbus::MessageWriter writer(&method_call);
239
240 TerminaVmStartupRequest request;
Garrick Evans0a189372020-02-07 08:55:27 +0900241 request.set_cid(cid);
Garrick Evans27b74032019-11-19 13:33:47 +0900242
243 if (!writer.AppendProtoAsArrayOfBytes(request)) {
244 LOG(ERROR) << "Failed to encode TerminaVmStartupRequest proto";
245 return false;
246 }
247
248 std::unique_ptr<dbus::Response> dbus_response = proxy_->CallMethodAndBlock(
249 &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT);
250 if (!dbus_response) {
251 LOG(ERROR) << "Failed to send dbus message to patchpanel service";
252 return false;
253 }
254
255 dbus::MessageReader reader(dbus_response.get());
256 TerminaVmStartupResponse response;
257 if (!reader.PopArrayOfBytesAsProto(&response)) {
258 LOG(ERROR) << "Failed to parse response proto";
259 return false;
260 }
261
262 if (!response.has_device()) {
263 LOG(ERROR) << "No device found";
264 return false;
265 }
266 *device = response.device();
267
268 if (response.has_container_subnet()) {
269 *container_subnet = response.container_subnet();
270 } else {
271 LOG(WARNING) << "No container subnet found";
272 }
273
274 return true;
275}
276
Garrick Evans0a189372020-02-07 08:55:27 +0900277bool Client::NotifyTerminaVmShutdown(uint32_t cid) {
Garrick Evans27b74032019-11-19 13:33:47 +0900278 dbus::MethodCall method_call(kPatchPanelInterface, kTerminaVmShutdownMethod);
279 dbus::MessageWriter writer(&method_call);
280
281 TerminaVmShutdownRequest request;
Garrick Evans0a189372020-02-07 08:55:27 +0900282 request.set_cid(cid);
Garrick Evans27b74032019-11-19 13:33:47 +0900283
284 if (!writer.AppendProtoAsArrayOfBytes(request)) {
285 LOG(ERROR) << "Failed to encode TerminaVmShutdownRequest proto";
286 return false;
287 }
288
289 std::unique_ptr<dbus::Response> dbus_response = proxy_->CallMethodAndBlock(
290 &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT);
291 if (!dbus_response) {
292 LOG(ERROR) << "Failed to send dbus message to patchpanel service";
293 return false;
294 }
295
296 dbus::MessageReader reader(dbus_response.get());
297 TerminaVmShutdownResponse response;
298 if (!reader.PopArrayOfBytesAsProto(&response)) {
299 LOG(ERROR) << "Failed to parse response proto";
300 return false;
301 }
302
303 return true;
304}
305
Garrick Evans376f0672020-01-07 15:31:50 +0900306bool Client::NotifyPluginVmStartup(uint64_t vm_id,
307 int subnet_index,
Garrick Evans3388a032020-03-24 11:25:55 +0900308 NetworkDevice* device) {
Garrick Evans376f0672020-01-07 15:31:50 +0900309 dbus::MethodCall method_call(kPatchPanelInterface, kPluginVmStartupMethod);
310 dbus::MessageWriter writer(&method_call);
311
312 PluginVmStartupRequest request;
313 request.set_id(vm_id);
314 request.set_subnet_index(subnet_index);
315
316 if (!writer.AppendProtoAsArrayOfBytes(request)) {
317 LOG(ERROR) << "Failed to encode PluginVmStartupRequest proto";
318 return false;
319 }
320
321 std::unique_ptr<dbus::Response> dbus_response = proxy_->CallMethodAndBlock(
322 &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT);
323 if (!dbus_response) {
324 LOG(ERROR) << "Failed to send dbus message to patchpanel service";
325 return false;
326 }
327
328 dbus::MessageReader reader(dbus_response.get());
329 PluginVmStartupResponse response;
330 if (!reader.PopArrayOfBytesAsProto(&response)) {
331 LOG(ERROR) << "Failed to parse response proto";
332 return false;
333 }
334
335 if (!response.has_device()) {
336 LOG(ERROR) << "No device found";
337 return false;
338 }
339 *device = response.device();
340
341 return true;
342}
343
344bool Client::NotifyPluginVmShutdown(uint64_t vm_id) {
345 dbus::MethodCall method_call(kPatchPanelInterface, kPluginVmShutdownMethod);
346 dbus::MessageWriter writer(&method_call);
347
348 PluginVmShutdownRequest request;
349 request.set_id(vm_id);
350
351 if (!writer.AppendProtoAsArrayOfBytes(request)) {
352 LOG(ERROR) << "Failed to encode PluginVmShutdownRequest proto";
353 return false;
354 }
355
356 std::unique_ptr<dbus::Response> dbus_response = proxy_->CallMethodAndBlock(
357 &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT);
358 if (!dbus_response) {
359 LOG(ERROR) << "Failed to send dbus message to patchpanel service";
360 return false;
361 }
362
363 dbus::MessageReader reader(dbus_response.get());
Garrick Evans9751a1e2020-02-20 11:02:10 +0900364 PluginVmShutdownResponse response;
Garrick Evans376f0672020-01-07 15:31:50 +0900365 if (!reader.PopArrayOfBytesAsProto(&response)) {
366 LOG(ERROR) << "Failed to parse response proto";
367 return false;
368 }
369
370 return true;
371}
372
Hugo Benichi7d9d8db2020-03-30 15:56:56 +0900373bool Client::DefaultVpnRouting(int socket) {
374 return SendSetVpnIntentRequest(socket, SetVpnIntentRequest::DEFAULT_ROUTING);
375}
376
377bool Client::RouteOnVpn(int socket) {
378 return SendSetVpnIntentRequest(socket, SetVpnIntentRequest::ROUTE_ON_VPN);
379}
380
381bool Client::BypassVpn(int socket) {
382 return SendSetVpnIntentRequest(socket, SetVpnIntentRequest::BYPASS_VPN);
383}
384
385bool Client::SendSetVpnIntentRequest(
386 int socket, SetVpnIntentRequest::VpnRoutingPolicy policy) {
387 dbus::MethodCall method_call(kPatchPanelInterface, kSetVpnIntentMethod);
388 dbus::MessageWriter writer(&method_call);
389
390 SetVpnIntentRequest request;
391 SetVpnIntentResponse response;
392 request.set_policy(policy);
393
394 if (!writer.AppendProtoAsArrayOfBytes(request)) {
395 LOG(ERROR) << "Failed to encode SetVpnIntentRequest proto";
396 return false;
397 }
398 writer.AppendFileDescriptor(socket);
399
400 std::unique_ptr<dbus::Response> dbus_response = proxy_->CallMethodAndBlock(
401 &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT);
402 if (!dbus_response) {
403 LOG(ERROR)
404 << "Failed to send SetVpnIntentRequest message to patchpanel service";
405 return false;
406 }
407
408 dbus::MessageReader reader(dbus_response.get());
409 if (!reader.PopArrayOfBytesAsProto(&response)) {
410 LOG(ERROR) << "Failed to parse SetVpnIntentResponse proto";
411 return false;
412 }
413
414 if (!response.success()) {
415 LOG(ERROR) << "SetVpnIntentRequest failed";
416 return false;
417 }
418 return true;
419}
420
Hugo Benichicc6850f2020-01-17 13:26:06 +0900421std::pair<base::ScopedFD, patchpanel::ConnectNamespaceResponse>
422Client::ConnectNamespace(pid_t pid,
423 const std::string& outbound_ifname,
424 bool forward_user_traffic) {
425 // Prepare and serialize the request proto.
426 ConnectNamespaceRequest request;
427 request.set_pid(static_cast<int32_t>(pid));
428 request.set_outbound_physical_device(outbound_ifname);
429 request.set_allow_user_traffic(forward_user_traffic);
430
431 dbus::MethodCall method_call(kPatchPanelInterface, kConnectNamespaceMethod);
432 dbus::MessageWriter writer(&method_call);
433 if (!writer.AppendProtoAsArrayOfBytes(request)) {
434 LOG(ERROR) << "Failed to encode ConnectNamespaceRequest proto";
435 return {};
436 }
437
438 // Prepare an fd pair and append one fd directly after the serialized request.
439 int pipe_fds[2] = {-1, -1};
440 if (pipe2(pipe_fds, O_CLOEXEC) < 0) {
441 PLOG(ERROR) << "Failed to create a pair of fds with pipe2()";
442 return {};
443 }
444 base::ScopedFD fd_local(pipe_fds[0]);
445 // MessageWriter::AppendFileDescriptor duplicates the fd, so use ScopeFD to
446 // make sure the original fd is closed eventually.
447 base::ScopedFD fd_remote(pipe_fds[1]);
448 writer.AppendFileDescriptor(pipe_fds[1]);
449
450 std::unique_ptr<dbus::Response> dbus_response = proxy_->CallMethodAndBlock(
451 &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT);
452 if (!dbus_response) {
453 LOG(ERROR) << "Failed to send ConnectNamespace message to patchpanel";
454 return {};
455 }
456
457 dbus::MessageReader reader(dbus_response.get());
458 ConnectNamespaceResponse response;
459 if (!reader.PopArrayOfBytesAsProto(&response)) {
460 LOG(ERROR) << "Failed to parse ConnectNamespaceResponse proto";
461 return {};
462 }
463
Hugo Benichi2fd0c6e2020-04-17 16:12:05 +0900464 if (response.peer_ifname().empty() || response.host_ifname().empty()) {
Hugo Benichicc6850f2020-01-17 13:26:06 +0900465 LOG(ERROR) << "ConnectNamespace for netns pid " << pid << " failed";
466 return {};
467 }
468
Garrick Evans3388a032020-03-24 11:25:55 +0900469 std::string subnet_info = IPv4AddressToCidrString(
Hugo Benichicc6850f2020-01-17 13:26:06 +0900470 response.ipv4_subnet().base_addr(), response.ipv4_subnet().prefix_len());
471 LOG(INFO) << "ConnectNamespace for netns pid " << pid
Hugo Benichi2fd0c6e2020-04-17 16:12:05 +0900472 << " succeeded: peer_ifname=" << response.peer_ifname()
473 << " peer_ipv4_address="
474 << IPv4AddressToString(response.peer_ipv4_address())
475 << " host_ifname=" << response.host_ifname()
476 << " host_ipv4_address="
477 << IPv4AddressToString(response.host_ipv4_address())
Hugo Benichicc6850f2020-01-17 13:26:06 +0900478 << " subnet=" << subnet_info;
479
480 return std::make_pair(std::move(fd_local), std::move(response));
481}
482
Jie Jiang0a70acf2020-10-02 11:57:32 +0900483void Client::GetTrafficCounters(const std::set<std::string>& devices,
484 GetTrafficCountersCallback callback) {
Jie Jiange02d1202020-07-27 16:57:04 +0900485 dbus::MethodCall method_call(kPatchPanelInterface, kGetTrafficCountersMethod);
486 dbus::MessageWriter writer(&method_call);
487
488 TrafficCountersRequest request;
489 for (const auto& device : devices) {
490 request.add_devices(device);
491 }
492
493 if (!writer.AppendProtoAsArrayOfBytes(request)) {
494 LOG(ERROR) << "Failed to encode TrafficCountersRequest proto";
Jie Jiang0a70acf2020-10-02 11:57:32 +0900495 std::move(callback).Run({});
496 return;
Jie Jiange02d1202020-07-27 16:57:04 +0900497 }
498
Jie Jiang0a70acf2020-10-02 11:57:32 +0900499 proxy_->CallMethod(
500 &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
501 base::BindOnce(&OnGetTrafficCountersDBusResponse, std::move(callback)));
Jie Jiange02d1202020-07-27 16:57:04 +0900502}
503
Jason Jeremy Iman6f1f3e72020-07-06 13:04:03 +0900504bool Client::ModifyPortRule(ModifyPortRuleRequest::Operation op,
505 ModifyPortRuleRequest::RuleType type,
506 ModifyPortRuleRequest::Protocol proto,
507 const std::string& input_ifname,
508 const std::string& input_dst_ip,
509 uint32_t input_dst_port,
510 const std::string& dst_ip,
511 uint32_t dst_port) {
512 dbus::MethodCall method_call(kPatchPanelInterface, kModifyPortRuleMethod);
513 dbus::MessageWriter writer(&method_call);
514
515 ModifyPortRuleRequest request;
516 ModifyPortRuleResponse response;
517
518 request.set_op(op);
519 request.set_type(type);
520 request.set_proto(proto);
521 request.set_input_ifname(input_ifname);
522 request.set_input_dst_ip(input_dst_ip);
523 request.set_input_dst_port(input_dst_port);
524 request.set_dst_ip(dst_ip);
525 request.set_dst_port(dst_port);
526
527 if (!writer.AppendProtoAsArrayOfBytes(request)) {
528 LOG(ERROR) << "Failed to encode ModifyPortRuleRequest proto " << request;
529 return false;
530 }
531
532 std::unique_ptr<dbus::Response> dbus_response = proxy_->CallMethodAndBlock(
533 &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT);
534 if (!dbus_response) {
535 LOG(ERROR)
536 << "Failed to send ModifyPortRuleRequest message to patchpanel service "
537 << request;
538 return false;
539 }
540
541 dbus::MessageReader reader(dbus_response.get());
542 if (!reader.PopArrayOfBytesAsProto(&response)) {
543 LOG(ERROR) << "Failed to parse ModifyPortRuleResponse proto " << request;
544 return false;
545 }
546
547 if (!response.success()) {
548 LOG(ERROR) << "ModifyPortRuleRequest failed " << request;
549 return false;
550 }
551 return true;
552}
553
Jie Jiange2e4c0b2020-09-16 18:48:43 +0900554void Client::RegisterNeighborConnectedStateChangedHandler(
555 NeighborConnectedStateChangedHandler handler) {
556 proxy_->ConnectToSignal(
557 kPatchPanelInterface, kNeighborConnectedStateChangedSignal,
558 base::BindRepeating(OnNeighborConnectedStateChangedSignal, handler),
559 base::BindOnce(OnSignalConnectedCallback));
560}
561
Garrick Evans08843932019-09-17 14:41:08 +0900562} // namespace patchpanel