I'm writing a program to communicate with an existing device via serial port, and I am noticing a weird pattern. This is being tested with both a real serial port, and a USB-to-serial adapter. I get the same results for both.
Immediately after booting up the computer or plugging in the adapter, serial port communication works fine. I can send binary data to the device, and get a response back. The program can continue to communicate with the device as long as it wants.
However, once the program ends (cleanly closing the port), running the program again results in failure to communicate. It can access the serial port just fine, but all it gets back is garbage.
(Oddly, the garbage appears to be binary data mixed with modem commands like ATE0Q0S0=0, which makes no sense. Again, I don't need to reset the device to communicate with it, just the port, so I don't know where this is coming from.)
Power-cycling or unplugging the device has no effect. It is only when I reboot the computer, or reset the USB device (via unplug, or driver reset), that I can run the program once more and get it to communicate successfully.
What would cause this? From the results, I can only assume that the serial port is not being left in a clean state after use, but I can find no documentation about properly cleaning the serial port state, other than re-applying ioctl attributes and closing the file descriptor after use, both of which I already do.
Maybe a serial port pin gets left on or something? I don't know how I would test for that, or why it would even happen.
My current "solution" is to just stick with the USB adapter, and have my program perform a USB driver reset before attempting to use the serial port, but I'm hoping there is a better solution.
Edit
As requested, here is the C program I'm using to test the serial port read/write.
include
#include <fcntl.h>
#include <inttypes.h>
#include <math.h>
#include <stdio.h>
#include <string.h>
#include <termios.h>
#include <time.h>
#include <unistd.h>
// Saved termios that we can re-apply when we exit
struct termios savedPortAttributes;
// Set the serial port attributes so we can use it
void setPortAttributes( int fd )
{
struct termios tty;
memset( &tty, 0, sizeof(tty) );
if ( tcgetattr( fd, &tty ) != 0 ) {
printf( "tcgetaddr error: $i\n", errno );
return;
}
cfsetispeed( &tty, B9600 );
cfsetospeed( &tty, B9600 );
cfmakeraw( &tty );
tty.c_lflag = 0;
tty.c_oflag = 0;
tty.c_iflag &= ~(IXON | IXOFF | IXANY);
tty.c_cflag |= (CLOCAL | CREAD);
tty.c_cflag &= ~(PARENB | PARODD);
tty.c_cflag &= ~CSTOPB;
tty.c_cflag &= ~CRTSCTS;
tty.c_cc[VMIN] = 0;
tty.c_cc[VTIME] = 5;
if ( tcsetattr( fd, TCSANOW, &tty ) != 0 ) {
printf( "tcsetaddr error: $i\n", errno );
return;
}
if ( tcflush( fd, TCIOFLUSH ) != 0 ) {
printf( "tcflush error: $i\n", errno );
return;
}
}
void test( int fd )
{
// Send a sample MODBUS command
printf( "Writing command\n" );
char sendBuffer[] = { 0x01, 0x03, 0x00, 0x0B, 0x00, 0x02, 0xB5, 0xC9 };
int bytesWritten = write( fd, sendBuffer, sizeof(sendBuffer) );
if ( bytesWritten < 0 ) {
printf( "Error writing command.\n" );
return;
}
// We don't want to wait more than 1000ms for a response
struct timespec spec;
clock_gettime( CLOCK_MONOTONIC, &spec );
int64_t startMs = spec.tv_sec * 1000 + round( spec.tv_nsec / 1.0e6 );
// Read data back from the port
printf( "Reading from port...\n" );
unsigned char buffer[1024];
int bufferOffset = 0;
int count = 0;
while ( 1 ) {
count = read( fd, &buffer[bufferOffset], sizeof(buffer) - bufferOffset );
if ( count < 0 ) {
printf( "Error reading command.\n" );
return;
}
if ( count > 0 ) {
printf( "Bytes read: " );
for ( int i = bufferOffset; i < bufferOffset + count; i++ ) {
printf( "%02x ", buffer[i] );
}
printf( "\n" );
}
bufferOffset += count;
// Test code. If we receive part of a valid MODBUS response, grab the
// field length byte so we know if we're done reading
if ( bufferOffset >= 3 && buffer[0] == 1 && buffer[1] == 3 ) {
int messageLength = buffer[2];
if ( bufferOffset >= messageLength + 5 ) {
break;
}
}
// If it's been 1000ms, stop reading
clock_gettime( CLOCK_MONOTONIC, &spec );
int64_t timeMs = spec.tv_sec * 1000 + round( spec.tv_nsec / 1.0e6 );
//printf( "%" PRId64 " , %" PRId64 "\n", startMs, timeMs );
if ( timeMs - startMs > 1000 ) {
break;
}
}
}
void main()
{
printf( "Opening port\n" );
int fd = open( "/dev/ttyUSB0", O_RDWR|O_NOCTTY );
if ( fd == -1 ) {
printf( "Unable to open port.\n" );
return;
}
tcgetattr( fd, &savedPortAttributes );
setPortAttributes( fd );
test( fd );
test( fd );
tcsetattr( fd, TCSANOW, &savedPortAttributes );
close( fd );
}
Related
I have code that's worked without change since the 90s that is now getting Permission denied on Linux when trying to turn off the Nagle algorithm. Read of the man pages and a google search don't indicate why. Any ideas?
int iFlags, iSize;
/* NOTE: Sol 2.8 header requires socklen_t but man page says int! */
int iSizeSize = sizeof( iSize );
#ifdef _WIN32
unsigned long ulMSDummy;
if ( ioctlsocket( iFD, FIONBIO, (u_long FAR*) &ulMSDummy ) != 0 ) {
printf( "%s: ioctlsocket( %s:%d, FIONBIO, 1 ): %s",
pszName, pszAddr, iPort, strerror(errno));
return -1;
}
#else
if ( ( iFlags = fcntl( iFD, F_GETFL, 0 ) ) < 0 ) {
AKWarn( "%s: fcntl( %s:%d, F_GETFL ): %s",
pszName, pszAddr, iPort, strerror(errno));
return -1;
}
// NOTE: O_NDELAY may need to be changed to FNDELAY on some
// platforms (which does the same thing) or O_NONBLOCK (which may
// cause AKread() to return different values when there's no data).
// Any of these three make the socket non-blocking, which is
// DIFFERENT from TCP_NODELAY (see below).
if ( fcntl( iFD, F_SETFL, iFlags | O_NDELAY ) < 0 ) {
printf( "%s: fcntl( %s:%d, F_SETFL, +NDELAY ): %s",
pszName, pszAddr, iPort, strerror(errno));
return -1;
}
#endif
// NOTE: TCP_NODELAY is unrelated to the various NDELAY/NONBLOCK
// options (above). Instead, it disables the "Nagle Algorithm",
// which caches tiny packets.
// NOTE: This option hardcodes a tradeoff for less latency and more
// packets. Actually this could be a configuration parameter.
iFlags = 1;
if ( setsockopt( iFD, SOL_SOCKET, TCP_NODELAY,
(char*) &iFlags, sizeof( int ) ) ) {
printf( "%s: setsockopt( %s:%d, TCP_NODELAY, %d ): %s",
pszName, pszAddr, iPort, iFlags, strerror(errno) );
#ifndef __linux__
return -1; // giving Permission denied on Linux???
#endif
}
if ( setsockopt( iFD, SOL_SOCKET, TCP_NODELAY,
This is simply wrong from start. It should be IPPROTO_TCP and not SOL_SOCKET. These are different constants. Likely it never properly worked before, i.e. did something else than you intended.
I am using a Linux system, not a Windows system. I've posted some code, below. Please bear in mind that this code was never intended to be "production quality."
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <netdb.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define PORT 9909
void die ( const char *fmt, ... )
{
va_list vargs;
va_start( vargs, fmt );
vfprintf( stderr, fmt, vargs );
va_end( vargs );
exit( 1 );
}
int main ( int argc, char **argv )
{
/* *** */
int listener = socket( PF_INET, SOCK_STREAM, 0 );
if( listener < 0 ) die( "socket(listener)" );
int flag = 1;
if( setsockopt( listener, SOL_SOCKET, SO_REUSEADDR, (char*)&flag, sizeof(int) ) < 0 )
die( "setsockopt()" );
struct sockaddr_in svr_addr;
memset( &svr_addr, 0, sizeof(struct sockaddr) );
svr_addr.sin_family = PF_INET;
svr_addr.sin_port = htons( PORT );
svr_addr.sin_addr.s_addr = INADDR_ANY;
if( bind( listener, (struct sockaddr*)&svr_addr, (socklen_t)sizeof(struct sockaddr) ) < 0 )
die( "bind()" );
if( listen( listener, 10 ) < 0 )
die( "listen()" );
/* *** */
fd_set fd_master;
fd_set fd_select;
int fd_max = listener;
FD_ZERO( &fd_master );
FD_ZERO( &fd_select );
FD_SET( listener, &fd_master );
while( 1 )
{
fd_select = fd_master;
if( select( fd_max + 1, &fd_select, NULL, NULL, NULL ) < 0 )
die( "select()" );
for( int ifd = 0; ifd <= fd_max; ++ifd )
{
if( ! FD_ISSET( ifd, &fd_select ) ) continue;
struct sockaddr_in cli_addr; memset( &cli_addr, 0, sizeof(cli_addr) );
socklen_t cli_alen = sizeof(cli_addr);
if( ifd == listener )
{
int cli = accept( listener, (struct sockaddr*)&cli_addr, &cli_alen );
if( cli < 0 ) die( "accept()" );
FD_SET( cli, &fd_master );
if( cli > fd_max ) fd_max = cli;
printf( "new connection> %s:%u\n", inet_ntoa( cli_addr.sin_addr ), ntohs( cli_addr.sin_port ) );
fflush( stdout );
}
else
{
char buf[256];
cli_alen = sizeof(cli_addr);
ssize_t nbytes = recvfrom( ifd, buf, sizeof(buf), 0, (struct sockaddr*)&cli_addr, &cli_alen );
if( nbytes <= 0 )
{
close( ifd );
FD_CLR( ifd, &fd_master );
if( nbytes == 0 )
printf( "connection hung up> %u\n", ifd );
else
printf( "recvfrom() : %s\n", strerror( errno ) );
fflush( stdout );
}
else
{
// build a "from identifier" for each of the recipients
char msg[sizeof(buf) * 2];
sprintf( msg, "%s:%u> ", inet_ntoa( cli_addr.sin_addr ), ntohs( cli_addr.sin_port ) );
memcpy( msg + strlen( msg ), buf, nbytes );
nbytes += strlen( msg );
// send incoming data to all clients (excluding the originator)
for( int ofd = 0; ofd <= fd_max; ++ofd )
{
if( FD_ISSET( ofd, &fd_master ) )
if( ofd != listener && ofd != ifd )
if( send( ofd, msg, nbytes, 0 ) < 0 )
{ printf( "send() %s\n", strerror( errno ) ); fflush( stdout ); }
}
}
}
}
}
return 0;
}
When the code is run and you connect from two or more clients (via telnet), each message shows the sender as "0.0.0.0" with a port of 0.
The Windows documentation for recvfrom() states "[t]he from and fromlen parameters are ignored for connection-oriented sockets." The Linux and POSIX documentation make no such claim and goes as far as to say that recvfrom() "...may be used to receive data on a socket whether or not it is connection-oriented." No where does it say that src_addr and addrlen will be ignored ... so I would expect these to be filled in.
On connected sockets you have to call getpeername and then carry on with your inet_ntoa (consider using inet_ntop instead as it supports multiple address families). As per the man pages:
int getpeername(int socket, struct sockaddr *restrict address, socklen_t *restrict address_len);
Nowhere does it say that src_addr and addrlen will be ignored.
That is simply untrue. It says
If src_addr is not NULL, and the underlying protocol provides the source address, this source address is filled in. [emphasis added]
You can argue about whether TCP can be said to provide the source address, but you can't claim 'nowhere does it say ...'.
I am trying to solve producer consumer problem using threads without semaphoere.In my client i create 4 threads 2 for producer and 2 for consumer, each of them send M produce/consume messages. Here is my client code.
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <pthread.h>
#define BUFSIZE 4096
#define N 4
/*
** Client
*/
int M = 10;
pthread_t threads[N];
char buf[BUFSIZE];
char *service;
char *host = "localhost";
int cc;
int csock;
int consumed,produced;
void *connect_and_handle(void *msg){
/* Create the socket to the controller */
if ( ( csock = connectsock( host, service, "tcp" )) == 0 ) {
fprintf( stderr, "Cannot connect to server.\n" );
exit( -1 );
}
printf( "The server is ready, please start entering commands.\n" );
fflush( stdout );
// Start the loop
int k;
//char msg[50];
for (k=0;k<M;k++){
strcpy(buf, msg);
// Send to the server
if ( write( csock, buf, strlen(buf) ) < 0 ) {
fprintf( stderr, "client write failed: %s\n", strerror(errno) );
exit( -1 );
}
if ( (cc = read( csock, buf, BUFSIZE )) <= 0 )
break;
buf[cc] = 0;
printf( "Server replied: %s\n", buf );
}
close( csock );
// exit thread
pthread_exit(NULL);
}
int main( int argc, char *argv[] ){
char *msg, *msg2;
switch( argc ) {
case 2:
service = argv[1];
break;
case 3:
host = argv[1];
service = argv[2];
break;
default:
fprintf( stderr, "usage: chat [host] port\n" );
exit(-1);
}
// thread code goes here
int i, n = N;
for (i=0;i<N;i++){
msg = (char*)malloc(32*sizeof(char));
msg2 = (char*)malloc(32*sizeof(char));
sprintf(msg,"PRODUCE This is the item #%i", i);
sprintf(msg2, "CONSUME");
//producer thread
produced = pthread_create( &threads[i], NULL, connect_and_handle, (void *) msg );
if ( produced != 0 ) { printf( "Error: pthread_create returned code %d.\n", produced); exit( -1 );}
//consumer thread
/*
i++;
consumed = pthread_create( &threads[i], NULL, connect_and_handle, (void *) msg2 );
if ( consumed != 0 ){ printf( "Error: pthread_create returned code %d.\n", consumed ); exit( -1 );}
*/
}
}
and server :
// This server implements part of the 333 protocol
// NUMBER - number of clients served
// NAMES - developers
// GOODBYE - close connection
// ADD - increment
// SUBTRACT- decrement
//
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <semaphore.h>
#define QLEN 5
#define BUFSIZE 4096
#define MAX 100
/*
** This server ... is threaded
*/
// Function prototypes
int passivesock( char *, char *, int, int * );
void *handle_a_client( void *arg );
// Global variables are shared by the threads
int clients = 0;
char *buffer[MAX];
char *maloc_buf;
int count=0;
int full_count=0;
int empty_count=MAX;
int main( int argc, char *argv[] ) {
char *service;
struct sockaddr_in fsin;
int alen;
int msock;
int ssock;
int rport = 0;
switch (argc) {
case 1:
// No args? let the OS choose a port and tell the user
rport = 1;
break;
case 2:
// User provides a port? then use it
service = argv[1];
break;
default:
fprintf( stderr, "usage: server [port]\n" );
exit(-1);
}
msock = passivesock( service, "tcp", QLEN, &rport );
if (rport) {
// Tell the user the selected port
printf( "server: port %d\n", rport );
fflush( stdout );
}
// Keep accepting clients until you are killed
for (;;) {
int ssock;
pthread_t pid;
alen = sizeof(fsin);
ssock = accept( msock, (struct sockaddr *)&fsin, &alen );
if (ssock < 0) {
fprintf( stderr, "accept: %s\n", strerror(errno) );
exit(-1);
}
clients++;
printf("connected , %i", clients);
// Launch a thread to manage this client
// YES, pid is getting overwritten each time, but it is unused
pthread_create( &pid, NULL, handle_a_client, (void *) ssock );
}
}
void *handle_a_client( void *arg ) {
char requestbuf[BUFSIZE];
char replybuf[BUFSIZE];
int ssock = (int) arg;
int cc;
for (;;) {
if ( (cc = read( ssock, requestbuf, BUFSIZE )) <= 0 ) {
printf( "The client has gone.\n");
(void) close(ssock);
pthread_exit(0);
break;
}
else {
// Remove the newline and null-terminate the string
requestbuf[cc] = '\0';
int size = cc-7;
printf( "The client on %d says: %s\n", ssock, requestbuf );
if ( strncasecmp( requestbuf, "goodbye", 7 ) == 0 ) {
close( ssock );
break;
}
else if ( strncasecmp( requestbuf, "PRODUCE", 7 ) == 0 ) {
if (full_count == MAX){
strcpy(replybuf,"FULL\n");
write(ssock,replybuf,strlen(replybuf));
}
else {
maloc_buf=(char*) malloc((size)*sizeof(char));
strcpy(maloc_buf, (requestbuf+8));
buffer[full_count]=maloc_buf;
int num=full_count+1;
sprintf(replybuf, "Client produced item no%i: %s",full_count, buffer[full_count]);
full_count++;
empty_count--;
}
}
else if ( strncasecmp( requestbuf, "CONSUME", 7 ) == 0 ) {
if (empty_count == MAX) {
strcpy(replybuf,"EMPTY\n");
write( ssock, replybuf, strlen(replybuf) );
}
else {
sprintf(replybuf,"OK %s", buffer
[full_count]);
free(buffer[full_count]);
full_count--;
empty_count++;
}
}
}
}
}
When i run my server and then try to connect to it in client, nothing happens. Debugging showed (i am not sure) that in client code after
if ( ( csock = connectsock( host, service, "tcp" )) == 0 ) {
i am exiting, nothing is printed to console both in client and server.
My goal is to set 2 threads for serial ports: one for read, one for write.
My example is refer to the [one](//refer to how to open, read, and write from serial port in C) heavily, but I added pthread to my code:
//refer to https://stackoverflow.com/questions/6947413/how-to-open-read-and-write-from-serial-port-in-c
//refer to https://stackoverflow.com/questions/6947413/how-to-open-read-and-write-from-serial-port-in-c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <termios.h>
#include <pthread.h> /* POSIX Threads */
#define MAX_STR_LEN 256
/*
* The values for speed are
* B115200, B230400, B9600, B19200, B38400, B57600, B1200, B2400, B4800, etc
*
* The values for parity are 0 (meaning no parity),
* PARENB|PARODD (enable parity and use odd),
* PARENB (enable parity and use even),
* PARENB|PARODD|CMSPAR (mark parity),
* and PARENB|CMSPAR (space parity).
* */
int SetInterfaceAttribs(int fd, int speed, int parity)
{
struct termios tty;
memset (&tty, 0, sizeof tty);
if (tcgetattr (fd, &tty) != 0) /* save current serial port settings */
{
printf("__LINE__ = %d, error %s\n", __LINE__, strerror(errno));
return -1;
}
cfsetospeed (&tty, speed);
cfsetispeed (&tty, speed);
tty.c_cflag = (tty.c_cflag & ~CSIZE) | CS8; // 8-bit chars
// disable IGNBRK for mismatched speed tests; otherwise receive break
// as \000 chars
tty.c_iflag &= ~IGNBRK; // disable break processing
tty.c_lflag = 0; // no signaling chars, no echo,
// no canonical processing
tty.c_oflag = 0; // no remapping, no delays
tty.c_cc[VMIN] = 0; // read doesn't block
tty.c_cc[VTIME] = 5; // 0.5 seconds read timeout
tty.c_iflag &= ~(IXON | IXOFF | IXANY); // shut off xon/xoff ctrl
tty.c_cflag |= (CLOCAL | CREAD);// ignore modem controls,
// enable reading
tty.c_cflag &= ~(PARENB | PARODD); // shut off parity
tty.c_cflag |= parity;
tty.c_cflag &= ~CSTOPB;
tty.c_cflag &= ~CRTSCTS;
if (tcsetattr (fd, TCSANOW, &tty) != 0)
{
printf("__LINE__ = %d, error %s\n", __LINE__, strerror(errno));
return -1;
}
return 0;
}/*set_interface_attribs*/
void SetBlocking(int fd, int should_block)
{
struct termios tty;
memset (&tty, 0, sizeof tty);
if (tcgetattr(fd, &tty) != 0)
{
printf("__LINE__ = %d, error %s\n", __LINE__, strerror(errno));
return;
}
tty.c_cc[VMIN] = should_block ? 1 : 0;
tty.c_cc[VTIME] = 5; // 0.5 seconds read timeout
if (tcsetattr (fd, TCSANOW, &tty) != 0)
printf("__LINE__ = %d, error %s\n", __LINE__, strerror(errno));
}/*SetBlocking*/
void *sendThread(void *parameters)
{
char sendBuff[MAX_STR_LEN];
memset(&sendBuff[0], 0, MAX_STR_LEN);
snprintf(&sendBuff[0], MAX_STR_LEN, "hello!");
int fd;
fd = *((int*)parameters);
while(1)
{
write(fd, &sendBuff[0], strlen(&sendBuff[0]) );
// sleep enough to transmit the length plus receive 25:
// approx 100 uS per char transmit
usleep((strlen(&sendBuff[0]) + 25) * 100);
}/*while*/
pthread_exit(0);
}/*sendThread */
void *readThread(void *parameters)
{
char readBuff[MAX_STR_LEN];
int fd;
fd = *((int*)parameters);
while(1)
{
ssize_t len;
memset(&readBuff[0], 0, MAX_STR_LEN);
len = read(fd, &readBuff[0], MAX_STR_LEN);
if (len == -1)
{
switch(errno)
{
case EAGAIN:
printf("__FUNCTION__ = %s, __LINE__ = %d\n", __FUNCTION__, __LINE__);
usleep(5*1000);
continue;
break;
default:
printf("__FUNCTION__ = %s, __LINE__ = %d\n", __FUNCTION__, __LINE__);
pthread_exit(0);
break;
}
}
// sleep enough to transmit the length plus receive 25:
// approx 100 uS per char transmit
usleep((len + 25) * 100);
printf("len = %d\n", (int)len);
int i;
for(i = 0; i< len; i++)
printf("%c(%d %#x)\t", readBuff[i], readBuff[i], readBuff[i]);
printf("\n");
}/*while*/
pthread_exit(0);
}/*readThread */
int main(int argc, char *argv[])
{
int fd, c, res;
struct termios oldtio,newtio;
char buf[MAX_STR_LEN];
int k;
char deviceName[MAX_STR_LEN];
memset(&deviceName[0], 0, MAX_STR_LEN);
snprintf(&deviceName[0], MAX_STR_LEN, "/dev/ttyUSB0");
k = 1;
while(argc > k)
{
if(0 == strncmp(argv[k], "-d", MAX_STR_LEN))
{
if(k + 1 < argc)
{
snprintf(&deviceName[0], MAX_STR_LEN, "%s", argv[k + 1]);
}
else
{
printf("error : -d should be follow a device!\n");
return 0;
}/*if */
}
k++;
}/*while k*/
printf("__FUNCTION__ = %s, __LINE__ = %d\n", __FUNCTION__, __LINE__);
fd = open(&deviceName[0], O_RDWR | O_NOCTTY |O_NONBLOCK| O_NDELAY);
if(0 > fd)
{
perror(&deviceName[0]);
exit(-1);
}/*if */
SetInterfaceAttribs(fd, B115200, 0); /* set speed to 115,200 bps, 8n1 (no parity)*/
SetBlocking(fd, 1);
pthread_t readThread_t, sendThread_t; /* thread variables */
pthread_create(&sendThread_t, NULL, (void *)sendThread, (void *)&fd);
pthread_create(&readThread_t, NULL, (void *)readThread, (void *)&fd);
pthread_join(sendThread_t, NULL);
pthread_join(readThread_t, NULL);
close(fd);
return 0;
}/*main*/
The send data thread works well.
But the read data thread : I could not set it as blocking, the read function returns immediately, even the read data length is zero.
How should I modify the code to make the read function be blocked?
fd = open(&deviceName[0], O_RDWR | O_NOCTTY |O_NONBLOCK| O_NDELAY);
Try removing O_NONBLOCK and O_NDELAY from your open call. Or is there a particular reason you have that even though you specifically want it to block?
I know we can use 'NETLINK_ROUTE' socket with RTM_F_NOTIFY flag to notify user if route changes according to the RFC3549. But I can't find one method to notify the user when arp table changes.
PS: I use linux kernel 3.0.6
One should use NETLINK_ROUTE socket and bind() to the group RTNLGRP_NEIGH.
After that one can get ndmsg notifications with recv(). Pls note, that all notifications must be consumed, otherwise the socket will raise the ENOBUF exception with the next recv() after the overflow.
Or with external ip(8) utility:
$ ip monitor all # get all the notifications
$ ip monitor neigh # get only arp notifications
Or with a Python library:
from pyroute2 import IPRoute
from pprint import pprint
ip = IPRoute()
ip.bind() # subscribe to all the events
while True:
pprint(ip.get())
If I understood you right - I would suggest you to analyse sources of "ip" program. When you run "ip monitor" and add or remove any ARP entry then "ip monitor" will show you appropriate message.
I'm not aware of pure arp notifications but you could use the arpd which runs in user-space and is intended for cases where the arp table is very large. If you use arpd and modify it you can make it send you notifications on arp table changes.
You can use this to catch arp add/del messages.
#include <stdio.h>
#include <string.h>
#include <netinet/in.h>
#include <linux/rtnetlink.h>
#include <errno.h>
#include <stdlib.h>
#include <unistd.h>
#include <syslog.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <time.h>
/* convert the netlink multicast group number into a bit map */
/* ( e.g. 4 => 16, 5 => 32 ) */
static __u32 nl_mgrp( __u32 group )
{
if ( group > 31 )
{
printf( "Netlink: Use setsockopt() for this group: %d\n", group ) ;
return 0 ;
}
return ( group ? ( 1 << ( group - 1 ) ) : 0 ) ;
}
void process_message( char *buf , int raw_msg_len )
{
struct nlmsghdr *nlmp ;
struct ndmsg *rtmp ;
struct rtattr *rtatp ;
int rtattrlen, len, req_len ;
struct in_addr *inp ;
char ipv4string[INET_ADDRSTRLEN] ;
printf( "process_message: raw_msg_len: %d\n" , raw_msg_len ) ;
for ( nlmp = ( struct nlmsghdr * ) buf ; raw_msg_len > sizeof( *nlmp ) ; )
{
int len = nlmp->nlmsg_len ;
int req_len = len - sizeof( *nlmp ) ;
//check if arp is changed
if(nlmp->nlmsg_type == RTM_NEWNEIGH){
printf("RTM_NEWNEIGH\n");
} else if (nlmp->nlmsg_type == RTM_DELNEIGH){
printf("RTM_DELNEIGH\n");
}
if ( ( req_len < 0 ) || ( len > raw_msg_len ) || ( ! NLMSG_OK( nlmp, raw_msg_len ) ) )
{
printf( "error\n" ) ;
// Should processing of the message continue if there are certain types of problems?
}
rtmp = ( struct ndmsg * ) NLMSG_DATA( nlmp ) ;
rtatp = ( struct rtattr * ) IFA_RTA( rtmp ) ;
rtattrlen = IFA_PAYLOAD( nlmp ) ;
for ( ; RTA_OK( rtatp, rtattrlen ) ; rtatp = RTA_NEXT( rtatp, rtattrlen ) )
{
if( rtatp->rta_type == NDA_DST )
{
inp = ( struct in_addr * ) RTA_DATA( rtatp ) ;
inet_ntop( AF_INET, inp, ipv4string, INET_ADDRSTRLEN ) ;
printf( "addr: %s\n" , ipv4string ) ;
}
}
raw_msg_len -= NLMSG_ALIGN( len ) ;
nlmp = ( struct nlmsghdr * ) ( ( char* ) nlmp + NLMSG_ALIGN( len ) ) ;
}
// How is the event to be parsed?
}
int main( int argc , char** argv )
{
int sock ;
static struct sockaddr_nl g_addr ;
char buffer[4096] ;
int received_bytes = 0 ;
int ret ;
/* Zeroing addr */
bzero( &g_addr, sizeof( g_addr ) ) ;
g_addr.nl_family = AF_NETLINK ;
g_addr.nl_groups = nl_mgrp( RTNLGRP_NEIGH ) ;
if ( ( sock = socket( AF_NETLINK, SOCK_RAW, NETLINK_ROUTE ) ) < 0 )
{
printf( "socket() error: %s\n", strerror( errno ) ) ;
return -1 ;
}
if ( bind( sock, ( struct sockaddr * ) &g_addr, sizeof( g_addr ) ) < 0 )
{
printf( "bind() error: %s\n" , strerror( errno ) ) ;
return -1 ;
}
while ( 1 )
{
received_bytes = recv( sock , buffer , sizeof( buffer ) , 0 ) ;
if ( received_bytes > 0 )
{
printf( "\n- Event -\n" ) ;
process_message( buffer, received_bytes ) ;
}
}
}