/****************************************************************************** * file lib/management/vty.c * author YuLiang * version 1.0.0 * date 09-Oct-2021 * brief This file provides all the vty operation functions. * ****************************************************************************** * Attention * *

© COPYRIGHT(c) 2021 LandPower

* * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * 3. Neither the name of LandPower nor the names of its contributors may be used to * endorse or promote products derived from this software without specific * prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * ******************************************************************************/ /* Includes ------------------------------------------------------------------*/ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #include #include #include #include #include #include #include #include "list.h" #include "sockunion.h" #include "vty.h" #include "cmd.h" /* Private define ------------------------------------------------------------*/ #define BUFFER_SIZE_DEFAULT 4096 #define BUF_MAX_CHUNKS 16 #define BUF_MAX_FLUSH 131072 /* Private macro -------------------------------------------------------------*/ #define BUFFER_DATA_FREE(D) XFREE(MTYPE_BUF_DATA, (D)) /* Private typedef -----------------------------------------------------------*/ typedef enum { BUFFER_ERROR = -1, /* I/O错误产生. */ BUFFER_EMPTY = 0, /* 数据发送成功, 并且没有其他数据要发送了. */ BUFFER_PENDING = 1 /* 数据发送成功, 但是数据并没有发送完. */ } BUF_STATUS_E; /* Private variables ---------------------------------------------------------*/ static hash_t *cpu_record = NULL; static struct timeval last_recent_time; static struct timeval relative_time_base; struct timeval recent_time; /* init flag */ static unsigned short timers_inited; /* Relative time, since startup */ static struct timeval relative_time; /* Configure lock. */ static int vty_config; static const char vty_backward_char = 0x08; static const char vty_space_char = ' '; thread_master_t *vty_master; /* Vector which store each vty structure. */ array_t *vtyvec; /* VTY server thread. */ array_t *vty_serv_thread; /* user array. */ array_t *vty_user; /* Vty access-class command */ static char *vty_accesslist_name = NULL; #ifdef HAVE_IPV6 /* Vty access-calss for IPv6. */ static char *vty_ipv6_accesslist_name = NULL; #endif /* Private function prototypes -----------------------------------------------*/ /* Internal functions --------------------------------------------------------*/ /* 在buf中添加一个data空间. */ static buf_data_t *_buf_add(buf_t *b) { buf_data_t *d; d = XMALLOC(MTYPE_BUF_DATA, l_offsetof(buf_data_t, data[b->size])); d->cp = d->sp = 0; d->next = NULL; if (b->tail) b->tail->next = d; else b->head = d; b->tail = d; return d; } /* 创建buf. */ buf_t *buf_create(size_t size) { buf_t *b = NULL; b = XMALLOC(MTYPE_BUF, sizeof(buf_t)); if (size) b->size = size; else { static size_t default_size; if (!default_size) { long pgsz = sysconf(_SC_PAGESIZE); default_size = (((BUFFER_SIZE_DEFAULT-1)/pgsz+1)*pgsz); } b->size = default_size; } return b; } /* 释放所有buf中的空间. */ void buf_reset(buf_t *b) { buf_data_t *data = NULL; buf_data_t *next = NULL; for (data = b->head; data; data = next) { next = data->next; BUFFER_DATA_FREE(data); } b->head = b->tail = NULL; } /* 释放buf_t结构,包括buf空间. */ void buf_free(buf_t *b) { buf_reset(b); XFREE(MTYPE_BUF, b); } /* 将数据存入buf. */ void buf_put(buf_t *b, const void *p, size_t size) { buf_data_t *data = b->tail; const char *ptr = p; /* We use even last one byte of data buffer. */ while(size) { size_t chunk = 0; /* If there is no data buffer add it. */ if (NULL == data || data->cp == b->size) data = _buf_add(b); chunk = ((size <= (b->size - data->cp)) ? size : (b->size - data->cp)); memcpy((data->data + data->cp), ptr, chunk); size -= chunk; ptr += chunk; data->cp += chunk; } } /* 设计用于非阻塞socket. 如果数据太大, 该接口并不会全部发送. 返回0如果数据全部发送, 1表示还有数据没有发送完, -1表示发送 产生了致命错误. */ BUF_STATUS_E buf_flush_available(buf_t *b, int32_t fd) { buf_data_t *d = NULL; size_t written = 0; struct iovec iov[BUF_MAX_CHUNKS]; size_t iovcnt = 0; size_t nbyte = 0; /* 将数据填入iov,最多使用BUF_MAX_CHUNKS个iov,并且不能超过 BUF_MAX_FLUSH字节. */ for(d = b->head, iovcnt = 0; d && (iovcnt < BUF_MAX_CHUNKS) && (nbyte < BUF_MAX_FLUSH); d = d->next, iovcnt++) { iov[iovcnt].iov_base = d->data + d->sp; nbyte += (iov[iovcnt].iov_len = d->cp - d->sp); } if (!nbyte) return BUFFER_EMPTY; /* 数据输出到终端. */ if ((ssize_t)(written = writev(fd, iov, iovcnt)) < 0) { if (ERRNO_IO_RETRY(errno)) return BUFFER_PENDING; printf("%s: write error on fd %d: %s.\n", __FUNCTION__, fd, safe_strerror(errno)); return BUFFER_ERROR; } /* 释放被输出到终端的data. */ while(written > 0) { if (!(d = b->head)) { printf("%s: corruption detected: buffer queue empty, but written is %lu", __func__, (u_long)written); break; } if (written < (d->cp - d->sp)) { d->sp += written; return BUFFER_PENDING; } written -= (d->cp-d->sp); if (!(b->head = d->next)) b->tail = NULL; BUFFER_DATA_FREE(d); } return b->head ? BUFFER_PENDING : BUFFER_EMPTY; } /* 发送数据到终端,仅用于telnet接口 */ BUF_STATUS_E buf_flush_window(buf_t *b, int fd, int32_t width, int32_t height, int32_t erase_flag, int32_t no_more_flag) { int32_t nbytes = 0; int32_t iov_alloc = 0; int32_t iov_index = 0; struct iovec *iov = NULL; struct iovec small_iov[3]; char more[] = " --More-- "; char erase[] = {0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08}; buf_data_t *data = NULL; uint32_t column = 0; if (!b->head) return BUFFER_EMPTY; /* 获取显示的高度与宽度. */ if (height < 1) { printf("%s called with non-positive window height %d, forcing to 1", __func__, height); height = 1; } else if (height >= 2) height--; if (width < 1) { printf("%s called with non-positive window width %d, forcing to 1", __func__, width); width = 1; } /* 计算发送一屏的数据要使用多少数据块. */ if (NULL == b->head->next) { iov_alloc = sizeof(small_iov)/sizeof(small_iov[0]); iov = small_iov; } else { iov_alloc = ((height * (width + 2)) / b->size) + 10; iov = XMALLOC(MTYPE_BUF_TMP, iov_alloc * sizeof(*iov)); } iov_index = 0; /* 处理清屏标志. */ if (erase_flag) { iov[iov_index].iov_base = erase; iov[iov_index].iov_len = sizeof(erase); iov_index++; } /* 整理发送数据. */ column = 1; /* 下一个字符显示的列地址. */ for (data = b->head; data && (height > 0); data = data->next) { size_t cp; /* 计算数据位置和行数,保证只显示height行(一屏)数据. */ cp = data->sp; while ((cp < data->cp) && (height > 0)) { if ('\r' == data->data[cp]) column = 1; else if (('\n' == data->data[cp]) || (column == width)) { column = 1; height--; } else column++; cp++; } iov[iov_index].iov_base = (char *)(data->data + data->sp); iov[iov_index++].iov_len = cp - data->sp; data->sp = cp; /* 以下情况应该不会发生,申请的块应该是足够发送数据的. */ if (iov_index == iov_alloc) { iov_alloc *= 2; if (iov != small_iov) { printf("%s: growing iov array to %d;width %d, height %d, size %lu", __func__, iov_alloc, width, height, (u_long)b->size); iov = XREALLOC(MTYPE_BUF_TMP, iov, iov_alloc * sizeof(*iov)); } else { printf("%s: corruption detected: iov_small overflowed;head %p, tail %p, head->next %p", __func__, b->head, b->tail, b->head->next); iov = XMALLOC(MTYPE_BUF_TMP, iov_alloc * sizeof(*iov)); memcpy(iov, small_iov, sizeof(small_iov)); } } } /* 显示" --More-- "提示. */ if (b->tail && (b->tail->sp < b->tail->cp) && !no_more_flag) { iov[iov_index].iov_base = more; iov[iov_index].iov_len = sizeof(more); iov_index++; } /* 发送数据. */ if ((nbytes = writev(fd, iov, iov_index)) < 0) printf("%s: writev to fd %d failed: %s", __func__, fd, safe_strerror(errno)); /* 释放发送过的数据空间. */ while (b->head && (b->head->sp == b->head->cp)) { buf_data_t *del = NULL; if (!(b->head = (del = b->head)->next)) b->tail = NULL; BUFFER_DATA_FREE(del); } if (iov != small_iov) XFREE(MTYPE_BUF_TMP, iov); return (nbytes < 0) ? BUFFER_ERROR : (b->head ? BUFFER_PENDING : BUFFER_EMPTY); } /* 持续发送数据, 直到数据发送完, 或者遇到错误,或者被拥塞. */ BUF_STATUS_E buf_flush_all(buf_t *b, int32_t fd) { BUF_STATUS_E ret = BUFFER_ERROR; buf_data_t *head = NULL; size_t head_sp = 0; if (!b->head) return BUFFER_EMPTY; head_sp = (head = b->head)->sp; while(BUFFER_PENDING == (ret = buf_flush_available(b, fd))) { /* 数据不空, 但是没有发送出去,可能是内核buf满了. */ if ((b->head == head) && (head_sp == head->sp) && (errno != EINTR)) return ret; head_sp = (head = b->head)->sp; } return ret; } /* Lookup and return hash backet in hash. If there is no * corresponding hash backet and alloc_func is specified, create new * hash backet. */ void *hash_get(hash_t *hash, void *data, hash_alloc_f alloc_func) { unsigned int key = 0; unsigned int index = 0; void *newdata = 0; hash_backet_t *backet = NULL; key = (*hash->hash_key) (data); index = key % hash->size; for(backet = hash->index[index]; backet != NULL; backet = backet->next) if (backet->key == key && (*hash->hash_cmp) (backet->data, data)) return backet->data; if (alloc_func) { newdata = (*alloc_func)(data); if (NULL == newdata) return NULL; backet = XMALLOC(MTYPE_HASH_BACKET, sizeof(hash_backet_t)); backet->data = newdata; backet->key = key; backet->next = hash->index[index]; hash->index[index] = backet; hash->count++; return backet->data; } return NULL; } /* gettimeofday wrapper, to keep recent_time updated */ static int _thread_gettimeofday(struct timeval *tv) { int ret = 0; assert(tv); if (!(ret = gettimeofday(&recent_time, NULL))) { /* init... */ if (!timers_inited) { relative_time_base = last_recent_time = recent_time; timers_inited = 1; } /* avoid copy if user passed recent_time pointer.. */ if (tv != &recent_time) *tv = recent_time; return 0; } return ret; } static int _thread_get_relative(struct timeval *tv) { int ret = 0; struct timespec tp; if (!(ret = clock_gettime(CLOCK_MONOTONIC, &tp))) { relative_time.tv_sec = tp.tv_sec; relative_time.tv_usec = tp.tv_nsec / 1000; } if (tv) *tv = relative_time; return ret; } /* Adjust so that tv_usec is in the range [0,TIMER_SECOND_MICRO). And change negative values to 0. */ static struct timeval _thread_timeval_adjust (struct timeval a) { while(a.tv_usec >= TIMER_SECOND_MICRO) { a.tv_usec -= TIMER_SECOND_MICRO; a.tv_sec++; } while(a.tv_usec < 0) { a.tv_usec += TIMER_SECOND_MICRO; a.tv_sec--; } if (a.tv_sec < 0) /* Change negative timeouts to 0. */ a.tv_sec = a.tv_usec = 0; return a; } static unsigned long _thread_timeval_elapsed(struct timeval a, struct timeval b) { return ((a.tv_sec - b.tv_sec) * TIMER_SECOND_MICRO) + (a.tv_usec - b.tv_usec); } /* Allocate a new hash. */ hash_t *_thread_hash_create_size(hash_key_f *hash_key, hash_cmp_f *hash_cmp, unsigned int size) { hash_t *hash; hash = XMALLOC(MTYPE_HASH, sizeof(hash_t)); hash->index = XMALLOC(MTYPE_HASH_INDEX, sizeof(hash_backet_t *) * size); hash->size = size; hash->hash_key = hash_key; hash->hash_cmp = hash_cmp; hash->count = 0; return hash; } /* Add a new thread to the list. */ static void _thread_list_enqueue(thread_list_t *list, thread_t *thread) { thread->next = NULL; thread->prev = list->tail; if (list->tail) list->tail->next = thread; else list->head = thread; list->tail = thread; list->count++; } /* Delete a thread from the list. */ static thread_t *_thread_list_dequeue(thread_list_t *list, thread_t *thread) { if (thread->next) thread->next->prev = thread->prev; else list->tail = thread->prev; if (thread->prev) thread->prev->next = thread->next; else list->head = thread->next; thread->next = thread->prev = NULL; list->count--; return thread; } /* Thread list is empty or not. */ static inline int _thread_empty(thread_list_t *list) { return list->head ? 0 : 1; } static long _thread_timeval_cmp(struct timeval a, struct timeval b) { return a.tv_sec == b.tv_sec ? a.tv_usec - b.tv_usec : a.tv_sec - b.tv_sec; } /* Delete top of the list and return it. */ static thread_t *_thread_list_pull(thread_list_t *list) { if (!_thread_empty(list)) return _thread_list_dequeue(list, list->head); return NULL; } /* Add a new thread to the list. */ static void _thread_list_add(thread_list_t *list, thread_t *thread) { thread->next = NULL; thread->prev = list->tail; if (list->tail) list->tail->next = thread; else list->head = thread; list->tail = thread; list->count++; } /* Add a new thread just before the point. */ static void _thread_list_add_before(thread_list_t *list, thread_t *point, thread_t *thread) { thread->next = point; thread->prev = point->prev; if (point->prev) point->prev->next = thread; else list->head = thread; point->prev = thread; list->count++; } /* Return remain time in second. */ unsigned long _thread_timer_remain_second(thread_t *thread) { _thread_get_relative(NULL); if (thread->u.sands.tv_sec - relative_time.tv_sec > 0) return thread->u.sands.tv_sec - relative_time.tv_sec; else return 0; } /* Trim blankspace and "()"s */ static char *_thread_strip_funcname(const char *funcname) { char buff[100] = {0}; char tmp = 0; char *ret = NULL; char *e = NULL; char *b = buff; strncpy(buff, funcname, sizeof(buff)); buff[sizeof(buff) -1] = '\0'; e = buff + strlen(buff) - 1; /* Wont work for funcname == "Word (explanation)" */ while (*b == ' ' || *b == '(') ++b; while (*e == ' ' || *e == ')') --e; e++; tmp = *e; *e = '\0'; ret = XSTRDUP(MTYPE_THREAD_FUNCNAME, b); *e = tmp; return ret; } /* Get new thread. */ static thread_t *_thread_get(thread_master_t *m, u_char type, thread_func_f *func, void *arg, const char* funcname) { thread_t *thread = NULL; if (!_thread_empty(&m->unuse)) { thread = _thread_list_pull(&m->unuse); if (thread->funcname) XFREE(MTYPE_THREAD_FUNCNAME, thread->funcname); } else { thread = XMALLOC(MTYPE_THREAD, sizeof(thread_t)); m->alloc++; } thread->type = type; thread->add_type = type; thread->master = m; thread->func = func; thread->arg = arg; thread->funcname = _thread_strip_funcname(funcname); return thread; } static void *_thread_cpu_record_hash_alloc(cpu_thread_history_t *a) { cpu_thread_history_t *history = NULL; history = XMALLOC(MTYPE_THREAD_STATS, sizeof(cpu_thread_history_t)); history->func = a->func; history->funcname = XSTRDUP(MTYPE_THREAD_FUNCNAME, a->funcname); return history; } /* Move thread to unuse list. */ static void _thread_add_unuse(thread_master_t *m, thread_t *thread) { assert(m != NULL && thread != NULL); assert(NULL == thread->next); assert(NULL == thread->prev); assert(THREAD_UNUSED == thread->type); _thread_list_add(&m->unuse, thread); /* XXX: Should we deallocate funcname here? */ } static unsigned int _thread_cpu_record_hash_key(cpu_thread_history_t *a) { return (uintptr_t)a->func; } static int _thread_cpu_record_hash_cmp (const cpu_thread_history_t *a, const cpu_thread_history_t *b) { return a->func == b->func; } static thread_t *_thread_run(thread_master_t *m, thread_t *thread, thread_t *fetch) { *fetch = *thread; thread->type = THREAD_UNUSED; thread->funcname = NULL; /* thread_call will free fetch's copied pointer */ _thread_add_unuse(m, thread); return fetch; } /* process a list en masse, e.g. for event thread lists */ static unsigned int _thread_process(thread_list_t *list) { thread_t *thread = NULL; unsigned int ready = 0; for(thread = list->head; thread; thread = thread->next) { _thread_list_dequeue(list, thread); thread->type = THREAD_READY; _thread_list_add(&thread->master->ready, thread); ready++; } return ready; } static struct timeval _timeval_subtract(struct timeval a, struct timeval b) { struct timeval ret; ret.tv_usec = a.tv_usec - b.tv_usec; ret.tv_sec = a.tv_sec - b.tv_sec; return _thread_timeval_adjust(ret); } static struct timeval *_thread_timer_wait(thread_list_t *tlist, struct timeval *timer_val) { if (!_thread_empty (tlist)) { *timer_val = _timeval_subtract(tlist->head->u.sands, relative_time); return timer_val; } return NULL; } /* Add all timers that have popped to the ready list. */ static uint32_t _thread_timer_process(thread_list_t *list, struct timeval *timenow) { thread_t *thread = NULL; uint32_t ready = 0; for (thread = list->head; thread; thread = thread->next) { if (_thread_timeval_cmp(*timenow, thread->u.sands) < 0) return ready; _thread_list_dequeue(list, thread); thread->type = THREAD_READY; _thread_list_add(&thread->master->ready, thread); ready++; } return ready; } static int32_t _thread_process_fd(thread_list_t *list, fd_set *fdset, fd_set *mfdset) { thread_t *thread = NULL; thread_t *next = NULL; int ready = 0; assert(list); for (thread = list->head; thread; thread = next) { next = thread->next; if (FD_ISSET(THREAD_FD(thread), fdset)) { assert(FD_ISSET(THREAD_FD(thread), mfdset)); FD_CLR(THREAD_FD (thread), mfdset); _thread_list_dequeue(list, thread); _thread_list_add(&thread->master->ready, thread); thread->type = THREAD_READY; ready++; } } return ready; } void thread_getrusage(RUSAGE_T *r) { _thread_get_relative(NULL); getrusage(RUSAGE_SELF, &(r->cpu)); r->real = relative_time; /* quagga_get_relative() only updates recent_time if gettimeofday * based, not when using CLOCK_MONOTONIC. As we export recent_time * and guarantee to update it before threads are run...*/ _thread_gettimeofday(&recent_time); } /* We check thread consumed time. If the system has getrusage, we'll use that to get in-depth stats on the performance of the thread in addition to wall clock time stats from gettimeofday. */ void thread_call(thread_t *thread) { unsigned long realtime = 0; unsigned long cputime = 0; RUSAGE_T ru; /* Cache a pointer to the relevant cpu history thread, if the thread * does not have it yet. * * Callers submitting 'dummy threads' hence must take care that * thread->cpu is NULL */ if (!thread->hist) { cpu_thread_history_t tmp; tmp.func = (int (*)(void*))thread->func; tmp.funcname = thread->funcname; thread->hist = hash_get(cpu_record, &tmp, (hash_alloc_f *)_thread_cpu_record_hash_alloc); } GETRUSAGE(&thread->ru); (*thread->func)(thread); GETRUSAGE(&ru); realtime = thread_consumed_time(&ru, &thread->ru, &cputime); thread->hist->real.total += realtime; if (thread->hist->real.max < realtime) thread->hist->real.max = realtime; thread->hist->cpu.total += cputime; if (thread->hist->cpu.max < cputime) thread->hist->cpu.max = cputime; ++(thread->hist->total_calls); thread->hist->types |= (1 << thread->add_type); if (realtime > TIMER_SECOND_MICRO * 2) { /* We have a CPU Hog on our hands. * Whinge about it now, so we're aware this is yet another task * to fix. */ printf("SLOW THREAD: task %s (%lx) ran for %lums (cpu time %lums)", thread->funcname, (unsigned long) thread->func, realtime/1000, cputime/1000); } XFREE(MTYPE_THREAD_FUNCNAME, thread->funcname); } /* Add new read thread. */ thread_t *thread_add_read(thread_master_t *m, thread_func_f *func, void *arg, int fd, const char* funcname) { thread_t *thread = NULL; assert(m != NULL); if (FD_ISSET(fd, &m->readfd)) { printf("There is already read fd [%d].\n", fd); return NULL; } thread = _thread_get(m, THREAD_READ, func, arg, funcname); FD_SET(fd, &m->readfd); thread->u.fd = fd; _thread_list_enqueue(&m->read, thread); return thread; } /* Add new write thread. */ thread_t *thread_add_write(thread_master_t *m, thread_func_f *func, void *arg, int fd, const char* funcname) { thread_t *thread = NULL; assert(m != NULL); if (FD_ISSET(fd, &m->writefd)) { printf("There is already write fd [%d]", fd); return NULL; } thread = _thread_get(m, THREAD_WRITE, func, arg, funcname); FD_SET(fd, &m->writefd); thread->u.fd = fd; _thread_list_add(&m->write, thread); return thread; } static thread_t * thread_add_timer_timeval(thread_master_t *m, thread_func_f *func, int type, void *arg, struct timeval *time_relative, const char* funcname) { thread_t *thread = NULL; thread_list_t *list = NULL; struct timeval alarm_time; thread_t *tt = NULL; assert(m != NULL); assert(type == THREAD_TIMER || type == THREAD_BACKGROUND); assert(time_relative); list = (type == THREAD_TIMER) ? &m->timer : &m->background; thread = _thread_get(m, type, func, arg, funcname); /* Do we need jitter here? */ _thread_get_relative(NULL); alarm_time.tv_sec = relative_time.tv_sec + time_relative->tv_sec; alarm_time.tv_usec = relative_time.tv_usec + time_relative->tv_usec; thread->u.sands = _thread_timeval_adjust(alarm_time); /* Sort by timeval. */ for(tt = list->head; tt; tt = tt->next) if (_thread_timeval_cmp(thread->u.sands, tt->u.sands) <= 0) break; if (tt) _thread_list_add_before(list, tt, thread); else _thread_list_add(list, thread); return thread; } /* Add timer event thread. */ thread_t *thread_add_timer(thread_master_t *m, thread_func_f *func, void *arg, long timer, const char* funcname) { struct timeval trel; assert(m != NULL); trel.tv_sec = timer; trel.tv_usec = 0; return thread_add_timer_timeval(m, func, THREAD_TIMER, arg, &trel, funcname); } /* Add timer event thread with "millisecond" resolution */ thread_t *thread_add_timer_msec(thread_master_t *m, thread_func_f *func, void *arg, long timer, const char* funcname) { struct timeval trel; assert(m != NULL); trel.tv_sec = timer / 1000; trel.tv_usec = 1000*(timer % 1000); return thread_add_timer_timeval(m, func, THREAD_TIMER, arg, &trel, funcname); } /* Add a background thread, with an optional millisec delay */ thread_t *thread_add_background(thread_master_t *m, thread_func_f *func, void *arg, long delay, const char *funcname) { struct timeval trel; assert(m != NULL); if (delay) { trel.tv_sec = delay / 1000; trel.tv_usec = 1000 * (delay % 1000); } else { trel.tv_sec = 0; trel.tv_usec = 0; } return thread_add_timer_timeval(m, func, THREAD_BACKGROUND, arg, &trel, funcname); } /* Add simple event thread. */ thread_t *thread_add_event(thread_master_t *m, thread_func_f *func, void *arg, int val, const char* funcname) { thread_t *thread = NULL; assert(m != NULL); thread = _thread_get(m, THREAD_EVENT, func, arg, funcname); thread->u.val = val; _thread_list_add(&m->event, thread); return thread; } /* Execute thread */ thread_t *thread_execute(thread_master_t *m, thread_func_f *func, void *arg, int val, const char* funcname) { thread_t dummy; memset(&dummy, 0, sizeof(thread_t)); dummy.type = THREAD_EVENT; dummy.add_type = THREAD_PERFORM; dummy.master = NULL; dummy.func = func; dummy.arg = arg; dummy.u.val = val; dummy.funcname = _thread_strip_funcname(funcname); thread_call(&dummy); return NULL; } /* Allocate new thread master. */ thread_master_t *thread_master_create() { if (cpu_record == NULL) cpu_record = _thread_hash_create_size((hash_key_f*)_thread_cpu_record_hash_key, (hash_cmp_f*)_thread_cpu_record_hash_cmp, 1011); return (thread_master_t*)XMALLOC(MTYPE_THREAD_MASTER, sizeof(thread_master_t)); } unsigned long thread_consumed_time(RUSAGE_T *now, RUSAGE_T *start, unsigned long *cputime) { /* This is 'user + sys' time. */ *cputime = _thread_timeval_elapsed(now->cpu.ru_utime, start->cpu.ru_utime) + _thread_timeval_elapsed(now->cpu.ru_stime, start->cpu.ru_stime); return _thread_timeval_elapsed(now->real, start->real); } /* Cancel thread from scheduler. */ void thread_cancel(thread_t *thread) { thread_list_t *list = NULL; switch(thread->type) { case THREAD_READ: assert(FD_ISSET(thread->u.fd, &thread->master->readfd)); FD_CLR(thread->u.fd, &thread->master->readfd); list = &thread->master->read; break; case THREAD_WRITE: assert(FD_ISSET (thread->u.fd, &thread->master->writefd)); FD_CLR(thread->u.fd, &thread->master->writefd); list = &thread->master->write; break; case THREAD_TIMER: list = &thread->master->timer; break; case THREAD_EVENT: list = &thread->master->event; break; case THREAD_READY: list = &thread->master->ready; break; case THREAD_BACKGROUND: list = &thread->master->background; break; default: return; } _thread_list_dequeue(list, thread); thread->type = THREAD_UNUSED; _thread_add_unuse(thread->master, thread); } /* Fetch next ready thread. */ thread_t *thread_fetch(thread_master_t *m, thread_t *fetch) { thread_t *thread = NULL; fd_set readfd; fd_set writefd; fd_set exceptfd; struct timeval timer_val = {.tv_sec = 0, .tv_usec = 0}; struct timeval timer_val_bg; struct timeval *timer_wait = &timer_val; struct timeval *timer_wait_bg = NULL; while (1) { int32_t num = 0; /* Drain the ready queue of already scheduled jobs, before scheduling * more. */ if ((thread = _thread_list_pull(&m->ready)) != NULL) return _thread_run(m, thread, fetch); /* To be fair to all kinds of threads, and avoid starvation, we * need to be careful to consider all thread types for scheduling * in each quanta. I.e. we should not return early from here on. */ /* Normal event are the next highest priority. */ _thread_process(&m->event); /* Structure copy. */ readfd = m->readfd; writefd = m->writefd; exceptfd = m->exceptfd; /* Calculate select wait timer if nothing else to do */ if (0 == m->ready.count) { _thread_get_relative(NULL); timer_wait = _thread_timer_wait(&m->timer, &timer_val); timer_wait_bg = _thread_timer_wait(&m->background, &timer_val_bg); if (timer_wait_bg && (!timer_wait || (_thread_timeval_cmp(*timer_wait, *timer_wait_bg) > 0))) timer_wait = timer_wait_bg; } /* 当可用的进程全为0时,不能让timer_wait为NULL,要不让该线程将永远停在select()处. */ if (!timer_wait) { timer_val.tv_sec = 2; timer_val.tv_usec = 0; timer_wait = &timer_val; } num = select(FD_SETSIZE, &readfd, &writefd, &exceptfd, timer_wait); /* Signals should get quick treatment */ if (num < 0) { if (EINTR == errno) continue; /* signal received - process it */ printf("select() error: %s\n", safe_strerror(errno)); return NULL; } /* Check foreground timers. Historically, they have had higher * priority than I/O threads, so let's push them onto the ready * list in front of the I/O threads. */ _thread_get_relative(NULL); _thread_timer_process(&m->timer, &relative_time); /* Got IO, process it */ if (num > 0) { /* Normal priority read thead. */ _thread_process_fd(&m->read, &readfd, &m->readfd); /* Write thead. */ _thread_process_fd(&m->write, &writefd, &m->writefd); } #if 0 /* If any threads were made ready above (I/O or foreground timer), * perhaps we should avoid adding background timers to the ready * list at this time. If this is code is uncommented, then background * timer threads will not run unless there is nothing else to do. */ if ((thread = thread_trim_head (&m->ready)) != NULL) return thread_run (m, thread, fetch); #endif /* Background timer/events, lowest priority */ _thread_timer_process(&m->background, &relative_time); if ((thread = _thread_list_pull(&m->ready)) != NULL) return _thread_run(m, thread, fetch); } } /* Get telnet window size. */ static int _vty_telnet_option(vty_t *vty, unsigned char *buf, int nbytes) { switch (buf[0]) { case SB: vty->sb_len = 0; vty->iac_sb_in_progress = 1; return 0; break; case SE: { if (!vty->iac_sb_in_progress) return 0; if ((vty->sb_len == 0) || (vty->sb_buf[0] == '\0')) { vty->iac_sb_in_progress = 0; return 0; } switch (vty->sb_buf[0]) { case TELOPT_NAWS: if (vty->sb_len != TELNET_NAWS_SB_LEN) printf("RFC 1073 violation detected: telnet NAWS option\ should send %d characters, but we received %lu.\n", TELNET_NAWS_SB_LEN, (u_long)vty->sb_len); else if (sizeof(vty->sb_buf) < TELNET_NAWS_SB_LEN) printf("Bug detected: sizeof(vty->sb_buf) %lu < %d, \ too small to handle the telnet NAWS option.\n", (u_long)sizeof(vty->sb_buf), TELNET_NAWS_SB_LEN); else { vty->width = ((vty->sb_buf[1] << 8)|vty->sb_buf[2]); vty->height = ((vty->sb_buf[3] << 8)|vty->sb_buf[4]); } break; } vty->iac_sb_in_progress = 0; return 0; break; } default: break; } return 1; } /* Basic function to write buffer to vty. */ static void _vty_write(vty_t *vty, const char *buf, size_t nbytes) { /* Should we do buffering here ? And make vty_flush (vty) ? */ buf_put(vty->out_buf, buf, nbytes); } /* This function redraw all of the command line character. */ static void _vty_redraw_line(vty_t *vty) { _vty_write(vty, vty->buf, vty->length); vty->cp = vty->length; } /* Quit print out to the buffer. */ static void _vty_buffer_reset(vty_t *vty) { buf_reset(vty->out_buf); vty_prompt(vty); _vty_redraw_line(vty); } /* Backward character. */ static void _vty_backward_char(vty_t *vty) { if (vty->cp > 0) { vty->cp--; _vty_write(vty, &vty_backward_char, 1); } } /* Forward character. */ static void _vty_forward_char(vty_t *vty) { if (vty->cp < vty->length) { _vty_write(vty, &vty->buf[vty->cp], 1); vty->cp++; } } /* Move to the beginning of the line. */ static void _vty_beginning_of_line(vty_t *vty) { while (vty->cp) _vty_backward_char(vty); } /* Move to the end of the line. */ static void _vty_end_of_line(vty_t *vty) { while(vty->cp < vty->length) _vty_forward_char(vty); } /* Kill rest of line from current point. */ static void _vty_kill_line(vty_t *vty) { unsigned int i = 0; unsigned int size = 0; size = vty->length - vty->cp; if (0 == size) return; for (i = 0; i < size; i++) _vty_write(vty, &vty_space_char, 1); for (i = 0; i < size; i++) _vty_write(vty, &vty_backward_char, 1); memset(&vty->buf[vty->cp], 0, size); vty->length = vty->cp; } /* Kill line from the beginning. */ static void _vty_kill_line_from_beginning(vty_t *vty) { _vty_beginning_of_line(vty); _vty_kill_line(vty); } /* Print command line history. This function is called from vty_next_line and vty_previous_line. */ static void _vty_history_print(vty_t *vty) { unsigned int length = 0; _vty_kill_line_from_beginning (vty); /* Get previous line from history buffer */ length = strlen(vty->hist[vty->hp]); memcpy(vty->buf, vty->hist[vty->hp], length); vty->cp = vty->length = length; /* Redraw current line */ _vty_redraw_line(vty); } /* Show previous command line history. */ static void _vty_previous_line(vty_t *vty) { unsigned int try_index = 0; try_index = vty->hp; if (try_index == 0) try_index = VTY_MAXHIST - 1; else try_index--; if (vty->hist[try_index] == NULL) return; else vty->hp = try_index; _vty_history_print(vty); } /* Show next command line history. */ static void _vty_next_line(vty_t *vty) { unsigned int try_index = 0; if (vty->hp == vty->hindex) return; /* Try is there history exist or not. */ try_index = vty->hp; if (try_index == (VTY_MAXHIST - 1)) try_index = 0; else try_index++; /* If there is not history return. */ if (vty->hist[try_index] == NULL) return; else vty->hp = try_index; _vty_history_print (vty); } /* Escape character command map. */ static void _vty_escape_map (unsigned char c, vty_t *vty) { switch (c) { case ('A'): _vty_previous_line (vty); break; case ('B'): _vty_next_line(vty); break; case ('C'): _vty_forward_char(vty); break; case ('D'): _vty_backward_char (vty); break; default: break; } /* Go back to normal mode. */ vty->escape = VTY_NO_ESCAPE; } /* Backward word. */ static void _vty_backward_word(vty_t *vty) { while(vty->cp > 0 && vty->buf[vty->cp - 1] == ' ') _vty_backward_char(vty); while(vty->cp > 0 && vty->buf[vty->cp - 1] != ' ') _vty_backward_char(vty); } /* Forward word. */ static void _vty_forward_word(vty_t *vty) { while (vty->cp != vty->length && vty->buf[vty->cp] != ' ') _vty_forward_char(vty); while (vty->cp != vty->length && vty->buf[vty->cp] == ' ') _vty_forward_char(vty); } /* Delete a charcter at the current point. */ static void _vty_delete_char(vty_t *vty) { unsigned int i = 0; unsigned int size = 0; if (vty->length == 0) return; if (vty->cp == vty->length) return; size = vty->length - vty->cp; vty->length--; memmove(&vty->buf[vty->cp], &vty->buf[vty->cp + 1], size - 1); vty->buf[vty->length] = '\0'; _vty_write(vty, &vty->buf[vty->cp], size - 1); _vty_write(vty, &vty_space_char, 1); for (i = 0; i < size; i++) _vty_write(vty, &vty_backward_char, 1); } /* Delete a character before the point. */ static void _vty_delete_backward_char(vty_t *vty) { if (vty->cp == 0) return; _vty_backward_char(vty); _vty_delete_char(vty); } /* Delete a word before the point. */ static void _vty_forward_kill_word(vty_t *vty) { while(vty->cp != vty->length && vty->buf[vty->cp] == ' ') _vty_delete_char(vty); while(vty->cp != vty->length && vty->buf[vty->cp] != ' ') _vty_delete_char(vty); } /* Delete a word before the point. */ static void _vty_backward_kill_word(vty_t *vty) { while(vty->cp > 0 && vty->buf[vty->cp - 1] == ' ') _vty_delete_backward_char(vty); while(vty->cp > 0 && vty->buf[vty->cp - 1] != ' ') _vty_delete_backward_char(vty); } /* Ensure length of input buffer. Is buffer is short, double it. */ static void _vty_buf_len_ensure(vty_t *vty, int length) { if (vty->max <= length) { vty->max *= 2; vty->buf = XREALLOC(MTYPE_VTY, vty->buf, vty->max); } } /* Basic function to insert character into vty. */ static void _vty_insert_char(vty_t *vty, char c) { unsigned int i = 0; unsigned int length = 0; _vty_buf_len_ensure(vty, vty->length + 1); length = vty->length - vty->cp; memmove(&vty->buf[vty->cp + 1], &vty->buf[vty->cp], length); vty->buf[vty->cp] = c; if (vty->node != PASSWORD_NODE) { _vty_write(vty, &vty->buf[vty->cp], length + 1); for (i = 0; i < length; i++) _vty_write(vty, &vty_backward_char, 1); } vty->cp++; vty->length++; } /* Insert a word into vty interface with overwrite mode. */ static void _vty_insert_word(vty_t *vty, char *str) { int len = strlen(str); _vty_write(vty, str, len); strcpy(&vty->buf[vty->cp], str); vty->cp += len; vty->length = vty->cp; } static void _vty_describe_command(vty_t *vty) { array_t *cmd_line = NULL; cmd_line = cmd_strs_create(vty->buf); /* In case of '> ?'. */ if (NULL == cmd_line) { cmd_line = array_init(1, MTYPE_CLI); array_append(cmd_line, '\0', MTYPE_CLI); } else if(vty->length && isspace((int)vty->buf[vty->length - 1])) array_append(cmd_line, '\0', MTYPE_CLI); vty_question(vty, cmd_line); cmd_strs_free(cmd_line); vty_prompt(vty); _vty_redraw_line(vty); return; } static void _vty_append_word(vty_t *vty, array_t *cmd_line, char *word, int status) { unsigned int index = array_active(cmd_line) - 1; _vty_end_of_line(vty); if (NULL == array_get(cmd_line, index)) _vty_insert_word(vty, word); else { _vty_backward_word(vty); _vty_insert_word(vty, word); } if (CMD_COMPLETE_FULL_MATCH == status) _vty_insert_char(vty, ' '); } int _vty_complete_command(vty_t *vty) { array_t *cmd_line = NULL; char **match_strs = NULL; int32_t complete_status = CMD_ERR_NO_MATCH; cmd_line = cmd_strs_create(vty->buf); if (NULL == cmd_line) { cmd_line = array_init(1, MTYPE_CLI); array_append(cmd_line, '\0', MTYPE_CLI); } else if (vty->length && isspace((int)vty->buf[vty->length - 1])) array_append(cmd_line, '\0', MTYPE_CLI); match_strs = cmd_complete_command(cmd_line, vty, &complete_status); if (NULL == match_strs) { cmd_strs_free(cmd_line); return 0; } if (CMD_COMPLETE_MATCH == complete_status || CMD_COMPLETE_FULL_MATCH == complete_status) _vty_append_word(vty, cmd_line, match_strs[0], complete_status); else { vty_print_word(vty, match_strs); vty_prompt(vty); _vty_redraw_line(vty); } vty_free_match_strs(match_strs); cmd_strs_free(cmd_line); return 0; } static int _vty_read(thread_t *thread) { int i = 0; int nbytes = 0; unsigned char buf[VTY_READ_BUFSIZ]; int vty_sock = THREAD_FD(thread); vty_t *vty = THREAD_ARG(thread); vty->t_read = NULL; /* Read raw data from socket */ if ((nbytes = read(vty->fd, buf, VTY_READ_BUFSIZ)) <= 0) { if (nbytes < 0) { if (ERRNO_IO_RETRY(errno)) { vty_event(VTY_READ, vty_sock, vty); return 0; } vty->monitor = 0; /* disable monitoring to avoid infinite recursion */ printf("%s: read error on vty client fd %d, closing: %s\n", __func__, vty->fd, safe_strerror(errno)); } buf_reset(vty->out_buf); vty->status = VTY_CLOSE; } for (i = 0; i < nbytes; i++) { if (IAC == buf[i]) { if (!vty->iac) { vty->iac = 1; continue; } else { vty->iac = 0; } } if (vty->iac_sb_in_progress && !vty->iac) { if (vty->sb_len < sizeof(vty->sb_buf)) vty->sb_buf[vty->sb_len] = buf[i]; vty->sb_len++; continue; } if (vty->iac) { /* In case of telnet command */ int ret = 0; ret = _vty_telnet_option(vty, &buf[i], nbytes - i); vty->iac = 0; i += ret; continue; } if (VTY_MORE == vty->status) { switch (buf[i]) { case CONTROL('C'): case 'q': case 'Q': _vty_buffer_reset(vty); break; default: break; } continue; } /* Escape character. */ if (VTY_ESCAPE == vty->escape) { _vty_escape_map(buf[i], vty); continue; } /* Pre-escape status. */ if (vty->escape == VTY_PRE_ESCAPE) { switch (buf[i]) { case '[': vty->escape = VTY_ESCAPE; break; case 'b': _vty_backward_word(vty); vty->escape = VTY_NO_ESCAPE; break; case 'f': _vty_forward_word(vty); vty->escape = VTY_NO_ESCAPE; break; case 'd': _vty_forward_kill_word(vty); vty->escape = VTY_NO_ESCAPE; break; case CONTROL('H'): case 0x7f: _vty_backward_kill_word(vty); vty->escape = VTY_NO_ESCAPE; break; default: vty->escape = VTY_NO_ESCAPE; break; } continue; } switch (buf[i]) { case CONTROL('A'): _vty_beginning_of_line(vty); break; case CONTROL('B'): _vty_backward_char(vty); break; case CONTROL('C'): //_vty_stop_input(vty); break; case CONTROL('D'): _vty_delete_char(vty); break; case CONTROL('E'): //_vty_end_of_line(vty); break; case CONTROL('F'): _vty_forward_char(vty); break; case CONTROL('H'): case 0x7f: _vty_delete_backward_char(vty); break; case CONTROL('K'): _vty_kill_line(vty); break; case CONTROL('N'): _vty_next_line(vty); break; case CONTROL('P'): _vty_previous_line(vty); break; case CONTROL('T'): //_vty_transpose_chars(vty); break; case CONTROL('U'): _vty_kill_line_from_beginning(vty); break; case CONTROL('W'): _vty_backward_kill_word(vty); break; case CONTROL('Z'): //_vty_end_config(vty); break; case '\n': case '\r': vty_out(vty, "%s", VTY_NEWLINE); vty_execute(vty); break; case '\t': _vty_complete_command(vty); break; case '?': _vty_describe_command(vty); break; case '\033': if (i + 1 < nbytes && buf[i + 1] == '[') { vty->escape = VTY_ESCAPE; i++; } else vty->escape = VTY_PRE_ESCAPE; break; default: if (buf[i] > 31 && buf[i] < 127) _vty_insert_char(vty, buf[i]); break; } } /* Check status. */ if (vty->status == VTY_CLOSE) vty_close(vty); else { vty_event(VTY_WRITE, vty_sock, vty); vty_event(VTY_READ, vty_sock, vty); } return 0; } static void _vty_thread(void *arg) { thread_t thread; /* Fetch next active thread. */ while(thread_fetch(vty_master, &thread)) thread_call(&thread); printf("vty thread is exit!\n"); return; } static void _vty_thread_init(void) { int32_t rv = 0; pthread_t ppid = 0; struct sched_param param; pthread_attr_t attr; pthread_attr_init(&attr); param.sched_priority = 50; pthread_attr_setschedpolicy(&attr, SCHED_RR); pthread_attr_setschedparam(&attr, ¶m); pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED); rv = pthread_create(&ppid, &attr, (void *)_vty_thread, NULL); if (rv != 0) printf("can't create pthread : %d(%s)\n", rv, safe_strerror(rv)); pthread_attr_destroy(&attr); } /* Create new vty structure. */ static vty_t *_vty_term_create(int vty_sock, SOCKUNION_U *su) { vty_t *vty = NULL; vty = vty_create(); vty->type = VTY_TERM; vty->node = USERNAME_NODE; vty->fd = vty_sock; vty->address = sockunion_su2str(su); vty->fail_count = 0; vty->cp = 0; vty->length = 0; memset(vty->hist, 0, sizeof(vty->hist)); vty->hp = 0; vty->hindex = 0; vty->status = VTY_NORMAL; if (host.lines >= 0) vty->lines = host.lines; else vty->lines = -1; vty->iac = 0; vty->iac_sb_in_progress = 0; vty->sb_len = 0; vty->v_timeout = VTY_TIMEOUT_VAL; array_set(vtyvec, vty_sock, vty, MTYPE_VTY_TMP); /* Setting up terminal. */ vty_will_echo(vty); vty_will_suppress_go_ahead(vty); vty_dont_linemode(vty); vty_do_window_size(vty); /* vty_dont_lflow_ahead (vty); */ vty_prompt(vty); /* Add read/write thread. */ vty_event(VTY_WRITE, vty_sock, vty); vty_event(VTY_READ, vty_sock, vty); return vty; } /* Accept connection from the network. */ static int _vty_accept(thread_t *thread) { int vty_sock = 0; SOCKUNION_U su; int ret = -1; unsigned int on = 0; int accept_sock = 0; prefix_t *p = NULL; access_list_t *acl = NULL; char *bufp = NULL; accept_sock = THREAD_FD(thread); /* We continue hearing vty socket. */ vty_event(VTY_SERV, accept_sock, NULL); memset(&su, 0, sizeof(SOCKUNION_U)); /* We can handle IPv4 or IPv6 socket. */ vty_sock = sockunion_accept(accept_sock, &su); if (vty_sock < 0) { printf("can't accept vty socket : %s\n", safe_strerror(errno)); return -1; } set_nonblocking(vty_sock); p = sockunion2hostprefix(&su); /* VTY's accesslist apply. */ if (AF_INET == p->family && vty_accesslist_name) { if ((acl = access_list_lookup(AFI_IP, vty_accesslist_name)) && (FILTER_DENY == access_list_apply(acl, p))) { char *buf = NULL; printf("Vty connection refused from %s\n", (buf = sockunion_su2str(&su))); XFREE(MTYPE_PREFIX, buf); close(vty_sock); /* continue accepting connections */ vty_event(VTY_SERV, accept_sock, NULL); prefix_free(p); return 0; } } #ifdef HAVE_IPV6 /* VTY's ipv6 accesslist apply. */ if (AF_INET6 == p->family && vty_ipv6_accesslist_name) { if ((acl = access_list_lookup(AFI_IP6, vty_ipv6_accesslist_name)) && (FILTER_DENY == access_list_apply(acl, p))) { char *buf = NULL; printf("Vty connection refused from %s\n", (buf = sockunion_su2str(&su))); XFREE(MTYPE_PREFIX, buf); close(vty_sock); /* continue accepting connections */ vty_event(VTY_SERV, accept_sock, NULL); prefix_free(p); return 0; } } #endif /* HAVE_IPV6 */ prefix_free(p); on = 1; ret = setsockopt(vty_sock, IPPROTO_TCP, TCP_NODELAY, (char *)&on, sizeof(on)); if (ret < 0) printf("can't set sockopt to vty_sock : %s\n", safe_strerror(errno)); printf("Vty connection from %s\n", (bufp = sockunion_su2str(&su))); if (bufp) XFREE(MTYPE_PREFIX, bufp); _vty_term_create(vty_sock, &su); return 0; } /* When time out occur output message then close connection. */ static int _vty_timeout(thread_t *thread) { vty_t *vty = THREAD_ARG(thread); vty->t_timeout = NULL; vty->v_timeout = 0; /* Clear buffer*/ buf_reset(vty->out_buf); vty_out(vty, "%sVty connection is timed out.%s\n", VTY_NEWLINE, VTY_NEWLINE); /* Close connection. */ vty->status = VTY_CLOSE; vty_close(vty); return 0; } /* Flush buffer to the vty. */ static int _vty_flush(thread_t *thread) { int32_t erase = 0; BUF_STATUS_E flushrc = BUFFER_ERROR; int vty_sock = THREAD_FD(thread); vty_t *vty = THREAD_ARG(thread); vty->t_write = NULL; /* Tempolary disable read thread. */ if ((0 == vty->lines) && vty->t_read) { thread_cancel(vty->t_read); vty->t_read = NULL; } /* Function execution continue. */ erase = ((VTY_MORE == vty->status || VTY_MORELINE == vty->status)); /* N.B. if width is 0, that means we don't know the window size. */ if ((vty->lines == 0) || (vty->width == 0)) flushrc = buf_flush_available(vty->out_buf, vty->fd); else if (VTY_MORELINE == vty->status) flushrc = buf_flush_window(vty->out_buf, vty->fd, vty->width, 1, erase, 0); else flushrc = buf_flush_window(vty->out_buf, vty->fd, vty->width, vty->lines >= 0 ? vty->lines : vty->height, erase, 0); switch (flushrc) { case BUFFER_ERROR: vty->monitor = 0; /* disable monitoring to avoid infinite recursion */ printf("buffer_flush failed on vty client fd %d, closing\n", vty->fd); buf_reset(vty->out_buf); vty_close(vty); return 0; case BUFFER_EMPTY: if (VTY_CLOSE == vty->status) vty_close (vty); else { vty->status = VTY_NORMAL; if (0 == vty->lines) vty_event(VTY_READ, vty_sock, vty); } break; case BUFFER_PENDING: /* There is more data waiting to be written. */ vty->status = VTY_MORE; if (0 == vty->lines) vty_event(VTY_WRITE, vty_sock, vty); break; } return 0; } static int _vty_log_out(vty_t *vty, const char *level, const char *proto_str, const char *format, char *time_str, va_list va) { int32_t ret = 0; int32_t len = 0; char *buf = NULL; buf = XMALLOC(MTYPE_VTY_TMP, VTY_TMP_BUFSIZ); if (NULL == buf) return -1; len = snprintf(buf, VTY_TMP_BUFSIZ, "%s", time_str); buf[len++] = ' '; if (level) ret = snprintf(buf + len, VTY_TMP_BUFSIZ - len - 1, "%s: %s: ", level, proto_str); else ret = snprintf(buf + len, VTY_TMP_BUFSIZ - len - 1, "%s: ", proto_str); len += ret; if ((ret < 0) || (len > VTY_TMP_BUFSIZ)) { XFREE(MTYPE_VTY_TMP, buf); return -1; } ret = vsnprintf(buf + len, VTY_TMP_BUFSIZ - len - 1, format, va); len += ret; if ((ret < 0) || (len > VTY_TMP_BUFSIZ)) { XFREE(MTYPE_VTY_TMP, buf); return -1; } vty_out(vty, "%s%s", buf, VTY_NEWLINE); buf_flush_all(vty->out_buf, vty->fd); XFREE(MTYPE_VTY_TMP, buf); return 0; } static int32_t _vty_user_cmp(vty_user_t *user) { int32_t i = 0; vty_user_t *node = NULL; for(i = 0; i < array_active(vty_user); i++) if ((node = array_get(vty_user, i)) != NULL) { if (0 == strcmp(user->username, node->username) && 0 == strcmp(user->password, node->password)) { return 0; } } return 1; } /* 在终端输出版本信息 */ void vty_version_print(vty_t *vty) { char buf[128] = {0}; time_t temp = 0; vty_out(vty, "%s%s", CL_COPYRIGHT, VTY_NEWLINE); vty_out(vty, "%s %s (%s).%s", PROGNAME, host.version, host.name ? host.name : "", VTY_NEWLINE); vty_out(vty, "Compile date: %s%s", host.compile, VTY_NEWLINE); vty_out(vty, "Id: %04d.%04d%s", device_info.id_major, device_info.id_minor, VTY_NEWLINE); vty_out(vty, "Mac: %02x:%02x:%02x:%02x:%02x:%02x%s", device_info.mac[0], device_info.mac[1], device_info.mac[2], device_info.mac[3], device_info.mac[4], device_info.mac[5], VTY_NEWLINE); vty_out(vty, "ip: %s %s%s", device_info.host_ip, device_info.host_mask, VTY_NEWLINE); vty_out(vty, "route: %s%s", device_info.host_gw, VTY_NEWLINE); temp = device_info.factory_date; strftime(buf, 128, "%Y-%m-%d %H:%M:%S", localtime(&temp)); vty_out(vty, "factory date: %s%s", buf, VTY_NEWLINE); temp = device_info.deployment_date; strftime(buf, 128, "%Y-%m-%d %H:%M:%S", localtime(&temp)); vty_out(vty, "deployment date: %s%s", buf, VTY_NEWLINE); } void vty_event(VTY_EVENT_E event, int sock, vty_t *vty) { thread_t *thread = NULL; switch (event) { case VTY_SERV: thread = THREAD_ADD_READ(vty_master, _vty_accept, vty, sock); array_set(vty_serv_thread, sock, thread, MTYPE_VTY_TMP); break; #if 0 case VTYSH_SERV: thread_add_read(vty_master, vtysh_accept, vty, sock); break; case VTYSH_READ: vty->t_read = THREAD_ADD_READ(vty_master, vtysh_read, vty, sock); break; case VTYSH_WRITE: vty->t_write = THREAD_ADD_WRITE(vty_master, vtysh_write, vty, sock); break; #endif /* VTYSH */ case VTY_READ: vty->t_read = THREAD_ADD_READ(vty_master, _vty_read, vty, sock); /* Time out treatment. */ if (vty->v_timeout) { if (vty->t_timeout) thread_cancel(vty->t_timeout); vty->t_timeout = THREAD_ADD_TIMER(vty_master, _vty_timeout, vty, vty->v_timeout); } break; case VTY_WRITE: if (! vty->t_write) vty->t_write = THREAD_ADD_WRITE(vty_master, _vty_flush, vty, sock); break; case VTY_TIMEOUT_RESET: if (vty->t_timeout) { thread_cancel(vty->t_timeout); vty->t_timeout = NULL; } if (vty->v_timeout) { vty->t_timeout = THREAD_ADD_TIMER(vty_master, _vty_timeout, vty, vty->v_timeout); } break; } } static void vty_clear_buf(vty_t *vty) { memset(vty->buf, 0, vty->max); } /* Put out prompt and wait input from user. */ void vty_prompt(vty_t *vty) { cmd_node_t *node = NULL; if (vty->type != VTY_SHELL) { node = cmd_node_get(vty->node); /* 比如用户密码输入 */ if (USERNAME_NODE == node->node || PASSWORD_NODE == node->node) { vty_out(vty, "%s", node->prompt); } else { vty_out(vty, node->prompt, host.name); } } } /* Add current command line to the history buffer. */ static void vty_hist_add(vty_t *vty) { int index; if (vty->length == 0) return; index = vty->hindex ? vty->hindex - 1 : VTY_MAXHIST - 1; /* Ignore the same string as previous one. */ if (vty->hist[index]) if(0 == strcmp(vty->buf, vty->hist[index])) { vty->hp = vty->hindex; return; } /* Insert history entry. */ if (vty->hist[vty->hindex]) XFREE(MTYPE_VTY_HIST, vty->hist[vty->hindex]); vty->hist[vty->hindex] = XSTRDUP(MTYPE_VTY_HIST, vty->buf); /* History index rotation. */ vty->hindex++; if (vty->hindex == VTY_MAXHIST) vty->hindex = 0; vty->hp = vty->hindex; } int vty_shell(vty_t *vty) { return vty->type == VTY_SHELL ? 1 : 0; } /* VTY standard output function. */ int vty_out (vty_t *vty, const char *format, ...) { va_list args; int len = 0; int size = 1024; char buf[1024]; char *p = NULL; if (vty_shell(vty)) { va_start(args, format); vprintf(format, args); va_end(args); } else { /* Try to write to initial buffer. */ va_start(args, format); len = vsnprintf(buf, sizeof buf, format, args); va_end(args); /* Initial buffer is not enough. */ if (len < 0 || len >= size) { while (1) { if (len > -1) size = len + 1; else size = size * 2; p = XREALLOC(MTYPE_VTY_OUT_BUF, p, size); if (!p) return -1; va_start(args, format); len = vsnprintf(p, size, format, args); va_end(args); if (len > -1 && len < size) break; } } /* When initial buffer is enough to store all output. */ if (!p) p = buf; /* Pointer p must point out buffer. */ buf_put(vty->out_buf, p, len); /* If p is not different with buf, it is allocated buffer. */ if (p != buf) XFREE(MTYPE_VTY_OUT_BUF, p); } return len; } /* Allocate new vty struct. */ vty_t *vty_create() { vty_t *vty_new = XMALLOC(MTYPE_VTY, sizeof(vty_t)); /* Use default buffer size. */ vty_new->out_buf = buf_create(0); vty_new->buf = XMALLOC(MTYPE_VTY, VTY_BUFSIZ); vty_new->max = VTY_BUFSIZ; return vty_new; } /* Close vty interface. Warning: call this only from functions that * will be careful not to access the vty afterwards (since it has * now been freed). This is safest from top-level functions (called * directly by the thread dispatcher). */ void vty_close(vty_t *vty) { int i = 0; /* Cancel threads.*/ if (vty->t_read) thread_cancel (vty->t_read); if (vty->t_write) thread_cancel (vty->t_write); if (vty->t_timeout) thread_cancel (vty->t_timeout); /* Flush buffer. */ buf_flush_all(vty->out_buf, vty->fd); /* Free input buffer. */ buf_free(vty->out_buf); /* Free command history. */ for(i = 0; i < VTY_MAXHIST; i++) if (vty->hist[i]) XFREE(MTYPE_VTY_HIST, vty->hist[i]); /* Unset vector. */ array_unset(vtyvec, vty->fd); /* Close socket. */ if (vty->fd > 0) close(vty->fd); if (vty->address) XFREE(MTYPE_PREFIX, vty->address); if (vty->buf) XFREE(MTYPE_VTY, vty->buf); /* Check configure. */ vty_config_unlock(vty); /* OK free vty. */ XFREE(MTYPE_VTY, vty); } /* Execute current command line. */ int vty_execute(vty_t *vty) { int ret = CMD_ERR_NO_MATCH; switch (vty->node) { case USERNAME_NODE: snprintf(vty->user.username, VTY_USERNAME_LEN, "%s", vty->buf); vty->node = PASSWORD_NODE; break; case PASSWORD_NODE: snprintf(vty->user.password, VTY_USERNAME_LEN, "%s", vty->buf); if (0 == _vty_user_cmp(&vty->user)) { vty->node = ENABLE_NODE; //vty_out(vty, "%s================================%s", VTY_NEWLINE, VTY_NEWLINE); //vty_version_print(vty); //vty_out(vty, "================================%s%s", VTY_NEWLINE, VTY_NEWLINE); } else { //vty_out(vty, "Username or password is not match!!!%s", VTY_NEWLINE); vty->node = USERNAME_NODE; } break; case ENABLE_NODE: case CONFIG_NODE: default: ret = cmd_execute(vty); if (vty->type != VTY_SHELL) vty_hist_add(vty); break; } /* Clear command line buffer. */ vty->cp = vty->length = 0; vty_clear_buf(vty); //if (vty->status != VTY_CLOSE ) // vty_prompt(vty); return ret; } int vty_config_lock(vty_t *vty) { if (0 == vty_config) { vty->config = 1; vty_config = 1; } return vty->config; } int vty_config_unlock(vty_t *vty) { if (1 == vty_config) vty_config = 0; if (1 == vty->config) vty->config = 0; return vty->config; } void vty_question(vty_t *vty, array_t *cmd_line) { int32_t ret = CMD_ERR_NO_MATCH; unsigned int i = 0; array_t *matchs = NULL; unsigned int width = 0; desc_t *desc = NULL; matchs = cmd_describe_command(cmd_line, vty, &ret); vty_out(vty, "%s", VTY_NEWLINE); /* Ambiguous and no match error. */ switch (ret) { case CMD_ERR_AMBIGUOUS: fprintf (stdout,"%% Ambiguous command.\n"); return; break; case CMD_ERR_NO_MATCH: fprintf (stdout,"%% There is no matched command.\n"); return; break; } /* Get width of command string. */ for (i = 0; i < array_active(matchs); i++) if ((desc = array_get(matchs, i)) != NULL) { unsigned int len = 0; if (desc->cmd[0] == '\0') continue; len = strlen(desc->cmd); if (desc->cmd[0] == '.') len--; if (width < len) width = len; } for (i = 0; i < array_active(matchs); i++) if ((desc = array_get(matchs, i)) != NULL) { if (desc->cmd[0] == '\0') continue; if (!desc->str) vty_out(vty, " %-s%s", desc->cmd[0] == '.' ? desc->cmd + 1 : desc->cmd, VTY_NEWLINE); else vty_out(vty, " %-*s %s%s", width, desc->cmd[0] == '.' ? desc->cmd + 1 : desc->cmd, desc->str, VTY_NEWLINE); } array_free(matchs, MTYPE_CLI); return; } void vty_print_word(vty_t *vty, char *strs[]) { char *str = NULL; unsigned int i = 0; vty_out(vty, "%s", VTY_NEWLINE); for(i = 0; (str = strs[i]) != NULL; i++) vty_out(vty, "%s", str); } void vty_free_match_strs(char *match_strs[]) { char *str = NULL; unsigned int i = 0; for(i = 0; (str = match_strs[i]) != NULL; i++) XFREE(MTYPE_CLI, str); XFREE(MTYPE_CLI, match_strs); } /* Send WILL TELOPT_ECHO to remote server. */ void vty_will_echo(vty_t *vty) { unsigned char cmd[] = {IAC, WILL, TELOPT_ECHO, '\0'}; vty_out(vty, "%s", cmd); } /* Make suppress Go-Ahead telnet option. */ void vty_will_suppress_go_ahead(vty_t *vty) { unsigned char cmd[] = {IAC, WILL, TELOPT_SGA, '\0'}; vty_out(vty, "%s", cmd); } /* Make don't use linemode over telnet. */ void vty_dont_linemode(vty_t *vty) { unsigned char cmd[] = {IAC, DONT, TELOPT_LINEMODE, '\0'}; vty_out(vty, "%s", cmd); } /* Use window size. */ void vty_do_window_size(vty_t *vty) { unsigned char cmd[] = {IAC, DO, TELOPT_NAWS, '\0'}; vty_out(vty, "%s", cmd); } /* Make vty server socket. */ void vty_serv_sock_family(const char* addr, unsigned short port, int family) { int32_t ret = 0; SOCKUNION_U su; int accept_sock = 0; void *naddr = NULL; memset(&su, 0, sizeof(SOCKUNION_U)); su.sa.sa_family = family; if(addr) switch(family) { case AF_INET: naddr = &su.sin.sin_addr; #ifdef HAVE_IPV6 case AF_INET6: naddr = &su.sin6.sin6_addr; #endif } if(naddr) switch(inet_pton(family, addr, naddr)) { case -1: printf("bad address %s\n", addr); naddr = NULL; break; case 0: printf("error translating address %s: %s\n", addr, safe_strerror(errno)); naddr = NULL; } /* Make new socket. */ accept_sock = sockunion_stream_socket(&su); if (accept_sock < 0) return; /* This is server, so reuse address. */ sockunion_reuseaddr(accept_sock); /* Bind socket to universal address and given port. */ ret = sockunion_bind(accept_sock, &su, port, naddr); if (ret < 0) { close(accept_sock); /* Avoid sd leak. */ return; } /* Listen socket under queue 3. */ ret = listen(accept_sock, 3); if (ret < 0) { printf("can't listen socket\n"); close(accept_sock); /* Avoid sd leak. */ return; } /* Add vty server event. */ vty_event(VTY_SERV, accept_sock, NULL); } /* Small utility function which output log to the VTY. */ void vty_log(const char *level, const char *proto_str, const char *format, char *time_str, va_list va) { uint32_t i = 0; vty_t *vty = NULL; if (!vtyvec) return; for(i = 0; i < array_active(vtyvec); i++) if ((vty = array_get(vtyvec, i)) != NULL) if (vty->monitor) { va_list ac; va_copy(ac, va); _vty_log_out(vty, level, proto_str, format, time_str, ac); va_end(ac); } } /* vty的print函数. */ void vty_print(const char *format, va_list va) { uint32_t i = 0; vty_t *vty = NULL; char *buf = NULL; if (!vtyvec) return; for(i = 0; i < array_active(vtyvec); i++) if ((vty = array_get(vtyvec, i)) != NULL) { buf = XMALLOC(MTYPE_VTY_TMP, VTY_TMP_BUFSIZ); if (NULL == buf) return; vsnprintf(buf, VTY_TMP_BUFSIZ, format, va); vty_out(vty, "%s", buf); buf_flush_all(vty->out_buf, vty->fd); XFREE(MTYPE_VTY_TMP, buf); } } /* Reset all VTY status. */ void vty_reset(void) { unsigned int i = 0; vty_t *vty = NULL; thread_t *serv_thread; for(i = 0; i < array_active(vtyvec); i++) if ((vty = array_get(vtyvec, i)) != NULL) { buf_reset(vty->out_buf); vty->status = VTY_CLOSE; vty_close(vty); } for (i = 0; i < array_active(vty_serv_thread); i++) if ((serv_thread = array_get(vty_serv_thread, i)) != NULL) { thread_cancel(serv_thread); array_get(vty_serv_thread, i) = NULL; close(i); } return; } /* Install vty's own commands like `who' command. */ void vty_init(void) { vty_user_t *user; vtyvec = array_init(ARRAY_MIN_SIZE, MTYPE_VTY_TMP); vty_serv_thread = array_init(ARRAY_MIN_SIZE, MTYPE_VTY_TMP); vty_user = array_init(ARRAY_MIN_SIZE, MTYPE_VTY_TMP); user = XMALLOC(MTYPE_VTY_TMP, sizeof(vty_user_t)); snprintf(user->username, VTY_USERNAME_LEN, "%s", VTY_USERNAME_DEFAULT); snprintf(user->password, VTY_USERNAME_LEN, "%s", VTY_PASSWORD_DEFAULT); user->level = 255; array_append(vty_user, user, MTYPE_VTY_TMP); vty_master = thread_master_create(); _vty_thread_init(); } /************************ (C) COPYRIGHT LandPower ***** END OF FILE ****************/