diff options
Diffstat (limited to 'net/ipv6/route.c')
-rw-r--r-- | net/ipv6/route.c | 380 |
1 files changed, 252 insertions, 128 deletions
diff --git a/net/ipv6/route.c b/net/ipv6/route.c index ce1f49b595b0..73efdadb9ab8 100644 --- a/net/ipv6/route.c +++ b/net/ipv6/route.c | |||
@@ -140,16 +140,6 @@ struct rt6_info ip6_null_entry = { | |||
140 | .rt6i_ref = ATOMIC_INIT(1), | 140 | .rt6i_ref = ATOMIC_INIT(1), |
141 | }; | 141 | }; |
142 | 142 | ||
143 | struct fib6_node ip6_routing_table = { | ||
144 | .leaf = &ip6_null_entry, | ||
145 | .fn_flags = RTN_ROOT | RTN_TL_ROOT | RTN_RTINFO, | ||
146 | }; | ||
147 | |||
148 | /* Protects all the ip6 fib */ | ||
149 | |||
150 | DEFINE_RWLOCK(rt6_lock); | ||
151 | |||
152 | |||
153 | /* allocate dst with ip6_dst_ops */ | 143 | /* allocate dst with ip6_dst_ops */ |
154 | static __inline__ struct rt6_info *ip6_dst_alloc(void) | 144 | static __inline__ struct rt6_info *ip6_dst_alloc(void) |
155 | { | 145 | { |
@@ -188,8 +178,14 @@ static __inline__ int rt6_check_expired(const struct rt6_info *rt) | |||
188 | time_after(jiffies, rt->rt6i_expires)); | 178 | time_after(jiffies, rt->rt6i_expires)); |
189 | } | 179 | } |
190 | 180 | ||
181 | static inline int rt6_need_strict(struct in6_addr *daddr) | ||
182 | { | ||
183 | return (ipv6_addr_type(daddr) & | ||
184 | (IPV6_ADDR_MULTICAST | IPV6_ADDR_LINKLOCAL)); | ||
185 | } | ||
186 | |||
191 | /* | 187 | /* |
192 | * Route lookup. Any rt6_lock is implied. | 188 | * Route lookup. Any table->tb6_lock is implied. |
193 | */ | 189 | */ |
194 | 190 | ||
195 | static __inline__ struct rt6_info *rt6_device_match(struct rt6_info *rt, | 191 | static __inline__ struct rt6_info *rt6_device_match(struct rt6_info *rt, |
@@ -441,27 +437,66 @@ int rt6_route_rcv(struct net_device *dev, u8 *opt, int len, | |||
441 | } | 437 | } |
442 | #endif | 438 | #endif |
443 | 439 | ||
444 | struct rt6_info *rt6_lookup(struct in6_addr *daddr, struct in6_addr *saddr, | 440 | #define BACKTRACK() \ |
445 | int oif, int strict) | 441 | if (rt == &ip6_null_entry && flags & RT6_F_STRICT) { \ |
442 | while ((fn = fn->parent) != NULL) { \ | ||
443 | if (fn->fn_flags & RTN_TL_ROOT) { \ | ||
444 | dst_hold(&rt->u.dst); \ | ||
445 | goto out; \ | ||
446 | } \ | ||
447 | if (fn->fn_flags & RTN_RTINFO) \ | ||
448 | goto restart; \ | ||
449 | } \ | ||
450 | } | ||
451 | |||
452 | static struct rt6_info *ip6_pol_route_lookup(struct fib6_table *table, | ||
453 | struct flowi *fl, int flags) | ||
446 | { | 454 | { |
447 | struct fib6_node *fn; | 455 | struct fib6_node *fn; |
448 | struct rt6_info *rt; | 456 | struct rt6_info *rt; |
449 | 457 | ||
450 | read_lock_bh(&rt6_lock); | 458 | read_lock_bh(&table->tb6_lock); |
451 | fn = fib6_lookup(&ip6_routing_table, daddr, saddr); | 459 | fn = fib6_lookup(&table->tb6_root, &fl->fl6_dst, &fl->fl6_src); |
452 | rt = rt6_device_match(fn->leaf, oif, strict); | 460 | restart: |
461 | rt = fn->leaf; | ||
462 | rt = rt6_device_match(rt, fl->oif, flags & RT6_F_STRICT); | ||
463 | BACKTRACK(); | ||
453 | dst_hold(&rt->u.dst); | 464 | dst_hold(&rt->u.dst); |
454 | rt->u.dst.__use++; | 465 | out: |
455 | read_unlock_bh(&rt6_lock); | 466 | read_unlock_bh(&table->tb6_lock); |
456 | 467 | ||
457 | rt->u.dst.lastuse = jiffies; | 468 | rt->u.dst.lastuse = jiffies; |
458 | if (rt->u.dst.error == 0) | 469 | rt->u.dst.__use++; |
459 | return rt; | 470 | |
460 | dst_release(&rt->u.dst); | 471 | return rt; |
472 | |||
473 | } | ||
474 | |||
475 | struct rt6_info *rt6_lookup(struct in6_addr *daddr, struct in6_addr *saddr, | ||
476 | int oif, int strict) | ||
477 | { | ||
478 | struct flowi fl = { | ||
479 | .oif = oif, | ||
480 | .nl_u = { | ||
481 | .ip6_u = { | ||
482 | .daddr = *daddr, | ||
483 | /* TODO: saddr */ | ||
484 | }, | ||
485 | }, | ||
486 | }; | ||
487 | struct dst_entry *dst; | ||
488 | int flags = strict ? RT6_F_STRICT : 0; | ||
489 | |||
490 | dst = fib6_rule_lookup(&fl, flags, ip6_pol_route_lookup); | ||
491 | if (dst->error == 0) | ||
492 | return (struct rt6_info *) dst; | ||
493 | |||
494 | dst_release(dst); | ||
495 | |||
461 | return NULL; | 496 | return NULL; |
462 | } | 497 | } |
463 | 498 | ||
464 | /* ip6_ins_rt is called with FREE rt6_lock. | 499 | /* ip6_ins_rt is called with FREE table->tb6_lock. |
465 | It takes new route entry, the addition fails by any reason the | 500 | It takes new route entry, the addition fails by any reason the |
466 | route is freed. In any case, if caller does not hold it, it may | 501 | route is freed. In any case, if caller does not hold it, it may |
467 | be destroyed. | 502 | be destroyed. |
@@ -471,10 +506,12 @@ int ip6_ins_rt(struct rt6_info *rt, struct nlmsghdr *nlh, | |||
471 | void *_rtattr, struct netlink_skb_parms *req) | 506 | void *_rtattr, struct netlink_skb_parms *req) |
472 | { | 507 | { |
473 | int err; | 508 | int err; |
509 | struct fib6_table *table; | ||
474 | 510 | ||
475 | write_lock_bh(&rt6_lock); | 511 | table = rt->rt6i_table; |
476 | err = fib6_add(&ip6_routing_table, rt, nlh, _rtattr, req); | 512 | write_lock_bh(&table->tb6_lock); |
477 | write_unlock_bh(&rt6_lock); | 513 | err = fib6_add(&table->tb6_root, rt, nlh, _rtattr, req); |
514 | write_unlock_bh(&table->tb6_lock); | ||
478 | 515 | ||
479 | return err; | 516 | return err; |
480 | } | 517 | } |
@@ -532,51 +569,40 @@ static struct rt6_info *rt6_alloc_clone(struct rt6_info *ort, struct in6_addr *d | |||
532 | return rt; | 569 | return rt; |
533 | } | 570 | } |
534 | 571 | ||
535 | #define BACKTRACK() \ | 572 | struct rt6_info *ip6_pol_route_input(struct fib6_table *table, struct flowi *fl, |
536 | if (rt == &ip6_null_entry) { \ | 573 | int flags) |
537 | while ((fn = fn->parent) != NULL) { \ | ||
538 | if (fn->fn_flags & RTN_ROOT) { \ | ||
539 | goto out; \ | ||
540 | } \ | ||
541 | if (fn->fn_flags & RTN_RTINFO) \ | ||
542 | goto restart; \ | ||
543 | } \ | ||
544 | } | ||
545 | |||
546 | |||
547 | void ip6_route_input(struct sk_buff *skb) | ||
548 | { | 574 | { |
549 | struct fib6_node *fn; | 575 | struct fib6_node *fn; |
550 | struct rt6_info *rt, *nrt; | 576 | struct rt6_info *rt, *nrt; |
551 | int strict; | 577 | int strict = 0; |
552 | int attempts = 3; | 578 | int attempts = 3; |
553 | int err; | 579 | int err; |
554 | int reachable = RT6_SELECT_F_REACHABLE; | 580 | int reachable = RT6_SELECT_F_REACHABLE; |
555 | 581 | ||
556 | strict = ipv6_addr_type(&skb->nh.ipv6h->daddr) & (IPV6_ADDR_MULTICAST|IPV6_ADDR_LINKLOCAL) ? RT6_SELECT_F_IFACE : 0; | 582 | if (flags & RT6_F_STRICT) |
583 | strict = RT6_SELECT_F_IFACE; | ||
557 | 584 | ||
558 | relookup: | 585 | relookup: |
559 | read_lock_bh(&rt6_lock); | 586 | read_lock_bh(&table->tb6_lock); |
560 | 587 | ||
561 | restart_2: | 588 | restart_2: |
562 | fn = fib6_lookup(&ip6_routing_table, &skb->nh.ipv6h->daddr, | 589 | fn = fib6_lookup(&table->tb6_root, &fl->fl6_dst, &fl->fl6_src); |
563 | &skb->nh.ipv6h->saddr); | ||
564 | 590 | ||
565 | restart: | 591 | restart: |
566 | rt = rt6_select(&fn->leaf, skb->dev->ifindex, strict | reachable); | 592 | rt = rt6_select(&fn->leaf, fl->iif, strict | reachable); |
567 | BACKTRACK(); | 593 | BACKTRACK(); |
568 | if (rt == &ip6_null_entry || | 594 | if (rt == &ip6_null_entry || |
569 | rt->rt6i_flags & RTF_CACHE) | 595 | rt->rt6i_flags & RTF_CACHE) |
570 | goto out; | 596 | goto out; |
571 | 597 | ||
572 | dst_hold(&rt->u.dst); | 598 | dst_hold(&rt->u.dst); |
573 | read_unlock_bh(&rt6_lock); | 599 | read_unlock_bh(&table->tb6_lock); |
574 | 600 | ||
575 | if (!rt->rt6i_nexthop && !(rt->rt6i_flags & RTF_NONEXTHOP)) | 601 | if (!rt->rt6i_nexthop && !(rt->rt6i_flags & RTF_NONEXTHOP)) |
576 | nrt = rt6_alloc_cow(rt, &skb->nh.ipv6h->daddr, &skb->nh.ipv6h->saddr); | 602 | nrt = rt6_alloc_cow(rt, &fl->fl6_dst, &fl->fl6_src); |
577 | else { | 603 | else { |
578 | #if CLONE_OFFLINK_ROUTE | 604 | #if CLONE_OFFLINK_ROUTE |
579 | nrt = rt6_alloc_clone(rt, &skb->nh.ipv6h->daddr); | 605 | nrt = rt6_alloc_clone(rt, &fl->fl6_dst); |
580 | #else | 606 | #else |
581 | goto out2; | 607 | goto out2; |
582 | #endif | 608 | #endif |
@@ -587,7 +613,7 @@ restart: | |||
587 | 613 | ||
588 | dst_hold(&rt->u.dst); | 614 | dst_hold(&rt->u.dst); |
589 | if (nrt) { | 615 | if (nrt) { |
590 | err = ip6_ins_rt(nrt, NULL, NULL, &NETLINK_CB(skb)); | 616 | err = ip6_ins_rt(nrt, NULL, NULL, NULL); |
591 | if (!err) | 617 | if (!err) |
592 | goto out2; | 618 | goto out2; |
593 | } | 619 | } |
@@ -596,7 +622,7 @@ restart: | |||
596 | goto out2; | 622 | goto out2; |
597 | 623 | ||
598 | /* | 624 | /* |
599 | * Race condition! In the gap, when rt6_lock was | 625 | * Race condition! In the gap, when table->tb6_lock was |
600 | * released someone could insert this route. Relookup. | 626 | * released someone could insert this route. Relookup. |
601 | */ | 627 | */ |
602 | dst_release(&rt->u.dst); | 628 | dst_release(&rt->u.dst); |
@@ -608,30 +634,54 @@ out: | |||
608 | goto restart_2; | 634 | goto restart_2; |
609 | } | 635 | } |
610 | dst_hold(&rt->u.dst); | 636 | dst_hold(&rt->u.dst); |
611 | read_unlock_bh(&rt6_lock); | 637 | read_unlock_bh(&table->tb6_lock); |
612 | out2: | 638 | out2: |
613 | rt->u.dst.lastuse = jiffies; | 639 | rt->u.dst.lastuse = jiffies; |
614 | rt->u.dst.__use++; | 640 | rt->u.dst.__use++; |
615 | skb->dst = (struct dst_entry *) rt; | 641 | |
616 | return; | 642 | return rt; |
617 | } | 643 | } |
618 | 644 | ||
619 | struct dst_entry * ip6_route_output(struct sock *sk, struct flowi *fl) | 645 | void ip6_route_input(struct sk_buff *skb) |
646 | { | ||
647 | struct ipv6hdr *iph = skb->nh.ipv6h; | ||
648 | struct flowi fl = { | ||
649 | .iif = skb->dev->ifindex, | ||
650 | .nl_u = { | ||
651 | .ip6_u = { | ||
652 | .daddr = iph->daddr, | ||
653 | .saddr = iph->saddr, | ||
654 | .flowlabel = (* (u32 *) iph)&IPV6_FLOWINFO_MASK, | ||
655 | }, | ||
656 | }, | ||
657 | .proto = iph->nexthdr, | ||
658 | }; | ||
659 | int flags = 0; | ||
660 | |||
661 | if (rt6_need_strict(&iph->daddr)) | ||
662 | flags |= RT6_F_STRICT; | ||
663 | |||
664 | skb->dst = fib6_rule_lookup(&fl, flags, ip6_pol_route_input); | ||
665 | } | ||
666 | |||
667 | static struct rt6_info *ip6_pol_route_output(struct fib6_table *table, | ||
668 | struct flowi *fl, int flags) | ||
620 | { | 669 | { |
621 | struct fib6_node *fn; | 670 | struct fib6_node *fn; |
622 | struct rt6_info *rt, *nrt; | 671 | struct rt6_info *rt, *nrt; |
623 | int strict; | 672 | int strict = 0; |
624 | int attempts = 3; | 673 | int attempts = 3; |
625 | int err; | 674 | int err; |
626 | int reachable = RT6_SELECT_F_REACHABLE; | 675 | int reachable = RT6_SELECT_F_REACHABLE; |
627 | 676 | ||
628 | strict = ipv6_addr_type(&fl->fl6_dst) & (IPV6_ADDR_MULTICAST|IPV6_ADDR_LINKLOCAL) ? RT6_SELECT_F_IFACE : 0; | 677 | if (flags & RT6_F_STRICT) |
678 | strict = RT6_SELECT_F_IFACE; | ||
629 | 679 | ||
630 | relookup: | 680 | relookup: |
631 | read_lock_bh(&rt6_lock); | 681 | read_lock_bh(&table->tb6_lock); |
632 | 682 | ||
633 | restart_2: | 683 | restart_2: |
634 | fn = fib6_lookup(&ip6_routing_table, &fl->fl6_dst, &fl->fl6_src); | 684 | fn = fib6_lookup(&table->tb6_root, &fl->fl6_dst, &fl->fl6_src); |
635 | 685 | ||
636 | restart: | 686 | restart: |
637 | rt = rt6_select(&fn->leaf, fl->oif, strict | reachable); | 687 | rt = rt6_select(&fn->leaf, fl->oif, strict | reachable); |
@@ -641,7 +691,7 @@ restart: | |||
641 | goto out; | 691 | goto out; |
642 | 692 | ||
643 | dst_hold(&rt->u.dst); | 693 | dst_hold(&rt->u.dst); |
644 | read_unlock_bh(&rt6_lock); | 694 | read_unlock_bh(&table->tb6_lock); |
645 | 695 | ||
646 | if (!rt->rt6i_nexthop && !(rt->rt6i_flags & RTF_NONEXTHOP)) | 696 | if (!rt->rt6i_nexthop && !(rt->rt6i_flags & RTF_NONEXTHOP)) |
647 | nrt = rt6_alloc_cow(rt, &fl->fl6_dst, &fl->fl6_src); | 697 | nrt = rt6_alloc_cow(rt, &fl->fl6_dst, &fl->fl6_src); |
@@ -667,7 +717,7 @@ restart: | |||
667 | goto out2; | 717 | goto out2; |
668 | 718 | ||
669 | /* | 719 | /* |
670 | * Race condition! In the gap, when rt6_lock was | 720 | * Race condition! In the gap, when table->tb6_lock was |
671 | * released someone could insert this route. Relookup. | 721 | * released someone could insert this route. Relookup. |
672 | */ | 722 | */ |
673 | dst_release(&rt->u.dst); | 723 | dst_release(&rt->u.dst); |
@@ -679,11 +729,21 @@ out: | |||
679 | goto restart_2; | 729 | goto restart_2; |
680 | } | 730 | } |
681 | dst_hold(&rt->u.dst); | 731 | dst_hold(&rt->u.dst); |
682 | read_unlock_bh(&rt6_lock); | 732 | read_unlock_bh(&table->tb6_lock); |
683 | out2: | 733 | out2: |
684 | rt->u.dst.lastuse = jiffies; | 734 | rt->u.dst.lastuse = jiffies; |
685 | rt->u.dst.__use++; | 735 | rt->u.dst.__use++; |
686 | return &rt->u.dst; | 736 | return rt; |
737 | } | ||
738 | |||
739 | struct dst_entry * ip6_route_output(struct sock *sk, struct flowi *fl) | ||
740 | { | ||
741 | int flags = 0; | ||
742 | |||
743 | if (rt6_need_strict(&fl->fl6_dst)) | ||
744 | flags |= RT6_F_STRICT; | ||
745 | |||
746 | return fib6_rule_lookup(fl, flags, ip6_pol_route_output); | ||
687 | } | 747 | } |
688 | 748 | ||
689 | 749 | ||
@@ -906,7 +966,8 @@ int ipv6_get_hoplimit(struct net_device *dev) | |||
906 | */ | 966 | */ |
907 | 967 | ||
908 | int ip6_route_add(struct in6_rtmsg *rtmsg, struct nlmsghdr *nlh, | 968 | int ip6_route_add(struct in6_rtmsg *rtmsg, struct nlmsghdr *nlh, |
909 | void *_rtattr, struct netlink_skb_parms *req) | 969 | void *_rtattr, struct netlink_skb_parms *req, |
970 | u32 table_id) | ||
910 | { | 971 | { |
911 | int err; | 972 | int err; |
912 | struct rtmsg *r; | 973 | struct rtmsg *r; |
@@ -914,6 +975,7 @@ int ip6_route_add(struct in6_rtmsg *rtmsg, struct nlmsghdr *nlh, | |||
914 | struct rt6_info *rt = NULL; | 975 | struct rt6_info *rt = NULL; |
915 | struct net_device *dev = NULL; | 976 | struct net_device *dev = NULL; |
916 | struct inet6_dev *idev = NULL; | 977 | struct inet6_dev *idev = NULL; |
978 | struct fib6_table *table; | ||
917 | int addr_type; | 979 | int addr_type; |
918 | 980 | ||
919 | rta = (struct rtattr **) _rtattr; | 981 | rta = (struct rtattr **) _rtattr; |
@@ -937,6 +999,12 @@ int ip6_route_add(struct in6_rtmsg *rtmsg, struct nlmsghdr *nlh, | |||
937 | if (rtmsg->rtmsg_metric == 0) | 999 | if (rtmsg->rtmsg_metric == 0) |
938 | rtmsg->rtmsg_metric = IP6_RT_PRIO_USER; | 1000 | rtmsg->rtmsg_metric = IP6_RT_PRIO_USER; |
939 | 1001 | ||
1002 | table = fib6_new_table(table_id); | ||
1003 | if (table == NULL) { | ||
1004 | err = -ENOBUFS; | ||
1005 | goto out; | ||
1006 | } | ||
1007 | |||
940 | rt = ip6_dst_alloc(); | 1008 | rt = ip6_dst_alloc(); |
941 | 1009 | ||
942 | if (rt == NULL) { | 1010 | if (rt == NULL) { |
@@ -1093,6 +1161,7 @@ install_route: | |||
1093 | rt->u.dst.metrics[RTAX_ADVMSS-1] = ipv6_advmss(dst_mtu(&rt->u.dst)); | 1161 | rt->u.dst.metrics[RTAX_ADVMSS-1] = ipv6_advmss(dst_mtu(&rt->u.dst)); |
1094 | rt->u.dst.dev = dev; | 1162 | rt->u.dst.dev = dev; |
1095 | rt->rt6i_idev = idev; | 1163 | rt->rt6i_idev = idev; |
1164 | rt->rt6i_table = table; | ||
1096 | return ip6_ins_rt(rt, nlh, _rtattr, req); | 1165 | return ip6_ins_rt(rt, nlh, _rtattr, req); |
1097 | 1166 | ||
1098 | out: | 1167 | out: |
@@ -1108,26 +1177,35 @@ out: | |||
1108 | int ip6_del_rt(struct rt6_info *rt, struct nlmsghdr *nlh, void *_rtattr, struct netlink_skb_parms *req) | 1177 | int ip6_del_rt(struct rt6_info *rt, struct nlmsghdr *nlh, void *_rtattr, struct netlink_skb_parms *req) |
1109 | { | 1178 | { |
1110 | int err; | 1179 | int err; |
1180 | struct fib6_table *table; | ||
1111 | 1181 | ||
1112 | write_lock_bh(&rt6_lock); | 1182 | table = rt->rt6i_table; |
1183 | write_lock_bh(&table->tb6_lock); | ||
1113 | 1184 | ||
1114 | err = fib6_del(rt, nlh, _rtattr, req); | 1185 | err = fib6_del(rt, nlh, _rtattr, req); |
1115 | dst_release(&rt->u.dst); | 1186 | dst_release(&rt->u.dst); |
1116 | 1187 | ||
1117 | write_unlock_bh(&rt6_lock); | 1188 | write_unlock_bh(&table->tb6_lock); |
1118 | 1189 | ||
1119 | return err; | 1190 | return err; |
1120 | } | 1191 | } |
1121 | 1192 | ||
1122 | static int ip6_route_del(struct in6_rtmsg *rtmsg, struct nlmsghdr *nlh, void *_rtattr, struct netlink_skb_parms *req) | 1193 | static int ip6_route_del(struct in6_rtmsg *rtmsg, struct nlmsghdr *nlh, |
1194 | void *_rtattr, struct netlink_skb_parms *req, | ||
1195 | u32 table_id) | ||
1123 | { | 1196 | { |
1197 | struct fib6_table *table; | ||
1124 | struct fib6_node *fn; | 1198 | struct fib6_node *fn; |
1125 | struct rt6_info *rt; | 1199 | struct rt6_info *rt; |
1126 | int err = -ESRCH; | 1200 | int err = -ESRCH; |
1127 | 1201 | ||
1128 | read_lock_bh(&rt6_lock); | 1202 | table = fib6_get_table(table_id); |
1203 | if (table == NULL) | ||
1204 | return err; | ||
1205 | |||
1206 | read_lock_bh(&table->tb6_lock); | ||
1129 | 1207 | ||
1130 | fn = fib6_locate(&ip6_routing_table, | 1208 | fn = fib6_locate(&table->tb6_root, |
1131 | &rtmsg->rtmsg_dst, rtmsg->rtmsg_dst_len, | 1209 | &rtmsg->rtmsg_dst, rtmsg->rtmsg_dst_len, |
1132 | &rtmsg->rtmsg_src, rtmsg->rtmsg_src_len); | 1210 | &rtmsg->rtmsg_src, rtmsg->rtmsg_src_len); |
1133 | 1211 | ||
@@ -1144,12 +1222,12 @@ static int ip6_route_del(struct in6_rtmsg *rtmsg, struct nlmsghdr *nlh, void *_r | |||
1144 | rtmsg->rtmsg_metric != rt->rt6i_metric) | 1222 | rtmsg->rtmsg_metric != rt->rt6i_metric) |
1145 | continue; | 1223 | continue; |
1146 | dst_hold(&rt->u.dst); | 1224 | dst_hold(&rt->u.dst); |
1147 | read_unlock_bh(&rt6_lock); | 1225 | read_unlock_bh(&table->tb6_lock); |
1148 | 1226 | ||
1149 | return ip6_del_rt(rt, nlh, _rtattr, req); | 1227 | return ip6_del_rt(rt, nlh, _rtattr, req); |
1150 | } | 1228 | } |
1151 | } | 1229 | } |
1152 | read_unlock_bh(&rt6_lock); | 1230 | read_unlock_bh(&table->tb6_lock); |
1153 | 1231 | ||
1154 | return err; | 1232 | return err; |
1155 | } | 1233 | } |
@@ -1161,10 +1239,15 @@ void rt6_redirect(struct in6_addr *dest, struct in6_addr *saddr, | |||
1161 | struct neighbour *neigh, u8 *lladdr, int on_link) | 1239 | struct neighbour *neigh, u8 *lladdr, int on_link) |
1162 | { | 1240 | { |
1163 | struct rt6_info *rt, *nrt = NULL; | 1241 | struct rt6_info *rt, *nrt = NULL; |
1164 | int strict; | ||
1165 | struct fib6_node *fn; | 1242 | struct fib6_node *fn; |
1243 | struct fib6_table *table; | ||
1166 | struct netevent_redirect netevent; | 1244 | struct netevent_redirect netevent; |
1167 | 1245 | ||
1246 | /* TODO: Very lazy, might need to check all tables */ | ||
1247 | table = fib6_get_table(RT6_TABLE_MAIN); | ||
1248 | if (table == NULL) | ||
1249 | return; | ||
1250 | |||
1168 | /* | 1251 | /* |
1169 | * Get the "current" route for this destination and | 1252 | * Get the "current" route for this destination and |
1170 | * check if the redirect has come from approriate router. | 1253 | * check if the redirect has come from approriate router. |
@@ -1175,10 +1258,9 @@ void rt6_redirect(struct in6_addr *dest, struct in6_addr *saddr, | |||
1175 | * is a bit fuzzy and one might need to check all possible | 1258 | * is a bit fuzzy and one might need to check all possible |
1176 | * routes. | 1259 | * routes. |
1177 | */ | 1260 | */ |
1178 | strict = ipv6_addr_type(dest) & (IPV6_ADDR_MULTICAST | IPV6_ADDR_LINKLOCAL); | ||
1179 | 1261 | ||
1180 | read_lock_bh(&rt6_lock); | 1262 | read_lock_bh(&table->tb6_lock); |
1181 | fn = fib6_lookup(&ip6_routing_table, dest, NULL); | 1263 | fn = fib6_lookup(&table->tb6_root, dest, NULL); |
1182 | restart: | 1264 | restart: |
1183 | for (rt = fn->leaf; rt; rt = rt->u.next) { | 1265 | for (rt = fn->leaf; rt; rt = rt->u.next) { |
1184 | /* | 1266 | /* |
@@ -1201,7 +1283,7 @@ restart: | |||
1201 | } | 1283 | } |
1202 | if (rt) | 1284 | if (rt) |
1203 | dst_hold(&rt->u.dst); | 1285 | dst_hold(&rt->u.dst); |
1204 | else if (strict) { | 1286 | else if (rt6_need_strict(dest)) { |
1205 | while ((fn = fn->parent) != NULL) { | 1287 | while ((fn = fn->parent) != NULL) { |
1206 | if (fn->fn_flags & RTN_ROOT) | 1288 | if (fn->fn_flags & RTN_ROOT) |
1207 | break; | 1289 | break; |
@@ -1209,7 +1291,7 @@ restart: | |||
1209 | goto restart; | 1291 | goto restart; |
1210 | } | 1292 | } |
1211 | } | 1293 | } |
1212 | read_unlock_bh(&rt6_lock); | 1294 | read_unlock_bh(&table->tb6_lock); |
1213 | 1295 | ||
1214 | if (!rt) { | 1296 | if (!rt) { |
1215 | if (net_ratelimit()) | 1297 | if (net_ratelimit()) |
@@ -1384,6 +1466,7 @@ static struct rt6_info * ip6_rt_copy(struct rt6_info *ort) | |||
1384 | #ifdef CONFIG_IPV6_SUBTREES | 1466 | #ifdef CONFIG_IPV6_SUBTREES |
1385 | memcpy(&rt->rt6i_src, &ort->rt6i_src, sizeof(struct rt6key)); | 1467 | memcpy(&rt->rt6i_src, &ort->rt6i_src, sizeof(struct rt6key)); |
1386 | #endif | 1468 | #endif |
1469 | rt->rt6i_table = ort->rt6i_table; | ||
1387 | } | 1470 | } |
1388 | return rt; | 1471 | return rt; |
1389 | } | 1472 | } |
@@ -1394,9 +1477,14 @@ static struct rt6_info *rt6_get_route_info(struct in6_addr *prefix, int prefixle | |||
1394 | { | 1477 | { |
1395 | struct fib6_node *fn; | 1478 | struct fib6_node *fn; |
1396 | struct rt6_info *rt = NULL; | 1479 | struct rt6_info *rt = NULL; |
1480 | struct fib6_table *table; | ||
1481 | |||
1482 | table = fib6_get_table(RT6_TABLE_INFO); | ||
1483 | if (table == NULL) | ||
1484 | return NULL; | ||
1397 | 1485 | ||
1398 | write_lock_bh(&rt6_lock); | 1486 | write_lock_bh(&table->tb6_lock); |
1399 | fn = fib6_locate(&ip6_routing_table, prefix ,prefixlen, NULL, 0); | 1487 | fn = fib6_locate(&table->tb6_root, prefix ,prefixlen, NULL, 0); |
1400 | if (!fn) | 1488 | if (!fn) |
1401 | goto out; | 1489 | goto out; |
1402 | 1490 | ||
@@ -1411,7 +1499,7 @@ static struct rt6_info *rt6_get_route_info(struct in6_addr *prefix, int prefixle | |||
1411 | break; | 1499 | break; |
1412 | } | 1500 | } |
1413 | out: | 1501 | out: |
1414 | write_unlock_bh(&rt6_lock); | 1502 | write_unlock_bh(&table->tb6_lock); |
1415 | return rt; | 1503 | return rt; |
1416 | } | 1504 | } |
1417 | 1505 | ||
@@ -1433,7 +1521,7 @@ static struct rt6_info *rt6_add_route_info(struct in6_addr *prefix, int prefixle | |||
1433 | rtmsg.rtmsg_flags |= RTF_DEFAULT; | 1521 | rtmsg.rtmsg_flags |= RTF_DEFAULT; |
1434 | rtmsg.rtmsg_ifindex = ifindex; | 1522 | rtmsg.rtmsg_ifindex = ifindex; |
1435 | 1523 | ||
1436 | ip6_route_add(&rtmsg, NULL, NULL, NULL); | 1524 | ip6_route_add(&rtmsg, NULL, NULL, NULL, RT6_TABLE_INFO); |
1437 | 1525 | ||
1438 | return rt6_get_route_info(prefix, prefixlen, gwaddr, ifindex); | 1526 | return rt6_get_route_info(prefix, prefixlen, gwaddr, ifindex); |
1439 | } | 1527 | } |
@@ -1442,12 +1530,14 @@ static struct rt6_info *rt6_add_route_info(struct in6_addr *prefix, int prefixle | |||
1442 | struct rt6_info *rt6_get_dflt_router(struct in6_addr *addr, struct net_device *dev) | 1530 | struct rt6_info *rt6_get_dflt_router(struct in6_addr *addr, struct net_device *dev) |
1443 | { | 1531 | { |
1444 | struct rt6_info *rt; | 1532 | struct rt6_info *rt; |
1445 | struct fib6_node *fn; | 1533 | struct fib6_table *table; |
1446 | 1534 | ||
1447 | fn = &ip6_routing_table; | 1535 | table = fib6_get_table(RT6_TABLE_DFLT); |
1536 | if (table == NULL) | ||
1537 | return NULL; | ||
1448 | 1538 | ||
1449 | write_lock_bh(&rt6_lock); | 1539 | write_lock_bh(&table->tb6_lock); |
1450 | for (rt = fn->leaf; rt; rt=rt->u.next) { | 1540 | for (rt = table->tb6_root.leaf; rt; rt=rt->u.next) { |
1451 | if (dev == rt->rt6i_dev && | 1541 | if (dev == rt->rt6i_dev && |
1452 | ((rt->rt6i_flags & (RTF_ADDRCONF | RTF_DEFAULT)) == (RTF_ADDRCONF | RTF_DEFAULT)) && | 1542 | ((rt->rt6i_flags & (RTF_ADDRCONF | RTF_DEFAULT)) == (RTF_ADDRCONF | RTF_DEFAULT)) && |
1453 | ipv6_addr_equal(&rt->rt6i_gateway, addr)) | 1543 | ipv6_addr_equal(&rt->rt6i_gateway, addr)) |
@@ -1455,7 +1545,7 @@ struct rt6_info *rt6_get_dflt_router(struct in6_addr *addr, struct net_device *d | |||
1455 | } | 1545 | } |
1456 | if (rt) | 1546 | if (rt) |
1457 | dst_hold(&rt->u.dst); | 1547 | dst_hold(&rt->u.dst); |
1458 | write_unlock_bh(&rt6_lock); | 1548 | write_unlock_bh(&table->tb6_lock); |
1459 | return rt; | 1549 | return rt; |
1460 | } | 1550 | } |
1461 | 1551 | ||
@@ -1474,28 +1564,31 @@ struct rt6_info *rt6_add_dflt_router(struct in6_addr *gwaddr, | |||
1474 | 1564 | ||
1475 | rtmsg.rtmsg_ifindex = dev->ifindex; | 1565 | rtmsg.rtmsg_ifindex = dev->ifindex; |
1476 | 1566 | ||
1477 | ip6_route_add(&rtmsg, NULL, NULL, NULL); | 1567 | ip6_route_add(&rtmsg, NULL, NULL, NULL, RT6_TABLE_DFLT); |
1478 | return rt6_get_dflt_router(gwaddr, dev); | 1568 | return rt6_get_dflt_router(gwaddr, dev); |
1479 | } | 1569 | } |
1480 | 1570 | ||
1481 | void rt6_purge_dflt_routers(void) | 1571 | void rt6_purge_dflt_routers(void) |
1482 | { | 1572 | { |
1483 | struct rt6_info *rt; | 1573 | struct rt6_info *rt; |
1574 | struct fib6_table *table; | ||
1575 | |||
1576 | /* NOTE: Keep consistent with rt6_get_dflt_router */ | ||
1577 | table = fib6_get_table(RT6_TABLE_DFLT); | ||
1578 | if (table == NULL) | ||
1579 | return; | ||
1484 | 1580 | ||
1485 | restart: | 1581 | restart: |
1486 | read_lock_bh(&rt6_lock); | 1582 | read_lock_bh(&table->tb6_lock); |
1487 | for (rt = ip6_routing_table.leaf; rt; rt = rt->u.next) { | 1583 | for (rt = table->tb6_root.leaf; rt; rt = rt->u.next) { |
1488 | if (rt->rt6i_flags & (RTF_DEFAULT | RTF_ADDRCONF)) { | 1584 | if (rt->rt6i_flags & (RTF_DEFAULT | RTF_ADDRCONF)) { |
1489 | dst_hold(&rt->u.dst); | 1585 | dst_hold(&rt->u.dst); |
1490 | 1586 | read_unlock_bh(&table->tb6_lock); | |
1491 | read_unlock_bh(&rt6_lock); | ||
1492 | |||
1493 | ip6_del_rt(rt, NULL, NULL, NULL); | 1587 | ip6_del_rt(rt, NULL, NULL, NULL); |
1494 | |||
1495 | goto restart; | 1588 | goto restart; |
1496 | } | 1589 | } |
1497 | } | 1590 | } |
1498 | read_unlock_bh(&rt6_lock); | 1591 | read_unlock_bh(&table->tb6_lock); |
1499 | } | 1592 | } |
1500 | 1593 | ||
1501 | int ipv6_route_ioctl(unsigned int cmd, void __user *arg) | 1594 | int ipv6_route_ioctl(unsigned int cmd, void __user *arg) |
@@ -1516,10 +1609,12 @@ int ipv6_route_ioctl(unsigned int cmd, void __user *arg) | |||
1516 | rtnl_lock(); | 1609 | rtnl_lock(); |
1517 | switch (cmd) { | 1610 | switch (cmd) { |
1518 | case SIOCADDRT: | 1611 | case SIOCADDRT: |
1519 | err = ip6_route_add(&rtmsg, NULL, NULL, NULL); | 1612 | err = ip6_route_add(&rtmsg, NULL, NULL, NULL, |
1613 | RT6_TABLE_MAIN); | ||
1520 | break; | 1614 | break; |
1521 | case SIOCDELRT: | 1615 | case SIOCDELRT: |
1522 | err = ip6_route_del(&rtmsg, NULL, NULL, NULL); | 1616 | err = ip6_route_del(&rtmsg, NULL, NULL, NULL, |
1617 | RT6_TABLE_MAIN); | ||
1523 | break; | 1618 | break; |
1524 | default: | 1619 | default: |
1525 | err = -EINVAL; | 1620 | err = -EINVAL; |
@@ -1593,6 +1688,7 @@ struct rt6_info *addrconf_dst_alloc(struct inet6_dev *idev, | |||
1593 | 1688 | ||
1594 | ipv6_addr_copy(&rt->rt6i_dst.addr, addr); | 1689 | ipv6_addr_copy(&rt->rt6i_dst.addr, addr); |
1595 | rt->rt6i_dst.plen = 128; | 1690 | rt->rt6i_dst.plen = 128; |
1691 | rt->rt6i_table = fib6_get_table(RT6_TABLE_LOCAL); | ||
1596 | 1692 | ||
1597 | atomic_set(&rt->u.dst.__refcnt, 1); | 1693 | atomic_set(&rt->u.dst.__refcnt, 1); |
1598 | 1694 | ||
@@ -1611,9 +1707,7 @@ static int fib6_ifdown(struct rt6_info *rt, void *arg) | |||
1611 | 1707 | ||
1612 | void rt6_ifdown(struct net_device *dev) | 1708 | void rt6_ifdown(struct net_device *dev) |
1613 | { | 1709 | { |
1614 | write_lock_bh(&rt6_lock); | 1710 | fib6_clean_all(fib6_ifdown, 0, dev); |
1615 | fib6_clean_tree(&ip6_routing_table, fib6_ifdown, 0, dev); | ||
1616 | write_unlock_bh(&rt6_lock); | ||
1617 | } | 1711 | } |
1618 | 1712 | ||
1619 | struct rt6_mtu_change_arg | 1713 | struct rt6_mtu_change_arg |
@@ -1663,13 +1757,12 @@ static int rt6_mtu_change_route(struct rt6_info *rt, void *p_arg) | |||
1663 | 1757 | ||
1664 | void rt6_mtu_change(struct net_device *dev, unsigned mtu) | 1758 | void rt6_mtu_change(struct net_device *dev, unsigned mtu) |
1665 | { | 1759 | { |
1666 | struct rt6_mtu_change_arg arg; | 1760 | struct rt6_mtu_change_arg arg = { |
1761 | .dev = dev, | ||
1762 | .mtu = mtu, | ||
1763 | }; | ||
1667 | 1764 | ||
1668 | arg.dev = dev; | 1765 | fib6_clean_all(rt6_mtu_change_route, 0, &arg); |
1669 | arg.mtu = mtu; | ||
1670 | read_lock_bh(&rt6_lock); | ||
1671 | fib6_clean_tree(&ip6_routing_table, rt6_mtu_change_route, 0, &arg); | ||
1672 | read_unlock_bh(&rt6_lock); | ||
1673 | } | 1766 | } |
1674 | 1767 | ||
1675 | static int inet6_rtm_to_rtmsg(struct rtmsg *r, struct rtattr **rta, | 1768 | static int inet6_rtm_to_rtmsg(struct rtmsg *r, struct rtattr **rta, |
@@ -1719,7 +1812,7 @@ int inet6_rtm_delroute(struct sk_buff *skb, struct nlmsghdr* nlh, void *arg) | |||
1719 | 1812 | ||
1720 | if (inet6_rtm_to_rtmsg(r, arg, &rtmsg)) | 1813 | if (inet6_rtm_to_rtmsg(r, arg, &rtmsg)) |
1721 | return -EINVAL; | 1814 | return -EINVAL; |
1722 | return ip6_route_del(&rtmsg, nlh, arg, &NETLINK_CB(skb)); | 1815 | return ip6_route_del(&rtmsg, nlh, arg, &NETLINK_CB(skb), r->rtm_table); |
1723 | } | 1816 | } |
1724 | 1817 | ||
1725 | int inet6_rtm_newroute(struct sk_buff *skb, struct nlmsghdr* nlh, void *arg) | 1818 | int inet6_rtm_newroute(struct sk_buff *skb, struct nlmsghdr* nlh, void *arg) |
@@ -1729,7 +1822,7 @@ int inet6_rtm_newroute(struct sk_buff *skb, struct nlmsghdr* nlh, void *arg) | |||
1729 | 1822 | ||
1730 | if (inet6_rtm_to_rtmsg(r, arg, &rtmsg)) | 1823 | if (inet6_rtm_to_rtmsg(r, arg, &rtmsg)) |
1731 | return -EINVAL; | 1824 | return -EINVAL; |
1732 | return ip6_route_add(&rtmsg, nlh, arg, &NETLINK_CB(skb)); | 1825 | return ip6_route_add(&rtmsg, nlh, arg, &NETLINK_CB(skb), r->rtm_table); |
1733 | } | 1826 | } |
1734 | 1827 | ||
1735 | struct rt6_rtnl_dump_arg | 1828 | struct rt6_rtnl_dump_arg |
@@ -1761,6 +1854,10 @@ static int rt6_fill_node(struct sk_buff *skb, struct rt6_info *rt, | |||
1761 | rtm->rtm_dst_len = rt->rt6i_dst.plen; | 1854 | rtm->rtm_dst_len = rt->rt6i_dst.plen; |
1762 | rtm->rtm_src_len = rt->rt6i_src.plen; | 1855 | rtm->rtm_src_len = rt->rt6i_src.plen; |
1763 | rtm->rtm_tos = 0; | 1856 | rtm->rtm_tos = 0; |
1857 | if (rt->rt6i_table) | ||
1858 | rtm->rtm_table = rt->rt6i_table->tb6_id; | ||
1859 | else | ||
1860 | rtm->rtm_table = RT6_TABLE_UNSPEC; | ||
1764 | rtm->rtm_table = RT_TABLE_MAIN; | 1861 | rtm->rtm_table = RT_TABLE_MAIN; |
1765 | if (rt->rt6i_flags&RTF_REJECT) | 1862 | if (rt->rt6i_flags&RTF_REJECT) |
1766 | rtm->rtm_type = RTN_UNREACHABLE; | 1863 | rtm->rtm_type = RTN_UNREACHABLE; |
@@ -1868,7 +1965,6 @@ static void fib6_dump_end(struct netlink_callback *cb) | |||
1868 | 1965 | ||
1869 | if (w) { | 1966 | if (w) { |
1870 | cb->args[0] = 0; | 1967 | cb->args[0] = 0; |
1871 | fib6_walker_unlink(w); | ||
1872 | kfree(w); | 1968 | kfree(w); |
1873 | } | 1969 | } |
1874 | cb->done = (void*)cb->args[1]; | 1970 | cb->done = (void*)cb->args[1]; |
@@ -1883,13 +1979,20 @@ static int fib6_dump_done(struct netlink_callback *cb) | |||
1883 | 1979 | ||
1884 | int inet6_dump_fib(struct sk_buff *skb, struct netlink_callback *cb) | 1980 | int inet6_dump_fib(struct sk_buff *skb, struct netlink_callback *cb) |
1885 | { | 1981 | { |
1982 | struct fib6_table *table; | ||
1886 | struct rt6_rtnl_dump_arg arg; | 1983 | struct rt6_rtnl_dump_arg arg; |
1887 | struct fib6_walker_t *w; | 1984 | struct fib6_walker_t *w; |
1888 | int res; | 1985 | int i, res = 0; |
1889 | 1986 | ||
1890 | arg.skb = skb; | 1987 | arg.skb = skb; |
1891 | arg.cb = cb; | 1988 | arg.cb = cb; |
1892 | 1989 | ||
1990 | /* | ||
1991 | * cb->args[0] = pointer to walker structure | ||
1992 | * cb->args[1] = saved cb->done() pointer | ||
1993 | * cb->args[2] = current table being dumped | ||
1994 | */ | ||
1995 | |||
1893 | w = (void*)cb->args[0]; | 1996 | w = (void*)cb->args[0]; |
1894 | if (w == NULL) { | 1997 | if (w == NULL) { |
1895 | /* New dump: | 1998 | /* New dump: |
@@ -1905,24 +2008,48 @@ int inet6_dump_fib(struct sk_buff *skb, struct netlink_callback *cb) | |||
1905 | w = kzalloc(sizeof(*w), GFP_ATOMIC); | 2008 | w = kzalloc(sizeof(*w), GFP_ATOMIC); |
1906 | if (w == NULL) | 2009 | if (w == NULL) |
1907 | return -ENOMEM; | 2010 | return -ENOMEM; |
1908 | RT6_TRACE("dump<%p", w); | ||
1909 | w->root = &ip6_routing_table; | ||
1910 | w->func = fib6_dump_node; | 2011 | w->func = fib6_dump_node; |
1911 | w->args = &arg; | 2012 | w->args = &arg; |
1912 | cb->args[0] = (long)w; | 2013 | cb->args[0] = (long)w; |
1913 | read_lock_bh(&rt6_lock); | 2014 | cb->args[2] = FIB6_TABLE_MIN; |
1914 | res = fib6_walk(w); | ||
1915 | read_unlock_bh(&rt6_lock); | ||
1916 | } else { | 2015 | } else { |
1917 | w->args = &arg; | 2016 | w->args = &arg; |
1918 | read_lock_bh(&rt6_lock); | 2017 | i = cb->args[2]; |
1919 | res = fib6_walk_continue(w); | 2018 | if (i > FIB6_TABLE_MAX) |
1920 | read_unlock_bh(&rt6_lock); | 2019 | goto end; |
2020 | |||
2021 | table = fib6_get_table(i); | ||
2022 | if (table != NULL) { | ||
2023 | read_lock_bh(&table->tb6_lock); | ||
2024 | w->root = &table->tb6_root; | ||
2025 | res = fib6_walk_continue(w); | ||
2026 | read_unlock_bh(&table->tb6_lock); | ||
2027 | if (res != 0) { | ||
2028 | if (res < 0) | ||
2029 | fib6_walker_unlink(w); | ||
2030 | goto end; | ||
2031 | } | ||
2032 | } | ||
2033 | |||
2034 | fib6_walker_unlink(w); | ||
2035 | cb->args[2] = ++i; | ||
1921 | } | 2036 | } |
1922 | #if RT6_DEBUG >= 3 | 2037 | |
1923 | if (res <= 0 && skb->len == 0) | 2038 | for (i = cb->args[2]; i <= FIB6_TABLE_MAX; i++) { |
1924 | RT6_TRACE("%p>dump end\n", w); | 2039 | table = fib6_get_table(i); |
1925 | #endif | 2040 | if (table == NULL) |
2041 | continue; | ||
2042 | |||
2043 | read_lock_bh(&table->tb6_lock); | ||
2044 | w->root = &table->tb6_root; | ||
2045 | res = fib6_walk(w); | ||
2046 | read_unlock_bh(&table->tb6_lock); | ||
2047 | if (res) | ||
2048 | break; | ||
2049 | } | ||
2050 | end: | ||
2051 | cb->args[2] = i; | ||
2052 | |||
1926 | res = res < 0 ? res : skb->len; | 2053 | res = res < 0 ? res : skb->len; |
1927 | /* res < 0 is an error. (really, impossible) | 2054 | /* res < 0 is an error. (really, impossible) |
1928 | res == 0 means that dump is complete, but skb still can contain data. | 2055 | res == 0 means that dump is complete, but skb still can contain data. |
@@ -2102,16 +2229,13 @@ static int rt6_info_route(struct rt6_info *rt, void *p_arg) | |||
2102 | 2229 | ||
2103 | static int rt6_proc_info(char *buffer, char **start, off_t offset, int length) | 2230 | static int rt6_proc_info(char *buffer, char **start, off_t offset, int length) |
2104 | { | 2231 | { |
2105 | struct rt6_proc_arg arg; | 2232 | struct rt6_proc_arg arg = { |
2106 | arg.buffer = buffer; | 2233 | .buffer = buffer, |
2107 | arg.offset = offset; | 2234 | .offset = offset, |
2108 | arg.length = length; | 2235 | .length = length, |
2109 | arg.skip = 0; | 2236 | }; |
2110 | arg.len = 0; | ||
2111 | 2237 | ||
2112 | read_lock_bh(&rt6_lock); | 2238 | fib6_clean_all(rt6_info_route, 0, &arg); |
2113 | fib6_clean_tree(&ip6_routing_table, rt6_info_route, 0, &arg); | ||
2114 | read_unlock_bh(&rt6_lock); | ||
2115 | 2239 | ||
2116 | *start = buffer; | 2240 | *start = buffer; |
2117 | if (offset) | 2241 | if (offset) |