Develop ser2tcpclient in openwrt (serial port to tcp to achieve high-speed transparent transmission)

Article directory

    • summary
    • overall architecture process
    • technical details
    • summary

Summary

The open source project ser2net is widely used as a serial-to-Ethernet server under the Linux/openwrt system, and is very suitable for use as an IoT device in a passive monitoring connection scenario. The ser2tcpclient developed in this paper is a client that converts serial ports to tcp protocols. , to facilitate the realization of high-speed point-to-point communication in the local area network.

Overall architecture process

1. The soc serial port uses the state machine protocol of mavlink to continuously receive the data frames reported by the mcu.

2. The wireless network manager keeps trying to search, connect, authenticate, shake hands, encrypt and decrypt communication data, synchronize virtual time of communication, two-way data transparent transmission and monitor wireless signals.

3. Monitor and handle the abnormal situation of the program itself.

Technical details

Use the mavlink protocol state machine to receive the data frame reported by the mcu

 while(TRUE){

readbuflen = serial_recv(gd_serial_fd,readbuf,RECV_BUFFER_SIZE);
if(readbuflen <= 0){
continue;
}
\t  
      // parse out the complete packet
#if 0
printf("retval=%d\\
",readbuflen);
print0x(readbuf,readbuflen);
      #endif
\t 

databuf = readbuf;
is_parse_complete = 0;

\t  
while(readbuflen--)
{
//if(Mavlink_Frame_Char_Buffer( &rxmsg_dev, &status_dev,*databuf++,FALSE)
if(Mavlink_Frame_Char_Buffer( &rxmsg_dev, &status_dev,*databuf++,TRUE)
== MAVLINK_FRAMING_OK)
//!= MAVLINK_FRAMING_INCOMPLETE)
{
is_parse_complete = 1;
}
}
\t  
if(!is_parse_complete) continue;

      #if 0
printf("recv on frame\\
");
      #endif

handle_mainboard_read( &rxmsg_dev);
\t  
}

Management of wireless signal pairing, connection, authentication, and status monitoring

/**
Effective wifi connection and tcp server connection
*/
void* wifi_connect_manager_thread(void*param)
{
     #define WIFI_CONNECT_TIMOUT (15)
#define IWINFO_NC_MAX_CNT (10)


UI8 trycnt;
     I8 data[MAX_BUFFER_CACHE_SIZE];
     I8*cur_board_connect_ssid;
\t 
BOOLEAN is_cur_board_connect_ssid;
I8*cur_board_connect_passwd;
BOOLEAN is_cur_board_connect_passwd;

     I8*wlan0ip;
     I8*iwinfoname;
I8*iwinfomac;

     UI8 iwinfo_nc_cnt = 0;
UI8 is_iwinfo_need_check = 0;
int tmp_read_cnt = 0;


     while(TRUE)
{
if(!strlen(curssid) || is_iwinfo_need_check){

             if(is_iwinfo_need_check) is_iwinfo_need_check = 0;

             iwinfoname = cmd_system(IWINFO_GET_NAME);
             if(iwinfoname != NULL & amp; & amp;
strlen(iwinfoname) > 0 & amp; & amp;
strncmp(IWINFO_GET_NAME_UNKNOWN, iwinfoname, strlen(IWINFO_GET_NAME_UNKNOWN)))
{
//bakup cur name
memset(data,0,sizeof(data));
//Need to remove two double quotes and add a newline
strncpy(data, iwinfoname + 1, strlen(iwinfoname) - 3 );

iwinfomac = cmd_system(IWINFO_GET_MAC);

                 if(iwinfomac != NULL & amp; & amp;
strlen(iwinfomac) > 0 & amp; & amp;
!strncmp(iwinfomac,DEVICE_FIX_MAC,10))
{
  #ifdef DEBUG
printf("iwinfo get name & amp; mac ok...\\
");
#endif

memset(curssid,'\0',sizeof(curssid));
strcpy(curssid,data);
memset(curpasswd,'\0',sizeof(curpasswd));
strcpy(curpasswd,WPS_MODE_FIX_PASSWD);
\t\t\t\t\t 
continue;
}
else {
#ifdef DEBUG
printf("iwinfo get mac is invalid...\\
");
#endif
}
}else{

#ifdef DEBUG
printf("iwinfo get name is invalid...\\
");
#endif
}


#ifdef DEBUG
printf("curssid is invalid...\\
");
#endif

usleep(DELAY_1S);
continue;
}

 
         #ifdef DEBUG
         printf("start to connect wifi=%s:%s\\
",curssid,curpasswd);
#endif


         //Avoid repeated connection to already connected WiFi
         is_cur_board_connect_ssid = FALSE;
         cur_board_connect_ssid = cmd_system(UCI_GET_SSID);
//ignore ending 0x0A
if(!strncmp(curssid,cur_board_connect_ssid,strlen(cur_board_connect_ssid) - 1)) is_cur_board_connect_ssid = TRUE;

is_cur_board_connect_passwd = FALSE;
cur_board_connect_passwd = cmd_system(UCI_GET_PASSWD);
//ignore ending 0x0A
if(!strncmp(curpasswd,cur_board_connect_passwd,strlen(cur_board_connect_passwd) - 1)) is_cur_board_connect_passwd = TRUE;

         if(!(is_cur_board_connect_ssid & amp; & amp;
is_cur_board_connect_passwd
)){

#ifdef DEBUG
printf("wifi_connect do...\\
");
#endif

wifi_connect(curssid,curpasswd);
//When switching WiFi, the currently connected WiFi has an impact on the status of wlan0
//So the delay is eliminated
             //usleep(DELAY_3S);
usleep(DELAY_2S);

#ifdef DEBUG
printf("wifi_connect do complete...\\
");
#endif

trycnt = 0;
while(!wlan0_status() & amp; & amp; trycnt + + < WIFI_CONNECT_TIMOUT){
#ifdef DEBUG
printf("wifi connect failed!\\
");
#endif
usleep(DELAY_100MS);
}

if(trycnt >= WIFI_CONNECT_TIMOUT){

//When the connection fails all the time, iwinfo should also check periodically
if(iwinfo_nc_cnt + + >= IWINFO_NC_MAX_CNT / 2){
iwinfo_nc_cnt = 0;
is_iwinfo_need_check = 1;
}

continue;
}else{
                 iwinfo_nc_cnt = 0;
}
}



/**
The WiFi configuration is all right, but the wireless network interface is not connected yet,
In this case, just wait for the system to reconnect to wifi,
Instead of having to do it yourself, it can save time in terms of automatic connection and disconnection after power on
*/
if(!wlan0_status())
{
#ifdef DEBUG
printf("wait system to connect target wifi...\\
");
#endif

usleep(DELAY_100MS);
continue;
}


         #ifdef DEBUG
         printf("wifi connect success!\\
");
#endif


//Different network segments have an impact on socket connections
wlan0ip = cmd_system(WLAN0_GET_IP);
trycnt = 0;
while((wlan0ip == NULL || strlen(wlan0ip) == 0) & amp; & amp; trycnt ++ < 5)
{
            usleep(DELAY_100MS);
wlan0ip = cmd_system(WLAN0_GET_IP);
}
if(trycnt >= 5) {
if(wlan0ip == NULL) wlan0ip = "";
}
\t\t 

         #ifdef DEBUG
printf("wlan0ip=%s\\
",wlan0ip);
         #endif

         if(strncmp(SERVER_IP,wlan0ip,9)){
            #ifdef DEBUG
printf("local ip SERVER_IP are not in lan\\
");
            #endif
continue;
         }

memset(data,0,sizeof(data));
sprintf(data,"%s &%s",curssid,curpasswd);

if(write_file(WIFI_CONFIG_PATH,data,strlen(data)) < 0){
#ifdef DEBUG
printf("save valid wifi failed!\\
");
#endif
}

         #ifdef DEBUG
         printf("save valid wifi success!\\
");
#endif


//When the connection has been successful but no valid connection has been established, iwinfo should also check periodically
if(iwinfo_nc_cnt + + >= IWINFO_NC_MAX_CNT){
iwinfo_nc_cnt = 0;
is_iwinfo_need_check = 1;

#ifdef DEBUG
printf("iwinfo period check!\\
");
#endif

continue;
}
\t\t 

         trycnt = 0;
is_wifi_info_prepare = 0;
\t\t 
//If there is a new connection request, disconnect the current connection and connect to the new connection
while(TRUE)
{
if(!is_tcp_client_connect)
{
                tcp_client_socket_deinit();

                usleep(DELAY_300MS);

if(tcp_client_socket_init() < 0){
if(trycnt + + < 3){
continue;
}else{
break;
}
}
                
is_tcp_client_connect = 1;
}
else
{
//new network request
if(is_wifi_info_prepare){
#ifdef DEBUG
printf("new wifi connect request...\\
");
#endif
                     is_tcp_client_connect = 0;
break;
}


/*When the user turns off the device, there is no feedback information, so the state detection is used*/
\t\t\t\t //Disconnected from the network
//Continuous accumulation
trycnt = 0;
                 while(!wlan0_status()){

#ifdef DEBUG
printf("wlan0_status is test\\
");
#endif

if(trycnt + + >= WIFI_CONNECT_TIMOUT){
tcp_client_socket_deinit();
is_tcp_client_connect = 0;

#ifdef DEBUG
printf("wlan0 status is down\\
");
#endif
\t\t\t\t\t\t
break;
}

usleep(DELAY_100MS);
}

\t\t\t\t //Disconnect
if(!is_tcp_client_connect){
#ifdef DEBUG
printf("tcp client disconnect 1\\
");
#endif

pthread_mutex_lock( & serial_mutex);
serial_send(gd_serial_fd, wifi_connect_failed, sizeof(wifi_connect_failed));
pthread_mutex_unlock( & serial_mutex);
\t\t\t\t\t
break;
}


//Indirectly detect whether tcp is disconnected
trycnt = 0;
tmp_read_cnt = global_read_cnt;
 
while(tmp_read_cnt == global_read_cnt)
                 {
/*
#ifdef DEBUG
printf("tmp_read_cnt=%d, global_read_cnt=%d, trycnt=%d\\
",
tmp_read_cnt, global_read_cnt, trycnt);
#endif
*/
//Wait for 6s
if(trycnt + + >= 12)
{
                        tcp_client_socket_deinit();
is_tcp_client_connect = 0;

#ifdef DEBUG
printf("wait 6s over...is_tcp_client_connect=%d\\
",is_tcp_client_connect);
#endif
break;
}

usleep(DELAY_500MS);
}



\t\t\t\t //Disconnect
if(!is_tcp_client_connect){
#ifdef DEBUG
printf("tcp client disconnect 2\\
");
#endif

pthread_mutex_lock( & serial_mutex);
serial_send(gd_serial_fd, wifi_connect_failed, sizeof(wifi_connect_failed));
pthread_mutex_unlock( & serial_mutex);
\t\t\t\t\t
break;
}

}


//The normal connection state needs to sleep
usleep(DELAY_100MS);
\t\t\t 
}
}

pthread_detach(pthread_self());
}

Network connection authentication, handshake, synchronization, encryption and decryption, two-way data transparent transmission management

//Responsible for connection initialization of client socket
int tcp_client_socket_init()
{
static pthread_t send_t, rec_t;
struct sockaddr_in serv_addr;
UI8 tcprxbuf[RECV_LENGTH];
int readlen;
int options = 1;
void*retcode = NULL;


    /**
There is no need to repeatedly allocate socket handles when reconnecting
*/
    if(gd_sock_client == -1){

#ifdef DEBUG
printf("new socket handler create...\\
");
#endif

gd_sock_client = socket(AF_INET, SOCK_STREAM, 0);
    }


if(gd_sock_client < 0){
#ifdef DEBUG
printf("The client socket is not create!\\
");
#endif
return -1;
}

memset( &serv_addr, 0, sizeof(serv_addr));

serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = inet_addr(SERVER_IP);
serv_addr.sin_port = htons(SERVER_PORT);



if (connect(gd_sock_client, (struct sockaddr*) & amp;serv_addr, sizeof(serv_addr)) < 0){

//global_tcp_close(gd_sock_client);
//gd_sock_client = -1;

#ifdef DEBUG
printf("The tcp client connect is not build!\\
");
#endif

return -1;
}



    //Configure socketfd blocking properties
//fcntl(gd_sock_client,F_SETFL,fcntl(gd_sock_client,F_GETFL,0) & ~O_NONBLOCK);
if(setsockopt(gd_sock_client, IPPROTO_TCP, TCP_NODELAY,(char *) & amp;options, sizeof(options)) == -1){

       #ifdef DEBUG
printf("setsockopt TCP_NODELAY failed\\
");
       #endif

global_tcp_close(gd_sock_client);
gd_sock_client = -1;
\t  
return -1;
}
\t

    //Set tcp heartbeat to keep connected
    if (setsockopt(gd_sock_client, SOL_SOCKET, SO_KEEPALIVE, (void *) & options, sizeof(options)) == -1) {

       #ifdef DEBUG
printf("setsockopt SO_KEEPALIVE failed\\
");
       #endif

global_tcp_close(gd_sock_client);
gd_sock_client = -1;
\t  
return -1;
    }



    memset(tcprxbuf,0,sizeof(tcprxbuf));
    // read connection status
readlen = read(gd_sock_client,tcprxbuf,RECV_LENGTH);

\t//supporting agreement
if(!strcmp(CONNECT_STATUS_ERROR,tcprxbuf)){
\t\t
global_tcp_close(gd_sock_client);
gd_sock_client = -1;
\t\t
#ifdef DEBUG
printf("socket connect status:%s\\
",CONNECT_STATUS_ERROR);
#endif
        return -1;
}else if(!strcmp(CONNECT_STATUS_SUCCESS,tcprxbuf)){
#ifdef DEBUG
        printf("socket connect status:%s\\
",CONNECT_STATUS_SUCCESS);
#endif
}else{
\t
global_tcp_close(gd_sock_client);
gd_sock_client = -1;

return -1;
}


    //protocol handshake
    if(handle_benchmark_opts() < 0) {

global_tcp_close(gd_sock_client);
gd_sock_client = -1;

return -1;
}

   
    if(handle_comm_protocol() < 0) {

global_tcp_close(gd_sock_client);
gd_sock_client = -1;

return -1;
}


    alarm_cnt = 0;


timer_setup();

    bm_calibrate_cnt = 0;
global_read_cnt = 0;


    // Clear the buffer queue
tcflush(gd_serial_fd, TCIFLUSH);
tcflush(gd_serial_fd, TCOFLUSH);

pthread_mutex_lock( & inqueue_mutex);
set_clear();
pthread_mutex_unlock( & inqueue_mutex);


    //
    if(is_send_stream_running)
    {
 pthread_cancel(send_t);
pthread_join(send_t, retcode);
\t\t
usleep(DELAY_100MS);

#ifdef DEBUG
printf("send_t thread exit code=%d\\
",(int)retcode);
#endif
    }
    
is_send_stream_break = 0;
if (pthread_create( & send_t, NULL, tcp_send_stream, NULL) != 0){

        is_send_stream_break = 1;
        global_tcp_close(gd_sock_client);
gd_sock_client = -1;
\t\t
#ifdef DEBUG
printf("The thread of send is not create!\\
");
#endif
return -1;
}

\t
    
if(is_recv_stream_running)
{
 pthread_cancel(rec_t);
pthread_join(rec_t, retcode);

usleep(DELAY_100MS);

#ifdef DEBUG
printf("rec_t thread exit code=%d\\
",(int)retcode);
#endif
}
\t
    is_recv_stream_break = 0;
if (pthread_create( &rec_t, NULL, tcp_recv_stream, NULL) != 0){

        is_recv_stream_break = 1;
global_tcp_close(gd_sock_client);
gd_sock_client = -1;
\t\t
#ifdef DEBUG
printf("The thread of rec is not create!\\
");
#endif
return -1;
}
\t

#ifdef DEBUG
printf("tcp connect server ok!\\
");
#endif

    pthread_mutex_lock( & serial_mutex);
serial_send(gd_serial_fd, wifi_connect_ok, sizeof(wifi_connect_ok));
    pthread_mutex_unlock( & serial_mutex);
\t
return 0;
}

Two-way transparent transmission of data uplink and downlink tasks

//Responsible for all sending tasks of tcp socket
void* tcp_send_stream(void*param)
{
UI8*sendata;
    UI8 sendatalen;
UI8 is_alloc;

    is_send_stream_running = 1;

while (!is_send_stream_break)
{
        if(gd_sock_client != -1 & amp; & amp; is_tcp_client_connect & amp; & amp; !is_empty()){

            pthread_mutex_lock( & inqueue_mutex);
out_queue( &sendata, &sendatalen, &is_alloc);
pthread_mutex_unlock( & inqueue_mutex);

            if(sendata != NULL & amp; & amp; sendatalen > 0){

if(tcp_data_io_write(gd_sock_client,sendata,sendatalen) < 0)
{

#ifdef DEBUG
printf("tcp_data_io_write failed\\
");
#endif

if(gd_sock_client != -1)
global_tcp_close(gd_sock_client);

is_send_stream_break = 1;
is_recv_stream_break = 1;
gd_sock_client = -1;
is_tcp_client_connect = 0;

//The dynamically allocated memory needs to be released manually
if(is_alloc)
{
buffree(sendata);
}
\t\t\t\t  
break;
}


if(is_alloc)
{
buffree(sendata);
}
            }
}
        else {

usleep(DELAY_1MS);
        }
}


    /**
The default state of creating a thread is joinable, if a thread finishes running but has not been joined,
Then its state is similar to Zombie Process in the process,
That is, some resources have not been recovered (exit status code),
So the creator of the thread should call pthread_join to wait for the thread to finish running,
And get the exit code of the thread, reclaim its resources (similar to wait, waitpid)
But after calling pthread_join(pthread_id), if the thread
did not run to end,
The caller will be blocked, in some cases we do not want this,
For example, in a web server, when the main thread creates a sub-thread for each new link to process,
The main thread does not want to be blocked by calling pthread_join (because it will continue to process the incoming links),
At this time, you can add code in the child thread
pthread_detach(pthread_self())
or the parent thread calls
pthread_detach(thread_id) (non-blocking, return immediately)
This sets the state of the child thread to detached, and all resources will be released automatically after the thread finishes running.
*/
    pthread_detach(pthread_self());

    is_send_stream_running = 0;

#ifdef DEBUG
printf("......tcp_send_stream exit......\\
");
#endif
}





//Responsible for all receiving tasks of tcp socket
void* tcp_recv_stream(void*param)
{
    UI8 tcprxbuf[RECV_LENGTH]={0};
    int count;
int try_cnt;
\t
is_recv_stream_running = 1;

while (!is_recv_stream_break)
{
count = read(gd_sock_client, tcprxbuf, RECV_LENGTH);
\t\t
if (count < 0) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
continue;
}

#ifdef DEBUG
printf("tcp_recv_stream count < 0\\
");
#endif

/* Got an error on the read, shut down the port. */
#if 1
\t\t\t
if(gd_sock_client != -1)
global_tcp_close(gd_sock_client);

is_send_stream_break = 1;
is_recv_stream_break = 1;
gd_sock_client = -1;
is_tcp_client_connect = 0;

break;
#endif
}
else if (count == 0) {
/* The other end closed the port, shut it down. */

#ifdef DEBUG
printf("tcp_recv_stream count==0\\
");
#endif

            if(gd_sock_client != -1)
global_tcp_close(gd_sock_client);

is_send_stream_break = 1;
is_recv_stream_break = 1;
gd_sock_client = -1;
is_tcp_client_connect = 0;
break;
}

#if 0
        #ifdef DEBUG
        printf("tcp_recv_stream:");
        print0x(tcprxbuf, count);
        #endif
#endif

        /
// length limit
UI8 buf_len = count;
if(buf_len < PROTOCOL_MIN_LEN || buf_len > PROTOCOL_MAX_LEN) continue;

        //forward to phone
        /*if(gd_local_mobile_client != -1){
           tcp_data_io_write(gd_local_mobile_client, tcprxbuf, buf_len);
        }*/
\t\t
\t\t
// XOR decryption
UI8* decbuf = decode_protocol(tcprxbuf,buf_len);


// check
UI32 sam = check_sam(decbuf,buf_len);
UI32 decbufsam = decbuf[PROTOCOL_SAMH_INDEX(buf_len)] << 8 | decbuf[PROTOCOL_SAML_INDEX(buf_len)];

#if 0
#ifdef DEBUG
print0x(decbuf,buf_len);
        printf("sam=x,decbufsam=x\\
",sam,decbufsam);
#endif
#endif
\t\t
if(sam != decbufsam) continue;


//Platform command filtering
if(handle_platform_opts(decbuf,buf_len) >= 0) continue;


//mavlink receiving and verification
UI8*prxmsg;
UI8 retval;
UI8 tmplen = buf_len - PROTOCOL_EXTRA_LEN;

status_tcp.parse_state = MAVLINK_PARSE_STATE_IDLE;
\t
while(tmplen--)
{
retval = Mavlink_Frame_Char_Buffer( &rxmsg_tcp, &status_tcp,*decbuf++,TRUE);
\t\t\t
if(MAVLINK_FRAMING_INCOMPLETE == retval)
continue;
else if(MAVLINK_FRAMING_BAD_CRC == retval)
break;
else
{
prxmsg = (UI8*) & rxmsg_tcp + CHECKSUM_OFFSET_LEN;
break;
}
}

#if 0
        #ifdef DEBUG
        printf("tcp_recv_stream retval=%d,tmplen=%d\\
",retval,tmplen);
print0x(prxmsg,buf_len - PROTOCOL_EXTRA_LEN);
#endif
#endif

        if(retval == MAVLINK_FRAMING_BAD_CRC) continue;

//Strengthen protocol integrity detection
if(!(tmplen == 0 & amp; & amp; retval == MAVLINK_FRAMING_OK)) continue;
\t    
\t  
        buf_len -= PROTOCOL_EXTRA_LEN;
        //Remote response reply command filtering
//if(!(buf_len >= NON_PAYLOAD_LEN & amp; & amp; prxmsg[5] == PROTOCOL_CUSTOM_ECHO_CTL)) continue;
 
prxmsg[PROTOCOL_MAVLINK_SAMH_INDEX(buf_len)] = MAVLINK_TAIL_REPLACE_CKA;
prxmsg[PROTOCOL_MAVLINK_SAML_INDEX(buf_len)] = MAVLINK_TAIL_REPLACE_CKB;

        //statistics
        global_read_cnt++;

        try_cnt = 0;
try_ser_write:
pthread_mutex_lock( & serial_mutex);
        count = serial_send(gd_serial_fd,prxmsg,buf_len);
pthread_mutex_unlock( & serial_mutex);
\t\t
if(count < 0 & amp; & amp; try_cnt + + < 3) goto try_ser_write;
}


     //Prevent memory leaks
//pthread_detach(pthread_self());

    pthread_detach(pthread_self());


    is_recv_stream_running = 0;

#ifdef DEBUG
printf("......tcp_recv_stream exit......\\
");
#endif
}






check_tcp_close_wait.sh monitors the situation caused by server exceptions in the background.

#Poll check CLOSE_WAIT, restart the blocked process
while true
do
   pid=$(netstat -anp | grep 192.168.2.1:16888 | grep CLOSE_WAIT | awk '{print $7}' | cut -d \/ -f1 | grep -oE "[[:digit:]]{ 1,}")

   if [ ! -n "$pid" ]; then
  echo "check_tcp_close_wait IS NULL"
   else
     killall -9 ser2tcpclient
 sleep 1
 ser2tcpclient &
  
     echo "check_tcp_close_wait NOT NULL"
   the fi

   sleep 3
done

Summary

full source code