blob: 160d98d4c0b4db590fdc19ae607bde289cd1a297 [file] [log] [blame]
/*
<:copyright-BRCM:2016:DUAL/GPL:standard
Broadcom Proprietary and Confidential.(c) 2016 Broadcom
All Rights Reserved
Unless you and Broadcom execute a separate written software license
agreement governing use of this software, this software is licensed
to you under the terms of the GNU General Public License version 2
(the "GPL"), available at http://www.broadcom.com/licenses/GPLv2.php,
with the following added to such license:
As a special exception, the copyright holders of this software give
you permission to link this software with independent modules, and
to copy and distribute the resulting executable under terms of your
choice, provided that you also meet, for each linked independent
module, the terms and conditions of the license of that module.
An independent module is a module which is not derived from this
software. The special exception does not apply to any modifications
of the software.
Not withstanding the above, under no circumstances may you combine
this software in any way with any other Broadcom software provided
under a license other than the GPL, without Broadcom's express prior
written consent.
:>
*/
/*******************************************************************
* bcmcli_server.c
*
* CLI engine - remote shell support
*
* This module is a back-end of remote shell support.
* - multiple servers
* - domain and TCP-based connections
* - session access level - per server
*******************************************************************/
#include <bcmcli_server.h>
typedef struct bcmclis_server bcmclis_server_t;
/* Server connection
*/
typedef struct bcmclis_conn
{
struct bcmclis_conn *next;
bcmclis_server_t *server;
const char *address; /* client address */
int sock; /* transport socket */
bdmf_task rx_thread;
bcmcli_session *session;
uint32_t bytes_sent;
uint32_t bytes_received;
bdmf_task conn_thread;
} bcmclis_conn_t;
/* Server control bdmfock
*/
struct bcmclis_server
{
bcmclis_server_t *next;
bcmclis_conn_t *conn_list;
int sock; /* listening socket */
bcmclis_parm_t parms;
int id;
int nconns;
bcmos_fastlock lock;
bdmf_task listen_thread;
};
/* socaddr variants */
typedef union
{
struct sockaddr sa;
struct sockaddr_un domain_sa;
struct sockaddr_in tcp_sa;
} sockaddr_any;
static bcmclis_server_t *bcmclis_servers;
static int bcmclis_server_id;
static bcmclis_server_t *bcmclis_id_to_server(int hs, bcmclis_server_t **prev)
{
bcmclis_server_t *s=bcmclis_servers;
if (prev)
*prev = NULL;
while(s)
{
if (s->id == hs)
break;
if (prev)
*prev = s;
s = s->next;
}
return s;
}
/* Parse address helper */
static int bcmclis_parse_address(const bcmclis_parm_t *parms, int *protocol, sockaddr_any *sa, int *len)
{
switch(parms->transport)
{
case BCMCLI_TRANSPORT_DOMAIN_SOCKET:
{
*protocol = AF_UNIX;
sa->domain_sa.sun_family = AF_UNIX; /* local is declared before socket() ^ */
strcpy(sa->domain_sa.sun_path, parms->address);
*len = strlen(sa->domain_sa.sun_path) + sizeof(sa->domain_sa.sun_family);
break;
}
case BCMCLI_TRANSPORT_TCP_SOCKET:
{
*protocol = AF_INET;
sa->tcp_sa.sin_family = AF_INET;
sa->tcp_sa.sin_port = htons(atoi(parms->address));
sa->tcp_sa.sin_addr.s_addr = INADDR_ANY;
*len = sizeof(sa->tcp_sa);
break;
}
default:
return BCM_ERR_PARM;
}
return 0;
}
/* disconnect client and clear resources */
static void bcmclis_disconnect(bcmclis_conn_t *conn)
{
bcmclis_server_t *s=conn->server;
bcmclis_conn_t *c=s->conn_list, *prev=NULL;
bcmos_fastlock_lock(&s->lock);
while(c && c!=conn)
{
prev = c;
c = c->next;
}
BUG_ON(!c);
if (prev)
prev->next = c->next;
else
s->conn_list = c->next;
--s->nconns;
bcmos_fastlock_unlock(&s->lock);
bcmcli_session_close(c->session);
close(c->sock);
bdmf_task_destroy(c->rx_thread);
bcmos_free(c);
}
/*
* Session callbacks
*/
/** Session's output function.
* returns the number of bytes written or <0 if error
*/
static int bcmclis_cb_sess_write(void *user_priv, const void *buf, uint32_t size)
{
bcmclis_conn_t *c=user_priv;
int rc;
rc = send(c->sock, buf, size, 0);
/* disconnect if IO error */
if (rc < size)
bcmclis_disconnect(c);
else
c->bytes_sent += rc;
return rc;
}
#define CHAR_EOT 0x04
/** Session's input function.
* returns the number of bytes read or <0 if error
*/
static char *bcmclis_read_line(bcmclis_conn_t *c, char *buf, uint32_t size)
{
int i;
int rc;
int len=0;
for(i=0; i<size-1; i++)
{
char ch;
rc = recv(c->sock, &ch, 1, MSG_WAITALL);
if (rc <= 0)
break;
if (ch == '\r')
continue;
if (ch == CHAR_EOT)
break;
buf[len++] = ch;
if (ch == '\n')
break;
}
c->bytes_received += i;
buf[len] = 0;
return (len ? buf : NULL);
}
/* Receive handler */
static int bcmclis_rx_thread_handler(void *arg)
{
char buf[512];
bcmclis_conn_t *c=arg;
while(!bcmcli_is_stopped(c->session) &&
bcmclis_read_line(c, buf, sizeof(buf)))
{
bcmcli_parse(c->session, buf);
}
bcmclis_disconnect(c);
return 0;
}
/* New client connection indication */
static void bcmclis_connect(bcmclis_server_t *s, char *addr, int sock)
{
bcmclis_conn_t *c;
bcmcli_session_parm sess_parm;
int rc;
if (s->parms.max_clients && s->nconns >= s->parms.max_clients)
{
bcmos_printf("bdmfmons: server %s: refused connection because max number has been reached\n", s->parms.address);
close(sock);
return;
}
c = bcmos_calloc(sizeof(*c) + strlen(addr) + 1);
if (!c)
goto cleanup;
c->address = (char *)c + sizeof(*c);
strcpy((char *)c->address, addr);
c->server = s;
c->sock = sock;
/* create new management session */
memset(&sess_parm, 0, sizeof(sess_parm));
sess_parm.access_right = s->parms.access;
sess_parm.write = bcmclis_cb_sess_write;
sess_parm.user_priv = c;
rc = bcmcli_session_open(&sess_parm, &c->session);
if (rc)
goto cleanup;
/* wait for receive in a separate thread */
rc = bdmf_task_create("bcmclis_rx",
BDMFSYS_DEFAULT_TASK_PRIORITY,
BDMFSYS_DEFAULT_TASK_STACK,
bcmclis_rx_thread_handler, c,
&c->rx_thread);
if (rc)
goto cleanup;
bcmos_fastlock_lock(&s->lock);
c->next = s->conn_list;
s->conn_list = c;
++s->nconns;
bcmos_fastlock_unlock(&s->lock);
return;
cleanup:
close(sock);
if (c->session)
bcmcli_session_close(c->session);
if (c)
bcmos_free(c);
}
/* Receive handler */
static int bcmclis_listen_thread_handler(void *arg)
{
bcmclis_server_t *s=arg;
sockaddr_any addr;
socklen_t len;
int sock;
while(1)
{
char caddr[64];
len = sizeof(addr);
sock = accept(s->sock, &addr.sa, &len);
if (sock < 0)
{
perror("accept");
break;
}
if (s->parms.transport==BCMCLI_TRANSPORT_DOMAIN_SOCKET)
strncpy(caddr, s->parms.address, sizeof(caddr)-1);
else
{
snprintf(caddr, sizeof(caddr)-1, "%s:%d",
inet_ntoa(addr.tcp_sa.sin_addr), ntohs(addr.tcp_sa.sin_port));
}
bcmclis_connect(s, caddr, sock);
}
return 0;
}
/*
* External API
*/
/** Create shell server.
* Immediately after creation server is ready to accept client connections
* \param[in] parms Server parameters
* \param[out] hs Server handle
* \return 0 - OK\n
* <0 - error code
*/
bcmos_errno bcmclis_server_create(const bcmclis_parm_t *parms, int *hs)
{
bcmclis_server_t *s;
int protocol;
sockaddr_any sa;
int len;
int rc;
if (!parms || !hs || !parms->address)
return BCM_ERR_PARM;
/* parse address */
if (bcmclis_parse_address(parms, &protocol, &sa, &len))
return BCM_ERR_PARM;
/* allocate server structure */
s = bcmos_calloc(sizeof(bcmclis_server_t)+strlen(parms->address)+1);
if (!s)
return BCM_ERR_NOMEM;
s->parms = *parms;
s->parms.address = (char *)s + sizeof(*s);
strcpy(s->parms.address, parms->address);
s->id = ++bcmclis_server_id;
bcmos_fastlock_init(&s->lock);
/* create socket and start listening */
s->sock = socket(protocol, SOCK_STREAM, 0);
if ((s->sock < 0) ||
(bind(s->sock, &sa.sa, len) < 0) ||
(listen(s->sock, 1) < 0))
{
perror("socket/bind/listen");
close(s->sock);
bcmos_free(s);
return BCM_ERR_PARM;
}
/* wait for connection(s) in a separate thread */
rc = bdmf_task_create("bcmclis_listen",
BDMFSYS_DEFAULT_TASK_PRIORITY,
BDMFSYS_DEFAULT_TASK_STACK,
bcmclis_listen_thread_handler, s,
&s->listen_thread);
if (rc)
{
close(s->sock);
bcmos_free(s);
return rc;
}
/* all good */
s->next = bcmclis_servers;
bcmclis_servers = s;
*hs = s->id;
return 0;
}
/** Destroy shell server.
* All client connections if any are closed
* \param[in] hs Server handle
* \return 0 - OK\n
* <0 - error code
*/
bcmos_errno bcmclis_server_destroy(int hs)
{
bcmclis_server_t *prev;
bcmclis_server_t *s = bcmclis_id_to_server(hs, &prev);
bcmclis_conn_t *c;
if (!s)
return BCM_ERR_NOENT;
bdmf_task_destroy(s->listen_thread);
close(s->sock);
/* disconnect all clients */
while((c = s->conn_list))
bcmclis_disconnect(c);
/* destroy server */
bcmos_fastlock_lock(&s->lock);
if (prev)
prev->next = s->next;
else
bcmclis_servers = s->next;
bcmos_fastlock_unlock(&s->lock);
bcmos_free(s);
return 0;
}
/*
* Shell command handlers
*/
static bcmcli_enum_val transport_type_enum_tabdmfe[] = {
{ .name="domain_socket", .val=BCMCLI_TRANSPORT_DOMAIN_SOCKET},
{ .name="tcp_socket", .val=BCMCLI_TRANSPORT_TCP_SOCKET},
BCMCLI_ENUM_LAST
};
static bcmcli_enum_val access_type_enum_tabdmfe[] = {
{ .name="guest", .val=BCMCLI_ACCESS_GUEST},
{ .name="admin", .val=BCMCLI_ACCESS_ADMIN},
{ .name="debug", .val=BCMCLI_ACCESS_DEBUG},
BCMCLI_ENUM_LAST
};
/* Create remote shell server
BCMCLI_MAKE_PARM_ENUM("transport", "Transport type", transport_type_enum_tabdmfe, 0),
BCMCLI_MAKE_PARM("address", "Bind address", BCMCLI_PARM_STRING, 0),
BCMCLI_MAKE_PARM_ENUM("access", "Access level", access_type_enum_tabdmfe, 0),
BCMCLI_MAKE_PARM_DEFVAL("max_clients", "Max clients. 0=default", BCMCLI_PARM_NUMBER, 0, 0),
*/
static int bcmclis_mon_create(bcmcli_session *session, const bcmcli_cmd_parm parm[], uint16_t n_parms)
{
bcmclis_transport_type_t transport = (bcmclis_transport_type_t)parm[0].value.number;
char *address = (char *)parm[1].value.number;
bcmcli_access_right access = (bcmcli_access_right)parm[2].value.number;
int max_clients = (int)parm[3].value.number;
bcmclis_parm_t parms;
int hs;
int rc;
memset(&parms, 0, sizeof(parms));
parms.transport = transport;
parms.access = access;
parms.address = address;
parms.max_clients = max_clients;
rc = bcmclis_server_create(&parms, &hs);
if (rc)
bcmcli_session_print(session, "bcmclis_server_create() failed with rc=%d - %s\n",
rc, bcmos_strerror(rc));
else
bcmcli_session_print(session, "Remote shell server created. Server id %d\n", hs);
return rc;
}
/* Destroy remote shell server
BCMCLI_MAKE_PARM("server_id", "Server id", BCMCLI_PARM_NUMBER, 0),
*/
static int bcmclis_mon_destroy(bcmcli_session *session, const bcmcli_cmd_parm parm[], uint16_t n_parms)
{
int hs = (int)parm[0].value.number;
int rc;
rc = bcmclis_server_destroy(hs);
bcmcli_session_print(session, "Remote shell server %d destroyed. rc=%d - %s\n",
hs, rc, bcmos_strerror(rc));
return rc;
}
/* Show remote shell servers
*/
static int bcmclis_mon_show(bcmcli_session *session, const bcmcli_cmd_parm parm[], uint16_t n_parms)
{
bcmclis_server_t *s=bcmclis_servers;
bcmclis_conn_t *c;
while(s)
{
bcmcli_session_print(session, "Remote server %d at %s\n", s->id, s->parms.address);
c = s->conn_list;
while(c)
{
bcmcli_session_print(session, "\t - %s. bytes sent:%d received:%d\n",
c->address, c->bytes_sent, c->bytes_received);
c = c->next;
}
s = s->next;
}
return 0;
}
/* Create shell_server directory in root_dir
Returns the "shell_server" directory handle
*/
bcmcli_entry *bcmclis_server_mon_init(bcmcli_entry *root_dir)
{
bcmcli_entry *shell_dir;
if ((shell_dir=bcmcli_dir_find(NULL, "shell_server"))!=NULL)
return NULL;
shell_dir = bcmcli_dir_add(root_dir, "shell_server",
"Remote Shell",
BCMCLI_ACCESS_GUEST, NULL);
{
static bcmcli_cmd_parm parms[]={
BCMCLI_MAKE_PARM_ENUM("transport", "Transport type", transport_type_enum_tabdmfe, 0),
BCMCLI_MAKE_PARM("address", "Bind address: domain_socket address or TCP port", BCMCLI_PARM_STRING, 0),
BCMCLI_MAKE_PARM_ENUM("access", "Access level", access_type_enum_tabdmfe, 0),
BCMCLI_MAKE_PARM_DEFVAL("max_clients", "Max clients. 0=default", BCMCLI_PARM_NUMBER, 0, 0),
BCMCLI_PARM_LIST_TERMINATOR
};
bcmcli_cmd_add(shell_dir, "create", bcmclis_mon_create,
"Create remote shell server",
BCMCLI_ACCESS_ADMIN, NULL, parms);
}
{
static bcmcli_cmd_parm parms[]={
BCMCLI_MAKE_PARM("server_id", "Server id", BCMCLI_PARM_NUMBER, 0),
BCMCLI_PARM_LIST_TERMINATOR
};
bcmcli_cmd_add(shell_dir, "destroy", bcmclis_mon_destroy,
"Destroy remote shell server",
BCMCLI_ACCESS_ADMIN, NULL, parms);
}
{
bcmcli_cmd_add(shell_dir, "show", bcmclis_mon_show,
"Show remote shell servers",
BCMCLI_ACCESS_GUEST, NULL, NULL);
}
return shell_dir;
}
/* Destroy shell_server directory
*/
void bcmclis_server_mon_destroy(void)
{
bcmcli_entry *shell_dir;
shell_dir=bcmcli_dir_find(NULL, "shell_server");
if (shell_dir)
bcmcli_token_destroy(shell_dir);
}