/* Serval Distributed Numbering Architecture (DNA) Copyright (C) 2010 Paul Gardner-Stephen This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "mphlr.h" #ifdef HAVE_IFADDRS_H #include #endif int overlay_ready=0; int overlay_interface_count=0; overlay_interface overlay_interfaces[OVERLAY_MAX_INTERFACES]; int overlay_last_interface_number=-1; struct interface_rules { char *namespec; unsigned long long speed_in_bits; int port; char type; char excludeP; struct interface_rules *next; }; struct interface_rules *interface_filter=NULL; unsigned int overlay_sequence_number=0; time_t overlay_sequence_start_time; /* Do we need to repeat our abbreviation policy? */ int overlay_interface_repeat_abbreviation_policy[OVERLAY_MAX_INTERFACES]={1}; long long overlay_gettime_ms() { struct timeval nowtv; if (gettimeofday(&nowtv,NULL)) return WHY("gettimeofday() failed"); /* Get current time in milliseconds */ long long now=(nowtv.tv_sec-overlay_sequence_start_time)*1000LL; now=now+nowtv.tv_usec/1000; return now; } int overlay_update_sequence_number() { long long now=overlay_gettime_ms(); overlay_sequence_number=now&0xffffffff; return 0; } int overlay_interface_type(char *s) { if (!strcasecmp(s,"ethernet")) return OVERLAY_INTERFACE_ETHERNET; if (!strcasecmp(s,"wifi")) return OVERLAY_INTERFACE_WIFI; if (!strcasecmp(s,"other")) return OVERLAY_INTERFACE_UNKNOWN; if (!strcasecmp(s,"catear")) return OVERLAY_INTERFACE_PACKETRADIO; return WHY("Invalid interface type -- consider using 'wifi','ethernet' or 'other'"); } int overlay_interface_arg(char *arg) { /* Parse an interface argument, of the form: <+|->[interfacename][=type] +interface tells DNA to sit on that interface -interface tells DNA to not sit on that interface +/- without an interface tells DNA to sit on all interfaces. The first match rules, so -en0+ tells DNA to use all interfaces, excepting en0 The optional =type specifier tells DNA how to handle the interface in terms of bandwidth:distance relationship for calculating tick times etc. The special type =custom allows full specification: XXX - Settle the custom specification now that we have changed the interface management. */ char sign[80]="+"; char interface_name[80]=""; char speed[80]="1m"; char typestring[80]="wifi"; int port=PORT_DNA; int type=OVERLAY_INTERFACE_UNKNOWN; int n=0; /* Too long */ if (strlen(arg)>79) return WHY("interface specification was >79 characters"); struct interface_rules *r=calloc(sizeof(struct interface_rules),1); if (!r) return WHY("calloc(struct interface rules),1) failed"); if (sscanf(arg,"%[+-]%n%[^=+-]%n=%[^:]%n:%d%n:%[^:]%n", sign,&n,interface_name,&n,typestring,&n,&port,&n,speed,&n)>=1) { if (n1) { free(r); return WHY("Sign must be + or -"); } switch(sign[0]) { case '+': break; case '-': r->excludeP=1; break; default: free(r); return WHY("Invalid interface list item: Must begin with + or -"); } long long speed_in_bits=parse_quantity(speed); if (speed_in_bits<=1) { free(r); return WHY("Interfaces must be capable of at least 1 bit per second"); } if (nnamespec=strdup(interface_name); r->speed_in_bits=speed_in_bits; r->port=port; r->type=type; r->next=interface_filter; interface_filter=r; return 0; } else { free(r); return WHY("Bad interface specification"); } } int overlay_interface_args(char *arg) { /* Parse series of comma-separated interface definitions from a single argument */ int i=0; char interface[80]; int len=0; for(i=0;arg[i];i++) { if (arg[i]==',') { interface[len]=0; if (overlay_interface_arg(interface)) return WHY("Could not add interface"); len=0; } else { if (len<79) { interface[len++]=arg[i]; interface[len]=0; } else return WHY("Interface definition is too long (each must be <80 characters)"); } } if (len) if (overlay_interface_arg(interface)) return WHY("Could not add final interface"); return 0; } int overlay_interface_init_socket(int interface,struct sockaddr_in src_addr,struct sockaddr_in broadcast, struct sockaddr_in netmask) { #define I(X) overlay_interfaces[interface].X I(local_address)=src_addr; I(broadcast_address)=broadcast; I(netmask)=netmask; I(fileP)=0; I(fd)=socket(PF_INET,SOCK_DGRAM,0); if (I(fd)<0) { return WHY("Could not create UDP socket for interface"); } src_addr.sin_family = AF_INET; src_addr.sin_port = htons( I(port) ); /* XXX Is this right? Are we really setting the local side address? I was in a plane when at the time, so couldn't Google it. */ if (debug&DEBUG_PACKETXFER) fprintf(stderr,"src_addr=%08x\n",(unsigned int)src_addr.sin_addr.s_addr); if(bind(I(fd),(struct sockaddr *)&src_addr,sizeof(src_addr))) { perror("bind()"); return WHY("MP HLR server could not bind to requested UDP port (bind() failed)"); } if (debug&(DEBUG_PACKETXFER|DEBUG_IO)) fprintf(stderr,"Bound to port 0x%04x\n",src_addr.sin_port); int broadcastP=1; if(setsockopt(I(fd), SOL_SOCKET, SO_BROADCAST, &broadcastP, sizeof(broadcastP)) < 0) { perror("setsockopt"); return WHY("setsockopt() failed"); } return 0; #undef I } int overlay_interface_init(char *name,struct sockaddr_in src_addr,struct sockaddr_in broadcast, struct sockaddr_in netmask,int speed_in_bits,int port,int type) { /* Too many interfaces */ if (overlay_interface_count>=OVERLAY_MAX_INTERFACES) return WHY("Too many interfaces -- Increase OVERLAY_MAX_INTERFACES"); #define I(X) overlay_interfaces[overlay_interface_count].X strcpy(I(name),name); /* Pick a reasonable default MTU. This will ultimately get tuned by the bandwidth and other properties of the interface */ I(mtu)=1200; I(observed)=1; I(bits_per_second)=speed_in_bits; I(port)=port; I(type)=type; I(tick_ms)=500; I(last_tick_ms)=0; I(fd)=0; switch(type) { case OVERLAY_INTERFACE_PACKETRADIO: I(tick_ms)=15000; break; case OVERLAY_INTERFACE_ETHERNET: I(tick_ms)=500; break; case OVERLAY_INTERFACE_WIFI: I(tick_ms)=500; break; } if (!overlay_sequence_start_time) overlay_sequence_start_time=time(0); if (name[0]=='>') { I(fileP)=1; I(fd) = open(&name[1],O_APPEND|O_RDWR); if (I(fd)<1) return WHY("could not open dummy interface file for append"); /* Seek to end of file as initial reading point */ I(offset)=lseek(I(fd),0,SEEK_END); /* socket gets reused to hold file offset */ /* XXX later add pretend location information so that we can decide which "packets" to receive based on closeness */ } else { if (overlay_interface_init_socket(overlay_interface_count,src_addr,broadcast,netmask)) return WHY("overlay_interface_init_socket() failed"); } overlay_interface_count++; #undef I return 0; } int overlay_rx_messages() { int i; /* Grab packets, unpackage and dispatch frames to consumers */ /* XXX Okay, so how are we managing out-of-process consumers? They need some way to register their interest in listening to a port. */ unsigned char packet[16384]; int plen=0; int c[OVERLAY_MAX_INTERFACES]; int count=0; /* Look at all interfaces */ for(i=0;i0); count+=c[i]; } /* Grab packets from interfaces in round-robin fashion until all have been grabbed, or until we have spent too long (maybe 10ms?) */ int now = overlay_gettime_ms(); while(count>0) { for(i=0;i=length) { if (debug&DEBUG_OVERLAYINTERFACES) fprintf(stderr,"At end of input on dummy interface #%d\n",i); } else { lseek(overlay_interfaces[i].fd,overlay_interfaces[i].offset,SEEK_SET); if (debug&DEBUG_OVERLAYINTERFACES) fprintf(stderr,"Reading from interface #%d log at offset %d, end of file at %lld.\n",i, overlay_interfaces[i].offset,length); if (read(overlay_interfaces[i].fd,&packet[0],2048)==2048) { overlay_interfaces[i].offset+=2048; plen=2048-128; plen=packet[110]+(packet[111]<<8); if (plen>(2048-128)) plen=-1; if (debug&DEBUG_PACKETXFER) serval_packetvisualise(stderr, "Read from dummy interface", &packet[128],plen); bzero(&transaction_id[0],8); bzero(&src_addr,sizeof(src_addr)); if ((plen>=0)&&(packet[0]==0x01)&&!(packet[1]|packet[2]|packet[3])) { { if (packetOk(i,&packet[128],plen,transaction_id, -1 /* fake TTL */, &src_addr,addrlen,1)) WHY("Malformed or unsupported packet from dummy interface (packetOK() failed)"); } } else WHY("Invalid packet version in dummy interface"); } else { if (debug&DEBUG_IO) fprintf(stderr,"Read NOTHING from dummy interface\n"); c[i]=0; count--; } } } else { /* Read from UDP socket */ int recvttl=1; plen=recvwithttl(overlay_interfaces[i].fd,packet,sizeof(packet), &recvttl,&src_addr,&addrlen); if (plen<0) { c[i]=0; count--; } else { /* We have a frame from this interface */ if (debug&DEBUG_PACKETXFER) serval_packetvisualise(stderr,"Read from real interface", packet,plen); if (debug&DEBUG_OVERLAYINTERFACES)fprintf(stderr,"Received %d bytes on interface #%d (%s)\n",plen,i,overlay_interfaces[i].name); if (packetOk(i,packet,plen,NULL,recvttl,&src_addr,addrlen,1)) WHY("Malformed packet"); } } } /* Don't sit here forever, or else we will never send any packets */ if (overlay_gettime_ms()>(now+10)) break; } return 0; } int overlay_tx_messages() { /* Check out the various queues, and add payloads to a new frame and send it out. */ /* XXX We may want to throttle the maximum packets/sec or KB/sec */ /* How are we going to pick and choose things from the various priority queues? We could simply pick the top item from each queue in round-robin until the frame is filled. That would be a start. We could certainly get more intelligent and stuff lots of little frames from a high priority queue in if that makes sense, especially if they look like getting delayed a bit. Perhaps we just reserve the first n bytes for the first queue, the first n+k bytes for the first two queues and so on? */ /* XXX Go through queue and separate into per-interface queues? */ return WHY("not implemented"); } int overlay_broadcast_ensemble(int interface_number, struct sockaddr_in *recipientaddr /* NULL == broadcast */, unsigned char *bytes,int len) { struct sockaddr_in s; memset(&s, '\0', sizeof(struct sockaddr_in)); if (recipientaddr) { bcopy(recipientaddr,&s,sizeof(struct sockaddr_in)); } else { s = overlay_interfaces[interface_number].broadcast_address; s.sin_family = AF_INET; if (debug&DEBUG_PACKETXFER) fprintf(stderr,"Port=%d\n",overlay_interfaces[interface_number].port); s.sin_port = htons( overlay_interfaces[interface_number].port ); } if (overlay_interfaces[interface_number].fileP) { char buf[2048]; /* Version information */ buf[0]=1; buf[1]=0; buf[2]=0; buf[3]=0; /* bytes 4-5 = half-power beam height (uint16) */ /* bytes 6-7 = half-power beam width (uint16) */ /* bytes 8-11 = range in metres, centre beam (uint32) */ /* bytes 16-47 = sender */ /* bytes 48-79 = next hop */ /* bytes 80-83 = latitude (uint32) */ /* bytes 84-87 = longitude (uint32) */ /* bytes 88-89 = X/Z direction (uint16) */ /* bytes 90-91 = Y direction (uint16) */ /* bytes 92-93 = speed in metres per second (uint16) */ /* bytes 94-97 = TX frequency in Hz, uncorrected for doppler (which must be done at the receiving end to take into account relative motion) */ /* bytes 98-109 = coding method (use for doppler response etc) null terminated string */ /* bytes 110-111 = length of packet body in bytes */ /* bytes 112-127 reserved for future use */ if (len>2048-128) { WHY("Truncating long packet to fit within 1920 byte limit for dummy interface"); len=2048-128; } /* Record length of packet */ buf[110]=len&0xff; buf[111]=(len>>8)&0xff; bzero(&buf[128+len],2048-(128+len)); bcopy(bytes,&buf[128],len); if (write(overlay_interfaces[interface_number].fd,buf,2048)!=2048) { perror("write()"); return WHY("write() failed"); } else return 0; } else { if(sendto(overlay_interfaces[interface_number].fd, bytes, len, 0, (struct sockaddr *)&s, sizeof(struct sockaddr_in)) != len) { /* Failed to send */ perror("sendto(c)"); return WHY("sendto() failed"); } else /* Sent okay */ return 0; } } /* This function is called to return old non-overlay requests back out the interface they came in. */ int overlay_sendto(struct sockaddr_in *recipientaddr,unsigned char *bytes,int len) { if (debug&DEBUG_PACKETXFER) fprintf(stderr,"Sending %d bytes.\n",len); if(overlay_broadcast_ensemble(overlay_last_interface_number,recipientaddr,bytes,len)) return -1; else return len; } time_t overlay_last_interface_discover_time=0; int overlay_interface_discover() { /* Don't waste too much time and effort on interface discovery, especially if we can't attach to a given interface for some reason. */ if ((time(0)-overlay_last_interface_discover_time)<2) return 0; overlay_last_interface_discover_time=time(0); #ifdef HAVE_IFADDRS_H struct ifaddrs *ifaddr,*ifa; int family,i; if (getifaddrs(&ifaddr) == -1) { perror("getifaddr()"); return WHY("getifaddrs() failed"); } /* Mark all interfaces as not observed, so that we know if we need to cull any */ for(i=0;inamespec[0]=='>') { for(i=0;inamespec)) break; if (inamespec,dummyaddr,dummyaddr,dummyaddr, 1000000,PORT_DNA,OVERLAY_INTERFACE_WIFI)) WHY("Could not initialise newly seen interface"); else if (debug&DEBUG_OVERLAYINTERFACES) fprintf(stderr,"Registered interface %s\n",r->namespec); } } r=r->next; } /* Check through actual network interfaces */ for (ifa=ifaddr;ifa!=NULL;ifa=ifa->ifa_next) { family=ifa->ifa_addr->sa_family; switch(family) { case AF_INET: { unsigned char *name=(unsigned char *)ifa->ifa_name; struct sockaddr_in local=*(struct sockaddr_in *)ifa->ifa_addr; struct sockaddr_in netmask=*(struct sockaddr_in *)ifa->ifa_netmask; unsigned int broadcast_bits=local.sin_addr.s_addr|~netmask.sin_addr.s_addr; struct sockaddr_in broadcast=local; broadcast.sin_addr.s_addr=broadcast_bits; if (debug&DEBUG_OVERLAYINTERFACES) printf("%s: %08x %08x %08x\n",name,local.sin_addr.s_addr,netmask.sin_addr.s_addr,broadcast.sin_addr.s_addr); /* Now register the interface, or update the existing interface registration */ struct interface_rules *r=interface_filter,*me=NULL; while(r) { if (!strcasecmp((char *)name,r->namespec)) me=r; if (!r->namespec[0]) me=r; r=r->next; } if (me&&(!me->excludeP)) { if (debug&DEBUG_OVERLAYINTERFACES) fprintf(stderr,"Interface %s is interesting.\n",name); /* We should register or update this interface. */ int i; for(i=0;ispeed_in_bits,me->port,me->type)) WHY("Could not initialise newly seen interface"); else if (debug&DEBUG_OVERLAYINTERFACES) fprintf(stderr,"Registered interface %s\n",name); } } break; } } } freeifaddrs(ifaddr); #endif return 0; } int overlay_stuff_packet_from_queue(int i,overlay_buffer *e,int q,long long now,overlay_frame *pax[],int *frame_pax,int frame_max_pax) { overlay_frame **p=&overlay_tx[q].first; while(p&&*p) { /* Throw away any stale frames */ overlay_frame *pp=*p; if (!pp) break; /* XXX Uses hardcoded freshness threshold, when it should get it from the queue */ if (now>((*p)->enqueued_at+overlay_tx[q].latencyTarget)) { /* Stale, so remove from queue. */ /* Get pointer to stale entry */ overlay_frame *stale=*p; fprintf(stderr,"Removing stale frame at %p (now=%lld, expiry=%lld)\n",stale, now,((*p)->enqueued_at+overlay_tx[q].latencyTarget)); /* Make ->next pointer that points to the stale node skip the stale node */ *p=stale->next; /* If there is an entry after the stale now, make it's prev point to the node before the stale node */ if (*p) (*p)->prev=stale->prev; if (overlay_tx[q].first==stale) overlay_tx[q].first=stale->next; if (overlay_tx[q].last==stale) overlay_tx[q].last=stale->prev; op_free(stale); overlay_tx[q].length--; } else { /* XXX Filter for those which should be sent via this interface. To do that we need to know the nexthop, and the best route to the next hop. */ /* We keep trying to queue frames in case they will fit, as not all frames are of equal size. This means that lower bit-rate codecs will get higher priority, which is probably not all bad. The only hard limit is the maximum number of payloads we allow in a frame, which is set so high as to be irrelevant, even on loopback or gigabit ethernet interface */ if (*frame_pax>=frame_max_pax) break; if (!overlay_frame_package_fmt1(*p,e)) { /* Add payload to list of payloads we are sending with this frame so that we can dequeue them if we send them. */ pax[(*frame_pax)++]=*p; } p=&(*p)->next; } } return 0; } int overlay_queue_dump(overlay_txqueue *q) { struct overlay_frame *f; fprintf(stderr,"overlay_txqueue @ 0x%p\n",q); fprintf(stderr," length=%d\n",q->length); fprintf(stderr," maxLenght=%d\n",q->maxLength); fprintf(stderr," latencyTarget=%d milli-seconds\n",q->latencyTarget); fprintf(stderr," first=%p\n",q->first); f=q->first; while(f) { fprintf(stderr," %p: ->next=%p, ->prev=%p\n", f,f->next,f->prev); if (f==f->next) { fprintf(stderr," LOOP!\n"); break; } f=f->next; } fprintf(stderr," last=%p\n",q->last); f=q->last; while(f) { fprintf(stderr," %p: ->next=%p, ->prev=%p\n", f,f->next,f->prev); if (f==f->prev) { fprintf(stderr," LOOP!\n"); break; } f=f->prev; } return 0; } int overlay_tick_interface(int i, long long now) { int frame_pax=0; #define MAX_FRAME_PAX 1024 overlay_frame *pax[MAX_FRAME_PAX]; if (overlay_interfaces[i].bits_per_second<1) { /* An interface with no speed budget is for listening only, so doesn't get ticked */ return 0; } if (debug&DEBUG_OVERLAYINTERFACES) fprintf(stderr,"Ticking interface #%d\n",i); /* Get a buffer ready, and limit it's size appropriately. XXX size limit should be reduced from MTU. XXX we should also take account of the volume of data likely to be in the TX buffer. */ overlay_buffer *e=ob_new(overlay_interfaces[i].mtu); if (!e) return WHY("ob_new() failed"); ob_limitsize(e,overlay_interfaces[i].mtu/4); /* 0. Setup Serval Mesh frame header. We do not use an explicit length field for these, as the various component payloads are all self-authenticating, or at least that is the theory. */ unsigned char bytes[]={/* Magic */ 'O',0x10, /* Version */ 0x00,0x01}; if (ob_append_bytes(e,bytes,4)) return WHY("ob_append_bytes() refused to append magic bytes."); /* 1. Send announcement about ourselves, including one SID that we host if we host more than one SID (the first SID we host becomes our own identity, saving a little bit of data here). */ overlay_add_selfannouncement(i,e); /* 2. Add any queued high-priority isochronous data (i.e. voice) to the frame. */ overlay_stuff_packet_from_queue(i,e,OQ_ISOCHRONOUS_VOICE,now,pax,&frame_pax,MAX_FRAME_PAX); ob_limitsize(e,overlay_interfaces[i].mtu/2); /* 3. Add some mesh reachability reports (unlike BATMAN we announce reachability to peers progressively). Give priority to newly observed nodes so that good news travels quickly to help roaming. XXX - Don't forget about PONGing reachability reports to allow use of monodirectional links. */ overlay_stuff_packet_from_queue(i,e,OQ_MESH_MANAGEMENT,now,pax,&frame_pax,MAX_FRAME_PAX); ob_limitsize(e,overlay_interfaces[i].mtu*3/4); /* Add advertisements for ROUTES not Rhizome bundles. Rhizome bundle advertisements are lower priority */ overlay_route_add_advertisements(i,e); ob_limitsize(e,overlay_interfaces[i].mtu); /* 4. XXX Add lower-priority queued data */ /* 5. XXX Fill the packet up to a suitable size with anything that seems a good idea */ overlay_rhizome_add_advertisements(i,e); /* Now send the frame. This takes the form of a special DNA packet with a different service code, which we setup earlier. */ if (debug&DEBUG_OVERLAYINTERFACES) fprintf(stderr,"Sending %d byte tick packet\n",e->length); if (!overlay_broadcast_ensemble(i,NULL,e->bytes,e->length)) { overlay_update_sequence_number(); if (debug&DEBUG_OVERLAYINTERFACES) fprintf(stderr,"Successfully transmitted tick frame #%lld on interface #%d (%d bytes)\n", (long long)overlay_sequence_number,i,e->length); /* De-queue the passengers who were aboard. */ int j,q; for(q=0;qnext; /* Now get rid of this frame once we have found it */ if (*p) { if (debug&DEBUG_QUEUES) { fprintf(stderr,"** dequeueing pax @ %p\n",*p); overlay_queue_dump(&overlay_tx[q]); } *p=pax[j]->next; if (overlay_tx[q].last==pax[j]) overlay_tx[q].last=pax[j]->prev; if (overlay_tx[q].first==pax[j]) overlay_tx[q].first=pax[j]->next; if (pax[j]->prev) pax[j]->prev->next=pax[j]->next; if (pax[j]->next) pax[j]->next->prev=pax[j]->prev; if (debug&DEBUG_QUEUES) { fprintf(stderr,"** dequeued pax @ %p\n",*p); overlay_queue_dump(&overlay_tx[q]); } if (op_free(pax[j])) { overlay_queue_dump(&overlay_tx[q]); WHY("op_free() failed"); if (debug&DEBUG_QUEUES) sleep(3600); } overlay_tx[q].length--; } } } return 0; } else return WHY("overlay_broadcast_ensemble() failed"); } int overlay_check_ticks() { /* Check if any interface(s) are due for a tick */ int i; /* Check for changes to interfaces */ overlay_interface_discover(); long long now=overlay_gettime_ms(); /* Now check if the next tick time for the interfaces is no later than that time. If so, trigger a tick on the interface. */ if (debug&DEBUG_OVERLAYINTERFACES) fprintf(stderr,"Examining %d interfaces.\n",overlay_interface_count); for(i=0;i0) { if (debug&DEBUG_VERBOSE_IO)fprintf(stderr,"Interface %s ticks every %dms, last at %lld.\n",overlay_interfaces[i].name, overlay_interfaces[i].tick_ms,overlay_interfaces[i].last_tick_ms); if (now>=overlay_interfaces[i].last_tick_ms+overlay_interfaces[i].tick_ms) { /* This interface is due for a tick */ overlay_tick_interface(i,now); overlay_interfaces[i].last_tick_ms=now; } } else if (debug&DEBUG_VERBOSE_IO)fprintf(stderr,"Interface %s is awol.\n",overlay_interfaces[i].name); } return 0; } long long overlay_time_until_next_tick() { /* By default only tick once per day */ long long nexttick=86400*1000; long long now=overlay_gettime_ms(); int i; if (debug&DEBUG_VERBOSE_IO)fprintf(stderr,"Tick-check on %d interfaces at %lldms\n",overlay_interface_count,now); for(i=0;i0) { if (debug&DEBUG_VERBOSE_IO) fprintf(stderr,"Interface %s ticks every %dms, last at T-%lldms.\n",overlay_interfaces[i].name, overlay_interfaces[i].tick_ms,now-overlay_interfaces[i].last_tick_ms); long long thistick=(overlay_interfaces[i].last_tick_ms+overlay_interfaces[i].tick_ms)-now; if (thistick<0) thistick=0; if (thistick