2 // Copyright (C) 2019 NetDEF, Inc.
5 // This program is free software; you can redistribute it and/or modify it
6 // under the terms of the GNU General Public License as published by the Free
7 // Software Foundation; either version 2 of the License, or (at your option)
10 // This program is distributed in the hope that it will be useful, but WITHOUT
11 // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12 // FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
15 // You should have received a copy of the GNU General Public License along
16 // with this program; see the file COPYING; if not, write to the Free Software
17 // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
26 #include "lib_errors.h"
27 #include "northbound.h"
28 #include "northbound_db.h"
35 #include <grpcpp/grpcpp.h>
36 #include "grpc/frr-northbound.grpc.pb.h"
38 #define GRPC_DEFAULT_PORT 50051
41 * NOTE: we can't use the FRR debugging infrastructure here since it uses
42 * atomics and C++ has a different atomics API. Enable gRPC debugging
43 * unconditionally until we figure out a way to solve this problem.
45 static bool nb_dbg_client_grpc
= 1;
47 static pthread_t grpc_pthread
;
49 class NorthboundImpl final
: public frr::Northbound::Service
60 for (auto it
= _candidates
.begin(); it
!= _candidates
.end();
62 delete_candidate(&it
->second
);
66 GetCapabilities(grpc::ServerContext
*context
,
67 frr::GetCapabilitiesRequest
const *request
,
68 frr::GetCapabilitiesResponse
*response
) override
70 if (nb_dbg_client_grpc
)
71 zlog_debug("received RPC GetCapabilities()");
73 // Response: string frr_version = 1;
74 response
->set_frr_version(FRR_VERSION
);
76 // Response: bool rollback_support = 2;
77 #ifdef HAVE_CONFIG_ROLLBACKS
78 response
->set_rollback_support(true);
80 response
->set_rollback_support(false);
83 // Response: repeated ModuleData supported_modules = 3;
84 struct yang_module
*module
;
85 RB_FOREACH (module
, yang_modules
, &yang_modules
) {
86 auto m
= response
->add_supported_modules();
88 m
->set_name(module
->name
);
89 if (module
->info
->rev_size
)
90 m
->set_revision(module
->info
->rev
[0].date
);
91 m
->set_organization(module
->info
->org
);
94 // Response: repeated Encoding supported_encodings = 4;
95 response
->add_supported_encodings(frr::JSON
);
96 response
->add_supported_encodings(frr::XML
);
98 return grpc::Status::OK
;
101 grpc::Status
Get(grpc::ServerContext
*context
,
102 frr::GetRequest
const *request
,
103 grpc::ServerWriter
<frr::GetResponse
> *writer
) override
105 // Request: DataType type = 1;
106 int type
= request
->type();
107 // Request: Encoding encoding = 2;
108 frr::Encoding encoding
= request
->encoding();
109 // Request: bool with_defaults = 3;
110 bool with_defaults
= request
->with_defaults();
112 if (nb_dbg_client_grpc
)
114 "received RPC Get(type: %u, encoding: %u, with_defaults: %u)",
115 type
, encoding
, with_defaults
);
117 // Request: repeated string path = 4;
118 auto paths
= request
->path();
119 for (const std::string
&path
: paths
) {
120 frr::GetResponse response
;
123 // Response: int64 timestamp = 1;
124 response
.set_timestamp(time(NULL
));
126 // Response: DataTree data = 2;
127 auto *data
= response
.mutable_data();
128 data
->set_encoding(request
->encoding());
129 status
= get_path(data
, path
, type
,
130 encoding2lyd_format(encoding
),
133 // Something went wrong...
137 writer
->Write(response
);
140 if (nb_dbg_client_grpc
)
141 zlog_debug("received RPC Get() end");
143 return grpc::Status::OK
;
147 CreateCandidate(grpc::ServerContext
*context
,
148 frr::CreateCandidateRequest
const *request
,
149 frr::CreateCandidateResponse
*response
) override
151 if (nb_dbg_client_grpc
)
152 zlog_debug("received RPC CreateCandidate()");
154 struct candidate
*candidate
= create_candidate();
157 grpc::StatusCode::RESOURCE_EXHAUSTED
,
158 "Can't create candidate configuration");
160 // Response: uint32 candidate_id = 1;
161 response
->set_candidate_id(candidate
->id
);
163 return grpc::Status::OK
;
167 DeleteCandidate(grpc::ServerContext
*context
,
168 frr::DeleteCandidateRequest
const *request
,
169 frr::DeleteCandidateResponse
*response
) override
171 // Request: uint32 candidate_id = 1;
172 uint32_t candidate_id
= request
->candidate_id();
174 if (nb_dbg_client_grpc
)
176 "received RPC DeleteCandidate(candidate_id: %u)",
179 struct candidate
*candidate
= get_candidate(candidate_id
);
182 grpc::StatusCode::NOT_FOUND
,
183 "candidate configuration not found");
185 delete_candidate(candidate
);
187 return grpc::Status::OK
;
191 UpdateCandidate(grpc::ServerContext
*context
,
192 frr::UpdateCandidateRequest
const *request
,
193 frr::UpdateCandidateResponse
*response
) override
195 // Request: uint32 candidate_id = 1;
196 uint32_t candidate_id
= request
->candidate_id();
198 if (nb_dbg_client_grpc
)
200 "received RPC UpdateCandidate(candidate_id: %u)",
203 struct candidate
*candidate
= get_candidate(candidate_id
);
206 grpc::StatusCode::NOT_FOUND
,
207 "candidate configuration not found");
209 if (candidate
->transaction
)
211 grpc::StatusCode::FAILED_PRECONDITION
,
212 "candidate is in the middle of a transaction");
214 if (nb_candidate_update(candidate
->config
) != NB_OK
)
216 grpc::StatusCode::INTERNAL
,
217 "failed to update candidate configuration");
219 return grpc::Status::OK
;
223 EditCandidate(grpc::ServerContext
*context
,
224 frr::EditCandidateRequest
const *request
,
225 frr::EditCandidateResponse
*response
) override
227 // Request: uint32 candidate_id = 1;
228 uint32_t candidate_id
= request
->candidate_id();
230 if (nb_dbg_client_grpc
)
232 "received RPC EditCandidate(candidate_id: %u)",
235 struct candidate
*candidate
= get_candidate(candidate_id
);
238 grpc::StatusCode::NOT_FOUND
,
239 "candidate configuration not found");
241 // Create a copy of the candidate. For consistency, we need to
242 // ensure that either all changes are accepted or none are (in
243 // the event of an error).
244 struct nb_config
*candidate_tmp
=
245 nb_config_dup(candidate
->config
);
247 auto pvs
= request
->update();
248 for (const frr::PathValue
&pv
: pvs
) {
249 if (yang_dnode_edit(candidate_tmp
->dnode
, pv
.path(),
252 nb_config_free(candidate_tmp
);
254 grpc::StatusCode::INVALID_ARGUMENT
,
255 "Failed to update \"" + pv
.path()
260 pvs
= request
->delete_();
261 for (const frr::PathValue
&pv
: pvs
) {
262 if (yang_dnode_delete(candidate_tmp
->dnode
, pv
.path())
264 nb_config_free(candidate_tmp
);
266 grpc::StatusCode::INVALID_ARGUMENT
,
267 "Failed to remove \"" + pv
.path()
272 // No errors, accept all changes.
273 nb_config_replace(candidate
->config
, candidate_tmp
, false);
275 return grpc::Status::OK
;
279 LoadToCandidate(grpc::ServerContext
*context
,
280 frr::LoadToCandidateRequest
const *request
,
281 frr::LoadToCandidateResponse
*response
) override
283 // Request: uint32 candidate_id = 1;
284 uint32_t candidate_id
= request
->candidate_id();
285 // Request: LoadType type = 2;
286 int load_type
= request
->type();
287 // Request: DataTree config = 3;
288 auto config
= request
->config();
290 if (nb_dbg_client_grpc
)
292 "received RPC LoadToCandidate(candidate_id: %u)",
295 struct candidate
*candidate
= get_candidate(candidate_id
);
298 grpc::StatusCode::NOT_FOUND
,
299 "candidate configuration not found");
301 struct lyd_node
*dnode
= dnode_from_data_tree(&config
, true);
304 grpc::StatusCode::INTERNAL
,
305 "Failed to parse the configuration");
307 struct nb_config
*loaded_config
= nb_config_new(dnode
);
309 if (load_type
== frr::LoadToCandidateRequest::REPLACE
)
310 nb_config_replace(candidate
->config
, loaded_config
,
312 else if (nb_config_merge(candidate
->config
, loaded_config
,
316 grpc::StatusCode::INTERNAL
,
317 "Failed to merge the loaded configuration");
319 return grpc::Status::OK
;
322 grpc::Status
Commit(grpc::ServerContext
*context
,
323 frr::CommitRequest
const *request
,
324 frr::CommitResponse
*response
) override
326 // Request: uint32 candidate_id = 1;
327 uint32_t candidate_id
= request
->candidate_id();
328 // Request: Phase phase = 2;
329 int phase
= request
->phase();
330 // Request: string comment = 3;
331 const std::string comment
= request
->comment();
333 if (nb_dbg_client_grpc
)
334 zlog_debug("received RPC Commit(candidate_id: %u)",
337 // Find candidate configuration.
338 struct candidate
*candidate
= get_candidate(candidate_id
);
341 grpc::StatusCode::NOT_FOUND
,
342 "candidate configuration not found");
345 uint32_t transaction_id
= 0;
347 // Check for misuse of the two-phase commit protocol.
349 case frr::CommitRequest::PREPARE
:
350 case frr::CommitRequest::ALL
:
351 if (candidate
->transaction
)
353 grpc::StatusCode::FAILED_PRECONDITION
,
354 "pending transaction in progress");
356 case frr::CommitRequest::ABORT
:
357 case frr::CommitRequest::APPLY
:
358 if (!candidate
->transaction
)
360 grpc::StatusCode::FAILED_PRECONDITION
,
361 "no transaction in progress");
367 // Execute the user request.
369 case frr::CommitRequest::VALIDATE
:
370 ret
= nb_candidate_validate(candidate
->config
);
372 case frr::CommitRequest::PREPARE
:
373 ret
= nb_candidate_commit_prepare(
374 candidate
->config
, NB_CLIENT_GRPC
, NULL
,
375 comment
.c_str(), &candidate
->transaction
);
377 case frr::CommitRequest::ABORT
:
378 nb_candidate_commit_abort(candidate
->transaction
);
380 case frr::CommitRequest::APPLY
:
381 nb_candidate_commit_apply(candidate
->transaction
, true,
384 case frr::CommitRequest::ALL
:
385 ret
= nb_candidate_commit(
386 candidate
->config
, NB_CLIENT_GRPC
, NULL
, true,
387 comment
.c_str(), &transaction_id
);
391 // Map northbound error codes to gRPC error codes.
393 case NB_ERR_NO_CHANGES
:
395 grpc::StatusCode::ABORTED
,
396 "No configuration changes detected");
399 grpc::StatusCode::UNAVAILABLE
,
400 "There's already a transaction in progress");
401 case NB_ERR_VALIDATION
:
402 return grpc::Status(grpc::StatusCode::INVALID_ARGUMENT
,
404 case NB_ERR_RESOURCE
:
406 grpc::StatusCode::RESOURCE_EXHAUSTED
,
407 "Failed do allocate resources");
409 return grpc::Status(grpc::StatusCode::INTERNAL
,
415 // Response: uint32 transaction_id = 1;
417 response
->set_transaction_id(transaction_id
);
419 return grpc::Status::OK
;
423 ListTransactions(grpc::ServerContext
*context
,
424 frr::ListTransactionsRequest
const *request
,
425 grpc::ServerWriter
<frr::ListTransactionsResponse
>
428 if (nb_dbg_client_grpc
)
429 zlog_debug("received RPC ListTransactions()");
431 nb_db_transactions_iterate(list_transactions_cb
, writer
);
433 return grpc::Status::OK
;
437 GetTransaction(grpc::ServerContext
*context
,
438 frr::GetTransactionRequest
const *request
,
439 frr::GetTransactionResponse
*response
) override
441 struct nb_config
*nb_config
;
443 // Request: uint32 transaction_id = 1;
444 uint32_t transaction_id
= request
->transaction_id();
445 // Request: Encoding encoding = 2;
446 frr::Encoding encoding
= request
->encoding();
447 // Request: bool with_defaults = 3;
448 bool with_defaults
= request
->with_defaults();
450 if (nb_dbg_client_grpc
)
452 "received RPC GetTransaction(transaction_id: %u, encoding: %u)",
453 transaction_id
, encoding
);
455 // Load configuration from the transactions database.
456 nb_config
= nb_db_transaction_load(transaction_id
);
458 return grpc::Status(grpc::StatusCode::INVALID_ARGUMENT
,
459 "Transaction not found");
461 // Response: DataTree config = 1;
462 auto config
= response
->mutable_config();
463 config
->set_encoding(encoding
);
465 // Dump data using the requested format.
466 if (data_tree_from_dnode(config
, nb_config
->dnode
,
467 encoding2lyd_format(encoding
),
470 nb_config_free(nb_config
);
471 return grpc::Status(grpc::StatusCode::INTERNAL
,
472 "Failed to dump data");
475 nb_config_free(nb_config
);
477 return grpc::Status::OK
;
480 grpc::Status
LockConfig(grpc::ServerContext
*context
,
481 frr::LockConfigRequest
const *request
,
482 frr::LockConfigResponse
*response
) override
484 if (nb_dbg_client_grpc
)
485 zlog_debug("received RPC LockConfig()");
487 if (nb_running_lock(NB_CLIENT_GRPC
, NULL
))
489 grpc::StatusCode::FAILED_PRECONDITION
,
490 "running configuration is locked already");
492 return grpc::Status::OK
;
495 grpc::Status
UnlockConfig(grpc::ServerContext
*context
,
496 frr::UnlockConfigRequest
const *request
,
497 frr::UnlockConfigResponse
*response
) override
499 if (nb_dbg_client_grpc
)
500 zlog_debug("received RPC UnlockConfig()");
502 if (nb_running_unlock(NB_CLIENT_GRPC
, NULL
))
504 grpc::StatusCode::FAILED_PRECONDITION
,
505 "failed to unlock the running configuration");
507 return grpc::Status::OK
;
510 grpc::Status
Execute(grpc::ServerContext
*context
,
511 frr::ExecuteRequest
const *request
,
512 frr::ExecuteResponse
*response
) override
514 struct nb_node
*nb_node
;
515 struct list
*input_list
;
516 struct list
*output_list
;
517 struct listnode
*node
;
518 struct yang_data
*data
;
521 // Request: string path = 1;
522 xpath
= request
->path().c_str();
524 if (nb_dbg_client_grpc
)
525 zlog_debug("received RPC Execute(path: \"%s\")", xpath
);
527 if (request
->path().empty())
528 return grpc::Status(grpc::StatusCode::INVALID_ARGUMENT
,
529 "Data path is empty");
531 nb_node
= nb_node_find(xpath
);
533 return grpc::Status(grpc::StatusCode::INVALID_ARGUMENT
,
534 "Unknown data path");
536 input_list
= yang_data_list_new();
537 output_list
= yang_data_list_new();
539 // Read input parameters.
540 auto input
= request
->input();
541 for (const frr::PathValue
&pv
: input
) {
542 // Request: repeated PathValue input = 2;
543 data
= yang_data_new(pv
.path().c_str(),
545 listnode_add(input_list
, data
);
548 // Execute callback registered for this XPath.
549 if (nb_node
->cbs
.rpc(xpath
, input_list
, output_list
) != NB_OK
) {
550 flog_warn(EC_LIB_NB_CB_RPC
,
551 "%s: rpc callback failed: %s", __func__
,
553 list_delete(&input_list
);
554 list_delete(&output_list
);
555 return grpc::Status(grpc::StatusCode::INTERNAL
,
559 // Process output parameters.
560 for (ALL_LIST_ELEMENTS_RO(output_list
, node
, data
)) {
561 // Response: repeated PathValue output = 1;
562 frr::PathValue
*pv
= response
->add_output();
563 pv
->set_path(data
->xpath
);
564 pv
->set_value(data
->value
);
568 list_delete(&input_list
);
569 list_delete(&output_list
);
571 return grpc::Status::OK
;
577 struct nb_config
*config
;
578 struct nb_transaction
*transaction
;
580 std::map
<uint32_t, struct candidate
> _candidates
;
581 uint32_t _nextCandidateId
;
583 static int yang_dnode_edit(struct lyd_node
*dnode
,
584 const std::string
&path
,
585 const std::string
&value
)
587 ly_errno
= LY_SUCCESS
;
588 dnode
= lyd_new_path(dnode
, ly_native_ctx
, path
.c_str(),
589 (void *)value
.c_str(),
590 (LYD_ANYDATA_VALUETYPE
)0,
591 LYD_PATH_OPT_UPDATE
);
592 if (!dnode
&& ly_errno
!= LY_SUCCESS
) {
593 flog_warn(EC_LIB_LIBYANG
, "%s: lyd_new_path() failed",
601 static int yang_dnode_delete(struct lyd_node
*dnode
,
602 const std::string
&path
)
604 dnode
= yang_dnode_get(dnode
, path
.c_str());
613 static LYD_FORMAT
encoding2lyd_format(enum frr::Encoding encoding
)
623 static int get_oper_data_cb(const struct lys_node
*snode
,
624 struct yang_translator
*translator
,
625 struct yang_data
*data
, void *arg
)
627 struct lyd_node
*dnode
= static_cast<struct lyd_node
*>(arg
);
628 int ret
= yang_dnode_edit(dnode
, data
->xpath
, data
->value
);
629 yang_data_free(data
);
631 return (ret
== 0) ? NB_OK
: NB_ERR
;
634 static void list_transactions_cb(void *arg
, int transaction_id
,
635 const char *client_name
,
636 const char *date
, const char *comment
)
638 grpc::ServerWriter
<frr::ListTransactionsResponse
> *writer
=
639 static_cast<grpc::ServerWriter
<
640 frr::ListTransactionsResponse
> *>(arg
);
641 frr::ListTransactionsResponse response
;
643 // Response: uint32 id = 1;
644 response
.set_id(transaction_id
);
646 // Response: string client = 2;
647 response
.set_client(client_name
);
649 // Response: string date = 3;
650 response
.set_date(date
);
652 // Response: string comment = 4;
653 response
.set_comment(comment
);
655 writer
->Write(response
);
658 static int data_tree_from_dnode(frr::DataTree
*dt
,
659 const struct lyd_node
*dnode
,
660 LYD_FORMAT lyd_format
,
666 SET_FLAG(options
, LYP_FORMAT
| LYP_WITHSIBLINGS
);
668 SET_FLAG(options
, LYP_WD_ALL
);
670 SET_FLAG(options
, LYP_WD_TRIM
);
672 if (lyd_print_mem(&strp
, dnode
, lyd_format
, options
) == 0) {
683 static struct lyd_node
*dnode_from_data_tree(const frr::DataTree
*dt
,
686 struct lyd_node
*dnode
;
690 options
= LYD_OPT_CONFIG
;
692 options
= LYD_OPT_DATA
| LYD_OPT_DATA_NO_YANGLIB
;
694 dnode
= lyd_parse_mem(ly_native_ctx
, dt
->data().c_str(),
695 encoding2lyd_format(dt
->encoding()),
701 static struct lyd_node
*get_dnode_config(const std::string
&path
)
703 struct lyd_node
*dnode
;
705 pthread_rwlock_rdlock(&running_config
->lock
);
707 dnode
= yang_dnode_get(running_config
->dnode
,
711 dnode
= yang_dnode_dup(dnode
);
713 pthread_rwlock_unlock(&running_config
->lock
);
718 static struct lyd_node
*get_dnode_state(const std::string
&path
)
720 struct lyd_node
*dnode
;
722 dnode
= yang_dnode_new(ly_native_ctx
, false);
723 if (nb_oper_data_iterate(path
.c_str(), NULL
, 0,
724 get_oper_data_cb
, dnode
)
726 yang_dnode_free(dnode
);
733 static grpc::Status
get_path(frr::DataTree
*dt
, const std::string
&path
,
734 int type
, LYD_FORMAT lyd_format
,
737 struct lyd_node
*dnode_config
= NULL
;
738 struct lyd_node
*dnode_state
= NULL
;
739 struct lyd_node
*dnode_final
;
741 // Configuration data.
742 if (type
== frr::GetRequest_DataType_ALL
743 || type
== frr::GetRequest_DataType_CONFIG
) {
744 dnode_config
= get_dnode_config(path
);
747 grpc::StatusCode::INVALID_ARGUMENT
,
748 "Data path not found");
752 if (type
== frr::GetRequest_DataType_ALL
753 || type
== frr::GetRequest_DataType_STATE
) {
754 dnode_state
= get_dnode_state(path
);
757 yang_dnode_free(dnode_config
);
759 grpc::StatusCode::INVALID_ARGUMENT
,
760 "Failed to fetch operational data");
765 case frr::GetRequest_DataType_ALL
:
767 // Combine configuration and state data into a single
770 if (lyd_merge(dnode_state
, dnode_config
,
773 yang_dnode_free(dnode_state
);
774 yang_dnode_free(dnode_config
);
776 grpc::StatusCode::INTERNAL
,
777 "Failed to merge configuration and state data");
780 dnode_final
= dnode_state
;
782 case frr::GetRequest_DataType_CONFIG
:
783 dnode_final
= dnode_config
;
785 case frr::GetRequest_DataType_STATE
:
786 dnode_final
= dnode_state
;
790 // Validate data to create implicit default nodes if necessary.
791 int validate_opts
= 0;
792 if (type
== frr::GetRequest_DataType_CONFIG
)
793 validate_opts
= LYD_OPT_CONFIG
;
795 validate_opts
= LYD_OPT_DATA
| LYD_OPT_DATA_NO_YANGLIB
;
796 lyd_validate(&dnode_final
, validate_opts
, ly_native_ctx
);
798 // Dump data using the requested format.
799 int ret
= data_tree_from_dnode(dt
, dnode_final
, lyd_format
,
801 yang_dnode_free(dnode_final
);
803 return grpc::Status(grpc::StatusCode::INTERNAL
,
804 "Failed to dump data");
806 return grpc::Status::OK
;
809 struct candidate
*create_candidate(void)
811 uint32_t candidate_id
= ++_nextCandidateId
;
813 // Check for overflow.
814 // TODO: implement an algorithm for unique reusable IDs.
815 if (candidate_id
== 0)
818 struct candidate
*candidate
= &_candidates
[candidate_id
];
819 candidate
->id
= candidate_id
;
820 pthread_rwlock_rdlock(&running_config
->lock
);
822 candidate
->config
= nb_config_dup(running_config
);
824 pthread_rwlock_unlock(&running_config
->lock
);
825 candidate
->transaction
= NULL
;
830 void delete_candidate(struct candidate
*candidate
)
832 _candidates
.erase(candidate
->id
);
833 nb_config_free(candidate
->config
);
834 if (candidate
->transaction
)
835 nb_candidate_commit_abort(candidate
->transaction
);
838 struct candidate
*get_candidate(uint32_t candidate_id
)
840 struct candidate
*candidate
;
842 if (_candidates
.count(candidate_id
) == 0)
845 return &_candidates
[candidate_id
];
849 static void *grpc_pthread_start(void *arg
)
851 unsigned long *port
= static_cast<unsigned long *>(arg
);
852 NorthboundImpl service
;
853 std::stringstream server_address
;
855 server_address
<< "0.0.0.0:" << *port
;
857 grpc::ServerBuilder builder
;
858 builder
.AddListeningPort(server_address
.str(),
859 grpc::InsecureServerCredentials());
860 builder
.RegisterService(&service
);
862 std::unique_ptr
<grpc::Server
> server(builder
.BuildAndStart());
864 zlog_notice("gRPC server listening on %s",
865 server_address
.str().c_str());
872 static int frr_grpc_init(unsigned long *port
)
874 /* Create a pthread for gRPC since it runs its own event loop. */
875 if (pthread_create(&grpc_pthread
, NULL
, grpc_pthread_start
, port
)) {
876 flog_err(EC_LIB_SYSTEM_CALL
, "%s: error creating pthread: %s",
877 __func__
, safe_strerror(errno
));
880 pthread_detach(grpc_pthread
);
885 static int frr_grpc_finish(void)
887 // TODO: cancel the gRPC pthreads gracefully.
892 static int frr_grpc_module_late_init(struct thread_master
*tm
)
894 static unsigned long port
= GRPC_DEFAULT_PORT
;
895 const char *args
= THIS_MODULE
->load_args
;
897 // Parse port number.
900 port
= std::stoul(args
);
902 throw std::invalid_argument(
903 "can't use privileged port");
904 if (port
> UINT16_MAX
)
905 throw std::invalid_argument(
906 "port number is too big");
907 } catch (std::exception
&e
) {
908 flog_err(EC_LIB_GRPC_INIT
,
909 "%s: failed to parse port number: %s",
915 if (frr_grpc_init(&port
) < 0)
918 hook_register(frr_fini
, frr_grpc_finish
);
923 flog_err(EC_LIB_GRPC_INIT
, "failed to initialize the gRPC module");
927 static int frr_grpc_module_init(void)
929 hook_register(frr_late_init
, frr_grpc_module_late_init
);
934 FRR_MODULE_SETUP(.name
= "frr_grpc", .version
= FRR_VERSION
,
935 .description
= "FRR gRPC northbound module",
936 .init
= frr_grpc_module_init
, )