-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathsession.c
More file actions
369 lines (323 loc) · 9.06 KB
/
Copy pathsession.c
File metadata and controls
369 lines (323 loc) · 9.06 KB
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
// {{{ includes
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "session.h"
// }}}
// {{{ time helpers
static int tv_cmp(const struct timeval *a,const struct timeval *b) { // {{{
if (a->tv_sec!=b->tv_sec)
return a->tv_sec<b->tv_sec?-1:1;
if (a->tv_usec!=b->tv_usec)
return a->tv_usec<b->tv_usec?-1:1;
return 0;
} // }}}
static struct timeval tv_add_ms(struct timeval t,int ms) { // {{{
t.tv_sec+=ms/1000;
t.tv_usec+=(ms%1000)*1000;
if (t.tv_usec>=1000000) {
t.tv_sec+=t.tv_usec/1000000;
t.tv_usec%=1000000;
}
return t;
} // }}}
// }}}
// {{{ free helpers
static uint8_t *raw_dup(const uint8_t *src,int len) { // {{{ copy a raw payload, NULL on failure/empty
uint8_t *p;
if (!src||len<=0)
return NULL;
p=malloc(len);
if (p)
memcpy(p,src,len);
return p;
} // }}}
static void msg_free(struct sess_model *m,struct sess_msg *mm) { // {{{ storage is bounded, so messages are dropped as new ones arrive; tell the viewer first so it cannot keep a pointer to freed memory
if (m->on_msg_free)
m->on_msg_free(mm,m->on_msg_free_user);
free(mm->raw);
free(mm);
} // }}}
static void tx_free(struct sess_model *m,struct sess_tx *tx) { // {{{
struct sess_msg *mm=tx->msgs;
while (mm) {
struct sess_msg *n=mm->next;
msg_free(m,mm);
mm=n;
}
free(tx);
} // }}}
static void sess_free_one(struct sess_model *m,struct sess *s) { // {{{
struct sess_tx *tx=s->txs;
while (tx) {
struct sess_tx *n=tx->next;
tx_free(m,tx);
tx=n;
}
free(s);
} // }}}
// }}}
void sess_model_init(struct sess_model *m,int timeout_ms) { // {{{
m->head=m->tail=NULL;
m->nsess=0;
m->now.tv_sec=0;
m->now.tv_usec=0;
m->timeout_ms=timeout_ms>0?timeout_ms:SESS_TIMEOUT_MS;
m->on_msg_free=NULL;
m->on_msg_free_user=NULL;
} // }}}
void sess_model_on_msg_free(struct sess_model *m,void (*cb)(const struct sess_msg *,void *),void *user) { // {{{
m->on_msg_free=cb;
m->on_msg_free_user=user;
} // }}}
void sess_model_free(struct sess_model *m) { // {{{
struct sess *s=m->head;
while (s) {
struct sess *n=s->next;
sess_free_one(m,s);
s=n;
}
m->head=m->tail=NULL;
m->nsess=0;
} // }}}
static int same_client(const struct sess *s,const struct dhcp_msg *msg) { // {{{ match a client by hardware address
int n=msg->hlen>16?16:msg->hlen;
// hlen must agree as well: comparing only the incoming message's hlen bytes
// let a client that understates hlen (say 1) alias onto the session of any
// client whose address starts with the same byte
if (s->hlen!=msg->hlen)
return 0;
if (n==0) // no hlen given, compare the whole field
n=16;
return s->htype==msg->htype&&memcmp(s->chaddr,msg->chaddr,n)==0;
} // }}}
static void sess_unlink(struct sess_model *m,struct sess *s) { // {{{
if (s->prev)
s->prev->next=s->next;
else
m->head=s->next;
if (s->next)
s->next->prev=s->prev;
else
m->tail=s->prev;
} // }}}
static void sess_push_tail(struct sess_model *m,struct sess *s) { // {{{ append, keeping a stable first-seen order
s->next=NULL;
s->prev=m->tail;
if (m->tail)
m->tail->next=s;
m->tail=s;
if (!m->head)
m->head=s;
} // }}}
static void sess_evict_lru(struct sess_model *m) { // {{{ drop the least recently active client without reordering the rest
struct sess *s,*lru=m->head;
if (!lru)
return;
for (s=m->head->next;s;s=s->next)
if (tv_cmp(&s->last,&lru->last)<0)
lru=s;
sess_unlink(m,lru);
m->nsess--;
sess_free_one(m,lru);
} // }}}
static struct sess *sess_find_or_new(struct sess_model *m,const struct dhcp_msg *msg) { // {{{
struct sess *s;
for (s=m->head;s;s=s->next)
if (same_client(s,msg)) // updated in place, list order stays stable
return s;
s=calloc(1,sizeof *s);
if (!s)
return NULL;
// Make room before linking s in, never after. Trimming afterwards lets the
// LRU scan pick s itself whenever this packet is older than every tracked
// client - which a capture file can state outright, and an out of order
// live capture can produce - and this function would return freed memory.
// One eviction is always enough: nsess grows by one client at a time and
// is trimmed here first, so it never gets past SESS_MAX to begin with.
if (m->nsess>=SESS_MAX)
sess_evict_lru(m);
memcpy(s->chaddr,msg->chaddr,16);
s->hlen=msg->hlen;
s->htype=msg->htype;
s->first=msg->ts;
s->last=msg->ts;
sess_push_tail(m,s);
m->nsess++;
return s;
} // }}}
static struct sess_tx *tx_find_or_new(struct sess_model *m,struct sess *s,const struct dhcp_msg *msg) { // {{{
struct sess_tx *tx;
for (tx=s->txs;tx;tx=tx->next)
if (tx->xid==msg->xid)
return tx;
tx=calloc(1,sizeof *tx);
if (!tx)
return NULL;
tx->xid=msg->xid;
tx->start=msg->ts;
tx->prev=NULL; // newest first
tx->next=s->txs;
if (s->txs)
s->txs->prev=tx;
s->txs=tx;
if (!s->txs_tail)
s->txs_tail=tx;
s->ntx++;
while (s->ntx>SESS_MAX_TX) { // drop the oldest transaction
struct sess_tx *old=s->txs_tail;
if (!old||old==tx)
break;
s->txs_tail=old->prev;
if (old->prev)
old->prev->next=NULL;
else
s->txs=NULL;
s->ntx--;
tx_free(m,old);
}
return tx;
} // }}}
static void tx_add_msg(struct sess_model *m,struct sess_tx *tx,const struct dhcp_msg *msg) { // {{{ append oldest first, collapsing retransmissions
struct sess_msg *mm;
// a retransmission (same type, direction and server as the last message)
// bumps a counter instead of piling up identical nodes
if (tx->msgs_tail&&tx->msgs_tail->msgtype==msg->msgtype
&&tx->msgs_tail->op==msg->op
&&tx->msgs_tail->serverid==msg->serverid) {
tx->msgs_tail->count++;
tx->msgs_tail->ts=msg->ts;
tx->msgs_tail->srcip=msg->srcip;
tx->msgs_tail->reqip=msg->reqip;
tx->msgs_tail->yiaddr=msg->yiaddr;
tx->msgs_tail->ciaddr=msg->ciaddr;
memcpy(tx->msgs_tail->vlan,msg->vlan,sizeof tx->msgs_tail->vlan);
tx->msgs_tail->nvlan=msg->nvlan;
free(tx->msgs_tail->raw); // keep the latest retransmission's packet
tx->msgs_tail->raw=raw_dup(msg->raw,msg->rawlen);
tx->msgs_tail->rawlen=tx->msgs_tail->raw?msg->rawlen:0;
return;
}
mm=calloc(1,sizeof *mm);
if (!mm)
return;
mm->ts=msg->ts;
mm->srcip=msg->srcip;
mm->serverid=msg->serverid;
mm->reqip=msg->reqip;
mm->yiaddr=msg->yiaddr;
mm->ciaddr=msg->ciaddr;
memcpy(mm->vlan,msg->vlan,sizeof mm->vlan);
mm->nvlan=msg->nvlan;
mm->msgtype=msg->msgtype;
mm->op=msg->op;
mm->count=1;
mm->raw=raw_dup(msg->raw,msg->rawlen);
mm->rawlen=mm->raw?msg->rawlen:0;
if (tx->msgs_tail)
tx->msgs_tail->next=mm;
else
tx->msgs=mm;
tx->msgs_tail=mm;
tx->nmsg++;
while (tx->nmsg>SESS_MAX_MSG) { // drop the oldest message
struct sess_msg *old=tx->msgs;
if (!old)
break;
tx->msgs=old->next;
if (tx->msgs_tail==old)
tx->msgs_tail=NULL;
msg_free(m,old);
tx->nmsg--;
}
} // }}}
void sess_ingest(struct sess_model *m,const struct dhcp_msg *msg) { // {{{
struct sess *s;
struct sess_tx *tx;
if (tv_cmp(&msg->ts,&m->now)>0) // advance the model clock
m->now=msg->ts;
s=sess_find_or_new(m,msg);
if (!s)
return;
s->last=msg->ts;
if (msg->nvlan) { // remember the latest tagging; a lone untagged reply does not clear it
s->nvlan=msg->nvlan;
memcpy(s->vlan,msg->vlan,sizeof s->vlan);
}
if (msg->hostname[0])
memcpy(s->hostname,msg->hostname,sizeof s->hostname);
if (msg->serverid)
s->server_ip=msg->serverid;
else if (msg->op==BOOTREPLY&&msg->siaddr)
s->server_ip=msg->siaddr;
tx=tx_find_or_new(m,s,msg);
if (!tx)
return;
tx->last=msg->ts;
tx_add_msg(m,tx,msg);
switch (msg->msgtype) { // advance the request/reply state machine
case 0: // plain BOOTP, no message type option
if (msg->op==BOOTREPLY) { // a BOOTREPLY concludes the exchange
tx->yiaddr=msg->yiaddr;
tx->awaiting=0;
tx->result=DHCPACK;
if (msg->yiaddr)
s->lease_ip=msg->yiaddr;
} else { // a BOOTREQUEST awaits a BOOTREPLY, which carries no type to name
tx->awaiting=SESS_AWAIT_ANY;
tx->deadline=tv_add_ms(msg->ts,m->timeout_ms);
}
break;
case DHCPDISCOVER:
tx->awaiting=DHCPOFFER;
tx->deadline=tv_add_ms(msg->ts,m->timeout_ms);
break;
case DHCPREQUEST:
if (msg->serverid) // the client commits to this server
tx->selected_server=msg->serverid;
tx->awaiting=DHCPACK;
tx->deadline=tv_add_ms(msg->ts,m->timeout_ms);
break;
case DHCPINFORM:
tx->awaiting=DHCPACK;
tx->deadline=tv_add_ms(msg->ts,m->timeout_ms);
break;
case DHCPOFFER:
tx->yiaddr=msg->yiaddr;
if (tx->awaiting==DHCPOFFER)
tx->awaiting=0;
break;
case DHCPACK:
tx->yiaddr=msg->yiaddr;
tx->awaiting=0;
tx->result=DHCPACK;
s->lease_ip=msg->yiaddr;
break;
case DHCPNAK:
tx->awaiting=0;
tx->result=DHCPNAK;
break;
case DHCPRELEASE:
tx->awaiting=0;
tx->result=DHCPACK; // terminal, no reply expected: the lease ended normally
break;
case DHCPDECLINE:
tx->awaiting=0;
tx->result=DHCPDECLINE; // terminal: the client refused the address
break;
}
} // }}}
enum tx_state sess_tx_state(const struct sess_tx *tx,const struct timeval *now) { // {{{
if (tx->result==DHCPNAK)
return TX_NAK;
if (tx->result==DHCPDECLINE)
return TX_DECLINED;
if (tx->result==DHCPACK)
return TX_COMPLETE;
if (tx->awaiting) {
if (now&&tv_cmp(now,&tx->deadline)>0)
return TX_TIMEOUT;
return TX_WAIT;
}
return TX_OFFERED;
} // }}}