/**************************************************************************** * apps/netutils/cmux/cmux.c * * SPDX-License-Identifier: Apache-2.0 * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. The * ASF licenses this file to you 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. * ****************************************************************************/ /**************************************************************************** * Included Files ****************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include "netutils/chat.h" #include "netutils/cmux.h" #include "cmux.h" /**************************************************************************** * Private Data ****************************************************************************/ #define CMUX_MIN_FRAME_LEN (5) #define CMUX_FRAME_PREFIX (5) #define CMUX_FRAME_POSFIX (2) #define CMUX_TASK_NAME ("cmux") #define CMUX_THREAD_PRIOR (100) #define CMUX_THREAD_STACK_SIZE (3072) #define cmux_inc_buffer(buf, p) \ p++; \ if (p == buf->endp) \ p = buf->data; #define cmux_buffer_length(buf) \ ((buf->readp > buf->writep) ? (CMUX_BUFFER_SZ - (buf->readp - buf->writep)) : (buf->writep - buf->readp)) #define cmux_buffer_free(buf) \ ((buf->readp > buf->writep) ? (buf->readp - buf->writep) : (CMUX_BUFFER_SZ - (buf->writep - buf->readp))) struct cmux_ctl_s { int fd; int total_ports; struct cmux_parse_s *parse; struct cmux_channel_s *channels; struct cmux_stream_buffer_s *stream; }; /**************************************************************************** * Private Functions ****************************************************************************/ /**************************************************************************** * Name: cmux_calulate_fcs * * Description: * Calculate the frame checking sequence. * ****************************************************************************/ static unsigned char cmux_calulate_fcs(const unsigned char *input, int count) { return crc8rohcpart(input, count, 0x00); } /**************************************************************************** * Name: cmux_parse_create * * Description: * Create a circular buffer to receive incoming packets. * ****************************************************************************/ static struct cmux_stream_buffer_s *cmux_stream_buffer_create(void) { struct cmux_stream_buffer_s *cmux_buffer = malloc(sizeof(struct cmux_stream_buffer_s)); if (cmux_buffer) { memset(cmux_buffer, 0, sizeof(struct cmux_stream_buffer_s)); cmux_buffer->readp = cmux_buffer->data; cmux_buffer->writep = cmux_buffer->data; cmux_buffer->endp = cmux_buffer->data + CMUX_BUFFER_SZ; } return cmux_buffer; } /**************************************************************************** * Name: cmux_buffer_write * * Description: * Write the input frames to the circular buffer. * ****************************************************************************/ static int cmux_buffer_write(struct cmux_stream_buffer_s *cmux_buffer, const char *input, int length) { int c = cmux_buffer->endp - cmux_buffer->writep; length = MIN(length, cmux_buffer_free(cmux_buffer)); if (length > c) { memcpy(cmux_buffer->writep, input, c); memcpy(cmux_buffer->data, input + c, length - c); cmux_buffer->writep = cmux_buffer->data + (length - c); } else { memcpy(cmux_buffer->writep, input, length); cmux_buffer->writep += length; if (cmux_buffer->writep == cmux_buffer->endp) { cmux_buffer->writep = cmux_buffer->data; } } return length; } /**************************************************************************** * Name: cmux_parse_reset * * Description: * Reset data buffer and parse struct. * ****************************************************************************/ static void cmux_parse_reset(struct cmux_parse_s *cmux_parse) { if (cmux_parse) { memset(cmux_parse->data, 0x00, CMUX_BUFFER_SZ); memset(cmux_parse, 0x00, sizeof(struct cmux_parse_s)); } } static int cmux_decode_frame(struct cmux_stream_buffer_s *cmux_buffer, struct cmux_parse_s *cmux_parse) { /* Minimal length to CMUX Frame : address, type, length, FCS and flag */ int length = CMUX_MIN_FRAME_LEN; unsigned char *data = NULL; unsigned char fcs = CMUX_FCS_MAX_VALUE; int end = 0; if (!cmux_buffer || !cmux_parse) { return -EACCES; } while (cmux_buffer_length(cmux_buffer) >= CMUX_MIN_FRAME_LEN) { cmux_buffer->flag_found = 0; length = CMUX_MIN_FRAME_LEN; while (!cmux_buffer->flag_found && cmux_buffer_length(cmux_buffer) > 0) { if (*cmux_buffer->readp == CMUX_OPEN_FLAG) { cmux_buffer->flag_found = 1; } cmux_inc_buffer(cmux_buffer, cmux_buffer->readp); } if (!cmux_buffer->flag_found) { return ERROR; } while (cmux_buffer_length(cmux_buffer) > 0 && (*cmux_buffer->readp == CMUX_OPEN_FLAG)) { cmux_inc_buffer(cmux_buffer, cmux_buffer->readp); } if (cmux_buffer_length(cmux_buffer) < length) { return ERROR; } data = cmux_buffer->readp; fcs = CMUX_FCS_MAX_VALUE; cmux_parse->address = ((*data & CMUX_ADDR_FIELD_CHECK) >> 2); fcs = crc8rohcincr(*data, fcs); cmux_inc_buffer(cmux_buffer, data); cmux_parse->control = *data; fcs = crc8rohcincr(*data, fcs); cmux_inc_buffer(cmux_buffer, data); cmux_parse->data_length = (*data & CMUX_LENGTH_FIELD_OPERATOR) >> 1; fcs = crc8rohcincr(*data, fcs); /* EA bit, should always have the value 1 */ if (!(*data & 1)) { cmux_buffer->readp = data; cmux_buffer->flag_found = 0; continue; } length += cmux_parse->data_length; if (!(cmux_buffer_length(cmux_buffer) >= length)) { return ERROR; } cmux_inc_buffer(cmux_buffer, data); if (cmux_parse->data_length > 0 && cmux_parse->data_length < CMUX_BUFFER_SZ) { end = cmux_buffer->endp - data; if (cmux_parse->data_length > end) { memcpy(cmux_parse->data, data, end); memcpy(cmux_parse->data + end, cmux_buffer->data, cmux_parse->data_length - end); data = cmux_buffer->data + (cmux_parse->data_length - end); } else { memcpy(cmux_parse->data, data, cmux_parse->data_length); data += cmux_parse->data_length; if (data == cmux_buffer->endp) { data = cmux_buffer->data; } } if (CMUX_FRAME_TYPE(CMUX_FRAME_TYPE_UI, cmux_parse)) { int i; for (i = 0; i < cmux_parse->data_length; i++) { fcs = crc8rohcincr(cmux_parse->data[i], fcs); } } } if (crc8rohcincr(*data, fcs) != CMUX_FCS_OPERATOR) { cmux_buffer->dropped_count++; cmux_buffer->readp = data; cmux_parse_reset(cmux_parse); continue; } cmux_inc_buffer(cmux_buffer, data); if (*data != CMUX_CLOSE_FLAG) { cmux_buffer->readp = data; cmux_buffer->dropped_count++; cmux_parse_reset(cmux_parse); continue; } cmux_buffer->received_count++; cmux_inc_buffer(cmux_buffer, data); cmux_buffer->readp = data; return OK; } return ERROR; } /**************************************************************************** * Name: cmux_encode_frame * * Description: * Encode a buffer to the CMUX protocol. * ****************************************************************************/ static int cmux_encode_frame(int fd, int channel, char *buffer, int frame_size, unsigned char type) { int prefix_len = 4; unsigned char frame_prefix[CMUX_FRAME_PREFIX] = { CMUX_OPEN_FLAG, (CMUX_ADDR_FIELD_BIT_EA | CMUX_ADDR_FIELD_BIT_CR), 0x00, 0x00, 0x00 }; unsigned char frame_posfix[CMUX_FRAME_POSFIX] = { CMUX_FCS_MAX_VALUE, CMUX_CLOSE_FLAG }; frame_prefix[CMUX_BIT1] = (frame_prefix[CMUX_BIT1] | ((CMUX_ADDR_FIELD_OPERATOR & (unsigned char)channel) << 2)); frame_prefix[CMUX_BIT2] = type; if (frame_size <= CMUX_FRAME_MAX_SIZE) { frame_prefix[CMUX_BIT3] = CMUX_ADDR_FIELD_BIT_EA | (frame_size << 1); prefix_len = 4; } else { frame_prefix[CMUX_BIT3] = (frame_size << 1) & CMUX_LENGTH_FIELD_OPERATOR; frame_prefix[CMUX_BIT4] = CMUX_ADDR_FIELD_BIT_EA | ((frame_size >> 7) << 1); prefix_len = 5; } frame_posfix[CMUX_BIT0] = cmux_calulate_fcs(frame_prefix + 1, prefix_len - 1); int ret = write(fd, frame_prefix, prefix_len); if (ret != prefix_len) { return ERROR; } if (frame_size > 0 && buffer != NULL) { ret = write(fd, buffer, frame_size); if (ret != frame_size) { ninfo("Failed to write buffer (wrote %d, expected %d)\n", ret, frame_size); return ERROR; } } ret = write(fd, frame_posfix, CMUX_FRAME_POSFIX); if (ret != CMUX_FRAME_POSFIX) { return ERROR; } return OK; } /**************************************************************************** * Name: cmux_open_pseudo_tty * * Description: * Open pseudo-terminals according to the number of channels. * ****************************************************************************/ static int cmux_open_pseudo_tty(struct cmux_channel_s *channel, int total_channels) { int ret = 0; struct termios options; if (!channel) { return -EACCES; } options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); options.c_iflag &= ~(INLCR | ICRNL | IGNCR); options.c_oflag &= ~OPOST; options.c_oflag &= ~OLCUC; options.c_oflag &= ~ONLRET; options.c_oflag &= ~ONOCR; options.c_oflag &= ~OCRNL; for (int i = 0; i < total_channels; i++) { ret = openpty(&channel[i].master_fd, &channel[i].slave_fd, (FAR char *)&channel[i].slave_path, &options, NULL); if (ret < 0) { perror("Failed to open pseudo terminal \n"); break; } else { ninfo("Open pseudo tty name: %s\n", channel[i].slave_path); channel[i].dlci = i + 1; channel[i].active = true; channel[i].last_activity = time(NULL); } } return ret; } /**************************************************************************** * Name: cmux_open_channels * * Description: * Open the controller and the logic channels. * ****************************************************************************/ static int cmux_open_channels(int fd, int total_channels) { int ret = 0; for (int i = 0; i < total_channels; i++) { ret = cmux_encode_frame(fd, i, NULL, 0x00, (CMUX_FRAME_TYPE_SABM | CMUX_CONTROL_FIELD_BIT_PF)); if (ret != OK) { perror("ERROR: Failed to open channel\n"); break; } sleep(1); } return ret; } /**************************************************************************** * Name: cmux_extract * * Description: * Extract a frame from the circular buffer according * to the input and length. * ****************************************************************************/ static int cmux_extract(struct cmux_ctl_s *ctl, char *input, int len) { int ret; int frames_extracted = 0; if (!input) { return ERROR; } ret = cmux_buffer_write(ctl->stream, input, len); if (ret < 0) { return ret; } while (cmux_decode_frame(ctl->stream, ctl->parse) >= 0) { if (CMUX_FRAME_TYPE(CMUX_FRAME_TYPE_UI, ctl->parse) || CMUX_FRAME_TYPE(CMUX_FRAME_TYPE_UIH, ctl->parse)) { if (ctl->parse->address > 0) { /* Logic channel */ ret = write(ctl->channels[ctl->parse->address].master_fd, ctl->parse->data, ctl->parse->data_length); if (ret != ctl->parse->data_length) { ninfo("Frame length less than expected\n"); continue; } } else { /* Control channel */ } } else { switch ((ctl->parse->control & ~CMUX_CONTROL_FIELD_BIT_PF)) { case CMUX_FRAME_TYPE_UA: ninfo("Frame type: UA \n"); break; case CMUX_FRAME_TYPE_DM: ninfo("Frame type: DM \n"); if (ctl->channels[ctl->parse->address].active) { ctl->channels[ctl->parse->address].active = 0; } break; case CMUX_FRAME_TYPE_DISC: ninfo("Frame type: DISC \n"); if (ctl->channels[ctl->parse->address].active) { ctl->channels[ctl->parse->address].active = false; ret = cmux_encode_frame(ctl->fd, ctl->parse->address, NULL, 0x00, (CMUX_FRAME_TYPE_UA | CMUX_CONTROL_FIELD_BIT_PF)); } else { ret = cmux_encode_frame(ctl->fd, ctl->parse->address, NULL, 0x00, (CMUX_FRAME_TYPE_DM | CMUX_CONTROL_FIELD_BIT_PF)); } if (ret < 0) { nwarn("Failed to encode the frame. Address (%d) \n", ctl->parse->address); } break; case CMUX_FRAME_TYPE_SABM: ninfo("Frame type: SABM\n"); if (!ctl->channels[ctl->parse->address].active) { if (!ctl->parse->address) { ninfo("Control channel opened.\n"); } else { ninfo("Logical channel %d opened.\n", ctl->parse->address); } } else { nwarn("Even though channel %d was already closed.\n", ctl->parse->address); } ctl->channels[ctl->parse->address].active = 1; ret = cmux_encode_frame(ctl->fd, ctl->parse->address, NULL, 0x00, (CMUX_FRAME_TYPE_UA | CMUX_CONTROL_FIELD_BIT_PF)); if (ret < 0) { nwarn("Failed to encode the frame. Address (%d) \n", ctl->parse->address); } break; default: ninfo("Frame type: UNKNOWN\n"); break; } } frames_extracted++; } cmux_parse_reset(ctl->parse); return frames_extracted; } /**************************************************************************** * Name: cmux_protocol_send * * Description: * Send encoded messages to a specific address. * ****************************************************************************/ static int cmux_send(struct cmux_ctl_s *ctl, char *buffer, int length, int address) { int ret; if (!buffer) { return ERROR; } ret = cmux_encode_frame(ctl->fd, address, buffer, length, CMUX_FRAME_TYPE_UIH); return ret; } /**************************************************************************** * Name: cmux_thread * * Description: * Start cmux thread. * ****************************************************************************/ static void *cmux_thread(void *args) { struct cmux_ctl_s *ctl = (struct cmux_ctl_s *)args; int ret = 0; fd_set rfds; struct timeval timeout; char buffer[CMUX_BUFFER_SZ]; while (true) { FD_ZERO(&rfds); FD_SET(ctl->fd, &rfds); int max_fd = ctl->fd; for (int i = 0; i < ctl->total_ports; i++) { if (ctl->channels[i].active) { FD_SET(ctl->channels[i].master_fd, &rfds); FD_SET(ctl->channels[i].slave_fd, &rfds); if (ctl->channels[i].master_fd > max_fd) { max_fd = ctl->channels[i].master_fd; } if (ctl->channels[i].slave_fd > max_fd) { max_fd = ctl->channels[i].slave_fd; } } } timeout.tv_usec = 100; timeout.tv_sec = 0; ret = select(max_fd + 1, &rfds, NULL, NULL, &timeout); if (ret > 0) { if (FD_ISSET(ctl->fd, &rfds)) { int bytes_read = read(ctl->fd, buffer, sizeof(buffer) - 1); if (bytes_read > 0) { buffer[bytes_read] = '\0'; ret = cmux_extract(ctl, buffer, bytes_read); if (ret < 0) { perror("ERROR: Failed to extract frames \n"); } } } for (int i = 0; i < ctl->total_ports; i++) { if (ctl->channels[i].active && FD_ISSET(ctl->channels[i].master_fd, &rfds)) { memset(buffer, 0, sizeof(buffer)); int bytes_read = read(ctl->channels[i].master_fd, buffer, sizeof(buffer) - 1); if (bytes_read > 0) { ret = cmux_send(ctl, buffer, bytes_read, i); if (ret < 0) { nwarn("WANING: Retransmit from pty/%d\n", i); } } } } } } return NULL; } /**************************************************************************** * Public Functions ****************************************************************************/ /**************************************************************************** * Name: cmux_create * * Description: * Create CMUX context. * ****************************************************************************/ int cmux_create(struct cmux_settings_s *settings) { int ret = 0; struct chat_ctl ctl; struct cmux_ctl_s *cmux_ctl = NULL; pthread_t cmux_thread_id; struct sched_param param; pthread_attr_t attr; cmux_ctl = malloc(sizeof(struct cmux_ctl_s)); if (!cmux_ctl) { perror("ERROR: Failed to allocate memory for CMUX daemon\n"); ret = -ENOMEM; return ret; } memset(cmux_ctl, 0, sizeof(struct cmux_ctl_s)); cmux_ctl->fd = open(settings->tty_name, O_RDWR | O_NONBLOCK); if (cmux_ctl->fd < 0) { perror("ERROR: Unable to open file %s\n"); goto exit; } ctl.echo = false; ctl.verbose = false; ctl.fd = cmux_ctl->fd; ctl.timeout = 30; ret = chat(&ctl, settings->script); if (ret < 0) { perror("ERROR:Failed to run cmux script\n"); goto exit; } cmux_ctl->channels = malloc(sizeof(struct cmux_channel_s) * settings->total_channels); if (!cmux_ctl->channels) { perror("ERROR:Failed to allocate memory to channels\n"); ret = -ENOMEM; goto exit; } cmux_ctl->parse = malloc(sizeof(struct cmux_parse_s)); if (!cmux_ctl->parse) { perror("ERROR: Failed to allocate memory to parse\n"); ret = -ENOMEM; goto exit; } cmux_parse_reset(cmux_ctl->parse); ret = cmux_open_pseudo_tty(cmux_ctl->channels, settings->total_channels); if (ret < 0) { perror("ERROR: Failed to open pseudo tty.\n"); goto exit; } cmux_ctl->stream = cmux_stream_buffer_create(); if (cmux_ctl->stream == NULL) { perror("ERROR: Failed to allocate memory to stream\n"); ret = -ENOMEM; goto exit; } ret = cmux_open_channels(cmux_ctl->fd, settings->total_channels); if (ret < 0) { perror("ERROR: Failed to open virtual channels.\n"); goto exit; } cmux_ctl->total_ports = settings->total_channels; pthread_attr_init(&attr); param.sched_priority = CMUX_THREAD_PRIOR; pthread_attr_setschedparam(&attr, ¶m); pthread_attr_setstacksize(&attr, CMUX_THREAD_STACK_SIZE); ret = pthread_create(&cmux_thread_id, &attr, cmux_thread, cmux_ctl); return ret; exit: if (cmux_ctl->channels) { for (int i = 0; i < settings->total_channels; i++) { if (cmux_ctl->channels[i].master_fd > 0) { close(cmux_ctl->channels[i].master_fd); } if (cmux_ctl->channels[i].slave_fd > 0) { close(cmux_ctl->channels[i].slave_fd); } } free(cmux_ctl->channels); } if (cmux_ctl->stream) { free(cmux_ctl->stream); } close(cmux_ctl->fd); return ret; }