1 /*
2    gnauralnet_main.c
3    Central code to achieve cooperative clock sync'ing between peers on a network
4    Depends on:
5    gnauralnet.h
6    gnauralnet_clocksync.c
7    gnauralnet_lists.c
8    gnauralnet_main.c
9    gnauralnet_socket.c
10 
11    Copyright (C) 2009  Bret Logan
12 
13    This program is free software; you can redistribute it and/or modify
14    it under the terms of the GNU General Public License as published by
15    the Free Software Foundation; either version 2 of the License, or
16    (at your option) any later version.
17 
18    This program is distributed in the hope that it will be useful,
19    but WITHOUT ANY WARRANTY; without even the implied warranty of
20    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21    GNU General Public License for more details.
22 
23    You should have received a copy of the GNU General Public License
24    along with this program; if not, write to the Free Software
25    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
26  */
27 
28 /////////////////////////////////////////////
29 //User creates these two:
30 //extern int GN_ProcessIncomingData ();
31 //extern int GN_MainLoop ();
32 
33 ////////////////////////////////////////
34 /*
35    GN_start([user function]) and GN_stop() are all user must run, then
36    for user function set up a Main Loop like this:
37 
38    while (GNAURALNET_RUNNING == GN_My.State)
39    {
40    GN_Socket_RecvMessage ([time to wait for data]);
41    GN_ProcessOutgoingData (all the friends);//optional
42    }
43    GN_My.State = GNAURALNET_STOPPED;
44    return GNAURALNET_SUCCESS;
45    }
46 
47    to wait for recv's from GN_Socket_RecvMessage(), handling incoming/outgoing/
48    invite/timing data with GN_Process**() functions
49    NOTE: if GNAURALNET_GTHREADS is set, GN_start() starts a thread
50    that stays in an internal loop calling GN_recv*Buffer(). If not set,
51    user needs to call GN_recv*Buffer() in their own loop like
52    while "(0 == GN_My.State) {}" until GN_stopped breaks it.
53 
54    To compile with the MinGW gcc cross compiler, be sure you have gnauralnet.h:
55    i586-mingw32msvc-g++ -g -O2 -o gnauralnet.exe -mms-bitfields -mwindows \
56    -I/usr/i586-mingw32msvc/include/glib-2.0 \
57    -I/usr/i586-mingw32msvc/lib/glib-2.0/include \
58    -DGNAURAL_WIN32 gnauralnet_main.c gnauralnet_clocksync.c gnauralnet_socket.c \
59    gnauralnet_lists.c -lwsock32
60 
61    You should strip it too:
62    i586-mingw32msvc-strip gnauralnet.exe
63  */
64 //VERSION INFO:
65 //This is the first
66 
67 #include "gnauralnet.h"
68 
69 //Global variables:
70 //this is the struct holding all major single-instance kinds of globals:
71 GN_Globals GN_My = { GNAURALNET_STOPPED };
72 
73 int GN_DebugFlag = 0;
74 GN_CommBuffer GN_RecvBuffer;
75 GN_CommBuffer GN_SendBuffer;
76 unsigned short GN_ScheduleFingerprint = 0;
77 //If user doesn't set these next two manually in GN_start (), they
78 //default to internals functions:
79 int (*GN_MainLoop) (void *ptr) = NULL;  //defaults to GN_MainLoop_default
80 void (*GN_ProcessIncomingData) (char *MsgBuf, int sizeof_MsgBuf, struct sockaddr_in * saddr_remote) = NULL;     //defaults to GN_ProcessIncomingData_default
81 
82 ///////////////////////////////////////////////////////////
83 //pass NULL to this and it just uses the built-in Main Loop, GN_MainLoop_default ()
GN_start(int (* main_loop_func)(void * ptr),void (* incoming_data_func)(char * MsgBuf,int sizeof_MsgBuf,struct sockaddr_in * saddr_remote))84 int GN_start (int (*main_loop_func) (void *ptr),
85               void (*incoming_data_func) (char *MsgBuf, int sizeof_MsgBuf,
86                                           struct sockaddr_in * saddr_remote))
87 {
88  if (NULL == main_loop_func)
89  {
90   GN_MainLoop = GN_MainLoop_default;
91  }
92  else
93  {
94   GN_MainLoop = main_loop_func;
95  }
96 
97  if (NULL == incoming_data_func)
98  {
99   GN_ProcessIncomingData = GN_ProcessIncomingData_default;
100  }
101  else
102  {
103   GN_ProcessIncomingData = incoming_data_func;
104  }
105 
106  GN_DBGOUT_INT ("Size of GN_CommBuffer:", sizeof (GN_CommBuffer));
107  //init network:
108  if (GN_init () != 0)
109  {
110   perror ("Init Network Failed\n");
111   GN_My.State = GNAURALNET_STOPPED;
112   return GNAURALNET_FAILURE;
113  }
114  GN_My.State = GNAURALNET_RUNNING;
115 #ifdef GNAURALNET_GTHREADS
116  GThread *GN_serverthread = NULL;
117  GError *thread_err = NULL;
118 
119  if (NULL ==
120      (GN_serverthread =
121       g_thread_create ((GThreadFunc) GN_MainLoop, (void *) NULL, FALSE,
122                        &thread_err)))
123  {
124   GN_ERROUT ("g_thread_create failed:");
125   GN_ERROUT (thread_err->message);
126   g_error_free (thread_err);
127   return GNAURALNET_FAILURE;
128  }
129  else
130  {
131   GN_DBGOUT ("Gnauralnet server thread created successfully");
132   return GNAURALNET_SUCCESS;
133  }
134 #endif
135  //at this point, users needs to set up their own send/recv loop, akin to
136  //the threaded GN_MainLoop ((void *) NULL) example.
137  return GNAURALNET_SUCCESS;
138 }
139 
140 /////////////////////////////////////////////////////////////
141 //This is the default internal one; user can create their own
142 //by setting
GN_MainLoop_default(void * arg)143 int GN_MainLoop_default (void *arg)
144 {
145  GN_DBGOUT ("Entering GnauralNet MainLoop ()...");
146  while (GNAURALNET_RUNNING == GN_My.State)
147  {
148   //Receive and process packets for GN_MainLoopInterval_usecs time:
149   GN_Socket_RecvMessage (GN_My.Socket, (char *) &GN_RecvBuffer,
150                          sizeof (GN_RecvBuffer), GN_MainLoopInterval_secs,
151                          GN_MainLoopInterval_usecs);
152 
153   //Send message to ONE friend on the FriendList if there are any:
154   //NOTE: the do-loop is just to be sure a friend still gets hit even
155   //      if defriending occurs.
156   int RepeatFlag;
157   GN_Friend *curFriend;
158 
159   do
160   {
161    RepeatFlag = GNAURALNET_FALSE;
162    curFriend = GN_FriendList_NextFriend ();
163 
164    //see if we have no friends:
165    if (NULL == curFriend)
166    {
167     break;      //having no friends isn't a failure!
168    }
169 
170    //See if Friend has strayed enough to be defriended:
171    if (GN_MISSED_RECV_LIMIT < ++(curFriend->SendRecvTally))
172    {
173     GN_DBGOUT ("Unresponsive, defriending:");
174     GN_PrintAddressInfo (curFriend->ID, curFriend->IP, curFriend->Port);
175     GN_FriendList_DeleteFriend (curFriend);
176     RepeatFlag = GNAURALNET_TRUE;
177    }
178   }
179   while (GNAURALNET_FALSE != RepeatFlag);
180 
181   //this does the send too (assuming curFriend != NULL):
182   GN_ProcessOutgoingData (curFriend);
183  }
184  GN_My.State = GNAURALNET_STOPPED;
185  return GNAURALNET_SUCCESS;
186 }
187 
188 ///////////////////////////////////////
189 //where incoming data gets processed
GN_ProcessIncomingData_default(char * MsgBuf,int sizeof_MsgBuf,struct sockaddr_in * saddr_remote)190 void GN_ProcessIncomingData_default (char *MsgBuf, int sizeof_MsgBuf,
191                                      struct sockaddr_in *saddr_remote)
192 {
193  //First check for the kind of data.
194  //The only kind we currently expect is a GN_RecvBuffer:
195  if (MsgBuf != (char *) &GN_RecvBuffer ||
196      sizeof (GN_RecvBuffer) != sizeof_MsgBuf)
197  {
198   GN_ERROUT ("recv'd buffer not correct type");
199   return;
200  }
201 
202  //to debug:
203  //GN_PrintPacketContents ();
204 
205  //Definitely a RecvBuffer, so can check if it is my ID:
206  //NOTE: shouldn't get here.
207  if (ntohl (GN_RecvBuffer.TimeInfo.ID) == GN_My.ID)
208  {
209   GN_ERROUT
210    ("Apparently I have several IP addresses and am contacting myself!");
211   return;
212  }
213 
214  unsigned int ID = ntohl (GN_RecvBuffer.TimeInfo.ID);
215 
216  //first see if this is an IP/Port I've heard from before:
217  //NOTE: Logically, this can prove it is a friend I know, but not
218  //that it is is a friend I don't know (multiple IP issue 20080113)
219  GN_Friend *curFriend =
220   GN_FriendList_GetFriend (ID, saddr_remote->sin_addr.s_addr,
221                            saddr_remote->sin_port);
222 
223  //we didn't know her, so see if she's a candidate for Friend:
224  if (NULL == curFriend)
225  {
226   curFriend =
227    GN_ProcessNewFriend (ID, saddr_remote->sin_addr.s_addr,
228                         saddr_remote->sin_port);
229 
230   //check to see if she really got added:
231   if (NULL == curFriend)
232    return;
233  }
234 
235  GN_DBGOUT ("Got data from:");
236  GN_PrintAddressInfo (ID, saddr_remote->sin_addr.s_addr,
237                       saddr_remote->sin_port);
238 
239  switch (GN_RecvBuffer.msgtype)
240  {
241  case GN_MSGTYPE_TIMEINFO:
242   ++(curFriend->RecvCount);
243   curFriend->SendRecvTally = 0;
244   curFriend->ID = ntohl (GN_RecvBuffer.TimeInfo.ID);
245   curFriend->FriendCount = ntohs (GN_RecvBuffer.TimeInfo.FriendCount);
246   curFriend->RunningID = ntohs (GN_RecvBuffer.TimeInfo.RunningID);
247   curFriend->ScheduleFingerprint =
248    ntohs (GN_RecvBuffer.TimeInfo.ScheduleFingerprint);
249   curFriend->RunningSeniority =
250    ntohs (GN_RecvBuffer.TimeInfo.RunningSeniority);
251   curFriend->CurrentSampleCount =
252    ntohl (GN_RecvBuffer.TimeInfo.CurrentSampleCount);
253 
254   //do time stuff:
255   GN_Time_ProcessIncomingData (curFriend);
256 
257   //check invitation and add if not already here
258   GN_ProcessInviteData (ntohl (GN_RecvBuffer.TimeInfo.InviteID),
259                         ntohl (GN_RecvBuffer.TimeInfo.InviteIP),
260                         ntohs (GN_RecvBuffer.TimeInfo.InvitePort));
261   break;
262 
263  default:
264   GN_DBGOUT ("Message type NOT PROCESSED");
265   break;
266  }
267 }
268 
269 /////////////////////////////////////////////////////
270 //This just does the generic things all outgoing packets need:
GN_ProcessOutgoingData(GN_Friend * curFriend)271 void GN_ProcessOutgoingData (GN_Friend * curFriend)
272 {
273  if (NULL == curFriend)
274   return;
275 
276  //dig up a random Friend to give receiver:
277  unsigned int InviteIP = 0;
278  unsigned short InvitePort = 0;
279  unsigned int InviteID = 0;
280  GN_Friend *randFriend = GN_FriendList_RandomFriend (curFriend);
281 
282  if (NULL != randFriend)
283  {
284   InviteIP = randFriend->IP;
285   InvitePort = randFriend->Port;
286   InviteID = randFriend->ID;
287  }
288 
289  GN_DBGOUT_INT ("Current friend count:", GN_My.FriendCount);
290 
291  //Now do the critical timing stuff:
292  GN_Time_ProcessOutgoingData (curFriend);
293 
294  //now put data in, setting proper byte order for network:
295  GN_SendBuffer.msgtype = GN_MSGTYPE_TIMEINFO;
296  GN_SendBuffer.TimeInfo.Time_DelayOnMe = htonl (curFriend->Time_DelayOnMe);
297  GN_SendBuffer.TimeInfo.FriendCount = htons (GN_My.FriendCount);
298  GN_SendBuffer.TimeInfo.InviteID = htonl (InviteID);
299  GN_SendBuffer.TimeInfo.InviteIP = htonl (InviteIP);
300  GN_SendBuffer.TimeInfo.InvitePort = htons (InvitePort);
301  GN_SendBuffer.TimeInfo.ID = htonl (GN_My.ID);
302  //now fill the Running data, set in GN_Running_ProcessOutgoingData:
303  GN_SendBuffer.TimeInfo.RunningID = htons (GN_My.RunningID);
304  GN_SendBuffer.TimeInfo.ScheduleFingerprint =
305   htons (GN_My.ScheduleFingerprint);
306  GN_SendBuffer.TimeInfo.RunningSeniority =
307   htons (curFriend->Time_Send.tv_sec - GN_My.RunningStartTime.tv_sec);
308  GN_SendBuffer.TimeInfo.CurrentSampleCount = htonl (GN_My.CurrentSampleCount);
309  GN_SendBuffer.TimeInfo.LoopCount = htons (GN_My.LoopCount);
310 
311  //Now the actual send:
312  GN_Socket_SendMessage (GN_My.Socket, (char *) &GN_SendBuffer,
313                         sizeof (GN_SendBuffer), curFriend->IP,
314                         curFriend->Port);
315 }
316 
317 ///////////////////////////////////////
318 //this is here simply to reduce code redundancy
GN_ProcessNewFriend(unsigned int ID,unsigned int IP,unsigned short Port)319 GN_Friend *GN_ProcessNewFriend (unsigned int ID,        //MUST BE IN NET ORDER
320                                 unsigned int IP,        //MUST BE IN NET ORDER
321                                 unsigned short Port)    //MUST BE IN NET ORDER)
322 {
323  GN_Friend *curFriend = NULL;
324 
325  //add her:
326  GN_DBGOUT ("Adding friend from:");
327  GN_PrintAddressInfo (ID, IP, Port);
328  curFriend = GN_FriendList_AddFriend (ID, IP, Port);
329 
330  //check to see if she really got added:
331  if (NULL == curFriend)
332   return NULL;
333 
334  //send him a quick greeting, as per Issue 20080112. NOTE:
335  //20080113: fixed bug here; was using the one CommBuffer to send
336  //BEFORE it had filed the data it recv'd. Solution: two commbuffs.
337  GN_ProcessOutgoingData (curFriend);
338  return curFriend;
339 }
340 
341 ///////////////////////////////////////
342 //these incoming vars are in NET ORDER
343 //Philosophy: Invites are not added directly as friends. Instead, their
344 //address is sent to, and when it responds, it gets added. This is a
345 //security feature, since it would be absurdly easy to introduce crap
346 //into the FriendLists via addresses.
GN_ProcessInviteData(unsigned int InviteID,unsigned int InviteIP,unsigned short InvitePort)347 void GN_ProcessInviteData (unsigned int InviteID,       //MUST BE IN NET ORDER
348                            unsigned int InviteIP,       //MUST BE IN NET ORDER
349                            unsigned short InvitePort)   //MUST BE IN NET ORDER
350 {
351  //see if it is invalid:
352  if (0 == InviteIP || 0 == InvitePort)
353   return;
354 
355  //see if it is ME:
356  if (InviteID == GN_My.ID)
357  {
358   GN_DBGOUT ("Invite was to myself!");
359   return;
360  }
361 
362  //see if I have that friend already:
363  GN_Friend *curFriend =
364   GN_FriendList_GetFriend (InviteID, InviteIP, InvitePort);
365 
366  //if it is not a friend already, send her an Invite and forget about her:
367  if (NULL == curFriend)
368  {
369   GN_FriendList_Invite (InviteIP, InvitePort);
370  }
371  else
372  {
373   //  GN_DBGOUT ("NOT adding Invited friend at:");
374   //  GN_PrintAddressInfo (InviteID, InviteIP, InvitePort);
375  }
376 }
377 
378 /////////////////////////////////////////////////////////////////////
379 //GN_init (): inits net subsystem, creates socket,
380 //sets blocking behavior
GN_init()381 int GN_init ()
382 {
383  memset (&GN_My, 0, sizeof (GN_Globals));       //precautionary
384  GN_My.Socket = 0;      //Win32: unsigned int, UNIX: int
385  GN_My.IP = 0;  //THIS IS IN NET ORDER
386  GN_My.Port = 0;        //THIS IS IN NET ORDER
387  GN_My.ID = 0;  //THIS IS IN NET ORDER
388  GN_My.RunningID = 0;   //0 if not in a running
389  GN_My.ScheduleFingerprint = GN_ScheduleFingerprint;
390  GN_My.CurrentSampleCount = 0;  //BB data, set in Running
391  GN_My.LoopCount = 0;   //BB data, set in Running
392  GN_My.FriendCount = 0; //holds snapshots/estimates of current number of friends in linked list
393  GN_My.LoverCount = 0;  //holds snapshots/estimates of current number of lovers in linked list
394  GN_My.FirstFriend = NULL;
395  GN_My.State = GNAURALNET_STOPPED;
396  GN_Time_ResetSeniority ();
397 
398 #ifdef GNAURAL_WIN32
399  // initiate use of WINSOCK.DLL
400  WSADATA wsadata;
401  WORD wVer = MAKEWORD (1, 1);
402 
403  if (WSAStartup (wVer, &wsadata) != 0)
404  {
405   GN_ERROUT ("Error occurred on WSAStartup()");
406   return GNAURALNET_FAILURE;
407  }
408  // Ensure the WINSOCK.DLL version is right
409  if (LOBYTE (wsadata.wVersion) != 1 || HIBYTE (wsadata.wVersion) != 1)
410  {
411   GN_ERROUT ("WINSOCK.DLL wrong version");
412   return GNAURALNET_FAILURE;
413  }
414 #endif
415 
416  //create the one socket:
417  //NOTE: if port is already in use, this will fail. For now,
418  //it is assumed that two Gnaurals are trying to
419  //network on same machine. This is legal, but subsequent Gnaurals
420  //get any unused port and wont be known externally:
421  if (GNAURAL_IS_INVALID_SOCKET
422      (GN_My.Socket = GN_Socket_MakeUDP (GNAURALNET_PORT)))
423  {
424   perror ("Socket creation failed: ");
425   GN_DBGOUT ("Going to try socket creation on a different port:");
426   if (GNAURAL_IS_INVALID_SOCKET (GN_My.Socket = GN_Socket_MakeUDP (0)))
427   {
428    perror ("GN_Socket_MakeUDP failed again:");
429    GN_ERROUT ("Failed to start Gnauralnet");
430    return GNAURALNET_FAILURE;
431   }
432  }
433 
434  //set local info globals:
435  GTimeVal curtime;
436 
437  g_get_current_time (&curtime);
438  srand (curtime.tv_sec + curtime.tv_usec);
439  if (GNAURALNET_FAILURE == GN_Socket_SetLocalInfo ())
440  {
441   GN_ERROUT ("Couldn't get local address (shouldn't happen)");
442   return GNAURALNET_FAILURE;
443  }
444 
445  GN_DBGOUT ("Socket Creation successful on:");
446  GN_PrintAddressInfo (GN_My.ID, GN_My.IP, GN_My.Port);
447 
448  //set the socket blocking behavior:
449  //NOTE: using non-blocking sockets with select()
450  //and timeouts seems to be the best choice; for one thing,
451  //timeouts allow me to unblock when trying to close the socket:
452  GN_Socket_SetBlocking (GN_My.Socket, 0);
453  GN_DBGOUT ("Successfully started Gnauralnet");
454  return GNAURALNET_SUCCESS;
455 }
456 
457 /////////////////////////////////////////////////////////////////////
458 //Note the handy G_USEC_PER_SEC
GN_sleep(unsigned int microsecs)459 void GN_sleep (unsigned int microsecs)
460 {
461 #ifdef GNAURAL_WIN32
462  Sleep (microsecs / 1000);
463 #else
464  usleep (microsecs);
465 #endif
466 }
467 
468 /////////////////////////////////////////////////////////////////
469 //don't call this directly; call GN_stop()
GN_cleanup()470 void GN_cleanup ()
471 {
472  GN_DBGOUT ("Cleaning up gnauralnet");
473  GNAURAL_CLOSE_SOCKET (GN_My.Socket);
474  //GN_My.Socket = -1;
475 #ifdef GNAURAL_WIN32
476  WSACleanup ();
477  //GN_My.Socket = INVALID_SOCKET;
478 #endif
479  GN_PrintFriends ();
480  GN_FriendList_DeleteAll ();
481 }
482 
483 /////////////////////////////////////////////////////////////////
484 //GN_start and GN_stop are all user must run.
485 //this sets GN_My.State to GNAURALNET_WAITINGTOSTOP to break
486 //any recv loops, then shuts down the socket
487 /////////////////////////////////////////////////////////////////
GN_stop()488 void GN_stop ()
489 {
490  if (GNAURALNET_RUNNING == GN_My.State)
491  {
492   GN_My.State = GNAURALNET_WAITINGTOSTOP;
493   int count = 4;
494 
495   while (GNAURALNET_WAITINGTOSTOP == GN_My.State && --count)
496   {
497    GN_DBGOUT ("Waiting for gnauralnet shutdown...");
498    GN_sleep (500000);
499   }
500   if (0 == count)
501    GN_DBGOUT ("Forcing gnauralnet shutdown");
502  }
503  GN_DBGOUT ("Connection to GnauralNet was terminated");
504  GN_My.State = GNAURALNET_STOPPED;
505  GN_cleanup ();
506 }
507 
508 /////////////////////////////////////////////////////
509 //incoming vars MUST be in net order
GN_PrintAddressInfo(unsigned int id,unsigned int ip,unsigned short port)510 void GN_PrintAddressInfo (unsigned int id, unsigned int ip,
511                           unsigned short port)
512 {
513  if (0 == GN_DebugFlag)
514  {
515   return;
516  }
517  //start debug:
518  struct in_addr tmp_in_addr;
519 
520  tmp_in_addr.s_addr = ip;
521  fprintf (stderr, "IP: %s \t Port: %d \t ID: %u\n", inet_ntoa (tmp_in_addr),
522           ntohs (port), id);
523  //end debug
524 }
525 
526 /////////////////////////////////////////////////////
527 //For debugging:
GN_PrintPacketContents()528 void GN_PrintPacketContents ()
529 {
530  if (0 == GN_DebugFlag)
531  {
532   return;
533  }
534 
535  //can do either Recv or Comm:
536  //GN_CommBuffer *buf = &GN_SendBuffer;
537  GN_CommBuffer *buf = &GN_RecvBuffer;
538 
539  switch (buf->msgtype)
540  {
541  case GN_MSGTYPE_TIMEINFO:
542   fprintf (stderr, "=====TimeInfo:\n");
543   fprintf (stderr, " msgtype: %d\n", buf->TimeInfo.msgtype);
544   fprintf (stderr, " empty: %d\n", buf->TimeInfo.empty);
545   fprintf (stderr, " InvitePort: %d\n", ntohs (buf->TimeInfo.InvitePort));
546   fprintf (stderr, " InviteIP: %d\n", ntohl (buf->TimeInfo.InviteIP));
547   fprintf (stderr, " InviteID: %d\n", ntohl (buf->TimeInfo.InviteID));
548   fprintf (stderr, " ID: %d\n", ntohl (buf->TimeInfo.ID));
549   fprintf (stderr, " Time_DelayOnMe: %d\n",
550            ntohl (buf->TimeInfo.Time_DelayOnMe));
551   fprintf (stderr, " FriendCount: %d\n", ntohs (buf->TimeInfo.FriendCount));
552   fprintf (stderr, " RunningID: %d\n", ntohs (buf->TimeInfo.RunningID));
553   fprintf (stderr, " ScheduleFingerprint: %d\n",
554            ntohs (buf->TimeInfo.ScheduleFingerprint));
555   fprintf (stderr, " RunningSeniority: %d\n",
556            ntohs (buf->TimeInfo.RunningSeniority));
557   fprintf (stderr, " CurrentSampleCount: %d\n",
558            ntohl (buf->TimeInfo.CurrentSampleCount));
559   fprintf (stderr, " LoopCount: %d\n", ntohs (buf->TimeInfo.LoopCount));
560   break;
561  }
562 }
563 
564 /////////////////////////////////////////////////////
565 //this is strictly a debugging tool
GN_PrintFriends()566 void GN_PrintFriends ()
567 {
568  if (0 == GN_DebugFlag)
569  {
570   return;
571  }
572 
573  GN_Friend *curFriend = GN_My.FirstFriend;
574  struct in_addr tmp_in_addr;
575 
576  GN_My.FriendCount = 0;
577  GN_My.LoverCount = 0;
578 
579  while (curFriend != NULL)
580  {
581   ++GN_My.FriendCount;
582   if (0 != curFriend->RunningID && curFriend->RunningID == GN_My.RunningID)
583   {
584    ++GN_My.LoverCount;
585   }
586   tmp_in_addr.s_addr = curFriend->IP;
587   fprintf (stderr, "%d)IP:%s\tPort:%d\tID:%u\tOffset:%g\n",
588            GN_My.FriendCount, inet_ntoa (tmp_in_addr),
589            ntohs (curFriend->Port), curFriend->ID,
590            curFriend->Time_AverageOffset);
591   //all list passes need this:
592   curFriend = curFriend->NextFriend;
593  }
594 }
595