-
Notifications
You must be signed in to change notification settings - Fork 2
/
l2tp_tunnel.c
4694 lines (4151 loc) · 159 KB
/
l2tp_tunnel.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
/*****************************************************************************
* Copyright (C) 2004,2005,2006,2007,2008 Katalix Systems Ltd
*
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*****************************************************************************/
#include "md5.h"
#include "usl.h"
#include "l2tp_private.h"
#include "hash.h"
#define L2TP_TUNNEL_MAX_CONFIGID 100 /* limits the number of tunnels between the same two IP hosts */
#define L2TP_TUNNEL_ESTABLISH_TIMEOUT 120 /* 2 minutes */
#define L2TP_TUNNEL_PERSIST_PEND_TIMEOUT 300 /* 5 minutes */
#define L2TP_TUNNEL_MAX_COUNT 0 /* unlimited */
#define L2TP_TUNNEL_HASH_BITS 5
#define L2TP_TUNNEL_HASH_SIZE (1 << L2TP_TUNNEL_HASH_BITS)
/* Each tunnel is represented by an instance of this structure.
* Each tunnel lives on 3 lists:-
* list - regular unsorted list, used when we need to do something
* for all lists
* id_hlist - hashed by tunnel_id
* name_hlist - hashed by name, not used if tunnel isn't given a name
*
* All configuration parameters are stored here, as well as status
* information from the peer.
*/
struct l2tp_tunnel {
struct usl_list_head list; /* unordered list of tunnels */
struct usl_hlist_node id_hlist; /* our list of tunnels hashed by id */
struct usl_hlist_node name_hlist; /* our list of tunnels hashed by name */
struct usl_list_head session_list; /* list of sessions on this tunnel */
struct usl_hlist_head session_id_hlist[L2TP_TUNNEL_HASH_SIZE]; /* hashed list of sessions, hashed by id */
struct usl_fsm_instance fsmi; /* state machine data */
struct l2tp_peer *my_peer;
void *setup_timer;
void *cleanup_timer;
int hold:1; /* suppress context delete (for post analysis) */
int up:1; /* debounce tunnel_up event */
int down:1; /* debounce tunnel_down event */
int close_acked:1; /* peer has acknowledged close */
int sccrq_sent:1; /* for peer collision detection */
int we_can_be_lac:1;
int we_can_be_lns:1;
int fd_is_connected:1;
int fd; /* UDP socket fd */
void *xprt; /* transport handle */
/* Config data, set up by management or by profiles. Most are
* derived from RFC2661.
*/
struct {
uint32_t flags; /* remembers which fields have been set */
uint32_t flags2;
struct sockaddr_in local_addr;
struct sockaddr_in peer_addr;
int config_id;
int hide_avps;
char *secret;
int secret_len;
enum l2tp_api_tunnel_auth_mode auth_mode;
char *host_name;
int host_name_len;
int framing_cap_sync:1;
int framing_cap_async:1;
int bearer_cap_digital:1;
int bearer_cap_analog:1;
int use_tiebreaker:1;
int allow_ppp_proxy:1;
int use_udp_checksums:1;
int do_pmtu_discovery:1;
int persist:1; /* recreate if tunnel fails? */
int mtu;
int hello_timeout;
int max_retries;
uint16_t rx_window_size;
uint16_t tx_window_size;
int retry_timeout;
int idle_timeout;
int trace_flags; /* controls debug trace */
int max_sessions;
char *peer_profile_name;
char *tunnel_profile_name;
char *session_profile_name;
char *ppp_profile_name;
char *tunnel_name; /* assigned by operator */
char *interface_name; /* not yet used */
} config;
/* status info, not set up by management or profiles */
struct {
uint16_t tunnel_id; /* assigned tunnel id */
int created_by_admin;
int num_sessions;
int use_count; /* see _inc_use_count(), _dec_use_count() */
enum l2tp_api_tunnel_mode mode;
int num_establish_retries; /* counts setup attempts */
struct l2tp_avp_tiebreaker tiebreaker;
int have_tiebreaker;
int actual_tx_window_size; /* negotiated with peer */
char *create_time;
} status;
/* Stored info from the tunnel peer */
struct {
struct l2tp_avp_host_name *host_name;
struct l2tp_avp_protocol_version protocol_version;
struct l2tp_avp_tunnel_id tunnel_id;
struct l2tp_avp_framing_cap framing_cap;
struct l2tp_avp_bearer_cap bearer_cap;
struct l2tp_avp_rx_window_size rx_window_size;
struct l2tp_avp_challenge *challenge;
int challenge_len;
struct l2tp_avp_challenge_response *challenge_response;
struct l2tp_avp_tiebreaker tiebreaker;
int have_tiebreaker;
struct l2tp_avp_firmware_revision firmware_revision;
struct l2tp_avp_vendor_name *vendor_name;
struct l2tp_avp_result_code *result_code;
int result_code_len;
} peer;
/* Temporary state, filled in by FSM actions */
struct l2tp_avp_challenge *my_challenge;
int my_challenge_len;
struct l2tp_avp_result_code *result_code; /* we send this info to peer when closing tunnels */
int result_code_len;
};
/* Tunnel profiles are named sets of tunnel configuration
* parameters.
*/
struct l2tp_tunnel_profile {
struct usl_list_head list;
char *profile_name;
uint32_t flags; /* remembers which fields have been set */
struct in_addr our_addr;
struct in_addr peer_addr;
uint16_t our_udp_port;
uint16_t peer_udp_port;
int hide_avps;
char *secret;
int secret_len;
enum l2tp_api_tunnel_auth_mode auth_mode;
char *host_name;
int host_name_len;
int framing_cap_sync:1;
int framing_cap_async:1;
int bearer_cap_digital:1;
int bearer_cap_analog:1;
int use_tiebreaker:1;
int allow_ppp_proxy:1;
int use_udp_checksums:1;
int do_pmtu_discovery:1;
int mtu;
int hello_timeout;
int max_retries;
uint16_t rx_window_size;
uint16_t tx_window_size;
int retry_timeout;
int idle_timeout;
int trace_flags;
int max_sessions;
char *peer_profile_name;
char *session_profile_name;
char *ppp_profile_name;
};
static void l2tp_tunnel_recreate(struct l2tp_tunnel *tunnel);
static int l2tp_tunnel_clone(struct l2tp_tunnel *tunnel, struct l2tp_api_tunnel_msg_data *result);
static int l2tp_tunnel_param_defaults(struct l2tp_tunnel *tunnel, char *tunnel_profile_name, char *peer_profile_name);
static void l2tp_tunnel_deleted_ind(struct l2tp_tunnel *tunnel);
static int l2tp_tunnel_is_ok_to_create(struct l2tp_tunnel *tunnel, int local_request);
static void l2tp_tunnel_idle_timeout(void *arg);
/* Hooks, overridable by plugins */
int (*l2tp_tunnel_created_hook)(uint16_t tunnel_id) = NULL;
int (*l2tp_tunnel_deleted_hook)(uint16_t tunnel_id) = NULL;
int (*l2tp_tunnel_modified_hook)(uint16_t tunnel_id) = NULL;
int (*l2tp_tunnel_up_hook)(uint16_t tunnel_id, uint16_t peer_tunnel_id) = NULL;
int (*l2tp_tunnel_down_hook)(uint16_t tunnel_id) = NULL;
/* Local data */
static struct l2tp_tunnel_profile *l2tp_tunnel_defaults;
static int l2tp_tunnel_count;
static int l2tp_tunnel_event_pipe[2] = { -1, -1 };
static USL_LIST_HEAD(l2tp_tunnel_list);
static USL_LIST_HEAD(l2tp_tunnel_profile_list);
/* Hashed tunnel list.
* We keep separate lists for searching by tunnel id or name.
*
* Although openl2tpd uses random numbers for its tunnel and session
* ids, some other L2TP implementations (most notably Microsoft) do
* not. So we still hash the id rather than using a bitmask, even
* though in some cases they are random.
*/
static struct usl_hlist_head l2tp_tunnel_id_list[L2TP_TUNNEL_HASH_SIZE];
static struct usl_hlist_head l2tp_tunnel_name_list[L2TP_TUNNEL_HASH_SIZE];
static inline struct usl_hlist_head *l2tp_tunnel_id_hash(uint16_t tunnel_id)
{
unsigned long hash_val = (unsigned long) tunnel_id;
return &l2tp_tunnel_id_list[hash_long(hash_val, L2TP_TUNNEL_HASH_BITS)];
}
static inline struct usl_hlist_head *l2tp_tunnel_name_hash(const char *tunnel_name)
{
unsigned hash = usl_hash_full_name((unsigned char *) tunnel_name, strlen(tunnel_name));
return &l2tp_tunnel_name_list[hash & (L2TP_TUNNEL_HASH_SIZE - 1)];
}
static inline struct usl_hlist_head *l2tp_tunnel_session_id_hash(struct l2tp_tunnel *tunnel, uint16_t session_id)
{
unsigned long hash_val = (unsigned long) session_id;
return &tunnel->session_id_hlist[hash_long(hash_val, L2TP_TUNNEL_HASH_BITS)];
}
/* These may be changed by the "system modify" command */
static int l2tp_tunnel_max_count;
static int l2tp_tunnel_drain;
static int l2tp_tunnel_establish_timeout;
static int l2tp_tunnel_deny_local_creates;
static int l2tp_tunnel_deny_remote_creates;
static int l2tp_tunnel_persist_pend_timeout;
/* If we need to set a result code but we run out of memory, use
* this preallocated one.
*/
#define L2TP_API_TUNNEL_EMERG_RESULT_CODE_SIZE 128
static struct l2tp_avp_result_code *l2tp_tunnel_emergency_result_code;
#undef RESC
#undef ERRC
#define RESC(x) L2TP_AVP_RESULT_STOPCCN_##x
#define ERRC(x) L2TP_AVP_ERROR_##x
/* A translation table for converting STOPCCN result codes into
* English text. See RFC2661.
*/
static const struct l2tp_result_codes l2tp_tunnel_stopccn_result_codes[] = {
{ RESC(RESERVED), ERRC(NO_ERROR), "Reserved" },
{ RESC(NORMAL_STOP), ERRC(NO_ERROR), "General request to clear control connection" },
{ RESC(GENERAL_ERROR), ERRC(NO_ERROR), "No general error" },
{ RESC(GENERAL_ERROR), ERRC(NO_TUNNEL_YET), "No control connection exists yet for this LAC-LNS pair" },
{ RESC(GENERAL_ERROR), ERRC(BAD_LENGTH), "Length is wrong" },
{ RESC(GENERAL_ERROR), ERRC(BAD_VALUE), "One of the field values was out of range or reserved field was non-zero" },
{ RESC(GENERAL_ERROR), ERRC(NO_RESOURCE), "Insufficient resources to handle this operation now" },
{ RESC(GENERAL_ERROR), ERRC(BAD_SESSION_ID), "The Session ID is invalid in this context" },
{ RESC(GENERAL_ERROR), ERRC(VENDOR_ERROR), "A generic vendor-specific error occurred in the LAC" },
{ RESC(GENERAL_ERROR), ERRC(TRY_ANOTHER), "Try another" },
{ RESC(GENERAL_ERROR), ERRC(MBIT_SHUTDOWN), "Unknown mandatory AVP received" },
{ RESC(ALREADY_EXISTS), ERRC(NO_ERROR), "Control channel already exists" },
{ RESC(AUTH_FAILED), ERRC(NO_ERROR), "Requester is not authorized to establish a control channel" },
{ RESC(BAD_PROTOCOL), ERRC(NO_ERROR), "The protocol version of the requester is not supported" },
{ RESC(BEING_SHUTDOWN), ERRC(NO_ERROR), "Requester is being shut down" },
{ RESC(STATE_ERROR), ERRC(NO_ERROR), "Finite State Machine error" },
{ -1, -1, NULL },
};
/*****************************************************************************
* Public interface
*****************************************************************************/
/* A bunch of access functions allowing external modules (including
* plugins) access to tunnel info. External modules should increase
* the tunnel reference count using l2tp_tunnel_inc_use_count() while
* they hold a pointer to a tunnel instance.
*/
struct usl_list_head *l2tp_tunnel_session_list(struct l2tp_tunnel *tunnel)
{
return &tunnel->session_list;
}
struct usl_hlist_head *l2tp_tunnel_session_id_hlist(struct l2tp_tunnel *tunnel, uint16_t session_id)
{
return l2tp_tunnel_session_id_hash(tunnel, session_id);
}
uint16_t l2tp_tunnel_id(struct l2tp_tunnel const *tunnel)
{
return tunnel->status.tunnel_id;
}
uint16_t l2tp_tunnel_peer_id(struct l2tp_tunnel const *tunnel)
{
return tunnel->peer.tunnel_id.value;
}
int l2tp_tunnel_get_fd(struct l2tp_tunnel const *tunnel)
{
return tunnel->fd;
}
int l2tp_tunnel_is_lac(struct l2tp_tunnel const *tunnel)
{
return tunnel->status.mode == L2TP_API_TUNNEL_MODE_LAC;
}
int l2tp_tunnel_is_lns(struct l2tp_tunnel const *tunnel)
{
return tunnel->status.mode == L2TP_API_TUNNEL_MODE_LNS;
}
int l2tp_tunnel_can_be_lac(struct l2tp_tunnel const *tunnel)
{
return tunnel->we_can_be_lac;
}
int l2tp_tunnel_can_be_lns(struct l2tp_tunnel const *tunnel)
{
return tunnel->we_can_be_lns;
}
int l2tp_tunnel_is_persistent(struct l2tp_tunnel const *tunnel)
{
return tunnel->config.persist;
}
int l2tp_tunnel_is_fd_connected(struct l2tp_tunnel const *tunnel)
{
return tunnel->fd_is_connected;
}
int l2tp_tunnel_is_created_by_admin(struct l2tp_tunnel const *tunnel)
{
return tunnel->status.created_by_admin;
}
struct l2tp_xprt *l2tp_tunnel_get_xprt(struct l2tp_tunnel const *tunnel)
{
return tunnel->xprt;
}
int l2tp_tunnel_get_mtu(struct l2tp_tunnel const *tunnel)
{
return tunnel->config.mtu;
}
int l2tp_tunnel_get_mtu_discovery(struct l2tp_tunnel const *tunnel)
{
return tunnel->config.do_pmtu_discovery;
}
struct sockaddr_in const *l2tp_tunnel_get_peer_addr(struct l2tp_tunnel const *tunnel)
{
return &tunnel->config.peer_addr;
}
struct sockaddr_in const *l2tp_tunnel_get_local_addr(struct l2tp_tunnel const *tunnel)
{
return &tunnel->config.local_addr;
}
int l2tp_tunnel_is_hide_avps(struct l2tp_tunnel const *tunnel)
{
return tunnel->config.hide_avps;
}
void l2tp_tunnel_get_secret(struct l2tp_tunnel const *tunnel, char **secret, int *secret_len)
{
*secret = tunnel->config.secret;
*secret_len = tunnel->config.secret_len;
}
int l2tp_tunnel_get_trace_flags(struct l2tp_tunnel const *tunnel)
{
return tunnel->config.trace_flags;
}
struct l2tp_peer *l2tp_tunnel_get_peer(struct l2tp_tunnel const *tunnel)
{
return tunnel->my_peer;
}
/* Gives external modules access to the profile names configured for
* the tunnel, which are therefore defaults for sessions, ppp etc.
*/
void l2tp_tunnel_get_profile_names(struct l2tp_tunnel const *tunnel, char **tunnel_profile_name,
char **session_profile_name, char **ppp_profile_name)
{
if (tunnel_profile_name != NULL) {
*tunnel_profile_name = tunnel->config.tunnel_profile_name ?
tunnel->config.tunnel_profile_name : L2TP_API_TUNNEL_PROFILE_DEFAULT_PROFILE_NAME;
}
if (session_profile_name != NULL) {
*session_profile_name = tunnel->config.session_profile_name ?
tunnel->config.session_profile_name : L2TP_API_SESSION_PROFILE_DEFAULT_PROFILE_NAME;
}
if (ppp_profile_name != NULL) {
*ppp_profile_name = tunnel->config.ppp_profile_name ?
tunnel->config.ppp_profile_name : L2TP_API_PPP_PROFILE_DEFAULT_PROFILE_NAME;
}
}
/* Called when tunnel addresses are known.
*/
void l2tp_tunnel_set_addresses(struct l2tp_tunnel *tunnel, struct sockaddr_in *src, struct sockaddr_in *dest)
{
struct l2tp_peer *peer = tunnel->my_peer;
if (src != NULL) {
tunnel->config.local_addr = *src;
if (peer->if_local_addr.s_addr == INADDR_ANY) {
peer->if_local_addr.s_addr = src->sin_addr.s_addr;
}
}
if (dest != NULL) {
tunnel->config.peer_addr = *dest;
}
tunnel->fd_is_connected = 1;
if (tunnel->config.do_pmtu_discovery) {
int mtu;
int result;
socklen_t optlen = sizeof(mtu);
result = l2tp_net_modify_socket(tunnel->fd, -1, 1);
if (result == 0) {
result = getsockopt(tunnel->fd, SOL_IP, IP_MTU, &mtu, &optlen);
if (result == 0) {
l2tp_tunnel_update_mtu(tunnel, tunnel->fd, mtu);
}
}
L2TP_DEBUG(L2TP_FUNC, "%s: tunl %hu: fd=%d, set pmtu discovery w/ initial mtu %d, result=%d",
__func__, tunnel->status.tunnel_id, tunnel->fd, mtu, result);
}
}
/* A heavily used function called whenever tunnel-related trace
* messages are being logged. Individual categories of message can be
* enabled/disabled per tunnel instance.
*/
void l2tp_tunnel_log(struct l2tp_tunnel const *tunnel, int category, int level, const char *fmt, ...)
{
if ((tunnel != NULL) && (category & tunnel->config.trace_flags)) {
va_list ap;
va_start(ap, fmt);
l2tp_vlog(level, fmt, ap);
va_end(ap);
}
}
/* Used by the state machine implementation to trace state changes.
*/
static void l2tp_tunnel_fsm_log(struct usl_fsm_instance const *fsmi, int level, const char *fmt, ...)
{
struct l2tp_tunnel *tunnel = ((void *) fsmi) - offsetof(struct l2tp_tunnel, fsmi);
if (tunnel->config.trace_flags & L2TP_FSM) {
va_list ap;
va_start(ap, fmt);
l2tp_vlog(level, fmt, ap);
va_end(ap);
}
}
/* Helper for logging info about errors.
*/
static void l2tp_tunnel_log_error(struct l2tp_tunnel *tunnel,
struct l2tp_avp_result_code *result_code,
int result_code_len)
{
const struct l2tp_result_codes *entry = &l2tp_tunnel_stopccn_result_codes[0];
if (result_code == NULL) {
goto out;
}
while (entry->result_code >= 0) {
if (entry->result_code == result_code->result_code) {
if (entry->error_code == result_code->error_code) {
l2tp_tunnel_log(tunnel, L2TP_PROTOCOL, LOG_INFO, "PROTO: tunl %hu: STOPCCN error %d/%d: %s%s%s",
tunnel->status.tunnel_id,
result_code->result_code,
result_code->error_code,
entry->error_string ? entry->error_string : "",
(result_code_len > sizeof(*result_code)) &&
result_code->error_message ? " - " : "",
(result_code_len > sizeof(*result_code)) &&
result_code->error_message ? result_code->error_message : "");
break;
}
}
entry++;
}
out:
return;
}
/* We come here to generate an event on a tunnel state machine
* instance. Events are handled inline.
*/
static void l2tp_tunnel_handle_event(struct l2tp_tunnel *tunnel, int event)
{
/* Bump the use count on the tunnel while the event is handled in case the event
* causes the tunnel to be deleted. The context must not be deleted until after
* the FSM event handler has returned.
*/
l2tp_tunnel_inc_use_count(tunnel);
usl_fsm_handle_event(&tunnel->fsmi, event, tunnel, NULL, NULL);
l2tp_tunnel_dec_use_count(tunnel);
}
struct l2tp_tunnel *l2tp_tunnel_find_by_id(uint16_t tunnel_id)
{
struct usl_hlist_node *tmp;
struct usl_hlist_node *walk;
struct l2tp_tunnel *tunnel;
usl_hlist_for_each(walk, tmp, l2tp_tunnel_id_hash(tunnel_id)) {
tunnel = usl_hlist_entry(walk, struct l2tp_tunnel, id_hlist);
if (tunnel->status.tunnel_id == tunnel_id) {
l2tp_test_tunnel_id_hash_inc_stats(1);
return tunnel;
}
l2tp_test_tunnel_id_hash_inc_stats(0);
}
return NULL;
}
struct l2tp_tunnel *l2tp_tunnel_find_by_name(const char *tunnel_name)
{
struct usl_hlist_node *tmp;
struct usl_hlist_node *walk;
struct l2tp_tunnel *tunnel;
usl_hlist_for_each(walk, tmp, l2tp_tunnel_name_hash(tunnel_name)) {
tunnel = usl_hlist_entry(walk, struct l2tp_tunnel, name_hlist);
if ((tunnel->config.tunnel_name != NULL) &&
(strcmp(tunnel->config.tunnel_name, tunnel_name) == 0)) {
l2tp_test_tunnel_name_hash_inc_stats(1);
return tunnel;
}
l2tp_test_tunnel_name_hash_inc_stats(0);
}
return NULL;
}
struct l2tp_tunnel *l2tp_tunnel_find_by_addr(struct sockaddr_in const *peer_addr, uint16_t peer_tunnel_id,
int config_id, int tunnel_id_valid)
{
struct usl_list_head *tmp;
struct usl_list_head *walk;
struct l2tp_tunnel *tunnel;
usl_list_for_each(walk, tmp, &l2tp_tunnel_list) {
tunnel = usl_list_entry(walk, struct l2tp_tunnel, list);
if ((config_id != 0) && (tunnel->my_peer == NULL)) {
continue;
}
if (tunnel->config.peer_addr.sin_addr.s_addr != peer_addr->sin_addr.s_addr) {
continue;
}
if ((tunnel_id_valid) && (tunnel->peer.tunnel_id.value != peer_tunnel_id)) {
continue;
}
if ((config_id != 0) && (tunnel->config.config_id != config_id)) {
continue;
}
return tunnel;
}
return NULL;
}
static struct l2tp_tunnel *l2tp_tunnel_find_by_socket_fd(int fd)
{
struct usl_list_head *tmp;
struct usl_list_head *walk;
struct l2tp_tunnel *tunnel;
usl_list_for_each(walk, tmp, &l2tp_tunnel_list) {
tunnel = usl_list_entry(walk, struct l2tp_tunnel, list);
if (tunnel->fd == fd) {
return tunnel;
}
}
return NULL;
}
void l2tp_tunnel_update_mtu(struct l2tp_tunnel *tunnel, int fd, int mtu)
{
if (tunnel == NULL) {
tunnel = l2tp_tunnel_find_by_socket_fd(fd);
if (tunnel == NULL) {
goto out;
}
}
if (!tunnel->fd_is_connected) {
goto out;
}
/* Allow for L2TP and PPP headers */
mtu -= 40;
/* PPP MTUs must be <= 16384 */
if (mtu > 16384) {
mtu = 16384;
}
if (mtu != tunnel->config.mtu) {
l2tp_tunnel_log(tunnel, L2TP_DATA, LOG_INFO, "tunl %hu: update mtu from %d to %d",
tunnel->status.tunnel_id, tunnel->config.mtu, mtu);
tunnel->config.mtu = mtu;
/* Tell all sessions */
l2tp_session_tunnel_modified(tunnel);
}
out:
return;
}
/* Tunnel ids are assigned random numbers, unless we're in a test
* mode. Try 10 times to find an unused id, then give up.
*/
static uint16_t l2tp_tunnel_allocate_id(void)
{
uint16_t tunnel_id;
int tries;
struct l2tp_tunnel *tunnel;
for (tries = 0; tries < 10; tries++) {
if (!l2tp_test_is_no_random_ids()) {
tunnel_id = l2tp_make_random_id();
} else {
tunnel_id = l2tp_test_alloc_tunnel_id();
}
if (tunnel_id == 0) {
continue;
}
tunnel = l2tp_tunnel_find_by_id(tunnel_id);
if (tunnel == NULL) {
return tunnel_id;
}
}
return 0;
}
/* A config_id is an SNMP thing. It is used as a uniqifier when
* tunnels are referenced by src-ip and dest-ip addresses. It is
* assigned a number starting at 1.
*/
static int l2tp_tunnel_alloc_config_id(struct sockaddr_in const *peer_addr)
{
int config_id = 1;
for (config_id = 1; config_id < L2TP_TUNNEL_MAX_CONFIGID; config_id++) {
if (l2tp_tunnel_find_by_addr(peer_addr, 0, config_id, 0) == NULL) {
return config_id;
}
}
return -L2TP_ERR_TUNNEL_TOO_MANY_SAME_IP;
}
/* We come here to record info about an event. This info is sent to
* the peer when the tunnel closes.
*/
static void l2tp_tunnel_set_result(struct l2tp_tunnel *tunnel, uint16_t result_code,
uint16_t error_code, char *error_string)
{
int len = 2;
L2TP_DEBUG(L2TP_FUNC, "%s: tunl %hu: code=%hu error=%hu msg=%s", __func__,
tunnel->status.tunnel_id, result_code, error_code, error_string ? error_string : "");
/* Don't overwrite a result that is already present */
if (tunnel->result_code != NULL) {
L2TP_DEBUG(L2TP_FUNC, "%s: tunl %hu: preserving current data: code=%hu error=%hu msg=%s", __func__,
tunnel->status.tunnel_id, tunnel->result_code->result_code,
tunnel->result_code->error_code, tunnel->result_code->error_message);
goto out;
}
/* Build result_code data structure */
if ((error_code != 0) || (error_string != NULL)) {
len += 2;
if (error_string != NULL) {
len += strlen(error_string) + 1;
}
}
tunnel->result_code = malloc(len <= 5 ? 5 : len);
if (tunnel->result_code == NULL) {
/* Use emergency result code */
tunnel->result_code = l2tp_tunnel_emergency_result_code;
}
tunnel->result_code->result_code = result_code;
if (len > 2) {
tunnel->result_code->error_code = error_code;
} else {
tunnel->result_code->error_code = 0;
}
if (len > 4) {
strncpy(&tunnel->result_code->error_message[0], error_string, len - sizeof(struct l2tp_avp_result_code));
} else {
tunnel->result_code->error_message[0] = '\0';
}
tunnel->result_code_len = len;
out:
return;
}
/* Come here to signal an error and tear the tunnel down.
*/
void l2tp_tunnel_protocol_error(struct l2tp_tunnel const *tunnel, int code, const char *str)
{
l2tp_tunnel_set_result((struct l2tp_tunnel *) tunnel, code, 0, (char *) str);
l2tp_tunnel_queue_event(tunnel->status.tunnel_id, L2TP_CCE_EVENT_CLOSE_REQ);
}
/*****************************************************************************
* Helpers to build AVPs.
*****************************************************************************/
/* Used for optional tunnel authentication.
*/
static int l2tp_tunnel_build_challenge(struct l2tp_tunnel *tunnel)
{
int result = 0;
if (tunnel->config.secret == NULL) {
l2tp_tunnel_set_result(tunnel, L2TP_AVP_RESULT_STOPCCN_AUTH_FAILED, 0,
"no tunnel secret available");
result = -EPERM;
goto out;
}
if (tunnel->my_challenge != NULL) {
free(tunnel->my_challenge);
tunnel->my_challenge = NULL;
tunnel->my_challenge_len = 0;
}
tunnel->my_challenge_len = 16; /* could be random length */
tunnel->my_challenge = malloc(tunnel->my_challenge_len);
if (tunnel->my_challenge == NULL) {
tunnel->my_challenge_len = 0;
result = -ENOMEM;
l2tp_tunnel_set_result(tunnel, L2TP_AVP_RESULT_STOPCCN_GENERAL_ERROR,
L2TP_AVP_ERROR_NO_RESOURCE, NULL);
l2tp_stats.no_control_frame_resources++;
goto out;
}
l2tp_make_random_vector(tunnel->my_challenge, tunnel->my_challenge_len);
L2TP_DEBUG(L2TP_PROTOCOL, "%s: value=%s", __func__, l2tp_buffer_hexify(tunnel->my_challenge, tunnel->my_challenge_len));
out:
return result;
}
/* Used for optional tunnel authentication.
*/
static void l2tp_tunnel_build_challenge_response(struct l2tp_tunnel *tunnel,
uint8_t id,
struct l2tp_avp_challenge *challenge,
int challenge_len,
struct l2tp_avp_challenge_response *challenge_response)
{
struct MD5Context ctx;
uint8_t *digest = &challenge_response->value[0];
uint8_t chap_id = id;
L2TP_DEBUG(L2TP_PROTOCOL, "%s: id=%02x secret=%s challenge=%s", __func__,
id, tunnel->config.secret, l2tp_buffer_hexify(challenge, challenge_len));
/* The challenge response is built by doing an MD5 hash of the following
* octet stream:
* ID + secret + challenge
*/
MD5Init(&ctx);
MD5Update(&ctx, &chap_id, 1);
MD5Update(&ctx, tunnel->config.secret, tunnel->config.secret_len);
MD5Update(&ctx, &challenge->value[0], challenge_len);
MD5Final(digest, &ctx);
L2TP_DEBUG(L2TP_PROTOCOL, "%s: value=%s", __func__, l2tp_buffer_hexify(&challenge_response->value[0], 16));
}
static int l2tp_tunnel_build_result_code(struct l2tp_tunnel *tunnel)
{
/* If there is a stored result_code in the tunnel, use it.
* Else build one with "general clear request".
*/
if (tunnel->result_code == NULL) {
tunnel->result_code = malloc(sizeof(struct l2tp_avp_result_code));
if (tunnel->result_code == NULL) {
tunnel->result_code = l2tp_tunnel_emergency_result_code;
}
tunnel->result_code->result_code = 1; /* general clear request */
tunnel->result_code->error_code = 0;
tunnel->result_code_len = sizeof(*tunnel->result_code);
}
return 0;
}
static int l2tp_tunnel_build_tiebreaker(struct l2tp_tunnel *tunnel,
struct l2tp_avp_tiebreaker *tiebreaker)
{
/* FIXME: Not sure of the criteria to use for generating a tie breaker.
* Use a random byte sequence for now.
*/
l2tp_make_random_vector(&tiebreaker->value[0], sizeof(tiebreaker->value));
return 0;
}
static struct l2tp_tunnel_profile *l2tp_tunnel_profile_find(const char *name)
{
struct usl_list_head *tmp;
struct usl_list_head *walk;
struct l2tp_tunnel_profile *profile;
usl_list_for_each(walk, tmp, &l2tp_tunnel_profile_list) {
profile = usl_list_entry(walk, struct l2tp_tunnel_profile, list);
if (strcmp(&profile->profile_name[0], name) == 0) {
return profile;
}
}
return NULL;
}
/* Come here to add a session to a tunnel in cases where it is already linked
* into the session list and has been allowed to create, i.e. to recreate
* sessions in persistent tunnels.
*/
void l2tp_tunnel_session_add_again(struct l2tp_tunnel *tunnel, struct usl_hlist_node *hlist, uint16_t session_id)
{
usl_hlist_add_head(hlist, l2tp_tunnel_session_id_hash(tunnel, session_id));
tunnel->status.num_sessions++;
L2TP_DEBUG(L2TP_FUNC, "%s: FUNC: tunl %hu: num_sessions=%d", __func__, tunnel->status.tunnel_id, tunnel->status.num_sessions);
}
int l2tp_tunnel_session_add(struct l2tp_tunnel *tunnel, struct usl_list_head *list, struct usl_hlist_node *hlist, uint16_t session_id)
{
int result = 0;
/* This will be decremented by l2tp_tunnel_session_remove() if
* something goes wrong below.
*/
tunnel->status.num_sessions++;
/* Check if new session is administratively disabled */
if (l2tp_tunnel_drain) {
result = -L2TP_ERR_TUNNEL_ADD_ADMIN_DISABLED;
l2tp_tunnel_log(tunnel, L2TP_FUNC, LOG_INFO, "FUNC: tunl %hu: new session rejected: drain_tunnels is set",
tunnel->status.tunnel_id);
goto out;
}
if (tunnel->config.max_sessions != 0) {
if (tunnel->status.num_sessions > tunnel->config.max_sessions) {
result = -L2TP_ERR_TUNNEL_TOO_MANY_SESSIONS;
l2tp_tunnel_log(tunnel, L2TP_FUNC, LOG_INFO, "FUNC: tunl %hu: new session rejected: tunnel session limit (%d) exceeded",
tunnel->status.tunnel_id, tunnel->config.max_sessions);
l2tp_stats.too_many_sessions++;
goto out;
}
}
/* If session count was zero and the setup timer was
* running, delete the timer.
*/
if ((tunnel->status.num_sessions == 0) && (tunnel->setup_timer != NULL)) {
usl_timer_delete(tunnel->setup_timer);
tunnel->setup_timer = NULL;
}
usl_list_add(list, &tunnel->session_list);
usl_hlist_add_head(hlist, l2tp_tunnel_session_id_hash(tunnel, session_id));
out:
L2TP_DEBUG(L2TP_FUNC, "%s: FUNC: tunl %hu: num_sessions=%d", __func__, tunnel->status.tunnel_id, tunnel->status.num_sessions);
return result;
}
void l2tp_tunnel_session_remove(struct l2tp_tunnel *tunnel, struct usl_list_head *list, struct usl_hlist_node *hlist, int persist)
{
#if 1
if (!persist) {
usl_list_del(list);
}
#else
usl_list_del(list);
#endif
if (!usl_hlist_unhashed(hlist)) {
usl_hlist_del(hlist);
}
tunnel->status.num_sessions--;
L2TP_DEBUG(L2TP_FUNC, "%s: FUNC: tunl %hu: num_sessions=%d", __func__, tunnel->status.tunnel_id, tunnel->status.num_sessions);
/* If session count is now zero and the idle_count parameter is set, start a
* timer to cleanup the tunnel. If another session comes along before the timer
* expires, continue as normal.
*/
if ((tunnel->status.num_sessions == 0) && (tunnel->config.idle_timeout != 0)) {
l2tp_tunnel_log(tunnel, L2TP_FUNC, LOG_INFO, "FUNC: tunl %hu: last session closed - starting cleanup timer",
tunnel->status.tunnel_id);
tunnel->setup_timer = usl_timer_create(USL_TIMER_TICKS(tunnel->config.idle_timeout), 0, l2tp_tunnel_idle_timeout, tunnel, NULL);
}
}
int l2tp_tunnel_send_hello(void *tun)
{
struct l2tp_tunnel *tunnel = tun;
struct l2tp_avp_desc avps[L2TP_AVP_TYPE_NUM_AVPS];
struct l2tp_avp_message_type msg_type;
struct l2tp_packet *pkt = NULL;
int result;
memset(&avps, 0, sizeof(avps));
/* HELLO messages have only one AVP -- the message type */
msg_type.type = L2TP_AVP_TYPE_MESSAGE;
avps[L2TP_AVP_TYPE_MESSAGE].value = (void *) &msg_type;
avps[L2TP_AVP_TYPE_MESSAGE].value_len = sizeof(msg_type);
/* build and send HELLO */
result = l2tp_avp_message_encode(L2TP_AVP_MSG_HELLO, &pkt, 0, avps, tunnel);
if (result < 0) {
l2tp_stats.encode_message_fails++;
goto error;
}
l2tp_tunnel_log(tunnel, L2TP_PROTOCOL, LOG_INFO, "PROTO: tunl %hu: sending HELLO", tunnel->status.tunnel_id);
result = l2tp_net_send(tunnel, tunnel->peer.tunnel_id.value, 0, pkt, L2TP_AVP_MSG_HELLO);
out:
return result;
error:
if (pkt != NULL) {
l2tp_pkt_free(pkt);
}
goto out;
}
/* Set our transmit window size to the lower of our configured value and
* the peer's advertised rx window size.
*/
static int l2tp_tunnel_adjust_tx_window_size(struct l2tp_tunnel *tunnel)
{
int result = 0;
if (tunnel->peer.rx_window_size.value < tunnel->config.tx_window_size) {
struct l2tp_xprt_tunnel_modify_data xprt_params;
l2tp_tunnel_log(tunnel, L2TP_PROTOCOL, LOG_INFO, "PROTO: tunl %hu: adjust tx_window_size: peer=%hu, ours=%hu",
tunnel->status.tunnel_id, tunnel->peer.rx_window_size.value, tunnel->config.tx_window_size);
memset(&xprt_params, 0, sizeof(xprt_params));
xprt_params.tx_window_size = tunnel->peer.rx_window_size.value;
xprt_params.flags |= L2TP_XPRT_TUN_FLAG_TX_WINDOW_SIZE;
result = l2tp_xprt_tunnel_modify(tunnel->xprt, &xprt_params);
if (result == 0) {
tunnel->status.actual_tx_window_size = tunnel->peer.rx_window_size.value;
}
} else {
tunnel->status.actual_tx_window_size = tunnel->config.tx_window_size;
}
return result;
}
/*****************************************************************************
* Timers
*****************************************************************************/
static void l2tp_tunnel_persist_timeout(void *arg)
{
struct l2tp_tunnel *tunnel = arg;
L2TP_DEBUG(L2TP_FUNC, "FUNC: tunl %hu: persist timer %p expired", tunnel->status.tunnel_id, tunnel->cleanup_timer);
/* Clone the tunnel config and recreate. */
l2tp_tunnel_recreate(tunnel);
/* Now delete the original tunnel */
l2tp_tunnel_dec_use_count(tunnel);