-
Notifications
You must be signed in to change notification settings - Fork 12
/
Copy pathtcplognke.c
executable file
·2612 lines (2285 loc) · 85.2 KB
/
tcplognke.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*
(c) Copyright 2005 Apple Computer, Inc. All rights reserved.
IMPORTANT: This Apple software is supplied to you by Apple Computer, Inc. (“Apple”) in
consideration of your agreement to the following terms, and your use, installation,
modification or redistribution of this Apple software constitutes acceptance of these
terms. If you do not agree with these terms, please do not use, install, modify or
redistribute this Apple software.
In consideration of your agreement to abide by the following terms, and subject to
these terms, Apple grants you a personal, non-exclusive license, under Apple’s copyrights
in this original Apple software (the “Apple Software”), to use, reproduce, modify and
redistribute the Apple Software, with or without modifications, in source and/or binary
forms; provided that if you redistribute the Apple Software in its entirety and without
modifications, you must retain this notice and the following text and disclaimers in all
such redistributions of the Apple Software. Neither the name, trademarks, service marks
or logos of Apple Computer, Inc. may be used to endorse or promote products derived
from the Apple Software without specific prior written permission from Apple. Except
as expressly stated in this notice, no other rights or licenses, express or implied,
are granted by Apple herein, including but not limited to any patent rights that may
be infringed by your derivative works or by other works in which the Apple Software
may be incorporated.
The Apple Software is provided by Apple on an "AS IS" basis. APPLE MAKES NO
WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED WARRANTIES
OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, REGARDING
THE APPLE SOFTWARE OR ITS USE AND OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS.
IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) ARISING
IN ANY WAY OUT OF THE USE, REPRODUCTION, MODIFICATION AND/OR DISTRIBUTION OF THE
APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING
NEGLIGENCE), STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
/*
Sample network kernel extension written for the updated NKE KPI's for the OS X Tiger
release. This sample NKE will not run under OS X 10.3.x or earlier. The sample also
demonstrates communication with an application process using a SYSTEM_CONTROL socket.
Change History
1.3 - added IPv6 support and provide a workaround solution for the problem such that
mbuf_tag_allocate foccasionally fails to tag outgoing IPv6 mbuf chains when the
mbuf has no PKTHDR bit set in the lead mbuf. The workaround is to prepend an
mbuf to the front of the chain, which does have the PKTHDR bit set. One cannot just
set the bit as there are internal structure settings which must be set properly
for which there are no accessor functions provided.
Make use of inet_ntop which is defined in Kernel.framework/Headers/netinet/in.h
under Mac OS X 10.4.x
*/
/*
Theory of operation:
At init time, we add ourselve to the list of socket filters for TCP.
For each new connection (active or passive), we log the endpoint
addresses, keep track of the stats of the conection and log the
results and close time.
At a minimum, the stats are: recv bytes, pkts; xmit bytes, pkts
The stats and other info are kept in the extension control block that
is attached to a TCP socket when created.
The stats are kept into two lists: one list for the active connections
and one list for the closed connections.
The stats for the closed connections can be read by an application using
a system control socket. In addition this sample shows how to use socket
options to apply and retrieve information over a system control socket.
At init time, a system control socket is registered. The included tcptool
finds the system control socket and communicates with the kernel extension
thru this socket.
An additional feature of the sample is to demonstrate packet swallowing and
packet re-injection. As packet swallowing is enabled, for both the data_in
and data_out functions, all unprocessed packets are tagged and swallowed -
that is, these routines return
the EJUSTRETURN result which tells the system to stop processing of the packet.
To disable this functionality, set SWALLOW_PACKETS to 0.
Once the packet is swallowed, a timer routine is scheduled, which will re-inject the
packet into the system for processing. A re-injected packet causes the corresponding
data_in or data_out function to be called again to process the packet. In order to
keep the function from swallowing the packet again, these functions check for the
presence of a tag. The presence of the tag tells these functions that the packet has already
been processed and to return with a result of 0 so that processing can continue on
the packet. Note that if a module swallows the packet and re-inserts the
packet, all modules of the same type will again see the packet. It is possible for a data_in/data_out
function to see the same packet more than once. For this reason, the tag mechanism makes
more sense to detect previous processing rather than maintaining a list of
"previous" processed mbuf pointers.
When swallowing a packet, save the mbuf_t value, not the reference value passed to the
sf_data_in_func/sf_data_out_func. The reference value to the mbuf_t parameter is no
longer valid upon return from the sf_data_in_func/sf_data_out_func.
In the datain/out functions, as the mbuf_t is passed by reference, the NKE can split the
contents of the mbuf_t, allowing the lead portion of the kext to be processed, and swallowing
the tail portion. Other modifications can also be made to the mbuf_t as demonstrated in this
sample. Refer to the prepend_mbuf_hdr function for an example.
This sample also implements the use of the "Fine Grain Locking" api's provided in
locks.h, as well as the OSMalloc call defined in <libkern/OSMalloc.h>.
Note the following differences with this sample written for OS X 10.4.x to socket
filters written for OS 10.3.x and earlier
a. When calling OSMalloc, the WaitOK version can be used since memory allocation will
not block packet processing.
b. With fine grain locking support now present in the kernel, the Network funnel is no
longer needed to serialize network access. The "Fine grain locking" calls are used to
serialize access to the various queues defined to log socket information,
control connection information, and to stored data_inbound and outbound swallowed packets.
A call to lck_mtx_lock blocks until the mutex parameter is freed with a call to
lck_mtx_unlock.
*/
#include <mach/vm_types.h>
#include <mach/kmod.h>
#include <sys/socket.h>
#include <sys/kpi_socket.h>
#include <sys/kpi_mbuf.h>
#include <sys/kpi_socket.h>
#include <sys/kpi_socketfilter.h>
#include <sys/systm.h>
#include <sys/proc.h>
#include <sys/mbuf.h>
#include <netinet/in.h>
#include <kern/locks.h>
#include <kern/assert.h>
#include <kern/debug.h>
#include "tcplogger.h"
#include <libkern/OSMalloc.h>
#include <libkern/OSAtomic.h>
#include <sys/kern_control.h>
#include <sys/kauth.h>
#include <sys/time.h>
#include <stdarg.h>
#if !defined(SWALLOW_PACKETS)
#define SWALLOW_PACKETS 1 // set this define to 1 to demonstrate all packet swallowing/re-injection
// set this define to 0 for for simple filtering of data - no packet swallowing
#endif
#if !defined(SHOW_PACKET_FLOW)
#define SHOW_PACKET_FLOW 0 // set to 1 to have data_in/out routines show packet processing
#endif
#if !defined(DEBUG)
#define DEBUG 1 // DEBUG == 1 - print logging messsages to system.log
// DEBUG == 0 - no logging messages.
#endif
#define kMY_TAG_TYPE 1
// values to use with the memory allocated by the tag function, to indicate which processing has been
// performed already.
typedef enum PACKETPROCFLAGS {
INBOUND_DONE = 1,
OUTBOUND_DONE
} PACKETPROCFLAGS;
#if SWALLOW_PACKETS
typedef enum DATATIMERSTATES {
TIMER_INACTIVE = 0,
TIMER_PENDING,
TIMER_RUNNING
}DATATIMERSTATES;
#endif // SWALLOW_PACKETS
static OSMallocTag gOSMallocTag; // tag for use with OSMalloc calls which is used to associate memory
// allocations made with this kext. Preferred to using MALLOC and FREE
static boolean_t gFilterRegistered_ip4 = FALSE;
static boolean_t gFilterRegistered_ip6 = FALSE;
static boolean_t gUnregisterProc_ip4_started = FALSE;
static boolean_t gUnregisterProc_ip6_started = FALSE;
static boolean_t gUnregisterProc_ip4_complete = FALSE;
static boolean_t gUnregisterProc_ip6_complete = FALSE;
static boolean_t gKernCtlRegistered = FALSE;
#if SWALLOW_PACKETS
static DATATIMERSTATES gTimerState = TIMER_INACTIVE; // used to prevent too many (extraneous) calls to bsd_timeout
#endif // SWALLOW_PACKETS
/* List of active 'Logging' sockets */
static struct tl_list tl_active; // protected by gmutex
/* List of terminated TCPLogEntry structs, waiting for harvesting the information*/
static struct tl_list tl_done; // protected by gmutex
/* Protect consistency of our data at entry points */
static lck_mtx_t *gmutex = NULL; // used to protect the tl_active and the tl_done queues
#if SWALLOW_PACKETS
static lck_mtx_t *g_swallowQ_mutex = NULL; // used to protect the queue where we place swallowed packets
#endif // SWALLOW_PACKETS
static lck_grp_t *gmutex_grp = NULL;
/* tag associated with this kext for use in marking packets that have been previously processed. */
static mbuf_tag_id_t gidtag;
/*
* Per socket extension control block for the log function
*/
struct TCPLogEntry {
TAILQ_ENTRY(TCPLogEntry) tle_link; /* link to next log entry item */
socket_t tle_so; /* Pointer to owning socket */
boolean_t tle_active;
boolean_t tle_in_detach;
struct TCPLogInfo tle_info;
uint32_t numPktInDefer;
uint32_t magic; /* magic value to ensure that system is passing me my buffer */
};
typedef struct TCPLogEntry TCPLogEntry;
#define kTCPLogEntryMagic 0xAABBCCDD
#define kTLCBEntryMagic 0xDDCCBBAA
TAILQ_HEAD(tl_list, TCPLogEntry);
/* the following are macros to access TCPLogInfo fields in the TCPLogEntry structure */
#define tle_len tle_info.tli_len
#define tle_state tle_info.tli_state
#define tle_genid tle_info.tli_genid
#define tle_len tle_info.tli_len
#define tle_bytes_in tle_info.tli_bytes_in
#define tle_bufs_in tle_info.tli_pkts_in
#define tle_bytes_out tle_info.tli_bytes_out
#define tle_bufs_out tle_info.tli_pkts_out
#define tle_create tle_info.tli_create
#define tle_start tle_info.tli_start
#define tle_stop tle_info.tli_stop
#define tle_pid tle_info.tli_pid
#define tle_uid tle_info.tli_uid
#define tle_protocol tle_info.tli_protocol
#define tle_local4 tle_info.tli_local.addr4
#define tle_remote4 tle_info.tli_remote.addr4
#define tle_local6 tle_info.tli_local.addr6
#define tle_remote6 tle_info.tli_remote.addr6
#if SWALLOW_PACKETS
/* the SwallowPktQueueItem record is used to store packet information when a packet is swallowed. The item is
queued to the swallow_queue regardless of direction. The data_inbound flag determines which direction the swallowed
packet will be processed. In a more complicated case it might be useful to implement separate queues
*/
struct SwallowPktQueueItem {
TAILQ_ENTRY(SwallowPktQueueItem) tlq_next; /* link to next swallow queued entry or NULL */
struct TCPLogEntry *tlp;
socket_t so;
mbuf_t data;
mbuf_t control;
boolean_t data_inbound;
sflt_data_flag_t flags;
};
TAILQ_HEAD(swallow_queue, SwallowPktQueueItem);
static struct swallow_queue swallow_queue;
#endif // SWALLOW_PACKETS
/* Max # log entries to keep if not connected to reader */
#define TCPLOGGER_QMAX_DEFAULT 200
#define kInvalidUnit 0xFFFFFFFF
/*
the tl_cb structure is used to track socket control requests to the kernel extension. Multiple processes
could communicate with this socket filter and express an interest in contolling some aspect of the filter
and/or requesting that the filter return connection information which this socket filter tracks.
*/
struct tl_cb {
TAILQ_ENTRY(tl_cb) t_link; // link to next control block record or NULL if end of chain.
kern_ctl_ref t_ref; // control reference to the connected process
u_int32_t t_unit; // unit number associated with the connected process
u_int32_t magic; /* magic value to ensure that system is passing me my buffer */
boolean_t t_connected;
};
static kern_ctl_ref gctl_ref;
TAILQ_HEAD(tl_cb_list, tl_cb) ; // definition of queue to store control block references. As each interested client
// connects to this socket filter, a tl_cb structure is allocated to store information
// about the connected process.
static struct tl_cb_list tl_cb_list;
static struct tl_stats tl_stats;
static void tl_inactive(struct TCPLogEntry *tlp);
static void tl_send_done_log_info_to_clients(void);
static void tl_flush_backlog(boolean_t all);
/* =================================== */
#pragma mark Utility Functions
/*
* Messages to the system log
*/
static void
tl_printf(const char *fmt, ...)
{
#if DEBUG
va_list listp;
char log_buffer[92];
va_start(listp, fmt);
vsnprintf(log_buffer, sizeof(log_buffer), fmt, listp);
printf("%s", log_buffer);
va_end(listp);
#endif
}
#if SWALLOW_PACKETS
/*
my_mbuf_freem is implemented to deal with the fact that the data_in and data_out functions are passed
mbuf_t* parameters instead of mbuf_t parameters. The mbuf_freem routine can handle a null parameter, but
the kext has to deal with the fact that it could be passed a NULL *mbuf_t parameter (normally the control
parameter).
*/
static void my_mbuf_freem(mbuf_t *mbuf)
{
if (mbuf != NULL)
{
if (*mbuf != NULL)
{
mbuf_freem(*mbuf);
}
}
}
#endif // SWALLOW_PACKETS
static errno_t alloc_locks(void)
{
errno_t result = 0;
/* Allocate a mutex lock */
/*
1. lck_grp_alloc_init allocates memory for the group lock and inits the lock with the
group name and default attributes
For each individual lock
2. lck_mtx_alloc_init allocates the memory for the lock and associates the
lock with the specified group.
gmutex is used to lock access to the tl_active and tl_done queues. when the lock
is active, the process has exclusive access to both queues.
g_swallowQ_mutex used to lock access to the swallow_queue
*/
// for the name, use the reverse dns name associated with this
// kernel extension
gmutex_grp = lck_grp_alloc_init(MYBUNDLEID, LCK_GRP_ATTR_NULL);
if (gmutex_grp == NULL)
{
tl_printf("error calling lck_grp_alloc_init\n");
result = ENOMEM;
}
if (result == 0)
{
gmutex = lck_mtx_alloc_init(gmutex_grp, LCK_ATTR_NULL);
if (gmutex == NULL)
{
tl_printf("error calling lck_mtx_alloc_init\n");
result = ENOMEM;
}
#if SWALLOW_PACKETS
if (result == 0)
{
/* allocate the lock for use on processing items in the output queue */
g_swallowQ_mutex = lck_mtx_alloc_init(gmutex_grp, LCK_ATTR_NULL);
if (g_swallowQ_mutex == NULL)
{
tl_printf("error calling lck_mtx_alloc_init\n");
result = ENOMEM;
}
}
#endif // SWALLOW_PACKETS
}
return result; // if we make it here, return success
}
static void free_locks(void)
{
if (gmutex)
{
lck_mtx_free(gmutex, gmutex_grp);
gmutex = NULL;
}
#if SWALLOW_PACKETS
if (g_swallowQ_mutex)
{
lck_mtx_free(g_swallowQ_mutex, gmutex_grp);
g_swallowQ_mutex = NULL;
}
#endif // SWALLOW_PACKETS
if (gmutex_grp)
{
lck_grp_free(gmutex_grp);
gmutex_grp = NULL;
}
}
#if defined(NDEBUG)
#define TCPLogEntryFromCookie(cookie) ((struct TCPLogEntry *) cookie)
#define tl_cb_EntryFromUnitInfo(cookied) ((struct tl_cb *) cookie)
#else
static struct TCPLogEntry * TCPLogEntryFromCookie(void *cookie)
{
struct TCPLogEntry * result;
result = (struct TCPLogEntry *) cookie;
assert(result != NULL);
assert(result->magic == kTCPLogEntryMagic);
return result;
}
static struct tl_cb * tl_cb_EntryFromUnitInfo(void *unitinfo)
{
struct tl_cb *result;
result = (struct tl_cb *) unitinfo;
assert(result != NULL);
assert(result->magic == kTLCBEntryMagic);
return result;
}
#endif
/*
prepend_mbuf_hdr - used to prepend an mbuf_t init'd for PKTHDR so that an mbuf_tag_allocate
call can be used to mark an mbuf. As per <rdar://problem/4786262>, on AFPoverIP IPv6 connections,
very infrequently, the mbuf does not have the PKTHDR bit set and the mbuf_tag_allocate function fails.
A workaround solution is to prepend a PKTHDR mbuf to the front of the mbuf chain, so that the mbuf can be
"tagged"
data - pointer to mbuf_t variable which has no PKTHDR bit set in the flags field
len - amount of data in the data mbuf chain
return 0 (KERN_SUCCESS - success, the PKTHDR mbuf was successfully allocated and prepended to the front of the mbuf
and data now points to the newly allocated mbuf_t.
return any other value - failure, the PKTHDR mbuf_t failed to be allocated.
*/
static errno_t
prepend_mbuf_hdr(mbuf_t *data, size_t pkt_len)
{
mbuf_t new_hdr;
errno_t status;
status = mbuf_gethdr(MBUF_WAITOK, MBUF_TYPE_DATA, &new_hdr);
if (KERN_SUCCESS == status)
{
/* we've created a replacement header, now we have to set things up */
/* set the mbuf argument as the next mbuf in the chain */
mbuf_setnext(new_hdr, *data);
/* set the next packet attached to the mbuf argument in the pkt hdr */
mbuf_setnextpkt(new_hdr, mbuf_nextpkt(*data));
/* set the total chain len field in the pkt hdr */
mbuf_pkthdr_setlen(new_hdr, pkt_len);
mbuf_setlen(new_hdr, 0);
mbuf_pkthdr_setrcvif(*data, NULL);
/* now set the new mbuf_t as the new header mbuf_t */
*data = new_hdr;
}
return status;
}
/*
CheckTag - see if there is a tag associated with the mbuf_t with the matching bitmap bits set in the
memory associated with the tag. Use global gidtag as id Tag to look for
input m - pointer to mbuf_t variable on which to search for tag
module_id - the tag_id obtained from the mbuf_tag_id_find call;
tag_type - specific tagType to look for
value - see if the tag_ref field has the expected value
return 1 - success, the value in allocated memory associated with tag gidtag has a matching value
return 0 - failure, either the mbuf_t is not tagged, or the allocated memory does not have the expected value
Note that in this example, the value of tag_ref is used to store bitmap values. the allocated memory is
process specific.
*/
static int CheckTag(mbuf_t *m, mbuf_tag_id_t module_id, mbuf_tag_type_t tag_type, PACKETPROCFLAGS value)
{
errno_t status;
int *tag_ref;
size_t len;
// Check whether we have seen this packet before.
status = mbuf_tag_find(*m, module_id, tag_type, &len, (void**)&tag_ref);
if ((status == 0) && (*tag_ref == value) && (len == sizeof(value)))
return 1;
return 0;
}
/*
- Set the tag associated with the mbuf_t with the bitmap bits set in bitmap
The SetTag calls makes a call to mbuf_tag_allocate with the MBUF_WAITOK flag set. Under OS X 10.4, waiting for the
memory allocation is ok from within a filter function.
10//06 - for AFPoverIP IPv6 connections, there are some packets which are passed which do not have the
PKTHDR bit set in the mbug_flags field. This will cause the mbuf_tag_allocate function to fail with
EINVAL error.
input m - mbuf_t pointer variable on which to search for tag
module_id - the tag_id obtained from the mbuf_tag_id_find call;
tag_type - specific tagType to look for
value - value to set in allocated memory
return 0 - success, the tag has been allocated and for the mbuf_t and the value has been set in the
allocated memory.
anything else - failure
*/
static errno_t SetTag(mbuf_t *data, mbuf_tag_id_t id_tag, mbuf_tag_type_t tag_type, PACKETPROCFLAGS value)
{
errno_t status;
int *tag_ref = NULL;
size_t len;
assert(data);
// look for existing tag
status = mbuf_tag_find(*data, id_tag, tag_type, &len, (void*)&tag_ref);
// allocate tag if needed
if (status != 0)
{
status = mbuf_tag_allocate(*data, id_tag, tag_type, sizeof(value), MBUF_WAITOK, (void**)&tag_ref);
if (status == 0)
*tag_ref = value; // set tag_ref
else if (status == EINVAL)
{
mbuf_flags_t flags;
// check to see if the mbuf_tag_allocate failed because the mbuf_t has the M_PKTHDR flag bit not set
flags = mbuf_flags(*data);
if ((flags & MBUF_PKTHDR) == 0)
{
mbuf_t m = *data;
size_t totalbytes = 0;
/* the packet is missing the MBUF_PKTHDR bit. In order to use the mbuf_tag_allocate, function,
we need to prepend an mbuf to the mbuf which has the MBUF_PKTHDR bit set.
We cannot just set this bit in the flags field as there are assumptions about the internal
fields which there are no API's to access.
*/
tl_printf("mbuf_t missing MBUF_PKTHDR bit\n");
while (m)
{
totalbytes += mbuf_len(m);
m = mbuf_next(m); // look at the next mbuf
}
status = prepend_mbuf_hdr(data, totalbytes);
if (status == KERN_SUCCESS)
{
status = mbuf_tag_allocate(*data, id_tag, tag_type, sizeof(value), MBUF_WAITOK, (void**)&tag_ref);
if (status)
{
tl_printf("mbuf_tag_allocate failed a second time, status was %d\n", status);
}
}
}
}
else
tl_printf("mbuf_tag_allocate failed, status was %d\n", status);
}
return status;
}
#if SWALLOW_PACKETS
/*
FreeDeferredData - used to scan the swallow_queue and free the mbuf_t's that match to the
input socket_t so parameter. The queue item is also released.
*/
static void FreeDeferredData(socket_t so)
{
struct SwallowPktQueueItem *tlq;
struct SwallowPktQueueItem *tlqnext;
assert(so); // check that so is not null
lck_mtx_lock(g_swallowQ_mutex); // allow only a single entry into the function at a time
// protect access to the swallow_queue
for (tlq = TAILQ_FIRST(&swallow_queue); tlq != NULL; tlq = tlqnext)
{
// get the next element pointer before we potentially corrupt it
tlqnext = TAILQ_NEXT(tlq, tlq_next);
// look for a match, if we find it, move it from the deferred
// data queue to our local queue
if (tlq->so == so)
{
if (tlq->data_inbound)
tl_printf("*********INBOUND PACKET FREED FROM SWALLOW QUEUE!!!!!!!! socket_t is 0x%X\n", so);
else
tl_printf("*********OUTBOUND PACKET FREED FROM SWALLOW QUEUE!!!!!!!! socket_t is 0x%X\n", so);
TAILQ_REMOVE(&swallow_queue, tlq, tlq_next);
my_mbuf_freem(&tlq->data);
my_mbuf_freem(&tlq->control);
OSFree(tlq, sizeof(struct SwallowPktQueueItem), gOSMallocTag);
}
}
lck_mtx_unlock(g_swallowQ_mutex);
}
/*
ReinjectDeferredData is used to scan the swallow_queue for packets to reinject in the stack.
input parameter so - NULL indicates to match all packets in the swallow queue and force them to
be re-injected
non-NULL - match only those packets associated with the socket_t so parameter.
Note: there is a potential timing issue. If the user forces the kext to be unloaded in the middle of
a data transfer, the kext will not get the normal notify messages - sock_evt_disconnecting and
sock_evt_disconnected. As such, the system will go straight to calling the detach_fn. It could be
that the data_timer has just fired and the ReinjectDeferredData routine has managed to move
packets from the swallow_queue to the packets_to_inject. If the detach_fn is called at this point,
it can clear the packets that might have been moved into the swallow queue via the FreeDeferredData call
above, but packets in the packets_to_inject queue will not be freed and will cause the
sock_inject_data_in/out functions to be called using a potentially invalid socket reference.
For this reason, there is code in the ReinjectDeferredData and in the detach_fn to check
whether there are still packet to be processed. If this occurs, a semaphore like system using msleep and
wakeup will allow the detach_fn to wait until ReinjectDeferredData has completed processing for
data on the socket_t ref that is being detached.
Note: the use of the packets_to_inject queue is to make it so that the g_swallowQ_mutex does not need to
held across the sock_inject_data_in/out calls. The g_swallowQ_mutex lock is only held while moving packets
from the swallow_queue to the local packets_to_inject queue. Then the lock is released.
A lock should never be held across system calls since one never knows whether the same lock will be accessed
within the system call.
*/
static void
ReinjectDeferredData(socket_t so)
{
struct swallow_queue packets_to_inject;
struct SwallowPktQueueItem *tlq;
struct SwallowPktQueueItem *tlqnext;
struct TCPLogEntry *tlp;
errno_t result;
// init the queue which we use to place the packets we want to re-inject into the tcp stream
TAILQ_INIT(&packets_to_inject);
lck_mtx_lock(g_swallowQ_mutex); // allow only a single entry into the function at a time
// protect access to the swallow_queue
// iterate the queue looking for matching entries; if we find a match,
// remove it from queue and put it on packets_to_inject; because we're
// removing elements from tl_deferred_data, we can't use TAILQ_FOREACH
for (tlq = TAILQ_FIRST(&swallow_queue); tlq != NULL; tlq = tlqnext)
{
// get the next element pointer before we potentially corrupt it
tlqnext = TAILQ_NEXT(tlq, tlq_next);
// look for a match, if we find it, move it from the deferred
// data queue to our local queue
if ( (so == NULL) || (tlq->so == so) )
{
TAILQ_REMOVE(&swallow_queue, tlq, tlq_next);
TAILQ_INSERT_TAIL(&packets_to_inject, tlq, tlq_next);
tlp = tlq->tlp; // get the log entry associated with this item
tlp->numPktInDefer++; // track the number of packets associated with this
// socket put into inject queue - access to this field
// protected by the g_swallowQ_mutex lock
}
}
// we're done with the global list, so release our lock on it
lck_mtx_unlock(g_swallowQ_mutex);
// now process the local list, injecting each packet we found
while ( ! TAILQ_EMPTY(&packets_to_inject) )
{
tlq = TAILQ_FIRST(&packets_to_inject);
TAILQ_REMOVE(&packets_to_inject, tlq, tlq_next);
tlp = tlq->tlp; /* get the log entry associated with this packet */
/* inject the packet, in the right direction */
if (tlq->data_inbound)
{
// NOTE: for TCP connections, the "to" parameter is NULL. For a UDP connection, there will likely be a valid
// destination required. For the UDP case, you can use a pointer to local storage to pass the UDP sockaddr
// setting. Refer to the header doc for the sock_inject_data_out function
result = sock_inject_data_in(tlq->so, NULL, tlq->data, tlq->control, tlq->flags);
}
else
{
// NOTE: for TCP connections, the "to" parameter is NULL. For a UDP connection, there will likely be a valid
// destination required. For the UDP case, you can use a pointer to local storage to pass the UDP sockaddr
// setting. Refer to the header dock for the sock_inject_data_out function
result = sock_inject_data_out(tlq->so, NULL, tlq->data, tlq->control, tlq->flags);
}
/* if the inject failed, check whether the data is inbound or outbound. As per the
sock_inject_data_out description - The data and control values are always freed
regardless of return value. However, for the sock_inject_data_in function - If the
function returns an error, the caller is responsible for freeing the mbuf.
*/
if (result)
{
printf("error calling sock_inject_data_in/out, dropping data - result was %dn", result);
if (tlq->data_inbound)
{
/*
only release mbuf for inbound injection failure
*/
mbuf_freem(tlq->data);
mbuf_freem(tlq->control);
}
}
// free the queue entry
OSFree(tlq, sizeof(struct SwallowPktQueueItem), gOSMallocTag);
lck_mtx_lock(g_swallowQ_mutex); // limit access to tlp fields
tlp->numPktInDefer--; // decrement the number of packets associated with this socket being processed
assert(tlp->numPktInDefer >= 0);
if ((tlp->tle_in_detach) && (tlp->numPktInDefer <= 0)) // is the socket in the detach_fn which if true means that it is
// in msleep waiting for the wakeup call.
{
lck_mtx_unlock(g_swallowQ_mutex); // release the lock before calling wakeup
wakeup(&(tlp->numPktInDefer));
}
else
{
lck_mtx_unlock(g_swallowQ_mutex);
}
}
// we don't need to do anything to tidy up packets_to_inject because
// a) the queue head is a local variable, and b) the queue elements
// are all gone (guaranteed by the while loop above).
}
/*
data_timer - timer routine used to demonstrate processing of swallowed packets at a point different
from the context of the data_in/out function. this routine checks for swallowed packets stored in
swallow_queue and calls ReinjectDeferredData to re-inject them into the appropriate stream.
Note that this routine already assumes that
the packets have been tagged since the data_in/out functions will be called to process these
re-injected packets.
*/
static void
data_timer(void * unused)
{
boolean_t done = FALSE;
// tl_printf("entered data_timer\n");
lck_mtx_lock(g_swallowQ_mutex); // only allow one access at a time to gTimerState
gTimerState = TIMER_RUNNING; // clear the flag which indicates there is a scheduled data_timer call pending
lck_mtx_unlock(g_swallowQ_mutex);
while (done == FALSE)
{
ReinjectDeferredData(NULL);
lck_mtx_lock(g_swallowQ_mutex); // only allow one access at a time to gTimerState
if (gTimerState == TIMER_RUNNING)
{
// the timer has not been scheduled by either data_in/out function.
// If the data_in/out function had scheduled the timer, gTimerState would be TIMER_PENDING instead.
gTimerState = TIMER_INACTIVE;
done = TRUE;
}
else
{
gTimerState = TIMER_RUNNING; // reset the timerstate to RUNNING since it had been moved to pending
}
lck_mtx_unlock(g_swallowQ_mutex);
}
}
#endif // #if SWALLOW_PACKETS
/* =================================== */
#pragma mark Socket Filter Functions
/*!
@typedef sf_unregistered_func
@discussion sf_unregistered_func is called to notify the filter it
has been unregistered. This is the last function the stack will
call and this function will only be called once all other
function calls in to your filter have completed. Once this
function has been called, your kext may safely unload.
@param handle The socket filter handle used to identify this filter.
*/
static void
tl_unregistered_fn_ip4(sflt_handle handle)
{
gUnregisterProc_ip4_complete = TRUE;
gFilterRegistered_ip4 = FALSE;
tl_printf("tl_unregistered_func_ip4 entered\n");
}
static void
tl_unregistered_fn_ip6(sflt_handle handle)
{
gUnregisterProc_ip6_complete = TRUE;
gFilterRegistered_ip6 = FALSE;
tl_printf("tl_unregistered_func_ip6 entered\n");
}
/*!
@typedef sf_attach_func_locked
@discussion sf_attach_func_locked is called by both sf_attach_funcs initialize internel
memory structures - assumption that the fine grain lock associated with the
tl_active queue is held so that the queue entry can be inserted atomically.
@param cookie Used to allow the socket filter to set the cookie for
this attachment.
@param so The socket the filter is being attached to.
tlp - pointer to ths log entry structure to be associated with this
socket reference for future socket filter calls
@result - assumes that no problem will occur.
*/
static void
tl_attach_fn_locked(socket_t so, struct TCPLogEntry *tlp)
{
tl_stats.tls_info++;
bzero(tlp, sizeof (*tlp));
tlp->tle_len = sizeof (*tlp);
if (++tl_stats.tls_active > tl_stats.tls_active_max)
tl_stats.tls_active_max = tl_stats.tls_active;
tl_stats.tls_inuse++;
tlp->tle_genid = ++tl_stats.tls_attached;
tlp->tle_so = so;
tlp->tle_active = TRUE;
tlp->tle_in_detach = FALSE;
tlp->magic = kTCPLogEntryMagic; // set the magic cookie for debugging purposes only to verify that the system
// only returns memory that I allocated.
microtime(&(tlp->tle_create)); /* Record start time for later */
microtime(&(tlp->tle_start)); /* Record start time for later */
// attach time is a good time to identify the calling process ID.
// could also make the proc_self call to obtain the proc_t value which is useful
// to get the ucred structure.
// important note: the pid associated with this socket is the pid of the process which created the
// socket. The socket may have been passed to another process with a different pid.
tlp->tle_pid = proc_selfpid();
// get the uid
tlp->tle_uid = kauth_getuid();
TAILQ_INSERT_TAIL(&tl_active, tlp, tle_link);
}
/*!
@typedef sf_attach_func
@discussion sf_attach_func is called to notify the filter it has
been attached to a new TCP socket. The filter may allocate memory for
this attachment and use the cookie to track it. This filter is
called in one of two cases:
1) You've installed a global filter and a new socket was created.
2) Your non-global socket filter is being attached using the SO_NKE
socket option.
@param cookie Used to allow the socket filter to set the cookie for
this attachment.
@param so The socket the filter is being attached to.
@result If you return a non-zero value, your filter will not be
attached to this socket.
*/
static errno_t
tl_attach_fn_ip4(void **cookie, socket_t so)
{
struct TCPLogEntry *tlp;
errno_t result = 0;
tl_printf("tl_attach_fn_ip4 - so: 0x%X\n", so);
if (tl_stats.tls_enabled != 0)
{
/* use new OSMalloc call which makes it easier to track memory allocations under Tiger */
/* this call can block */
tlp = (struct TCPLogEntry *)OSMalloc(sizeof (struct TCPLogEntry), gOSMallocTag);
if (tlp == NULL)
{
return ENOBUFS;
}
/* save the log entry as the cookie associated with this socket ref */
*(struct TCPLogEntry**)cookie = tlp;
lck_mtx_lock(gmutex); // take the lock so that we can protect against the tlp structure access
// until after we complete our current access
/* do not set tlp fields until after return from tl_attach_fn_locked as it
clears the tlp structure */
tl_attach_fn_locked(so, tlp);
tlp->tle_protocol = AF_INET; /* indicate that this is an IPv4 connection */
lck_mtx_unlock(gmutex);
}
else
{
*cookie = NULL;
/* return an error so that the socket filter is disassociated with this socket.
which means that the remaining socket filter calls will not be entered for activity on this socket. */
result = ENXIO;
}
return result;
}
static errno_t
tl_attach_fn_ip6(void **cookie, socket_t so)
{
struct TCPLogEntry *tlp;
errno_t result = 0;
tl_printf("tl_attach_fn_ip6 - so: 0x%X\n", so);
if (tl_stats.tls_enabled != 0)
{
// use new OSMalloc call which makes it easier to track memory allocations under Tiger
tlp = (struct TCPLogEntry *)OSMalloc(sizeof (struct TCPLogEntry), gOSMallocTag);
if (tlp == NULL)
{
return ENOBUFS;
}
// save the log entry as the cookie associated with this socket ref
*(struct TCPLogEntry**)cookie = tlp;
lck_mtx_lock(gmutex); // take the lock so that we can protect against the tlp structure access
// until after we complete our current access
// do not set tlp fields until after return from tl_attach_fn_locked as it
// clears the tlp structure
tl_attach_fn_locked(so, tlp);
tlp->tle_protocol = AF_INET6; /* indicate that this is an IPv6 connection */
lck_mtx_unlock(gmutex);
}
else
{
*cookie = NULL;
// return an error so that the socket filter is disassociated with this socket.
// which means that the remaining socket filter calls will not be entered for activity on this socket.
result = ENXIO;
}
return result;
}
/*
@typedef sf_detach_func
@discussion sf_detach_func is called to notify the filter it has
been detached from a socket. If the filter allocated any memory
for this attachment, it should be freed. This function will
be called when the socket is disposed of.
@param cookie Cookie value specified when the filter attach was
called.
@param so The socket the filter is attached to.
@result If you return a non-zero value, your filter will not be
attached to this socket.
*/
static void
tl_detach_fn4(void *cookie, socket_t so)
{
struct TCPLogEntry *tlp = TCPLogEntryFromCookie(cookie);
struct timeval now, timediff;
#if SWALLOW_PACKETS
boolean_t done = FALSE;
#endif // SWALLOW_PACKETS
tl_printf("tl_detach_fn_ipv4 - so: 0x%X, ", so);
if (tlp == NULL)
goto bail;
microtime(&now); /* Record stop time */
timersub(&now, &(tlp->tle_start), &timediff);
tlp->tle_stop = now;
if (tl_stats.tls_log > 0)
{
tl_printf("duration %d.%6d sec, in: %d pkts, %d bytes, out: %d pkts, %d bytes\n",
timediff.tv_sec, timediff.tv_usec, tlp->tle_bufs_in, tlp->tle_bytes_in,
tlp->tle_bufs_out, tlp->tle_bytes_out);
}
#if SWALLOW_PACKETS
/* It could be that the kernel extension is forcibly unloaded while it is actively transferring
data - for example in the middle of transferring data, the nke is unloaded. In this case, the
notify_fn will not be called with either the sock_evt_disconnecting, nor the sock_evt_disconnected
events to be able to force cleanup of the swallowed packets. Need to make sure that
all swallowed packets associated with this socket_t reference are deleted, before the detach_fn
returns. Following the detach_fn, the socket reference is no lnoger valid. and any calls which use
the "detached" socket_t reference could panic the system.
*/
FreeDeferredData(so);
/*
It could be that there is still some swallowed packet that are in the packets_to_inject
queue when we cleared the DeferredData.
*/
while (done == FALSE)
{
int numPktsInDefer;
lck_mtx_lock(g_swallowQ_mutex); // set lock to access the numPktInDefer field
numPktsInDefer = tlp->numPktInDefer;
assert(numPktsInDefer >= 0);
if (numPktsInDefer == 0)
{
// this is the normal processing state
done = TRUE;
lck_mtx_unlock(g_swallowQ_mutex); // release the lock
}
else
{