|
@@ -19,6 +19,7 @@
|
19
|
19
|
|
20
|
20
|
FILE_LICENCE ( GPL2_OR_LATER );
|
21
|
21
|
|
|
22
|
+#include <stdlib.h>
|
22
|
23
|
#include <string.h>
|
23
|
24
|
#include <errno.h>
|
24
|
25
|
#include <byteswap.h>
|
|
@@ -36,6 +37,9 @@ FILE_LICENCE ( GPL2_OR_LATER );
|
36
|
37
|
*
|
37
|
38
|
*/
|
38
|
39
|
|
|
40
|
+static int ipv6conf_rx_router_advertisement ( struct net_device *netdev,
|
|
41
|
+ unsigned int flags );
|
|
42
|
+
|
39
|
43
|
/**
|
40
|
44
|
* Transmit NDP packet with link-layer address option
|
41
|
45
|
*
|
|
@@ -147,7 +151,7 @@ struct neighbour_discovery ndp_discovery = {
|
147
|
151
|
* @v netdev Network device
|
148
|
152
|
* @ret rc Return status code
|
149
|
153
|
*/
|
150
|
|
-int ndp_tx_router_solicitation ( struct net_device *netdev ) {
|
|
154
|
+static int ndp_tx_router_solicitation ( struct net_device *netdev ) {
|
151
|
155
|
struct ndp_router_solicitation_header rsol;
|
152
|
156
|
struct sockaddr_in6 sin6_dest;
|
153
|
157
|
int rc;
|
|
@@ -443,35 +447,33 @@ static int ndp_rx_option ( struct net_device *netdev,
|
443
|
447
|
}
|
444
|
448
|
|
445
|
449
|
/**
|
446
|
|
- * Process received NDP packet
|
|
450
|
+ * Process received NDP packet options
|
447
|
451
|
*
|
448
|
|
- * @v iobuf I/O buffer
|
449
|
452
|
* @v netdev Network device
|
450
|
453
|
* @v sin6_src Source socket address
|
|
454
|
+ * @v ndp NDP header
|
451
|
455
|
* @v offset Offset to NDP options
|
|
456
|
+ * @v len Length of NDP packet
|
452
|
457
|
* @ret rc Return status code
|
453
|
458
|
*/
|
454
|
|
-static int ndp_rx ( struct io_buffer *iobuf,
|
455
|
|
- struct net_device *netdev,
|
456
|
|
- struct sockaddr_in6 *sin6_src,
|
457
|
|
- size_t offset ) {
|
458
|
|
- union ndp_header *ndp = iobuf->data;
|
|
459
|
+static int ndp_rx_options ( struct net_device *netdev,
|
|
460
|
+ struct sockaddr_in6 *sin6_src,
|
|
461
|
+ union ndp_header *ndp, size_t offset, size_t len ) {
|
459
|
462
|
union ndp_option *option;
|
460
|
463
|
size_t remaining;
|
461
|
464
|
size_t option_len;
|
462
|
465
|
int rc;
|
463
|
466
|
|
464
|
467
|
/* Sanity check */
|
465
|
|
- if ( iob_len ( iobuf ) < offset ) {
|
|
468
|
+ if ( len < offset ) {
|
466
|
469
|
DBGC ( netdev, "NDP packet too short at %zd bytes (min %zd "
|
467
|
|
- "bytes)\n", iob_len ( iobuf ), offset );
|
468
|
|
- rc = -EINVAL;
|
469
|
|
- goto done;
|
|
470
|
+ "bytes)\n", len, offset );
|
|
471
|
+ return -EINVAL;
|
470
|
472
|
}
|
471
|
473
|
|
472
|
474
|
/* Search for option */
|
473
|
475
|
option = ( ( ( void * ) ndp ) + offset );
|
474
|
|
- remaining = ( iob_len ( iobuf ) - offset );
|
|
476
|
+ remaining = ( len - offset );
|
475
|
477
|
while ( remaining ) {
|
476
|
478
|
|
477
|
479
|
/* Sanity check */
|
|
@@ -481,27 +483,21 @@ static int ndp_rx ( struct io_buffer *iobuf,
|
481
|
483
|
NDP_OPTION_BLKSZ ) ) ) {
|
482
|
484
|
DBGC ( netdev, "NDP bad option length:\n" );
|
483
|
485
|
DBGC_HDA ( netdev, 0, option, remaining );
|
484
|
|
- rc = -EINVAL;
|
485
|
|
- goto done;
|
|
486
|
+ return -EINVAL;
|
486
|
487
|
}
|
487
|
488
|
option_len = ( option->header.blocks * NDP_OPTION_BLKSZ );
|
488
|
489
|
|
489
|
490
|
/* Handle option */
|
490
|
491
|
if ( ( rc = ndp_rx_option ( netdev, sin6_src, ndp, option,
|
491
|
492
|
option_len ) ) != 0 )
|
492
|
|
- goto done;
|
|
493
|
+ return rc;
|
493
|
494
|
|
494
|
495
|
/* Move to next option */
|
495
|
496
|
option = ( ( ( void * ) option ) + option_len );
|
496
|
497
|
remaining -= option_len;
|
497
|
498
|
}
|
498
|
499
|
|
499
|
|
- /* Success */
|
500
|
|
- rc = 0;
|
501
|
|
-
|
502
|
|
- done:
|
503
|
|
- free_iob ( iobuf );
|
504
|
|
- return rc;
|
|
500
|
+ return 0;
|
505
|
501
|
}
|
506
|
502
|
|
507
|
503
|
/**
|
|
@@ -519,9 +515,18 @@ static int ndp_rx_neighbour ( struct io_buffer *iobuf,
|
519
|
515
|
struct sockaddr_in6 *sin6_dest __unused ) {
|
520
|
516
|
union ndp_header *ndp = iobuf->data;
|
521
|
517
|
struct ndp_neighbour_header *neigh = &ndp->neigh;
|
|
518
|
+ size_t len = iob_len ( iobuf );
|
|
519
|
+ int rc;
|
|
520
|
+
|
|
521
|
+ /* Process options */
|
|
522
|
+ if ( ( rc = ndp_rx_options ( netdev, sin6_src, ndp,
|
|
523
|
+ offsetof ( typeof ( *neigh ), option ),
|
|
524
|
+ len ) ) != 0 )
|
|
525
|
+ goto err_options;
|
522
|
526
|
|
523
|
|
- return ndp_rx ( iobuf, netdev, sin6_src,
|
524
|
|
- offsetof ( typeof ( *neigh ), option ) );
|
|
527
|
+ err_options:
|
|
528
|
+ free_iob ( iobuf );
|
|
529
|
+ return rc;
|
525
|
530
|
}
|
526
|
531
|
|
527
|
532
|
/**
|
|
@@ -540,9 +545,24 @@ ndp_rx_router_advertisement ( struct io_buffer *iobuf,
|
540
|
545
|
struct sockaddr_in6 *sin6_dest __unused ) {
|
541
|
546
|
union ndp_header *ndp = iobuf->data;
|
542
|
547
|
struct ndp_router_advertisement_header *radv = &ndp->radv;
|
|
548
|
+ size_t len = iob_len ( iobuf );
|
|
549
|
+ int rc;
|
543
|
550
|
|
544
|
|
- return ndp_rx ( iobuf, netdev, sin6_src,
|
545
|
|
- offsetof ( typeof ( *radv ), option ) );
|
|
551
|
+ /* Process options */
|
|
552
|
+ if ( ( rc = ndp_rx_options ( netdev, sin6_src, ndp,
|
|
553
|
+ offsetof ( typeof ( *radv ), option ),
|
|
554
|
+ len ) ) != 0 )
|
|
555
|
+ goto err_options;
|
|
556
|
+
|
|
557
|
+ /* Pass to IPv6 autoconfiguration */
|
|
558
|
+ if ( ( rc = ipv6conf_rx_router_advertisement ( netdev,
|
|
559
|
+ radv->flags ) ) != 0 )
|
|
560
|
+ goto err_ipv6conf;
|
|
561
|
+
|
|
562
|
+ err_ipv6conf:
|
|
563
|
+ err_options:
|
|
564
|
+ free_iob ( iobuf );
|
|
565
|
+ return rc;
|
546
|
566
|
}
|
547
|
567
|
|
548
|
568
|
/** NDP ICMPv6 handlers */
|
|
@@ -560,3 +580,174 @@ struct icmpv6_handler ndp_handlers[] __icmpv6_handler = {
|
560
|
580
|
.rx = ndp_rx_router_advertisement,
|
561
|
581
|
},
|
562
|
582
|
};
|
|
583
|
+
|
|
584
|
+/****************************************************************************
|
|
585
|
+ *
|
|
586
|
+ * IPv6 autoconfiguration
|
|
587
|
+ *
|
|
588
|
+ */
|
|
589
|
+
|
|
590
|
+/** An IPv6 configurator */
|
|
591
|
+struct ipv6conf {
|
|
592
|
+ /** Reference count */
|
|
593
|
+ struct refcnt refcnt;
|
|
594
|
+ /** List of configurators */
|
|
595
|
+ struct list_head list;
|
|
596
|
+
|
|
597
|
+ /** Job control interface */
|
|
598
|
+ struct interface job;
|
|
599
|
+
|
|
600
|
+ /** Network device being configured */
|
|
601
|
+ struct net_device *netdev;
|
|
602
|
+
|
|
603
|
+ /** Retransmission timer */
|
|
604
|
+ struct retry_timer timer;
|
|
605
|
+};
|
|
606
|
+
|
|
607
|
+/** List of IPv6 configurators */
|
|
608
|
+static LIST_HEAD ( ipv6confs );
|
|
609
|
+
|
|
610
|
+/**
|
|
611
|
+ * Free IPv6 configurator
|
|
612
|
+ *
|
|
613
|
+ * @v refcnt Reference count
|
|
614
|
+ */
|
|
615
|
+static void ipv6conf_free ( struct refcnt *refcnt ) {
|
|
616
|
+ struct ipv6conf *ipv6conf =
|
|
617
|
+ container_of ( refcnt, struct ipv6conf, refcnt );
|
|
618
|
+
|
|
619
|
+ netdev_put ( ipv6conf->netdev );
|
|
620
|
+ free ( ipv6conf );
|
|
621
|
+}
|
|
622
|
+
|
|
623
|
+/**
|
|
624
|
+ * Identify IPv6 configurator by network device
|
|
625
|
+ *
|
|
626
|
+ * @v netdev Network device
|
|
627
|
+ * @ret ipv6 IPv6 configurator, or NULL
|
|
628
|
+ */
|
|
629
|
+static struct ipv6conf * ipv6conf_demux ( struct net_device *netdev ) {
|
|
630
|
+ struct ipv6conf *ipv6conf;
|
|
631
|
+
|
|
632
|
+ list_for_each_entry ( ipv6conf, &ipv6confs, list ) {
|
|
633
|
+ if ( ipv6conf->netdev == netdev )
|
|
634
|
+ return ipv6conf;
|
|
635
|
+ }
|
|
636
|
+ return NULL;
|
|
637
|
+}
|
|
638
|
+
|
|
639
|
+/**
|
|
640
|
+ * Finish IPv6 autoconfiguration
|
|
641
|
+ *
|
|
642
|
+ * @v ipv6 IPv6 configurator
|
|
643
|
+ * @v rc Reason for finishing
|
|
644
|
+ */
|
|
645
|
+static void ipv6conf_done ( struct ipv6conf *ipv6conf, int rc ) {
|
|
646
|
+
|
|
647
|
+ /* Shut down interfaces */
|
|
648
|
+ intf_shutdown ( &ipv6conf->job, rc );
|
|
649
|
+
|
|
650
|
+ /* Stop timer */
|
|
651
|
+ stop_timer ( &ipv6conf->timer );
|
|
652
|
+
|
|
653
|
+ /* Remove from list and drop list's reference */
|
|
654
|
+ list_del ( &ipv6conf->list );
|
|
655
|
+ ref_put ( &ipv6conf->refcnt );
|
|
656
|
+}
|
|
657
|
+
|
|
658
|
+/**
|
|
659
|
+ * Handle IPv6 configurator timer expiry
|
|
660
|
+ *
|
|
661
|
+ * @v timer Retry timer
|
|
662
|
+ * @v fail Failure indicator
|
|
663
|
+ */
|
|
664
|
+static void ipv6conf_expired ( struct retry_timer *timer, int fail ) {
|
|
665
|
+ struct ipv6conf *ipv6conf =
|
|
666
|
+ container_of ( timer, struct ipv6conf, timer );
|
|
667
|
+
|
|
668
|
+ /* If we have failed, terminate autoconfiguration */
|
|
669
|
+ if ( fail ) {
|
|
670
|
+ ipv6conf_done ( ipv6conf, -ETIMEDOUT );
|
|
671
|
+ return;
|
|
672
|
+ }
|
|
673
|
+
|
|
674
|
+ /* Otherwise, transmit router solicitation and restart timer */
|
|
675
|
+ start_timer ( &ipv6conf->timer );
|
|
676
|
+ ndp_tx_router_solicitation ( ipv6conf->netdev );
|
|
677
|
+}
|
|
678
|
+
|
|
679
|
+/**
|
|
680
|
+ * Handle router advertisement during IPv6 autoconfiguration
|
|
681
|
+ *
|
|
682
|
+ * @v netdev Network device
|
|
683
|
+ * @v flags Router flags
|
|
684
|
+ * @ret rc Return status code
|
|
685
|
+ */
|
|
686
|
+static int ipv6conf_rx_router_advertisement ( struct net_device *netdev,
|
|
687
|
+ unsigned int flags ) {
|
|
688
|
+ struct ipv6conf *ipv6conf;
|
|
689
|
+
|
|
690
|
+ /* Identify IPv6 configurator, if any */
|
|
691
|
+ ipv6conf = ipv6conf_demux ( netdev );
|
|
692
|
+ if ( ! ipv6conf ) {
|
|
693
|
+ /* Not an error; router advertisements are processed
|
|
694
|
+ * as a background activity even when no explicit
|
|
695
|
+ * autoconfiguration is taking place.
|
|
696
|
+ */
|
|
697
|
+ return 0;
|
|
698
|
+ }
|
|
699
|
+
|
|
700
|
+ /* Fail if stateful address autoconfiguration is required */
|
|
701
|
+ if ( flags & NDP_ROUTER_MANAGED ) {
|
|
702
|
+ ipv6conf_done ( ipv6conf, -ENOTSUP );
|
|
703
|
+ return -ENOTSUP;
|
|
704
|
+ }
|
|
705
|
+
|
|
706
|
+ /* Mark autoconfiguration as complete */
|
|
707
|
+ ipv6conf_done ( ipv6conf, 0 );
|
|
708
|
+
|
|
709
|
+ return 0;
|
|
710
|
+}
|
|
711
|
+
|
|
712
|
+/** IPv6 configurator job interface operations */
|
|
713
|
+static struct interface_operation ipv6conf_job_op[] = {
|
|
714
|
+ INTF_OP ( intf_close, struct ipv6conf *, ipv6conf_done ),
|
|
715
|
+};
|
|
716
|
+
|
|
717
|
+/** IPv6 configurator job interface descriptor */
|
|
718
|
+static struct interface_descriptor ipv6conf_job_desc =
|
|
719
|
+ INTF_DESC ( struct ipv6conf, job, ipv6conf_job_op );
|
|
720
|
+
|
|
721
|
+/**
|
|
722
|
+ * Start IPv6 autoconfiguration
|
|
723
|
+ *
|
|
724
|
+ * @v job Job control interface
|
|
725
|
+ * @v netdev Network device
|
|
726
|
+ * @ret rc Return status code
|
|
727
|
+ */
|
|
728
|
+int start_ipv6conf ( struct interface *job, struct net_device *netdev ) {
|
|
729
|
+ struct ipv6conf *ipv6conf;
|
|
730
|
+
|
|
731
|
+ /* Allocate and initialise structure */
|
|
732
|
+ ipv6conf = zalloc ( sizeof ( *ipv6conf ) );
|
|
733
|
+ if ( ! ipv6conf )
|
|
734
|
+ return -ENOMEM;
|
|
735
|
+ ref_init ( &ipv6conf->refcnt, ipv6conf_free );
|
|
736
|
+ intf_init ( &ipv6conf->job, &ipv6conf_job_desc, &ipv6conf->refcnt );
|
|
737
|
+ timer_init ( &ipv6conf->timer, ipv6conf_expired, &ipv6conf->refcnt );
|
|
738
|
+ ipv6conf->netdev = netdev_get ( netdev );
|
|
739
|
+
|
|
740
|
+ /* Start timer to initiate router solicitation */
|
|
741
|
+ start_timer_nodelay ( &ipv6conf->timer );
|
|
742
|
+
|
|
743
|
+ /* Attach parent interface, transfer reference to list, and return */
|
|
744
|
+ intf_plug_plug ( &ipv6conf->job, job );
|
|
745
|
+ list_add ( &ipv6conf->list, &ipv6confs );
|
|
746
|
+ return 0;
|
|
747
|
+}
|
|
748
|
+
|
|
749
|
+/** IPv6 network device configurator */
|
|
750
|
+struct net_device_configurator ipv6_configurator __net_device_configurator = {
|
|
751
|
+ .name = "ipv6",
|
|
752
|
+ .start = start_ipv6conf,
|
|
753
|
+};
|