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
|
||||
Expected: September 2022
|
||||
|
||||
### New features
|
||||
|
||||
* RESTCONF call home according to RFC 8071
|
||||
|
||||
## 5.8.0
|
||||
28 July 2022
|
||||
|
||||
|
|
|
|||
|
|
@ -455,6 +455,8 @@ restconf_http1_path_root(clicon_handle h,
|
|||
retval = 0;
|
||||
done:
|
||||
clicon_debug(1, "%s %d", __FUNCTION__, retval);
|
||||
if (subject)
|
||||
free(subject);
|
||||
if (xerr)
|
||||
xml_free(xerr);
|
||||
if (cvv)
|
||||
|
|
|
|||
|
|
@ -820,6 +820,7 @@ restconf_config_init(clicon_handle h,
|
|||
goto done;
|
||||
}
|
||||
|
||||
|
||||
/*! Create and bind restconf socket
|
||||
*
|
||||
* @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 retval = -1;
|
||||
struct sockaddr * sa;
|
||||
struct sockaddr_in6 sin6 = { 0 };
|
||||
struct sockaddr_in sin = { 0 };
|
||||
size_t sin_len;
|
||||
struct sockaddr sa = {0,};
|
||||
size_t sa_len;
|
||||
const char *netns;
|
||||
|
||||
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;
|
||||
else
|
||||
netns = netns0;
|
||||
if (strcmp(addrtype, "inet:ipv6-address") == 0) {
|
||||
sin_len = sizeof(struct sockaddr_in6);
|
||||
sin6.sin6_port = htons(port);
|
||||
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)
|
||||
if (clixon_inet2sin(addrtype, addrstr, port, &sa, &sa_len) < 0)
|
||||
goto done;
|
||||
if (clixon_netns_socket(netns, &sa, sa_len, backlog, flags, addrstr, ss) < 0)
|
||||
goto done;
|
||||
clicon_debug(1, "%s ss=%d", __FUNCTION__, *ss);
|
||||
retval = 0;
|
||||
|
|
@ -888,8 +869,9 @@ restconf_socket_init(const char *netns0,
|
|||
* @param[out] namespace
|
||||
* @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] port
|
||||
* @param[out] ssl
|
||||
* @param[out] port TCP Port
|
||||
* @param[out] ssl SSL enabled?
|
||||
* @param[out] callhome Callhome enabled?
|
||||
*/
|
||||
int
|
||||
restconf_socket_extract(clicon_handle h,
|
||||
|
|
@ -899,7 +881,8 @@ restconf_socket_extract(clicon_handle h,
|
|||
char **address,
|
||||
char **addrtype,
|
||||
uint16_t *port,
|
||||
uint16_t *ssl)
|
||||
uint16_t *ssl,
|
||||
int *callhome)
|
||||
{
|
||||
int retval = -1;
|
||||
cxobj *x;
|
||||
|
|
@ -979,6 +962,10 @@ restconf_socket_extract(clicon_handle h,
|
|||
goto done;
|
||||
}
|
||||
}
|
||||
if ((x = xpath_first(xs, nsc, "call-home")) != NULL)
|
||||
*callhome = 1;
|
||||
else
|
||||
*callhome = 0;
|
||||
retval = 0;
|
||||
done:
|
||||
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_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_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_ */
|
||||
|
||||
|
|
|
|||
|
|
@ -583,7 +583,7 @@ ssl_alpn_check(clicon_handle h,
|
|||
int ret;
|
||||
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 */
|
||||
if (alpn && alpnlen == 8 && memcmp("http/1.1", alpn, 8) == 0){
|
||||
*proto = HTTP_11;
|
||||
|
|
@ -642,23 +642,20 @@ ssl_alpn_check(clicon_handle h,
|
|||
return retval;
|
||||
} /* ssl_alpn_check */
|
||||
|
||||
/*! Accept new socket client
|
||||
|
||||
/*! Accept new socket client (ssl not ip)
|
||||
* @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)
|
||||
restconf_ssl_accept_client(clicon_handle h,
|
||||
int s,
|
||||
restconf_socket *rsock)
|
||||
{
|
||||
int retval = -1;
|
||||
restconf_socket *rsock;
|
||||
restconf_native_handle *rh = NULL;
|
||||
restconf_conn *rc = NULL;
|
||||
clicon_handle h;
|
||||
int s;
|
||||
struct sockaddr from = {0,};
|
||||
socklen_t len;
|
||||
char *name = NULL;
|
||||
int ret;
|
||||
int e;
|
||||
|
|
@ -668,30 +665,18 @@ restconf_accept_client(int fd,
|
|||
unsigned int alpnlen = 0;
|
||||
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
|
||||
#ifndef HAVE_HTTP1
|
||||
proto = HTTP_2; /* If nghttp2 only let default be 2.0 */
|
||||
#endif
|
||||
#endif
|
||||
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;
|
||||
#if 1
|
||||
if ((rh = restconf_native_handle_get(h)) == NULL){
|
||||
clicon_err(OE_XML, EFAULT, "No openssl handle");
|
||||
goto done;
|
||||
}
|
||||
len = sizeof(from);
|
||||
if ((s = accept(rsock->rs_ss, &from, &len)) < 0){
|
||||
clicon_err(OE_UNIX, errno, "accept");
|
||||
goto done;
|
||||
}
|
||||
#endif
|
||||
/*
|
||||
* Register callbacks for actual data socket
|
||||
*/
|
||||
|
|
@ -919,6 +904,50 @@ restconf_accept_client(int fd,
|
|||
if (name)
|
||||
free(name);
|
||||
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 */
|
||||
|
||||
/*!
|
||||
|
|
@ -1019,10 +1048,63 @@ restconf_clixon_backend(clicon_handle h,
|
|||
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
|
||||
* @param[in] h Clicon handle
|
||||
* @param[in] xs XML config of single restconf socket
|
||||
* @param[in] nsc Namespace context
|
||||
* @retval 0 OK
|
||||
* @retval -1 Error
|
||||
*/
|
||||
static int
|
||||
openssl_init_socket(clicon_handle h,
|
||||
|
|
@ -1033,6 +1115,7 @@ openssl_init_socket(clicon_handle h,
|
|||
char *netns = NULL;
|
||||
char *address = NULL;
|
||||
char *addrtype = NULL;
|
||||
int callhome = 0;
|
||||
uint16_t ssl = 0;
|
||||
uint16_t port = 0;
|
||||
int ss = -1;
|
||||
|
|
@ -1041,23 +1124,9 @@ openssl_init_socket(clicon_handle h,
|
|||
|
||||
clicon_debug(1, "%s", __FUNCTION__);
|
||||
/* 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;
|
||||
/* 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
|
||||
* See restconf_native_terminate for freeing
|
||||
|
|
@ -1068,8 +1137,33 @@ openssl_init_socket(clicon_handle h,
|
|||
}
|
||||
memset(rsock, 0, sizeof *rsock);
|
||||
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_ssl = ssl;
|
||||
|
||||
if ((rsock->rs_addrstr = strdup(address)) == NULL){
|
||||
clicon_err(OE_UNIX, errno, "strdup");
|
||||
goto done;
|
||||
|
|
@ -1081,29 +1175,24 @@ openssl_init_socket(clicon_handle h,
|
|||
rsock->rs_port = port;
|
||||
INSQ(rsock, rh->rh_sockets);
|
||||
|
||||
/* ss is a server socket that the clients connect to. The callback
|
||||
therefore accepts clients on ss */
|
||||
#ifdef RESTCONF_HTTP1_UNITTEST
|
||||
{
|
||||
restconf_conn *rc;
|
||||
if ((rc = restconf_conn_new(h, 0)) == NULL)
|
||||
goto done;
|
||||
rc->rc_s = 0;
|
||||
if (restconf_stream_data_new(rc, 0) == NULL)
|
||||
goto done;
|
||||
if (clixon_event_reg_fd(0, restconf_connection, rc, "restconf socket") < 0)
|
||||
if (callhome){
|
||||
if (restconf_callhome_timer(ss, rsock) < 0)
|
||||
goto done;
|
||||
}
|
||||
#else
|
||||
else {
|
||||
/* ss is a server socket that the clients connect to. The callback
|
||||
therefore accepts clients on ss */
|
||||
if (clixon_event_reg_fd(ss, restconf_accept_client, rsock, "restconf socket") < 0)
|
||||
goto done;
|
||||
#endif
|
||||
}
|
||||
retval = 0;
|
||||
done:
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*! Init openssl, open and register server socket (ready for accept)
|
||||
*
|
||||
* Given a fully populated configuration tree.
|
||||
* @param[in] h Clicon handle
|
||||
* @param[in] dbg0 Manually set debug flag, if set overrides configuration setting
|
||||
* @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)
|
||||
goto done;
|
||||
#ifndef RESTCONF_HTTP1_UNITTEST /* Ignore content-length */
|
||||
if (status == 1){
|
||||
(*readmore)++;
|
||||
goto ok;
|
||||
}
|
||||
#endif
|
||||
/* nginx compatible, set HTTPS parameter if SSL */
|
||||
if (rc->rc_ssl)
|
||||
if (restconf_param_set(h, "HTTPS", "https") < 0)
|
||||
|
|
@ -715,15 +713,9 @@ restconf_http1_process(restconf_conn *rc,
|
|||
/* main restconf processing */
|
||||
if (restconf_http1_path_root(h, rc) < 0)
|
||||
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),
|
||||
rc->rc_s, rc->rc_ssl) < 0)
|
||||
goto done;
|
||||
#endif
|
||||
cvec_reset(sd->sd_outp_hdrs); /* Can be done in native_send_reply */
|
||||
cbuf_reset(sd->sd_outp_buf);
|
||||
cbuf_reset(sd->sd_inbuf);
|
||||
|
|
@ -951,16 +943,5 @@ restconf_connection(int s,
|
|||
retval = 0;
|
||||
done:
|
||||
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;
|
||||
} /* restconf_connection */
|
||||
|
|
|
|||
|
|
@ -152,11 +152,6 @@
|
|||
*/
|
||||
#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
|
||||
* Note, if client has locked or had edits in progress, these will be lost
|
||||
* 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 clixon_inet2sin(const char *addrtype, const char *addrstr, uint16_t port, struct sockaddr *sa, size_t *sa_len);
|
||||
|
||||
#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");
|
||||
* @endcode
|
||||
* @see clixon_event_unreg_fd
|
||||
*/
|
||||
int
|
||||
clixon_event_reg_fd(int fd,
|
||||
|
|
|
|||
|
|
@ -810,3 +810,49 @@ detect_endtag(char *tag,
|
|||
*state = 0;
|
||||
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_LIB_REV="2021-12-05"
|
||||
CLIXON_CONFIG_REV="2022-03-21"
|
||||
CLIXON_RESTCONF_REV="2022-03-21"
|
||||
CLIXON_RESTCONF_REV="2022-08-01"
|
||||
CLIXON_EXAMPLE_REV="2020-12-01"
|
||||
|
||||
# Length of TSL RSA key
|
||||
|
|
|
|||
|
|
@ -61,6 +61,12 @@ function memonce(){
|
|||
|
||||
memerr=0
|
||||
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
|
||||
perfnr=1000 # Limit performance tests
|
||||
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
|
||||
ifdef with_restconf
|
||||
APPSRC += clixon_util_stream.c # Needs curl
|
||||
APPSRC += clixon_restconf_callhome_client.c
|
||||
endif
|
||||
ifdef with_http2
|
||||
APPSRC += clixon_util_ssl.c # requires http/2
|
||||
|
|
@ -156,13 +157,13 @@ ifdef with_restconf
|
|||
clixon_util_stream: clixon_util_stream.c $(LIBDEPS)
|
||||
$(CC) $(INCLUDES) $(CPPFLAGS) @CFLAGS@ $(LDFLAGS) $^ $(LIBS) -lcurl -o $@
|
||||
|
||||
clixon_util_ssl: clixon_util_ssl.c $(LIBDEPS)
|
||||
$(CC) $(INCLUDES) $(CPPFLAGS) @CFLAGS@ $(LDFLAGS) $^ $(LIBS) -o $@
|
||||
|
||||
clixon_util_grpc: clixon_util_grpc.c $(LIBDEPS)
|
||||
clixon_restconf_callhome_client: clixon_restconf_callhome_client.c $(LIBDEPS)
|
||||
$(CC) $(INCLUDES) $(CPPFLAGS) @CFLAGS@ $(LDFLAGS) $^ $(LIBS) -o $@
|
||||
endif
|
||||
|
||||
#clixon_util_grpc: clixon_util_grpc.c $(LIBDEPS)
|
||||
# $(CC) $(INCLUDES) $(CPPFLAGS) @CFLAGS@ $(LDFLAGS) $^ $(LIBS) -o $@
|
||||
|
||||
distclean: clean
|
||||
rm -f Makefile *~ .depend
|
||||
|
||||
|
|
|
|||
|
|
@ -105,6 +105,39 @@ callhome_connect(struct sockaddr *sa,
|
|||
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
|
||||
ssh_server_exec(int s,
|
||||
char *sshdbin,
|
||||
|
|
@ -220,10 +253,8 @@ main(int argc,
|
|||
int c;
|
||||
char *family = "ipv4";
|
||||
char *addr = NULL;
|
||||
struct sockaddr *sa;
|
||||
struct sockaddr_in6 sin6 = { 0 };
|
||||
struct sockaddr_in sin = { 0 };
|
||||
size_t sin_len;
|
||||
struct sockaddr sa = {0, };
|
||||
size_t sa_len;
|
||||
int dbg = 0;
|
||||
uint16_t port = NETCONF_CH_SSH;
|
||||
int s = -1;
|
||||
|
|
@ -273,6 +304,10 @@ main(int argc,
|
|||
usage(argv[0]);
|
||||
goto done;
|
||||
}
|
||||
#if 1
|
||||
if (inet2sin(family, addr, port, &sa, &sa_len) < 0)
|
||||
goto done;
|
||||
#else
|
||||
if (strcmp(family, "ipv6") == 0){
|
||||
sin_len = sizeof(struct sockaddr_in6);
|
||||
sin6.sin6_port = htons(port);
|
||||
|
|
@ -291,7 +326,8 @@ main(int argc,
|
|||
fprintf(stderr, "-f <%s> is invalid family\n", family);
|
||||
goto done;
|
||||
}
|
||||
if (callhome_connect(sa, sin_len, &s) < 0)
|
||||
#endif
|
||||
if (callhome_connect(&sa, sa_len, &s) < 0)
|
||||
goto done;
|
||||
/* For some reason this sshd returns -1 which is unclear why */
|
||||
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-rfc5277@2008-07-01.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
|
||||
|
||||
all:
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ module clixon-restconf {
|
|||
in clixon-config
|
||||
|
||||
***** 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
|
||||
|
||||
|
|
@ -53,6 +53,17 @@ module clixon-restconf {
|
|||
|
||||
***** 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 {
|
||||
description
|
||||
"Added log-destination for restconf
|
||||
|
|
@ -74,7 +85,6 @@ module clixon-restconf {
|
|||
description
|
||||
"Initial release";
|
||||
}
|
||||
|
||||
feature fcgi {
|
||||
description
|
||||
"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.";
|
||||
}
|
||||
|
||||
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 {
|
||||
type enumeration {
|
||||
enum none {
|
||||
|
|
@ -140,6 +163,14 @@ module clixon-restconf {
|
|||
For example, if the restconf daemon is under systemd management, the restconf
|
||||
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 {
|
||||
type http-auth-type;
|
||||
description
|
||||
|
|
@ -240,6 +271,38 @@ module clixon-restconf {
|
|||
default true;
|
||||
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 {
|
||||
Loading…
Add table
Add a link
Reference in a new issue