Restconf RFC8071 call-home first working prototype
This commit is contained in:
parent
a3b94f4781
commit
7d8ddf7697
18 changed files with 1115 additions and 122 deletions
|
|
@ -39,6 +39,10 @@
|
||||||
## 5.9.0
|
## 5.9.0
|
||||||
Expected: September 2022
|
Expected: September 2022
|
||||||
|
|
||||||
|
### New features
|
||||||
|
|
||||||
|
* RESTCONF call home according to RFC 8071
|
||||||
|
|
||||||
## 5.8.0
|
## 5.8.0
|
||||||
28 July 2022
|
28 July 2022
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -455,6 +455,8 @@ restconf_http1_path_root(clicon_handle h,
|
||||||
retval = 0;
|
retval = 0;
|
||||||
done:
|
done:
|
||||||
clicon_debug(1, "%s %d", __FUNCTION__, retval);
|
clicon_debug(1, "%s %d", __FUNCTION__, retval);
|
||||||
|
if (subject)
|
||||||
|
free(subject);
|
||||||
if (xerr)
|
if (xerr)
|
||||||
xml_free(xerr);
|
xml_free(xerr);
|
||||||
if (cvv)
|
if (cvv)
|
||||||
|
|
|
||||||
|
|
@ -820,6 +820,7 @@ restconf_config_init(clicon_handle h,
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/*! Create and bind restconf socket
|
/*! Create and bind restconf socket
|
||||||
*
|
*
|
||||||
* @param[in] netns0 Network namespace, special value "default" is same as NULL
|
* @param[in] netns0 Network namespace, special value "default" is same as NULL
|
||||||
|
|
@ -840,10 +841,8 @@ restconf_socket_init(const char *netns0,
|
||||||
int *ss)
|
int *ss)
|
||||||
{
|
{
|
||||||
int retval = -1;
|
int retval = -1;
|
||||||
struct sockaddr * sa;
|
struct sockaddr sa = {0,};
|
||||||
struct sockaddr_in6 sin6 = { 0 };
|
size_t sa_len;
|
||||||
struct sockaddr_in sin = { 0 };
|
|
||||||
size_t sin_len;
|
|
||||||
const char *netns;
|
const char *netns;
|
||||||
|
|
||||||
clicon_debug(1, "%s %s %s %s %hu", __FUNCTION__, netns0, addrtype, addrstr, port);
|
clicon_debug(1, "%s %s %s %s %hu", __FUNCTION__, netns0, addrtype, addrstr, port);
|
||||||
|
|
@ -852,27 +851,9 @@ restconf_socket_init(const char *netns0,
|
||||||
netns = NULL;
|
netns = NULL;
|
||||||
else
|
else
|
||||||
netns = netns0;
|
netns = netns0;
|
||||||
if (strcmp(addrtype, "inet:ipv6-address") == 0) {
|
if (clixon_inet2sin(addrtype, addrstr, port, &sa, &sa_len) < 0)
|
||||||
sin_len = sizeof(struct sockaddr_in6);
|
goto done;
|
||||||
sin6.sin6_port = htons(port);
|
if (clixon_netns_socket(netns, &sa, sa_len, backlog, flags, addrstr, ss) < 0)
|
||||||
sin6.sin6_family = AF_INET6;
|
|
||||||
|
|
||||||
inet_pton(AF_INET6, addrstr, &sin6.sin6_addr);
|
|
||||||
sa = (struct sockaddr *)&sin6;
|
|
||||||
}
|
|
||||||
else if (strcmp(addrtype, "inet:ipv4-address") == 0) {
|
|
||||||
sin_len = sizeof(struct sockaddr_in);
|
|
||||||
sin.sin_family = AF_INET;
|
|
||||||
sin.sin_port = htons(port);
|
|
||||||
sin.sin_addr.s_addr = inet_addr(addrstr);
|
|
||||||
|
|
||||||
sa = (struct sockaddr *)&sin;
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
clicon_err(OE_XML, EINVAL, "Unexpected addrtype: %s", addrtype);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
if (clixon_netns_socket(netns, sa, sin_len, backlog, flags, addrstr, ss) < 0)
|
|
||||||
goto done;
|
goto done;
|
||||||
clicon_debug(1, "%s ss=%d", __FUNCTION__, *ss);
|
clicon_debug(1, "%s ss=%d", __FUNCTION__, *ss);
|
||||||
retval = 0;
|
retval = 0;
|
||||||
|
|
@ -888,8 +869,9 @@ restconf_socket_init(const char *netns0,
|
||||||
* @param[out] namespace
|
* @param[out] namespace
|
||||||
* @param[out] address Address as string, eg "0.0.0.0", "::"
|
* @param[out] address Address as string, eg "0.0.0.0", "::"
|
||||||
* @param[out] addrtype One of inet:ipv4-address or inet:ipv6-address
|
* @param[out] addrtype One of inet:ipv4-address or inet:ipv6-address
|
||||||
* @param[out] port
|
* @param[out] port TCP Port
|
||||||
* @param[out] ssl
|
* @param[out] ssl SSL enabled?
|
||||||
|
* @param[out] callhome Callhome enabled?
|
||||||
*/
|
*/
|
||||||
int
|
int
|
||||||
restconf_socket_extract(clicon_handle h,
|
restconf_socket_extract(clicon_handle h,
|
||||||
|
|
@ -899,7 +881,8 @@ restconf_socket_extract(clicon_handle h,
|
||||||
char **address,
|
char **address,
|
||||||
char **addrtype,
|
char **addrtype,
|
||||||
uint16_t *port,
|
uint16_t *port,
|
||||||
uint16_t *ssl)
|
uint16_t *ssl,
|
||||||
|
int *callhome)
|
||||||
{
|
{
|
||||||
int retval = -1;
|
int retval = -1;
|
||||||
cxobj *x;
|
cxobj *x;
|
||||||
|
|
@ -979,6 +962,10 @@ restconf_socket_extract(clicon_handle h,
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if ((x = xpath_first(xs, nsc, "call-home")) != NULL)
|
||||||
|
*callhome = 1;
|
||||||
|
else
|
||||||
|
*callhome = 0;
|
||||||
retval = 0;
|
retval = 0;
|
||||||
done:
|
done:
|
||||||
if (cv)
|
if (cv)
|
||||||
|
|
|
||||||
|
|
@ -97,7 +97,7 @@ int restconf_drop_privileges(clicon_handle h);
|
||||||
int restconf_authentication_cb(clicon_handle h, void *req, int pretty, restconf_media media_out);
|
int restconf_authentication_cb(clicon_handle h, void *req, int pretty, restconf_media media_out);
|
||||||
int restconf_config_init(clicon_handle h, cxobj *xrestconf);
|
int restconf_config_init(clicon_handle h, cxobj *xrestconf);
|
||||||
int restconf_socket_init(const char *netns0, const char *addrstr, const char *addrtype, uint16_t port, int backlog, int flags, int *ss);
|
int restconf_socket_init(const char *netns0, const char *addrstr, const char *addrtype, uint16_t port, int backlog, int flags, int *ss);
|
||||||
int restconf_socket_extract(clicon_handle h, cxobj *xs, cvec *nsc, char **namespace, char **address, char **addrtype, uint16_t *port, uint16_t *ssl);
|
int restconf_socket_extract(clicon_handle h, cxobj *xs, cvec *nsc, char **namespace, char **address, char **addrtype, uint16_t *port, uint16_t *ssl, int *callhome);
|
||||||
|
|
||||||
#endif /* _RESTCONF_LIB_H_ */
|
#endif /* _RESTCONF_LIB_H_ */
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -583,7 +583,7 @@ ssl_alpn_check(clicon_handle h,
|
||||||
int ret;
|
int ret;
|
||||||
cbuf *cberr = NULL;
|
cbuf *cberr = NULL;
|
||||||
|
|
||||||
clicon_debug(1, "%s %s", __FUNCTION__, alpn);
|
clicon_debug(1, "%s", __FUNCTION__);
|
||||||
/* Alternatively, call restconf_str2proto but alpn is not a proper string */
|
/* Alternatively, call restconf_str2proto but alpn is not a proper string */
|
||||||
if (alpn && alpnlen == 8 && memcmp("http/1.1", alpn, 8) == 0){
|
if (alpn && alpnlen == 8 && memcmp("http/1.1", alpn, 8) == 0){
|
||||||
*proto = HTTP_11;
|
*proto = HTTP_11;
|
||||||
|
|
@ -642,23 +642,20 @@ ssl_alpn_check(clicon_handle h,
|
||||||
return retval;
|
return retval;
|
||||||
} /* ssl_alpn_check */
|
} /* ssl_alpn_check */
|
||||||
|
|
||||||
/*! Accept new socket client
|
|
||||||
|
/*! Accept new socket client (ssl not ip)
|
||||||
* @param[in] fd Socket (unix or ip)
|
* @param[in] fd Socket (unix or ip)
|
||||||
* @param[in] arg typecast clicon_handle
|
* @param[in] arg typecast clicon_handle
|
||||||
* @see openssl_init_socket where this callback is registered
|
* @see openssl_init_socket where this callback is registered
|
||||||
*/
|
*/
|
||||||
static int
|
static int
|
||||||
restconf_accept_client(int fd,
|
restconf_ssl_accept_client(clicon_handle h,
|
||||||
void *arg)
|
int s,
|
||||||
|
restconf_socket *rsock)
|
||||||
{
|
{
|
||||||
int retval = -1;
|
int retval = -1;
|
||||||
restconf_socket *rsock;
|
|
||||||
restconf_native_handle *rh = NULL;
|
restconf_native_handle *rh = NULL;
|
||||||
restconf_conn *rc = NULL;
|
restconf_conn *rc = NULL;
|
||||||
clicon_handle h;
|
|
||||||
int s;
|
|
||||||
struct sockaddr from = {0,};
|
|
||||||
socklen_t len;
|
|
||||||
char *name = NULL;
|
char *name = NULL;
|
||||||
int ret;
|
int ret;
|
||||||
int e;
|
int e;
|
||||||
|
|
@ -668,30 +665,18 @@ restconf_accept_client(int fd,
|
||||||
unsigned int alpnlen = 0;
|
unsigned int alpnlen = 0;
|
||||||
restconf_http_proto proto = HTTP_11; /* Non-SSL negotiation NYI */
|
restconf_http_proto proto = HTTP_11; /* Non-SSL negotiation NYI */
|
||||||
|
|
||||||
clicon_debug(1, "%s %d", __FUNCTION__, fd);
|
clicon_debug(1, "%s", __FUNCTION__);
|
||||||
#ifdef HAVE_LIBNGHTTP2
|
#ifdef HAVE_LIBNGHTTP2
|
||||||
#ifndef HAVE_HTTP1
|
#ifndef HAVE_HTTP1
|
||||||
proto = HTTP_2; /* If nghttp2 only let default be 2.0 */
|
proto = HTTP_2; /* If nghttp2 only let default be 2.0 */
|
||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
if ((rsock = (restconf_socket *)arg) == NULL){
|
#if 1
|
||||||
clicon_err(OE_YANG, EINVAL, "rsock is NULL");
|
|
||||||
goto done;
|
|
||||||
}
|
|
||||||
clicon_debug(1, "%s type:%s addr:%s port:%hu", __FUNCTION__,
|
|
||||||
rsock->rs_addrtype,
|
|
||||||
rsock->rs_addrstr,
|
|
||||||
rsock->rs_port);
|
|
||||||
h = rsock->rs_h;
|
|
||||||
if ((rh = restconf_native_handle_get(h)) == NULL){
|
if ((rh = restconf_native_handle_get(h)) == NULL){
|
||||||
clicon_err(OE_XML, EFAULT, "No openssl handle");
|
clicon_err(OE_XML, EFAULT, "No openssl handle");
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
len = sizeof(from);
|
#endif
|
||||||
if ((s = accept(rsock->rs_ss, &from, &len)) < 0){
|
|
||||||
clicon_err(OE_UNIX, errno, "accept");
|
|
||||||
goto done;
|
|
||||||
}
|
|
||||||
/*
|
/*
|
||||||
* Register callbacks for actual data socket
|
* Register callbacks for actual data socket
|
||||||
*/
|
*/
|
||||||
|
|
@ -919,6 +904,50 @@ restconf_accept_client(int fd,
|
||||||
if (name)
|
if (name)
|
||||||
free(name);
|
free(name);
|
||||||
return retval;
|
return retval;
|
||||||
|
} /* restconf_ssl_accept_client */
|
||||||
|
|
||||||
|
/*! Accept new socket client
|
||||||
|
* @param[in] fd Socket (unix or ip)
|
||||||
|
* @param[in] arg typecast clicon_handle
|
||||||
|
* @see openssl_init_socket where this callback is registered
|
||||||
|
*/
|
||||||
|
static int
|
||||||
|
restconf_accept_client(int fd,
|
||||||
|
void *arg)
|
||||||
|
|
||||||
|
{
|
||||||
|
int retval = -1;
|
||||||
|
restconf_socket *rsock;
|
||||||
|
clicon_handle h;
|
||||||
|
int s;
|
||||||
|
struct sockaddr from = {0,};
|
||||||
|
socklen_t len;
|
||||||
|
char *name = NULL;
|
||||||
|
|
||||||
|
clicon_debug(1, "%s %d", __FUNCTION__, fd);
|
||||||
|
if ((rsock = (restconf_socket *)arg) == NULL){
|
||||||
|
clicon_err(OE_YANG, EINVAL, "rsock is NULL");
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
clicon_debug(1, "%s type:%s addr:%s port:%hu", __FUNCTION__,
|
||||||
|
rsock->rs_addrtype,
|
||||||
|
rsock->rs_addrstr,
|
||||||
|
rsock->rs_port);
|
||||||
|
h = rsock->rs_h;
|
||||||
|
len = sizeof(from);
|
||||||
|
if ((s = accept(rsock->rs_ss, &from, &len)) < 0){
|
||||||
|
clicon_err(OE_UNIX, errno, "accept");
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
/* Accept SSL */
|
||||||
|
if (restconf_ssl_accept_client(h, s, rsock) < 0)
|
||||||
|
goto done;
|
||||||
|
retval = 0;
|
||||||
|
done:
|
||||||
|
clicon_debug(1, "%s retval %d", __FUNCTION__, retval);
|
||||||
|
if (name)
|
||||||
|
free(name);
|
||||||
|
return retval;
|
||||||
} /* restconf_accept_client */
|
} /* restconf_accept_client */
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
|
|
@ -1019,10 +1048,63 @@ restconf_clixon_backend(clicon_handle h,
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*! Periodically try to connect to callhome client
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
restconf_callhome_timer(int fd,
|
||||||
|
void *arg)
|
||||||
|
{
|
||||||
|
int retval = -1;
|
||||||
|
clicon_handle h;
|
||||||
|
struct timeval now;
|
||||||
|
struct timeval t;
|
||||||
|
struct timeval t1 = {1, 0}; // XXX once every second
|
||||||
|
restconf_socket *rs;
|
||||||
|
struct sockaddr sa = {0,};
|
||||||
|
size_t sa_len;
|
||||||
|
int s;
|
||||||
|
|
||||||
|
clicon_debug(1, "%s", __FUNCTION__);
|
||||||
|
gettimeofday(&now, NULL);
|
||||||
|
if ((rs = (restconf_socket *)arg) == NULL){
|
||||||
|
clicon_err(OE_YANG, EINVAL, "rsock is NULL");
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
h = rs->rs_h;
|
||||||
|
if (clixon_inet2sin(rs->rs_addrtype, rs->rs_addrstr, rs->rs_port, &sa, &sa_len) < 0)
|
||||||
|
goto done;
|
||||||
|
if ((s = socket(sa.sa_family, SOCK_STREAM, 0)) < 0) {
|
||||||
|
clicon_err(OE_UNIX, errno, "socket");
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
clicon_debug(1, "%s connect", __FUNCTION__);
|
||||||
|
if (connect(s, &sa, sa_len) < 0){
|
||||||
|
close(s);
|
||||||
|
/* Fail: Initiate new timer */
|
||||||
|
timeradd(&now, &t1, &t);
|
||||||
|
if (clixon_event_reg_timeout(t,
|
||||||
|
restconf_callhome_timer, /* this function */
|
||||||
|
rs,
|
||||||
|
"restconf callhome timer") < 0)
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
rs->rs_ss = s;
|
||||||
|
if (restconf_ssl_accept_client(h, rs->rs_ss, rs) < 0)
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
clicon_debug(1, "%s connect done", __FUNCTION__);
|
||||||
|
retval = 0;
|
||||||
|
done:
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
|
||||||
/*! Per-socket openssl inits
|
/*! Per-socket openssl inits
|
||||||
* @param[in] h Clicon handle
|
* @param[in] h Clicon handle
|
||||||
* @param[in] xs XML config of single restconf socket
|
* @param[in] xs XML config of single restconf socket
|
||||||
* @param[in] nsc Namespace context
|
* @param[in] nsc Namespace context
|
||||||
|
* @retval 0 OK
|
||||||
|
* @retval -1 Error
|
||||||
*/
|
*/
|
||||||
static int
|
static int
|
||||||
openssl_init_socket(clicon_handle h,
|
openssl_init_socket(clicon_handle h,
|
||||||
|
|
@ -1033,6 +1115,7 @@ openssl_init_socket(clicon_handle h,
|
||||||
char *netns = NULL;
|
char *netns = NULL;
|
||||||
char *address = NULL;
|
char *address = NULL;
|
||||||
char *addrtype = NULL;
|
char *addrtype = NULL;
|
||||||
|
int callhome = 0;
|
||||||
uint16_t ssl = 0;
|
uint16_t ssl = 0;
|
||||||
uint16_t port = 0;
|
uint16_t port = 0;
|
||||||
int ss = -1;
|
int ss = -1;
|
||||||
|
|
@ -1041,23 +1124,9 @@ openssl_init_socket(clicon_handle h,
|
||||||
|
|
||||||
clicon_debug(1, "%s", __FUNCTION__);
|
clicon_debug(1, "%s", __FUNCTION__);
|
||||||
/* Extract socket parameters from single socket config: ns, addr, port, ssl */
|
/* Extract socket parameters from single socket config: ns, addr, port, ssl */
|
||||||
if (restconf_socket_extract(h, xs, nsc, &netns, &address, &addrtype, &port, &ssl) < 0)
|
if (restconf_socket_extract(h, xs, nsc, &netns, &address, &addrtype, &port, &ssl, &callhome) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
/* Open restconf socket and bind */
|
|
||||||
if (restconf_socket_init(netns, address, addrtype, port,
|
|
||||||
SOCKET_LISTEN_BACKLOG,
|
|
||||||
#ifdef RESTCONF_OPENSSL_NONBLOCKING
|
|
||||||
SOCK_NONBLOCK, /* Also 0 is possible */
|
|
||||||
#else /* blocking */
|
|
||||||
0,
|
|
||||||
#endif
|
|
||||||
&ss
|
|
||||||
) < 0)
|
|
||||||
goto done;
|
|
||||||
if ((rh = restconf_native_handle_get(h)) == NULL){
|
|
||||||
clicon_err(OE_XML, EFAULT, "No openssl handle");
|
|
||||||
goto done;
|
|
||||||
}
|
|
||||||
/*
|
/*
|
||||||
* Create per-socket openssl handle
|
* Create per-socket openssl handle
|
||||||
* See restconf_native_terminate for freeing
|
* See restconf_native_terminate for freeing
|
||||||
|
|
@ -1068,8 +1137,33 @@ openssl_init_socket(clicon_handle h,
|
||||||
}
|
}
|
||||||
memset(rsock, 0, sizeof *rsock);
|
memset(rsock, 0, sizeof *rsock);
|
||||||
rsock->rs_h = h;
|
rsock->rs_h = h;
|
||||||
|
rsock->rs_ssl = ssl; /* true/false */
|
||||||
|
if (callhome){
|
||||||
|
if (!ssl){
|
||||||
|
clicon_err(OE_SSL, EINVAL, "Restconf callhome requires SSL");
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else { /* listen/accept */
|
||||||
|
/* Open restconf socket and bind for later accept */
|
||||||
|
if (restconf_socket_init(netns, address, addrtype, port,
|
||||||
|
SOCKET_LISTEN_BACKLOG,
|
||||||
|
#ifdef RESTCONF_OPENSSL_NONBLOCKING
|
||||||
|
SOCK_NONBLOCK, /* Also 0 is possible */
|
||||||
|
#else /* blocking */
|
||||||
|
0,
|
||||||
|
#endif
|
||||||
|
&ss
|
||||||
|
) < 0)
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
if ((rh = restconf_native_handle_get(h)) == NULL){
|
||||||
|
clicon_err(OE_XML, EFAULT, "No openssl handle");
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
|
||||||
rsock->rs_ss = ss;
|
rsock->rs_ss = ss;
|
||||||
rsock->rs_ssl = ssl;
|
|
||||||
if ((rsock->rs_addrstr = strdup(address)) == NULL){
|
if ((rsock->rs_addrstr = strdup(address)) == NULL){
|
||||||
clicon_err(OE_UNIX, errno, "strdup");
|
clicon_err(OE_UNIX, errno, "strdup");
|
||||||
goto done;
|
goto done;
|
||||||
|
|
@ -1081,29 +1175,24 @@ openssl_init_socket(clicon_handle h,
|
||||||
rsock->rs_port = port;
|
rsock->rs_port = port;
|
||||||
INSQ(rsock, rh->rh_sockets);
|
INSQ(rsock, rh->rh_sockets);
|
||||||
|
|
||||||
/* ss is a server socket that the clients connect to. The callback
|
if (callhome){
|
||||||
therefore accepts clients on ss */
|
if (restconf_callhome_timer(ss, rsock) < 0)
|
||||||
#ifdef RESTCONF_HTTP1_UNITTEST
|
goto done;
|
||||||
{
|
}
|
||||||
restconf_conn *rc;
|
else {
|
||||||
if ((rc = restconf_conn_new(h, 0)) == NULL)
|
/* ss is a server socket that the clients connect to. The callback
|
||||||
goto done;
|
therefore accepts clients on ss */
|
||||||
rc->rc_s = 0;
|
if (clixon_event_reg_fd(ss, restconf_accept_client, rsock, "restconf socket") < 0)
|
||||||
if (restconf_stream_data_new(rc, 0) == NULL)
|
|
||||||
goto done;
|
|
||||||
if (clixon_event_reg_fd(0, restconf_connection, rc, "restconf socket") < 0)
|
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
#else
|
|
||||||
if (clixon_event_reg_fd(ss, restconf_accept_client, rsock, "restconf socket") < 0)
|
|
||||||
goto done;
|
|
||||||
#endif
|
|
||||||
retval = 0;
|
retval = 0;
|
||||||
done:
|
done:
|
||||||
return retval;
|
return retval;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*! Init openssl, open and register server socket (ready for accept)
|
/*! Init openssl, open and register server socket (ready for accept)
|
||||||
|
*
|
||||||
|
* Given a fully populated configuration tree.
|
||||||
* @param[in] h Clicon handle
|
* @param[in] h Clicon handle
|
||||||
* @param[in] dbg0 Manually set debug flag, if set overrides configuration setting
|
* @param[in] dbg0 Manually set debug flag, if set overrides configuration setting
|
||||||
* @param[in] xrestconf XML tree containing restconf config
|
* @param[in] xrestconf XML tree containing restconf config
|
||||||
|
|
|
||||||
|
|
@ -702,12 +702,10 @@ restconf_http1_process(restconf_conn *rc,
|
||||||
*/
|
*/
|
||||||
if ((ret = http1_check_content_length(h, sd, &status)) < 0)
|
if ((ret = http1_check_content_length(h, sd, &status)) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
#ifndef RESTCONF_HTTP1_UNITTEST /* Ignore content-length */
|
|
||||||
if (status == 1){
|
if (status == 1){
|
||||||
(*readmore)++;
|
(*readmore)++;
|
||||||
goto ok;
|
goto ok;
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
/* nginx compatible, set HTTPS parameter if SSL */
|
/* nginx compatible, set HTTPS parameter if SSL */
|
||||||
if (rc->rc_ssl)
|
if (rc->rc_ssl)
|
||||||
if (restconf_param_set(h, "HTTPS", "https") < 0)
|
if (restconf_param_set(h, "HTTPS", "https") < 0)
|
||||||
|
|
@ -715,15 +713,9 @@ restconf_http1_process(restconf_conn *rc,
|
||||||
/* main restconf processing */
|
/* main restconf processing */
|
||||||
if (restconf_http1_path_root(h, rc) < 0)
|
if (restconf_http1_path_root(h, rc) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
#ifdef RESTCONF_HTTP1_UNITTEST
|
|
||||||
if (native_buf_write(cbuf_get(sd->sd_outp_buf), cbuf_len(sd->sd_outp_buf),
|
|
||||||
1, rc->rc_ssl) < 0)
|
|
||||||
goto done;
|
|
||||||
#else
|
|
||||||
if (native_buf_write(cbuf_get(sd->sd_outp_buf), cbuf_len(sd->sd_outp_buf),
|
if (native_buf_write(cbuf_get(sd->sd_outp_buf), cbuf_len(sd->sd_outp_buf),
|
||||||
rc->rc_s, rc->rc_ssl) < 0)
|
rc->rc_s, rc->rc_ssl) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
#endif
|
|
||||||
cvec_reset(sd->sd_outp_hdrs); /* Can be done in native_send_reply */
|
cvec_reset(sd->sd_outp_hdrs); /* Can be done in native_send_reply */
|
||||||
cbuf_reset(sd->sd_outp_buf);
|
cbuf_reset(sd->sd_outp_buf);
|
||||||
cbuf_reset(sd->sd_inbuf);
|
cbuf_reset(sd->sd_inbuf);
|
||||||
|
|
@ -951,16 +943,5 @@ restconf_connection(int s,
|
||||||
retval = 0;
|
retval = 0;
|
||||||
done:
|
done:
|
||||||
clicon_debug(1, "%s retval %d", __FUNCTION__, retval);
|
clicon_debug(1, "%s retval %d", __FUNCTION__, retval);
|
||||||
#ifdef RESTCONF_HTTP1_UNITTEST /* unit test */
|
|
||||||
if (rc){
|
|
||||||
if (close(rc->rc_s) < 0){
|
|
||||||
clicon_err(OE_UNIX, errno, "close");
|
|
||||||
goto done;
|
|
||||||
}
|
|
||||||
clixon_event_unreg_fd(rc->rc_s, restconf_connection);
|
|
||||||
restconf_conn_free(rc);
|
|
||||||
}
|
|
||||||
exit(0);
|
|
||||||
#endif
|
|
||||||
return retval;
|
return retval;
|
||||||
} /* restconf_connection */
|
} /* restconf_connection */
|
||||||
|
|
|
||||||
|
|
@ -152,11 +152,6 @@
|
||||||
*/
|
*/
|
||||||
#define USE_CONFIG_FLAG_CACHE
|
#define USE_CONFIG_FLAG_CACHE
|
||||||
|
|
||||||
/*! Restconf native unit test for fuzzing of http/1 parser
|
|
||||||
* See test/fuzz/http1
|
|
||||||
*/
|
|
||||||
#undef RESTCONF_HTTP1_UNITTEST
|
|
||||||
|
|
||||||
/*! If backend is restarted, cli and netconf client will retry (once) and reconnect
|
/*! If backend is restarted, cli and netconf client will retry (once) and reconnect
|
||||||
* Note, if client has locked or had edits in progress, these will be lost
|
* Note, if client has locked or had edits in progress, these will be lost
|
||||||
* A warning will be printed
|
* A warning will be printed
|
||||||
|
|
|
||||||
|
|
@ -97,4 +97,6 @@ int send_msg_reply(int s, char *data, uint32_t datalen);
|
||||||
|
|
||||||
int detect_endtag(char *tag, char ch, int *state);
|
int detect_endtag(char *tag, char ch, int *state);
|
||||||
|
|
||||||
|
int clixon_inet2sin(const char *addrtype, const char *addrstr, uint16_t port, struct sockaddr *sa, size_t *sa_len);
|
||||||
|
|
||||||
#endif /* _CLIXON_PROTO_H_ */
|
#endif /* _CLIXON_PROTO_H_ */
|
||||||
|
|
|
||||||
|
|
@ -172,6 +172,7 @@ clicon_sig_ignore_get(void)
|
||||||
* }
|
* }
|
||||||
* clixon_event_reg_fd(fd, fn, (void*)42, "call fn on input on fd");
|
* clixon_event_reg_fd(fd, fn, (void*)42, "call fn on input on fd");
|
||||||
* @endcode
|
* @endcode
|
||||||
|
* @see clixon_event_unreg_fd
|
||||||
*/
|
*/
|
||||||
int
|
int
|
||||||
clixon_event_reg_fd(int fd,
|
clixon_event_reg_fd(int fd,
|
||||||
|
|
|
||||||
|
|
@ -810,3 +810,49 @@ detect_endtag(char *tag,
|
||||||
*state = 0;
|
*state = 0;
|
||||||
return retval;
|
return retval;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*! Given family, addr str, port, return sockaddr and length
|
||||||
|
*
|
||||||
|
* @param[in] addrtype Address family: inet:ipv4-address or inet:ipv6-address
|
||||||
|
* @param[in] addrstr IP address as string
|
||||||
|
* @param[in] port TCP port host byte order
|
||||||
|
* @param[out] sa sockaddr, should be allocated
|
||||||
|
* @param[out] salen length of sockaddr data
|
||||||
|
* @code
|
||||||
|
* struct sockaddr sa = {0,};
|
||||||
|
* size_t sa_len;
|
||||||
|
* if (clixon_inet2sin(inet:ipv4-address, "0.0.0.0", 80, &sa, &sa_len) < 0)
|
||||||
|
* err;
|
||||||
|
* @endcode
|
||||||
|
* Probably misplaced, need a clixon_network file?
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
clixon_inet2sin(const char *addrtype,
|
||||||
|
const char *addrstr,
|
||||||
|
uint16_t port,
|
||||||
|
struct sockaddr *sa,
|
||||||
|
size_t *sa_len)
|
||||||
|
{
|
||||||
|
struct sockaddr_in6 *sin6;
|
||||||
|
struct sockaddr_in *sin;
|
||||||
|
|
||||||
|
if (strcmp(addrtype, "inet:ipv6-address") == 0) {
|
||||||
|
sin6 = (struct sockaddr_in6 *)sa;
|
||||||
|
*sa_len = sizeof(struct sockaddr_in6);
|
||||||
|
sin6->sin6_port = htons(port);
|
||||||
|
sin6->sin6_family = AF_INET6;
|
||||||
|
inet_pton(AF_INET6, addrstr, &sin6->sin6_addr);
|
||||||
|
}
|
||||||
|
else if (strcmp(addrtype, "inet:ipv4-address") == 0) {
|
||||||
|
sin = (struct sockaddr_in *)sa;
|
||||||
|
*sa_len = sizeof(struct sockaddr_in);
|
||||||
|
sin->sin_family = AF_INET;
|
||||||
|
sin->sin_port = htons(port);
|
||||||
|
sin->sin_addr.s_addr = inet_addr(addrstr);
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
clicon_err(OE_XML, EINVAL, "Unexpected addrtype: %s", addrtype);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -75,7 +75,7 @@ DATASTORE_TOP="config"
|
||||||
CLIXON_AUTOCLI_REV="2022-02-11"
|
CLIXON_AUTOCLI_REV="2022-02-11"
|
||||||
CLIXON_LIB_REV="2021-12-05"
|
CLIXON_LIB_REV="2021-12-05"
|
||||||
CLIXON_CONFIG_REV="2022-03-21"
|
CLIXON_CONFIG_REV="2022-03-21"
|
||||||
CLIXON_RESTCONF_REV="2022-03-21"
|
CLIXON_RESTCONF_REV="2022-08-01"
|
||||||
CLIXON_EXAMPLE_REV="2020-12-01"
|
CLIXON_EXAMPLE_REV="2020-12-01"
|
||||||
|
|
||||||
# Length of TSL RSA key
|
# Length of TSL RSA key
|
||||||
|
|
|
||||||
|
|
@ -61,6 +61,12 @@ function memonce(){
|
||||||
|
|
||||||
memerr=0
|
memerr=0
|
||||||
for test in $pattern; do
|
for test in $pattern; do
|
||||||
|
# Can happen if no pattern, eg pattern=foo but "foo" does not exist
|
||||||
|
if [ ! -f $test ]; then
|
||||||
|
echo -e "\e[31mNo such file: $test"
|
||||||
|
echo -ne "\e[0m"
|
||||||
|
exit -1
|
||||||
|
fi
|
||||||
if [ $testnr != 0 ]; then echo; fi
|
if [ $testnr != 0 ]; then echo; fi
|
||||||
perfnr=1000 # Limit performance tests
|
perfnr=1000 # Limit performance tests
|
||||||
testfile=$test
|
testfile=$test
|
||||||
|
|
|
||||||
243
test/test_restconf_callhome.sh
Executable file
243
test/test_restconf_callhome.sh
Executable file
|
|
@ -0,0 +1,243 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# test of Restconf callhome
|
||||||
|
# See RFC 8071 NETCONF Call Home and RESTCONF Call Home
|
||||||
|
# No NACM for now
|
||||||
|
|
||||||
|
# Magic line must be first in script (see README.md)
|
||||||
|
s="$_" ; . ./lib.sh || if [ "$s" = $0 ]; then exit 0; else return 0; fi
|
||||||
|
|
||||||
|
APPNAME=example
|
||||||
|
|
||||||
|
: ${clixon_restconf_callhome_client:=clixon_restconf_callhome_client}
|
||||||
|
|
||||||
|
cfg=$dir/conf_yang.xml
|
||||||
|
fyang=$dir/clixon-example.yang
|
||||||
|
clispec=$dir/spec.cli
|
||||||
|
|
||||||
|
certdir=$dir/certs
|
||||||
|
cakey=$certdir/ca_key.pem
|
||||||
|
cacert=$certdir/ca_cert.pem
|
||||||
|
srvkey=$certdir/srv_key.pem
|
||||||
|
srvcert=$certdir/srv_cert.pem
|
||||||
|
|
||||||
|
users="andy" # generate certs for some users
|
||||||
|
|
||||||
|
RCPROTO=https
|
||||||
|
# Callhome stream is HTTP/1.1, other communication is HTTP/2
|
||||||
|
HVER=2
|
||||||
|
HVERCH=1.1
|
||||||
|
|
||||||
|
test -d $certdir || mkdir $certdir
|
||||||
|
|
||||||
|
cat <<EOF > $cfg
|
||||||
|
<clixon-config xmlns="http://clicon.org/config">
|
||||||
|
<CLICON_CONFIGFILE>$cfg</CLICON_CONFIGFILE>
|
||||||
|
<CLICON_FEATURE>ietf-netconf:startup</CLICON_FEATURE>
|
||||||
|
<CLICON_YANG_DIR>${YANG_INSTALLDIR}</CLICON_YANG_DIR>
|
||||||
|
<CLICON_YANG_MAIN_FILE>$fyang</CLICON_YANG_MAIN_FILE>
|
||||||
|
<CLICON_CLISPEC_DIR>/usr/local/lib/$APPNAME/clispec</CLICON_CLISPEC_DIR>
|
||||||
|
<CLICON_BACKEND_DIR>/usr/local/lib/$APPNAME/backend</CLICON_BACKEND_DIR>
|
||||||
|
<CLICON_BACKEND_REGEXP>example_backend.so$</CLICON_BACKEND_REGEXP>
|
||||||
|
<CLICON_CLI_DIR>/usr/local/lib/$APPNAME/cli</CLICON_CLI_DIR>
|
||||||
|
<CLICON_CLI_MODE>$APPNAME</CLICON_CLI_MODE>
|
||||||
|
<CLICON_CLISPEC_DIR>$dir</CLICON_CLISPEC_DIR>
|
||||||
|
<CLICON_YANG_LIBRARY>false</CLICON_YANG_LIBRARY>
|
||||||
|
<CLICON_SOCK>$dir/$APPNAME.sock</CLICON_SOCK>
|
||||||
|
<CLICON_BACKEND_PIDFILE>/usr/local/var/$APPNAME/$APPNAME.pidfile</CLICON_BACKEND_PIDFILE>
|
||||||
|
<CLICON_XMLDB_DIR>$dir</CLICON_XMLDB_DIR>
|
||||||
|
<restconf>
|
||||||
|
<enable>true</enable>
|
||||||
|
<auth-type>client-certificate</auth-type>
|
||||||
|
<pretty>false</pretty>
|
||||||
|
<server-cert-path>$srvcert</server-cert-path>
|
||||||
|
<server-key-path>$srvkey</server-key-path>
|
||||||
|
<server-ca-cert-path>$cacert</server-ca-cert-path>
|
||||||
|
<debug>1</debug>
|
||||||
|
<socket>
|
||||||
|
<namespace>default</namespace>
|
||||||
|
<call-home>
|
||||||
|
<connection-type>
|
||||||
|
<persistent/>
|
||||||
|
</connection-type>
|
||||||
|
</call-home>
|
||||||
|
<address>127.0.0.1</address>
|
||||||
|
<port>4336</port>
|
||||||
|
<ssl>true</ssl>
|
||||||
|
</socket>
|
||||||
|
<socket>
|
||||||
|
<namespace>default</namespace>
|
||||||
|
<address>0.0.0.0</address>
|
||||||
|
<port>443</port>
|
||||||
|
<ssl>true</ssl>
|
||||||
|
</socket>
|
||||||
|
</restconf>
|
||||||
|
<autocli>
|
||||||
|
<module-default>false</module-default>
|
||||||
|
<list-keyword-default>kw-nokey</list-keyword-default>
|
||||||
|
<treeref-state-default>false</treeref-state-default>
|
||||||
|
<rule>
|
||||||
|
<name>include clixon-example</name>
|
||||||
|
<operation>enable</operation>
|
||||||
|
<module-name>clixon-example</module-name>
|
||||||
|
</rule>
|
||||||
|
<rule>
|
||||||
|
<name>include ietf-netconf-server</name>
|
||||||
|
<operation>enable</operation>
|
||||||
|
<module-name>ietf-netconf-server</module-name>
|
||||||
|
</rule>
|
||||||
|
<rule>
|
||||||
|
<name>include ietf-keystore</name>
|
||||||
|
<operation>enable</operation>
|
||||||
|
<module-name>ietf-keystore</module-name>
|
||||||
|
</rule>
|
||||||
|
<rule>
|
||||||
|
<name>include ietf-truststore</name>
|
||||||
|
<operation>enable</operation>
|
||||||
|
<module-name>ietf-truststore</module-name>
|
||||||
|
</rule>
|
||||||
|
</autocli>
|
||||||
|
</clixon-config>
|
||||||
|
EOF
|
||||||
|
|
||||||
|
cat <<EOF > $fyang
|
||||||
|
module clixon-example{
|
||||||
|
yang-version 1.1;
|
||||||
|
namespace "urn:example:clixon";
|
||||||
|
prefix ex;
|
||||||
|
/* Generic config data */
|
||||||
|
container table{
|
||||||
|
list parameter{
|
||||||
|
key name;
|
||||||
|
leaf name{
|
||||||
|
type string;
|
||||||
|
}
|
||||||
|
leaf value{
|
||||||
|
type string;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
cat <<EOF > $clispec
|
||||||
|
CLICON_MODE="example";
|
||||||
|
CLICON_PROMPT="%U@%H %W> ";
|
||||||
|
CLICON_PLUGIN="example_cli";
|
||||||
|
|
||||||
|
# Autocli syntax tree operations
|
||||||
|
set @datamodel, cli_auto_set();
|
||||||
|
merge @datamodel, cli_auto_merge();
|
||||||
|
create @datamodel, cli_auto_create();
|
||||||
|
delete("Delete a configuration item") @datamodel, cli_auto_del();
|
||||||
|
validate("Validate changes"), cli_validate();
|
||||||
|
commit("Commit the changes"), cli_commit();
|
||||||
|
quit("Quit"), cli_quit();
|
||||||
|
show("Show a particular state of the system"){
|
||||||
|
configuration("Show configuration"), cli_auto_show("datamodel", "candidate", "text", true, false);{
|
||||||
|
xml("Show configuration as XML"), cli_auto_show("datamodel", "candidate", "xml", true, false);
|
||||||
|
cli("Show configuration as CLI commands"), cli_auto_show("datamodel", "candidate", "cli", false, false, "set ");
|
||||||
|
netconf("Show configuration as netconf edit-config operation"), cli_auto_show("datamodel", "candidate", "netconf", false, false);
|
||||||
|
text("Show configuration as text"), cli_auto_show("datamodel", "candidate", "text", false, false);
|
||||||
|
json("Show configuration as JSON"), cli_auto_show("datamodel", "candidate", "json", false, false);
|
||||||
|
}
|
||||||
|
state("Show configuration and state"), cli_auto_show("datamodel", "running", "xml", false, true);
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Create server certs
|
||||||
|
cacerts $cakey $cacert
|
||||||
|
servercerts $cakey $cacert $srvkey $srvcert
|
||||||
|
|
||||||
|
# Create client certs
|
||||||
|
|
||||||
|
for name in $users; do
|
||||||
|
cat<<EOF > $dir/$name.cnf
|
||||||
|
[req]
|
||||||
|
prompt = no
|
||||||
|
distinguished_name = dn
|
||||||
|
[dn]
|
||||||
|
CN = $name # This can be verified using SSL_set1_host
|
||||||
|
emailAddress = $name@foo.bar
|
||||||
|
O = Clixon
|
||||||
|
L = Stockholm
|
||||||
|
C = SE
|
||||||
|
EOF
|
||||||
|
# Create client key
|
||||||
|
openssl genpkey -algorithm RSA -out "$certdir/$name.key" || err "Generate client key"
|
||||||
|
|
||||||
|
# Generate CSR (signing request)
|
||||||
|
openssl req -new -config $dir/$name.cnf -key $certdir/$name.key -out $certdir/$name.csr
|
||||||
|
|
||||||
|
# Sign by CA
|
||||||
|
openssl x509 -req -extfile $dir/$name.cnf -days 7 -passin "pass:password" -in $certdir/$name.csr -CA $cacert -CAkey $cakey -CAcreateserial -out $certdir/$name.crt || err "Generate signing client cert"
|
||||||
|
done
|
||||||
|
|
||||||
|
# hardcoded to andy
|
||||||
|
cat <<EOF > $dir/startup_db
|
||||||
|
<${DATASTORE_TOP}>
|
||||||
|
</${DATASTORE_TOP}>
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Callhome request from client
|
||||||
|
cat <<EOF > $dir/data
|
||||||
|
GET /restconf/data HTTP/$HVERCH
|
||||||
|
Host: localhost
|
||||||
|
Accept: application/yang-data+xml
|
||||||
|
|
||||||
|
EOF
|
||||||
|
|
||||||
|
new "test params: -f $cfg"
|
||||||
|
# Bring your own backend
|
||||||
|
if [ $BE -ne 0 ]; then
|
||||||
|
# kill old backend (if any)
|
||||||
|
new "kill old backend"
|
||||||
|
sudo clixon_backend -zf $cfg
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
err
|
||||||
|
fi
|
||||||
|
|
||||||
|
new "start backend -s startup -f $cfg"
|
||||||
|
start_backend -s startup -f $cfg
|
||||||
|
fi
|
||||||
|
|
||||||
|
new "wait backend"
|
||||||
|
wait_backend
|
||||||
|
|
||||||
|
if [ $RC -ne 0 ]; then
|
||||||
|
new "kill old restconf daemon"
|
||||||
|
stop_restconf_pre
|
||||||
|
|
||||||
|
new "start restconf daemon"
|
||||||
|
start_restconf -f $cfg
|
||||||
|
fi
|
||||||
|
|
||||||
|
new "wait restconf"
|
||||||
|
wait_restconf
|
||||||
|
|
||||||
|
new "restconf Add init data"
|
||||||
|
expectpart "$(curl $CURLOPTS --key $certdir/andy.key --cert $certdir/andy.crt -X POST -H "Accept: application/yang-data+json" -H "Content-Type: application/yang-data+json" -d '{"clixon-example:table":{"parameter":{"name":"x","value":"foo"}}}' $RCPROTO://127.0.0.1/restconf/data)" 0 "HTTP/$HVER 201"
|
||||||
|
|
||||||
|
new "Send GET via callhome client"
|
||||||
|
expectpart "$(${clixon_restconf_callhome_client} -D $DBG -f $dir/data -a 127.0.0.1 -c $srvcert -k $srvkey -C $cacert)" 0 "HTTP/$HVERCH 200 OK" "Content-Type: application/yang-data+xml"
|
||||||
|
|
||||||
|
# Kill old
|
||||||
|
if [ $RC -ne 0 ]; then
|
||||||
|
new "Kill restconf daemon"
|
||||||
|
stop_restconf
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ $BE -ne 0 ]; then
|
||||||
|
new "Kill backend"
|
||||||
|
# Check if premature kill
|
||||||
|
pid=$(pgrep -u root -f clixon_backend)
|
||||||
|
if [ -z "$pid" ]; then
|
||||||
|
err "backend already dead"
|
||||||
|
fi
|
||||||
|
# kill backend
|
||||||
|
stop_backend -f $cfg
|
||||||
|
fi
|
||||||
|
|
||||||
|
rm -rf $dir
|
||||||
|
|
||||||
|
new "endtest"
|
||||||
|
endtest
|
||||||
|
|
@ -97,6 +97,7 @@ APPSRC += clixon_netconf_ssh_callhome.c
|
||||||
APPSRC += clixon_netconf_ssh_callhome_client.c
|
APPSRC += clixon_netconf_ssh_callhome_client.c
|
||||||
ifdef with_restconf
|
ifdef with_restconf
|
||||||
APPSRC += clixon_util_stream.c # Needs curl
|
APPSRC += clixon_util_stream.c # Needs curl
|
||||||
|
APPSRC += clixon_restconf_callhome_client.c
|
||||||
endif
|
endif
|
||||||
ifdef with_http2
|
ifdef with_http2
|
||||||
APPSRC += clixon_util_ssl.c # requires http/2
|
APPSRC += clixon_util_ssl.c # requires http/2
|
||||||
|
|
@ -156,13 +157,13 @@ ifdef with_restconf
|
||||||
clixon_util_stream: clixon_util_stream.c $(LIBDEPS)
|
clixon_util_stream: clixon_util_stream.c $(LIBDEPS)
|
||||||
$(CC) $(INCLUDES) $(CPPFLAGS) @CFLAGS@ $(LDFLAGS) $^ $(LIBS) -lcurl -o $@
|
$(CC) $(INCLUDES) $(CPPFLAGS) @CFLAGS@ $(LDFLAGS) $^ $(LIBS) -lcurl -o $@
|
||||||
|
|
||||||
clixon_util_ssl: clixon_util_ssl.c $(LIBDEPS)
|
clixon_restconf_callhome_client: clixon_restconf_callhome_client.c $(LIBDEPS)
|
||||||
$(CC) $(INCLUDES) $(CPPFLAGS) @CFLAGS@ $(LDFLAGS) $^ $(LIBS) -o $@
|
|
||||||
|
|
||||||
clixon_util_grpc: clixon_util_grpc.c $(LIBDEPS)
|
|
||||||
$(CC) $(INCLUDES) $(CPPFLAGS) @CFLAGS@ $(LDFLAGS) $^ $(LIBS) -o $@
|
$(CC) $(INCLUDES) $(CPPFLAGS) @CFLAGS@ $(LDFLAGS) $^ $(LIBS) -o $@
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
#clixon_util_grpc: clixon_util_grpc.c $(LIBDEPS)
|
||||||
|
# $(CC) $(INCLUDES) $(CPPFLAGS) @CFLAGS@ $(LDFLAGS) $^ $(LIBS) -o $@
|
||||||
|
|
||||||
distclean: clean
|
distclean: clean
|
||||||
rm -f Makefile *~ .depend
|
rm -f Makefile *~ .depend
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -105,6 +105,39 @@ callhome_connect(struct sockaddr *sa,
|
||||||
return retval;
|
return retval;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* @see clixon_inet2sin */
|
||||||
|
static int
|
||||||
|
inet2sin(const char *addrtype,
|
||||||
|
const char *addrstr,
|
||||||
|
uint16_t port,
|
||||||
|
struct sockaddr *sa,
|
||||||
|
size_t *sa_len)
|
||||||
|
{
|
||||||
|
struct sockaddr_in6 *sin6;
|
||||||
|
struct sockaddr_in *sin;
|
||||||
|
|
||||||
|
if (strcmp(addrtype, "inet:ipv6-address") == 0) {
|
||||||
|
sin6 = (struct sockaddr_in6 *)sa;
|
||||||
|
*sa_len = sizeof(struct sockaddr_in6);
|
||||||
|
sin6->sin6_port = htons(port);
|
||||||
|
sin6->sin6_family = AF_INET6;
|
||||||
|
inet_pton(AF_INET6, addrstr, &sin6->sin6_addr);
|
||||||
|
}
|
||||||
|
else if (strcmp(addrtype, "inet:ipv4-address") == 0) {
|
||||||
|
sin = (struct sockaddr_in *)sa;
|
||||||
|
*sa_len = sizeof(struct sockaddr_in);
|
||||||
|
sin->sin_family = AF_INET;
|
||||||
|
sin->sin_port = htons(port);
|
||||||
|
sin->sin_addr.s_addr = inet_addr(addrstr);
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
fprintf(stderr, "Unexpected addrtype: %s\n", addrtype);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static int
|
static int
|
||||||
ssh_server_exec(int s,
|
ssh_server_exec(int s,
|
||||||
char *sshdbin,
|
char *sshdbin,
|
||||||
|
|
@ -220,10 +253,8 @@ main(int argc,
|
||||||
int c;
|
int c;
|
||||||
char *family = "ipv4";
|
char *family = "ipv4";
|
||||||
char *addr = NULL;
|
char *addr = NULL;
|
||||||
struct sockaddr *sa;
|
struct sockaddr sa = {0, };
|
||||||
struct sockaddr_in6 sin6 = { 0 };
|
size_t sa_len;
|
||||||
struct sockaddr_in sin = { 0 };
|
|
||||||
size_t sin_len;
|
|
||||||
int dbg = 0;
|
int dbg = 0;
|
||||||
uint16_t port = NETCONF_CH_SSH;
|
uint16_t port = NETCONF_CH_SSH;
|
||||||
int s = -1;
|
int s = -1;
|
||||||
|
|
@ -273,6 +304,10 @@ main(int argc,
|
||||||
usage(argv[0]);
|
usage(argv[0]);
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
|
#if 1
|
||||||
|
if (inet2sin(family, addr, port, &sa, &sa_len) < 0)
|
||||||
|
goto done;
|
||||||
|
#else
|
||||||
if (strcmp(family, "ipv6") == 0){
|
if (strcmp(family, "ipv6") == 0){
|
||||||
sin_len = sizeof(struct sockaddr_in6);
|
sin_len = sizeof(struct sockaddr_in6);
|
||||||
sin6.sin6_port = htons(port);
|
sin6.sin6_port = htons(port);
|
||||||
|
|
@ -291,7 +326,8 @@ main(int argc,
|
||||||
fprintf(stderr, "-f <%s> is invalid family\n", family);
|
fprintf(stderr, "-f <%s> is invalid family\n", family);
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
if (callhome_connect(sa, sin_len, &s) < 0)
|
#endif
|
||||||
|
if (callhome_connect(&sa, sa_len, &s) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
/* For some reason this sshd returns -1 which is unclear why */
|
/* For some reason this sshd returns -1 which is unclear why */
|
||||||
if (ssh_server_exec(s, sshdbin, sshdconfigfile, clixonconfigfile, dbg) < 0)
|
if (ssh_server_exec(s, sshdbin, sshdconfigfile, clixonconfigfile, dbg) < 0)
|
||||||
|
|
|
||||||
537
util/clixon_restconf_callhome_client.c
Normal file
537
util/clixon_restconf_callhome_client.c
Normal file
|
|
@ -0,0 +1,537 @@
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
***** BEGIN LICENSE BLOCK *****
|
||||||
|
|
||||||
|
Copyright (C) 2022 Olof Hagsand and Rubicon Communications, LLC (Netgate)
|
||||||
|
|
||||||
|
This file is part of CLIXON.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
|
||||||
|
Alternatively, the contents of this file may be used under the terms of
|
||||||
|
the GNU General Public License Version 3 or later (the "GPL"),
|
||||||
|
in which case the provisions of the GPL are applicable instead
|
||||||
|
of those above. If you wish to allow use of your version of this file only
|
||||||
|
under the terms of the GPL, and not to allow others to
|
||||||
|
use your version of this file under the terms of Apache License version 2,
|
||||||
|
indicate your decision by deleting the provisions above and replace them with
|
||||||
|
the notice and other provisions required by the GPL. If you do not delete
|
||||||
|
the provisions above, a recipient may use your version of this file under
|
||||||
|
the terms of any one of the Apache License version 2 or the GPL.
|
||||||
|
|
||||||
|
***** END LICENSE BLOCK *****
|
||||||
|
|
||||||
|
See RFC 8071 NETCONF Call Home and RESTCONF Call Home
|
||||||
|
|
||||||
|
device/server client
|
||||||
|
+-----------------+ 1) tcp connect +-----------------+
|
||||||
|
| clixon_restconf | ----------------> | callhome-client | <------ 3) HTTP
|
||||||
|
| | 2) tls | |
|
||||||
|
+-----------------+ <--------------- +-----------------+
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifdef HAVE_CONFIG_H
|
||||||
|
#include "clixon_config.h" /* generated by config & autoconf */
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <syslog.h>
|
||||||
|
#include <signal.h>
|
||||||
|
#include <netdb.h> /* gethostbyname */
|
||||||
|
#include <arpa/inet.h> /* inet_pton */
|
||||||
|
#include <netinet/tcp.h> /* TCP_NODELAY */
|
||||||
|
|
||||||
|
#include <openssl/ssl.h>
|
||||||
|
|
||||||
|
/* cligen */
|
||||||
|
#include <cligen/cligen.h>
|
||||||
|
|
||||||
|
/* clixon */
|
||||||
|
#include "clixon/clixon.h"
|
||||||
|
|
||||||
|
#define UTIL_TLS_OPTS "hD:f:F:a:p:c:C:k:"
|
||||||
|
|
||||||
|
#define RESTCONF_CH_TLS 4336
|
||||||
|
|
||||||
|
/* User struct for context / accept */
|
||||||
|
typedef struct {
|
||||||
|
int ta_ss; /* accept socket */
|
||||||
|
SSL_CTX *ta_ctx; /* SSL context */
|
||||||
|
FILE *ta_f; /* Input data file */
|
||||||
|
} tls_accept_handle;
|
||||||
|
|
||||||
|
/* User connection-specific data handle */
|
||||||
|
typedef struct {
|
||||||
|
int sd_s; /* data socket */
|
||||||
|
SSL *sd_ssl; /* SSL connection data */
|
||||||
|
} tls_session_data;
|
||||||
|
|
||||||
|
/*! Create and bind stream socket
|
||||||
|
* @param[in] sa Socketaddress
|
||||||
|
* @param[in] sa_len Length of sa. Tecynicaliyu to be independent of sockaddr sa_len
|
||||||
|
* @param[in] backlog Listen backlog, queie of pending connections
|
||||||
|
* @param[out] sock Server socket (bound for accept)
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
callhome_bind(struct sockaddr *sa,
|
||||||
|
size_t sin_len,
|
||||||
|
int backlog,
|
||||||
|
int *sock)
|
||||||
|
{
|
||||||
|
int retval = -1;
|
||||||
|
int s = -1;
|
||||||
|
int on = 1;
|
||||||
|
|
||||||
|
if (sock == NULL){
|
||||||
|
errno = EINVAL;
|
||||||
|
perror("sock");
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
/* create inet socket */
|
||||||
|
if ((s = socket(sa->sa_family, SOCK_STREAM, 0)) < 0) {
|
||||||
|
perror("socket");
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
if (setsockopt(s, SOL_SOCKET, SO_KEEPALIVE, (void *)&on, sizeof(on)) == -1) {
|
||||||
|
perror("setsockopt SO_KEEPALIVE");
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (void *)&on, sizeof(on)) == -1) {
|
||||||
|
perror("setsockopt SO_REUSEADDR");
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
/* only bind ipv6, otherwise it may bind to ipv4 as well which is strange but seems default */
|
||||||
|
if (sa->sa_family == AF_INET6 &&
|
||||||
|
setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY, &on, sizeof(on)) == -1) {
|
||||||
|
perror("setsockopt IPPROTO_IPV6");
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
if (bind(s, sa, sin_len) == -1) {
|
||||||
|
perror("bind");
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
if (listen(s, backlog) < 0){
|
||||||
|
perror("listen");
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
if (sock)
|
||||||
|
*sock = s;
|
||||||
|
retval = 0;
|
||||||
|
done:
|
||||||
|
if (retval != 0 && s != -1)
|
||||||
|
close(s);
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*! Client data socket
|
||||||
|
*/
|
||||||
|
static int
|
||||||
|
tls_input_cb(int s,
|
||||||
|
void *arg)
|
||||||
|
{
|
||||||
|
int retval = -1;
|
||||||
|
tls_session_data *sd = (tls_session_data *)arg;
|
||||||
|
SSL *ssl;
|
||||||
|
char buf[1024];
|
||||||
|
int n;
|
||||||
|
|
||||||
|
clicon_debug(1, "%s", __FUNCTION__);
|
||||||
|
ssl = sd->sd_ssl;
|
||||||
|
/* get reply & decrypt */
|
||||||
|
if ((n = SSL_read(ssl, buf, sizeof(buf))) < 0){
|
||||||
|
clicon_err(OE_XML, errno, "SSL_read");
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
if (n == 0){
|
||||||
|
clicon_debug(1, "%s closed", __FUNCTION__);
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
buf[n] = 0;
|
||||||
|
fprintf(stdout, "%s\n", buf);
|
||||||
|
SSL_shutdown(ssl);
|
||||||
|
SSL_free(ssl);
|
||||||
|
clixon_event_unreg_fd(s, tls_input_cb);
|
||||||
|
close(s);
|
||||||
|
free(sd);
|
||||||
|
clixon_exit_set(1); /* XXX more elaborate logic: 1) continue request, 2) close and accept new */
|
||||||
|
done:
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*! Create ssl connection, select alpn, connect and verify
|
||||||
|
*/
|
||||||
|
static int
|
||||||
|
tls_ssl_init_connect(SSL_CTX *ctx,
|
||||||
|
int s,
|
||||||
|
SSL **sslp)
|
||||||
|
{
|
||||||
|
int retval = -1;
|
||||||
|
SSL *ssl = NULL;
|
||||||
|
unsigned char protos[10];
|
||||||
|
int ret;
|
||||||
|
int verify;
|
||||||
|
int sslerr;
|
||||||
|
|
||||||
|
/* create new SSL connection state */
|
||||||
|
if ((ssl = SSL_new(ctx)) == NULL){
|
||||||
|
clicon_err(OE_SSL, 0, "SSL_new.");
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
SSL_set_fd(ssl, s); /* attach the socket descriptor */
|
||||||
|
SSL_set_mode(ssl, SSL_MODE_AUTO_RETRY);
|
||||||
|
|
||||||
|
protos[0] = 8;
|
||||||
|
strncpy((char*)&protos[1], "http/1.1", 9);
|
||||||
|
if ((retval = SSL_set_alpn_protos(ssl, protos, 9)) != 0){
|
||||||
|
clicon_err(OE_SSL, retval, "SSL_set_alpn_protos.");
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
#if 0
|
||||||
|
SSL_get0_next_proto_negotiated(conn_.tls.ssl, &next_proto, &next_proto_len);
|
||||||
|
SSL_get0_alpn_selected(conn_.tls.ssl, &next_proto, &next_proto_len);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* perform the connection
|
||||||
|
TLSEXT_TYPE_application_layer_protocol_negotiation
|
||||||
|
int SSL_set_alpn_protos(SSL *ssl, const unsigned char *protos,
|
||||||
|
unsigned int protos_len);
|
||||||
|
see
|
||||||
|
https://www.openssl.org/docs/man3.0/man3/SSL_CTX_set_alpn_select_cb.html
|
||||||
|
*/
|
||||||
|
if ((ret = SSL_connect(ssl)) < 1){
|
||||||
|
sslerr = SSL_get_error(ssl, ret);
|
||||||
|
clicon_debug(1, "%s SSL_read() n:%d errno:%d sslerr:%d", __FUNCTION__, ret, errno, sslerr);
|
||||||
|
|
||||||
|
switch (sslerr){
|
||||||
|
case SSL_ERROR_SSL: /* 1 */
|
||||||
|
goto done;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
clicon_err(OE_XML, errno, "SSL_connect");
|
||||||
|
goto done;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* check certificate verification result */
|
||||||
|
verify = SSL_get_verify_result(ssl);
|
||||||
|
switch (verify) {
|
||||||
|
case X509_V_OK:
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
clicon_err(OE_SSL, errno, "verify problems: %d", verify);
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
*sslp = ssl;
|
||||||
|
retval = 0;
|
||||||
|
done:
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*! Read data from file/stdin and write to TLS data socket
|
||||||
|
*/
|
||||||
|
static int
|
||||||
|
tls_write_file(FILE *fp,
|
||||||
|
SSL *ssl)
|
||||||
|
{
|
||||||
|
int retval = -1;
|
||||||
|
char *buf = NULL;
|
||||||
|
int buflen = 1024; /* start size */
|
||||||
|
char ch;
|
||||||
|
int ret;
|
||||||
|
int sslerr;
|
||||||
|
size_t len = 0;
|
||||||
|
|
||||||
|
if ((buf = malloc(buflen)) == NULL){
|
||||||
|
clicon_err(OE_UNIX, errno, "malloc");
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
memset(buf, 0, buflen);
|
||||||
|
while (1){
|
||||||
|
if ((ret = fread(&ch, 1, 1, fp)) < 0){
|
||||||
|
clicon_err(OE_JSON, errno, "read");
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
if (ret == 0)
|
||||||
|
break;
|
||||||
|
buf[len++] = ch;
|
||||||
|
// XXX No realloc, can overflow
|
||||||
|
}
|
||||||
|
if ((ret = SSL_write(ssl, buf, len)) < 1){
|
||||||
|
sslerr = SSL_get_error(ssl, ret);
|
||||||
|
clicon_debug(1, "%s SSL_read() n:%d errno:%d sslerr:%d", __FUNCTION__, ret, errno, sslerr);
|
||||||
|
}
|
||||||
|
retval = 0;
|
||||||
|
done:
|
||||||
|
if (buf)
|
||||||
|
free(buf);
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*! Callhome-server accept socket
|
||||||
|
*/
|
||||||
|
static int
|
||||||
|
tls_accept_cb(int ss,
|
||||||
|
void *arg)
|
||||||
|
{
|
||||||
|
int retval = -1;
|
||||||
|
tls_accept_handle *ta = (tls_accept_handle *)arg;
|
||||||
|
tls_session_data *sd = NULL;
|
||||||
|
int s;
|
||||||
|
struct sockaddr from = {0,};
|
||||||
|
socklen_t len;
|
||||||
|
SSL *ssl = NULL;
|
||||||
|
|
||||||
|
clicon_debug(1, "%s", __FUNCTION__);
|
||||||
|
len = sizeof(from);
|
||||||
|
if ((s = accept(ss, &from, &len)) < 0){
|
||||||
|
perror("accept");
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
clicon_debug(1, "accepted");
|
||||||
|
if (tls_ssl_init_connect(ta->ta_ctx, s, &ssl) < 0)
|
||||||
|
goto done;
|
||||||
|
clicon_debug(1, "connected");
|
||||||
|
if ((sd = malloc(sizeof(*sd))) == NULL){
|
||||||
|
clicon_err(OE_UNIX, errno, "malloc");
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
memset(sd, 0, sizeof(*sd));
|
||||||
|
sd->sd_s = s;
|
||||||
|
sd->sd_ssl = ssl;
|
||||||
|
/* Write HTTP request on socket */
|
||||||
|
if (tls_write_file(ta->ta_f, ssl) < 0)
|
||||||
|
goto done;
|
||||||
|
/* register callback for reply */
|
||||||
|
if (clixon_event_reg_fd(s, tls_input_cb, sd, "tls data") < 0)
|
||||||
|
goto done;
|
||||||
|
retval = 0;
|
||||||
|
done:
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* out must be set to point to the selected protocol (which may be within in).
|
||||||
|
*/
|
||||||
|
static int
|
||||||
|
tls_proto_select_cb(SSL *s,
|
||||||
|
unsigned char **out,
|
||||||
|
unsigned char *outlen,
|
||||||
|
const unsigned char *in,
|
||||||
|
unsigned int inlen,
|
||||||
|
void *arg)
|
||||||
|
{
|
||||||
|
clicon_debug(1, "%s", __FUNCTION__);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*! Verify tls auth
|
||||||
|
* @see tlsauth_verify_callback
|
||||||
|
* This code needs a "X509 store", see X509_STORE_new()
|
||||||
|
* crl_file / crl_dir
|
||||||
|
*/
|
||||||
|
static int
|
||||||
|
tls_auth_verify_callback(int preverify_ok,
|
||||||
|
X509_STORE_CTX *x509_ctx)
|
||||||
|
{
|
||||||
|
return 1; /* success */
|
||||||
|
}
|
||||||
|
|
||||||
|
static SSL_CTX *
|
||||||
|
tls_ctx_init(const char *cert_path,
|
||||||
|
const char *key_path,
|
||||||
|
const char *ca_cert_path)
|
||||||
|
{
|
||||||
|
SSL_CTX *ctx = NULL;
|
||||||
|
|
||||||
|
if ((ctx = SSL_CTX_new(TLS_client_method())) == NULL) {
|
||||||
|
clicon_err(OE_SSL, 0, "SSL_CTX_new");
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, tls_auth_verify_callback);
|
||||||
|
/* get peer certificate
|
||||||
|
nc_client_tls_update_opts */
|
||||||
|
if (SSL_CTX_use_certificate_file(ctx, cert_path, SSL_FILETYPE_PEM) != 1) {
|
||||||
|
clicon_err(OE_SSL, 0, "SSL_CTX_use_certificate_file");
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
if (SSL_CTX_use_PrivateKey_file(ctx, key_path, SSL_FILETYPE_PEM) != 1) {
|
||||||
|
clicon_err(OE_SSL, 0, "SSL_CTX_use_PrivateKey_file");
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
if (SSL_CTX_load_verify_locations(ctx, ca_cert_path, NULL) != 1) {
|
||||||
|
clicon_err(OE_SSL, 0, "SSL_CTX_load_verify_locations");
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
(void)SSL_CTX_set_next_proto_select_cb(ctx, tls_proto_select_cb, NULL);
|
||||||
|
return ctx;
|
||||||
|
done:
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
static int
|
||||||
|
usage(char *argv0)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "usage:%s [options]\n"
|
||||||
|
"where options are\n"
|
||||||
|
"\t-h \t\tHelp\n"
|
||||||
|
"\t-D <level> \tDebug\n"
|
||||||
|
"\t-f <file> \tHHTP input file (overrides stdin)\n"
|
||||||
|
"\t-F ipv4|ipv6 \tSocket address family(ipv4 default)\n"
|
||||||
|
"\t-a <addrstr> \tIP address (eg 1.2.3.4) - mandatory\n"
|
||||||
|
"\t-p <port> \tPort (default %d)\n"
|
||||||
|
"\t-c <path> \tcert\n"
|
||||||
|
"\t-C <path> \tcacert\n"
|
||||||
|
"\t-k <path> \tkey\n"
|
||||||
|
,
|
||||||
|
argv0,
|
||||||
|
RESTCONF_CH_TLS);
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
main(int argc,
|
||||||
|
char **argv)
|
||||||
|
{
|
||||||
|
int retval = -1;
|
||||||
|
clicon_handle h;
|
||||||
|
int c;
|
||||||
|
uint16_t port = RESTCONF_CH_TLS;
|
||||||
|
SSL_CTX *ctx = NULL;
|
||||||
|
int ss = -1;
|
||||||
|
int dbg = 0;
|
||||||
|
tls_accept_handle *ta = NULL;
|
||||||
|
char *input_filename = NULL;
|
||||||
|
char *ca_cert_path = NULL;
|
||||||
|
char *cert_path = NULL;
|
||||||
|
char *key_path = NULL;
|
||||||
|
FILE *fp = stdin; /* base file, stdin, can be overridden with -f */
|
||||||
|
size_t sa_len;
|
||||||
|
char *addr = "127.0.0.1";
|
||||||
|
char *family = "inet:ipv4-address";
|
||||||
|
struct sockaddr sa = {0,};
|
||||||
|
|
||||||
|
/* In the startup, logs to stderr & debug flag set later */
|
||||||
|
clicon_log_init(__FILE__, LOG_INFO, CLICON_LOG_STDERR);
|
||||||
|
|
||||||
|
if ((h = clicon_handle_init()) == NULL)
|
||||||
|
goto done;
|
||||||
|
while ((c = getopt(argc, argv, UTIL_TLS_OPTS)) != -1)
|
||||||
|
switch (c) {
|
||||||
|
case 'h':
|
||||||
|
usage(argv[0]);
|
||||||
|
break;
|
||||||
|
case 'D':
|
||||||
|
if (sscanf(optarg, "%d", &dbg) != 1)
|
||||||
|
usage(argv[0]);
|
||||||
|
break;
|
||||||
|
case 'f':
|
||||||
|
if (optarg == NULL || *optarg == '-')
|
||||||
|
usage(argv[0]);
|
||||||
|
input_filename = optarg;
|
||||||
|
break;
|
||||||
|
case 'F':
|
||||||
|
family = optarg;
|
||||||
|
break;
|
||||||
|
case 'a':
|
||||||
|
addr = optarg;
|
||||||
|
break;
|
||||||
|
case 'p':
|
||||||
|
if (sscanf(optarg, "%hu", &port) != 1)
|
||||||
|
usage(argv[0]);
|
||||||
|
break;
|
||||||
|
case 'c':
|
||||||
|
if (optarg == NULL || *optarg == '-')
|
||||||
|
usage(argv[0]);
|
||||||
|
cert_path = optarg;
|
||||||
|
break;
|
||||||
|
case 'C':
|
||||||
|
if (optarg == NULL || *optarg == '-')
|
||||||
|
usage(argv[0]);
|
||||||
|
ca_cert_path = optarg;
|
||||||
|
break;
|
||||||
|
case 'k':
|
||||||
|
if (optarg == NULL || *optarg == '-')
|
||||||
|
usage(argv[0]);
|
||||||
|
key_path = optarg;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
usage(argv[0]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (cert_path == NULL || key_path == NULL || ca_cert_path == NULL){
|
||||||
|
fprintf(stderr, "-c <cert path> and -k <key path> -C <ca-cert> are mandatory\n");
|
||||||
|
usage(argv[0]);
|
||||||
|
}
|
||||||
|
clicon_debug_init(dbg, NULL);
|
||||||
|
|
||||||
|
if (input_filename){
|
||||||
|
if ((fp = fopen(input_filename, "r")) == NULL){
|
||||||
|
clicon_err(OE_YANG, errno, "open(%s)", input_filename);
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ((ctx = tls_ctx_init(cert_path, key_path, ca_cert_path)) == NULL)
|
||||||
|
goto done;
|
||||||
|
if (port == 0){
|
||||||
|
fprintf(stderr, "-p <port> is invalid\n");
|
||||||
|
usage(argv[0]);
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
if (addr == NULL){
|
||||||
|
fprintf(stderr, "-a <addr> is NULL\n");
|
||||||
|
usage(argv[0]);
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
if (clixon_inet2sin(family, addr, port, &sa, &sa_len) < 0)
|
||||||
|
goto done;
|
||||||
|
/* Bind port */
|
||||||
|
if (callhome_bind(&sa, sa_len, 1, &ss) < 0)
|
||||||
|
goto done;
|
||||||
|
clicon_debug(1, "bind");
|
||||||
|
if ((ta = malloc(sizeof(*ta))) == NULL){
|
||||||
|
clicon_err(OE_UNIX, errno, "malloc");
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
memset(ta, 0, sizeof(*ta));
|
||||||
|
ta->ta_ctx = ctx;
|
||||||
|
ta->ta_ss = ss;
|
||||||
|
ta->ta_f = fp;
|
||||||
|
if (clixon_event_reg_fd(ss, tls_accept_cb, ta, "tls accept socket") < 0)
|
||||||
|
goto done;
|
||||||
|
if (clixon_event_loop(h) < 0)
|
||||||
|
goto done;
|
||||||
|
retval = 0;
|
||||||
|
done:
|
||||||
|
if (ss != -1)
|
||||||
|
clixon_event_unreg_fd(ss, tls_accept_cb);
|
||||||
|
if (ta)
|
||||||
|
free(ta);
|
||||||
|
if (fp)
|
||||||
|
fclose(fp);
|
||||||
|
if (ss != -1)
|
||||||
|
close(ss);
|
||||||
|
if (ctx)
|
||||||
|
SSL_CTX_free(ctx); /* release context */
|
||||||
|
clicon_handle_exit(h); /* frees h and options (and streams) */
|
||||||
|
clixon_err_exit();
|
||||||
|
clicon_debug(1, "clixon_restconf_callhome_client pid:%u done", getpid());
|
||||||
|
clicon_log_exit(); /* Must be after last clicon_debug */
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
|
@ -46,7 +46,7 @@ YANGSPECS = clixon-config@2022-03-21.yang # 5.7
|
||||||
YANGSPECS += clixon-lib@2021-12-05.yang # 5.5
|
YANGSPECS += clixon-lib@2021-12-05.yang # 5.5
|
||||||
YANGSPECS += clixon-rfc5277@2008-07-01.yang
|
YANGSPECS += clixon-rfc5277@2008-07-01.yang
|
||||||
YANGSPECS += clixon-xml-changelog@2019-03-21.yang
|
YANGSPECS += clixon-xml-changelog@2019-03-21.yang
|
||||||
YANGSPECS += clixon-restconf@2022-03-21.yang # 5.7
|
YANGSPECS += clixon-restconf@2022-08-01.yang # 5.9
|
||||||
YANGSPECS += clixon-autocli@2022-02-11.yang # 5.6
|
YANGSPECS += clixon-autocli@2022-02-11.yang # 5.6
|
||||||
|
|
||||||
all:
|
all:
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ module clixon-restconf {
|
||||||
in clixon-config
|
in clixon-config
|
||||||
|
|
||||||
***** BEGIN LICENSE BLOCK *****
|
***** BEGIN LICENSE BLOCK *****
|
||||||
Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC(Netgate)
|
Copyright (C) 2020-2022 Olof Hagsand and Rubicon Communications, LLC(Netgate)
|
||||||
|
|
||||||
This file is part of CLIXON
|
This file is part of CLIXON
|
||||||
|
|
||||||
|
|
@ -53,6 +53,17 @@ module clixon-restconf {
|
||||||
|
|
||||||
***** END LICENSE BLOCK *****";
|
***** END LICENSE BLOCK *****";
|
||||||
|
|
||||||
|
revision 2022-08-01 {
|
||||||
|
description
|
||||||
|
"Added socket/call-home container
|
||||||
|
Released in Clixon 5.9";
|
||||||
|
}
|
||||||
|
revision 2022-03-21 {
|
||||||
|
description
|
||||||
|
"Added feature:
|
||||||
|
http-data - Limited static http server
|
||||||
|
Released in Clixon 5.7";
|
||||||
|
}
|
||||||
revision 2021-05-20 {
|
revision 2021-05-20 {
|
||||||
description
|
description
|
||||||
"Added log-destination for restconf
|
"Added log-destination for restconf
|
||||||
|
|
@ -74,7 +85,6 @@ module clixon-restconf {
|
||||||
description
|
description
|
||||||
"Initial release";
|
"Initial release";
|
||||||
}
|
}
|
||||||
|
|
||||||
feature fcgi {
|
feature fcgi {
|
||||||
description
|
description
|
||||||
"This feature indicates that the restconf server supports the fast-cgi reverse
|
"This feature indicates that the restconf server supports the fast-cgi reverse
|
||||||
|
|
@ -89,6 +99,19 @@ module clixon-restconf {
|
||||||
"This feature allows the use of authentication-type none.";
|
"This feature allows the use of authentication-type none.";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
feature http-data {
|
||||||
|
description
|
||||||
|
"This feature allows for a very limited static http-data function as
|
||||||
|
addition to RESTCONF.
|
||||||
|
It is limited to:
|
||||||
|
1. path: Local static files within WWW_DATA_ROOT
|
||||||
|
2. operation GET, HEAD, OPTIONS
|
||||||
|
3. query parameters not supported
|
||||||
|
4. indata should be NULL (no write operations)
|
||||||
|
5. Limited media: text/html, JavaScript, image, and css
|
||||||
|
6. Authentication as restconf
|
||||||
|
7. HTTP/1+2, TLS as restconf";
|
||||||
|
}
|
||||||
typedef http-auth-type {
|
typedef http-auth-type {
|
||||||
type enumeration {
|
type enumeration {
|
||||||
enum none {
|
enum none {
|
||||||
|
|
@ -140,6 +163,14 @@ module clixon-restconf {
|
||||||
For example, if the restconf daemon is under systemd management, the restconf
|
For example, if the restconf daemon is under systemd management, the restconf
|
||||||
daemon will only start if enable=true.";
|
daemon will only start if enable=true.";
|
||||||
}
|
}
|
||||||
|
leaf enable-http-data {
|
||||||
|
type boolean;
|
||||||
|
default "false";
|
||||||
|
if-feature "http-data";
|
||||||
|
description
|
||||||
|
"Enables Limited static http-data functionality.
|
||||||
|
enable must be true for this option to be meaningful.";
|
||||||
|
}
|
||||||
leaf auth-type {
|
leaf auth-type {
|
||||||
type http-auth-type;
|
type http-auth-type;
|
||||||
description
|
description
|
||||||
|
|
@ -240,6 +271,38 @@ module clixon-restconf {
|
||||||
default true;
|
default true;
|
||||||
description "Enable for HTTPS otherwise HTTP protocol";
|
description "Enable for HTTPS otherwise HTTP protocol";
|
||||||
}
|
}
|
||||||
|
/* Some of this in-lined from ietf-restconf-server@2022-05-24.yang */
|
||||||
|
container call-home {
|
||||||
|
presence
|
||||||
|
"Identifies that the server has been configured to initiate
|
||||||
|
call home connections.
|
||||||
|
If set, address/port refers to destination.";
|
||||||
|
description
|
||||||
|
"See RFC 8071 NETCONF Call Home and RESTCONF Call Home";
|
||||||
|
container connection-type {
|
||||||
|
description
|
||||||
|
"Indicates the RESTCONF server's preference for how the
|
||||||
|
RESTCONF connection is maintained.";
|
||||||
|
choice connection-type {
|
||||||
|
mandatory true;
|
||||||
|
description
|
||||||
|
"Selects between available connection types.";
|
||||||
|
case persistent-connection {
|
||||||
|
container persistent {
|
||||||
|
presence
|
||||||
|
"Indicates that a persistent connection is to be
|
||||||
|
maintained.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case periodic-connection {
|
||||||
|
container periodic {
|
||||||
|
presence
|
||||||
|
"Indicates periodic connects";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
container restconf {
|
container restconf {
|
||||||
Loading…
Add table
Add a link
Reference in a new issue