|
25 | 25 | #include <freeradius-devel/bio/fd_priv.h> |
26 | 26 | #include <freeradius-devel/util/file.h> |
27 | 27 | #include <freeradius-devel/util/cap.h> |
| 28 | +#include <freeradius-devel/util/rand.h> |
28 | 29 |
|
29 | 30 | #include <sys/stat.h> |
30 | 31 | #include <net/if.h> |
@@ -723,14 +724,66 @@ static int fr_bio_fd_socket_bind(fr_bio_fd_t *my, fr_bio_fd_config_t const *cfg) |
723 | 724 | */ |
724 | 725 | if (fr_ipaddr_to_sockaddr(&salocal, &salen, &my->info.socket.inet.src_ipaddr, my->info.socket.inet.src_port) < 0) return -1; |
725 | 726 |
|
726 | | - if (bind(my->info.socket.fd, (struct sockaddr *) &salocal, salen) < 0) { |
727 | | - fr_strerror_printf("Failed binding to socket: %s", fr_syserror(errno)); |
| 727 | + /* |
| 728 | + * If we have a fixed source port, just use that. |
| 729 | + */ |
| 730 | + if (my->info.cfg->src_port || !my->info.cfg->src_port_start) { |
| 731 | + if (bind(my->info.socket.fd, (struct sockaddr *) &salocal, salen) < 0) { |
| 732 | + fr_strerror_printf("Failed binding to socket: %s", fr_syserror(errno)); |
| 733 | + return -1; |
| 734 | + } |
| 735 | + } else { |
| 736 | + uint16_t src_port, current; |
| 737 | + struct sockaddr_in *sin = (struct sockaddr_in *) &salocal; |
| 738 | + |
| 739 | + fr_assert(my->info.cfg->src_port_start); |
| 740 | + fr_assert(my->info.cfg->src_port_end); |
| 741 | + fr_assert(my->info.cfg->src_port_end >= my->info.cfg->src_port_start); |
| 742 | + |
| 743 | + src_port = fr_rand() % (my->info.cfg->src_port_end - my->info.cfg->src_port_start); |
| 744 | + |
| 745 | + /* |
| 746 | + * We only care about sin_port, which is in the same location for sockaddr_in and sockaddr_in6. |
| 747 | + */ |
| 748 | + fr_assert(sin->sin_port == 0); |
| 749 | + |
| 750 | + sin->sin_port = htons(my->info.cfg->src_port_start + src_port); |
| 751 | + |
| 752 | + /* |
| 753 | + * We've picked a random port in what is hopefully a large range. If that works, we're |
| 754 | + * done. |
| 755 | + */ |
| 756 | + if (bind(my->info.socket.fd, (struct sockaddr *) &salocal, salen) >= 0) goto done; |
| 757 | + |
| 758 | + /* |
| 759 | + * Hunt & peck. Which is horrible. |
| 760 | + */ |
| 761 | + current = src_port; |
| 762 | + while (true) { |
| 763 | + if (current == my->info.cfg->src_port_end) { |
| 764 | + current = my->info.cfg->src_port_start; |
| 765 | + } else { |
| 766 | + current++; |
| 767 | + } |
| 768 | + |
| 769 | + /* |
| 770 | + * We've already checked this, so stop. |
| 771 | + */ |
| 772 | + if (current == src_port) break; |
| 773 | + |
| 774 | + sin->sin_port = htons(current); |
| 775 | + |
| 776 | + if (bind(my->info.socket.fd, (struct sockaddr *) &salocal, salen) >= 0) goto done; |
| 777 | + } |
| 778 | + |
| 779 | + fr_strerror_const("There are no open ports between 'src_port_start' and 'src_port_end'"); |
728 | 780 | return -1; |
729 | 781 | } |
730 | 782 |
|
731 | 783 | /* |
732 | 784 | * The source IP may have changed, so get the new one. |
733 | 785 | */ |
| 786 | +done: |
734 | 787 | return fr_bio_fd_socket_name(my); |
735 | 788 | } |
736 | 789 |
|
|
0 commit comments