—=head1 NAME
Socket::More - Scoped listening/passive addresses and network utility routines
=head1 SYNOPSIS
Bring into your namespace.
use v5.36;
use Socket::More;
Flexible way to create interface scoped passive (listen) address across
families. Special 'unix' interface for ease of use. All invalid combinations of
family, port and paths are discarded:
# Create passive (listening) sockets for selected interfaces
#
my @passive=sockaddr_passive {
interface=> ["eth0", "unix"],
port=> [5566, 7788],
path=> "path_to_sock"
};
#All invalid family/interface/port/path combinations are filtered out
#leaving only valid info for socket creation and binding:
#
for(@passive){
say $_->{address};
socket my $socket, $_->{family}, $_->{socktype}, 0;
bind $socket $_->{addr};
}
Please see EXAMPLES section for more.
=head1 DESCRIPTION
Intended as an alternative for L<Socket>, implementing only 'modern subset' of
routines. It providing extra routines to make listening addresses easy, to
solve problems like this:
'listen on interfaces eth0 and eth1, using IPv6 and port numbers 9090
and 9091, but limit to link local addresses, and stream types'.
'listen on eth0 and unix, port 1000 and path test.sock, using datagram
type sockets,'.
'listen on all interfaces on port 8080 and 8081, but only on link local
ipv6 address'
It also is a umbrella package, which reexports L<Socket::More::Constants>,
L<Socket::More::Lookup> and L<Socket::More::Interface> for you.
It also makes it easy to generate 'random' ports to bind to, B<before> your
program binds, to aid in testing server scenarios.
Note this is a subset of L<Socket> functionality. The 'old school' inet_* functions are
deliberately not included, to encourage the usage of getnameinfo/getaddrinfo.
From version v0.5.0, the module has been decomposed to to separate modules on
CPAN for targeted usage:
=over
=item L<Socket::More> (This Module)
=over
=item wrapper over C<socket> to make it more flexible
=item Methods for creating address structure for listening (passive)
sockets using a query and consise command line syntax
=item String/constant mapping
=item General pack/unpack of address structures
=item Address family and socket types as string names
=item Imports and rexports the modules listed below.
=back
=item L<Socket::More::Constants>
Contains all the networking constants (ie C<AF_INET>, C<NI_NUMERICHOST>, etc)
for your platform.
=item L<Socket::More::Lookup>
Implements and exports C<getaddrinfo>, C<getnameinfo> and C<gai_strerror>, with
a different calling convention then Perl core Socket implementation. More like
C<sysread> convention
=item L<Socket::More::Interface>
Implements and exports C<getifaddrs>, C<if_nametoindex>, C<if_indextoname> and
C<if_nameindex> to query the interfaces of your system
=back
Other packages/distributions not reexported but part of the family:
=over
=item L<Socket::More::Resolver>
Non blocking and event loop integration of system resolver functions.
=item L<Socket::More::IPRanges>
Grouping information on IP addresses
=back
=head1 MOTIVATION
I wanted an easy way to listen on a particular interface ONLY. The normal way
of wild card addresses "0.0.0.0" or "::", will listen on all interfaces. Any
restrictions on connecting sockets will either need to be implemented in the
firewall or in application code accepting and then closing the connection. This
is a waste of resources and a potential security problem.
Manually creating the multitude of potential addresses on the same interface
(especially for IPv6) is a pain to maintain. This module reduces the effort by
generating all combinations of parameters and then filters out what doesn't
make sense and what you don't want. See C<sockaddr_passive> below for more
information.
=head1 API
From version v0.5.0 the structure of the module has been refactored into other
modules. The same API is accessible from this module, as it imports them and
reexports their subroutines/constants. If you don't need the easy listening
features of this module, then you can use these modules independently.
=head2 getifaddrs (L<Socket::More::Interface>)
=head2 if_nametoindex (L<Socket::More::Interface>)
=head2 if_indextoname (L<Socket::More::Interface>)
=head2 if_nameindex (L<Socket::More::Interface>)
=head2 getaddrinfo (L<Socket::More::Lookup>)
=head2 getnameinfo (L<Socket::More::Lookup>)
=head2 gai_strerror (L<Socket::More::Lookup>)
=head2 family_to_string
my $string=family_to_string($family);
Returns a string label representing an address family C<$family>. For example,
calling with constant C<AF_INET>, will return a string C<"AF_INET">
=head2 string_to_family
my @family=string_to_family($pattern);
Performs a match of all AF_.* names against C<$pattern>. Returns a list of
integer constants for the corresponding address family that matched. Returns an
empty list if the patten/string does not match. The match is performed
insensitive to case
For example calling with C<"INET"> will return a list of two elements,
C<AF_INET> and C<AF_INET6>.
This is useful for handling address families supplied from the command line, as
abbreviated names can be matched.
=head2 socktype_to_string or sock_to_string
my $string=socktype_to_string($type);
Returns a string label representing a socket type C<$type>. For example,
calling with the integer constant C<SOCK_STREAM>, will return a string
C<"SOCK_STREAM">
=head2 string_to_socktype or string_to_type
my @type=string_to_socktype($string);
Performs a match of all SOCK_.* names against C<$pattern>. Returns a list of
integers for the corresponding socket types that matched. Returns an empty list
if the patten/string does not match. The match is performed insensitive to
case.
For example calling with C<"STREAM"> will return a list of one element,
C<SOCK_STREAM>.
This is useful for handling address families supplied from the command line, as
abbreviated names can be matched.
=head2 sockaddr_passive
my @interfaces=sockadd_passive $specification;
Returns a list of 'interface' structures (similar to getifaddr above) which
provide meta data and packed address structures suitable for passive use (i.e
bind) and matching the C<$specification>. The resulting data is sorted by
interface name, then by family and finally by type.
It has some overlapping function of C<getaddrinfo>, however it is specifically
for creating addresses for binding, allows the use of interface names and
operates with UNIX domain configurations through a synthetic 'unix' interface.
From B<v0.5.0> the results will return interface information in an addition
field.
A specification hash has optional keys which dictate what addresses are
generated and filtered:
{
interface=>"en",
family=>"INET",
port=>[1234]
...
}
The only required keys are C<port> and/or C<path>. These are used in the
address generation and not as a filter. Without at least one of these keys, no
results will be generated.
Other keys like C<interface>, C<family> and C<socktype> for example are used
to restrict addresses created to the match
Keys like C<address> and C<group> are a filter which are directly matched
against the address and group.
Keys themselves can be shortened all the way down to the shortest unique
substring. So instead of 'interface', it could be 'inter', 'int' or just 'i'
for example. This aids in usage from the command line. The shortest unique keys
are:
{
i=>... #interface
f=>... #family
po=>... #port
pa=>... #path
a=>... #address
s=>... #socktype
g=>... #group
}
It can include the following keys:
=over
=item interface
examples:
interface=>"eth0"
interface=>"eth\d*";
interface=>["eth0", "lo"];
interface=>"unix";
interface=>["unix", "lo"];
A string or array ref of strings which are used as regex to match interface
names currently available.
=item family
examples: family=>AF_INET family=>[AF_INET, AF_INET6, AF_UNIX]
A integer or array ref of integers representing the family type an interface
supports.
B<From v0.4.0:> Also can be a string or array ref of strings, which are matched
against supported families. See C<parse_passive_spec> for matching details
=item socktype (was type)
examples: socktype=>SOCK_STREAM socktype=>[SOCK_STREAM, SOCK_DGRAM]
A integer or array ref of integers representing the socket type an interface
supports.
B<From v0.4.0:> Also can be a string or array ref of strings, which are matched
against supported socket types. See C<parse_passive_spec> for matching details
=item port
examples: port=>55554 port=>[12345,12346]
The ports used in generating a passive address. Only applied to AF_INET*
families. Ignored for others.
Either C<port> or C<path> are required, otherwise no addresses will be
generated.
=item path
examples: path=>"path_to_socket.sock" path=>["path_to_socket1.sock",
"path_to_socket2.sock"]
The path used in generating a passive address. Only applied to AF_UNIX
families. Ignored for others.
Either C<port> or C<path> are required, otherwise no addresses will be
generated.
B<NOTE> The actual path resulting from the specification will have a '_D' or
'_S' appended to the path. This is done to ensure sockets of different type
don't attempt to use the same path.
=item address
exmples:
address=>"192\.168\.1\.1"
address=>"169\.254\."
As string used to match the textual representation of an address. In the
special case of '0.0.0.0" or "::", any interface specification is ignored.
=item group
examples:
group=>"PRIVATE'
The group the address belongs to as per L<Net::IP>
=item data
examples:
data=>[$scalar]
data=>[{ ca=>$ca_path, pkey=>$p_path}]
A user field which will be included in each item in the output list.
B<NOTE> It is recommended this value is an array ref, wrapping actual data. This
makes it more consistent when the data key is parsed from the command line
=back
=head2 parse_passive_spec
my @spec=parse_passive_spec($string);
Parses a concise string intended to be supplied as a command line argument. The
string consists of one or more fields separated by commas.
The fields are in key value pairs in the form
key=value
C<key> can be any key used in a specification for C<sockaddr_passive>, and
C<value> is interpreted as a path, number or a string (regex), depending on the
key.
C<port> and C<path> keys take literal values.
C<family> and C<socktype> keys take regex values, which match against the
family/socktype names (using C<string_to_sock> and C<string_to_family>) and are
replaced with the integer values internally.
Other keys treat the value as a string/regex to match against.
The keys can be used repeatedly within multiple fields. For example that means
the following will match interfaces eth0, eth1 and lo.
in=>eth0,port=1000,in='lo|eth1'
Only the first "=" within a field is split. this allows the data field itself
to take more key value pairs:
eg:
data=key1=value,data=key2=another
data=ca=ca_path.pem,data=key=private.pem
B<NOTE> Because repeat C<data> keys can be used, the specification generated from
C<parse_passive_spec> will contain a C<data> key with an array as its value.
For example, the following parse a C<sockaddr_passive> specification which would
match SOCK_STREAM sockets, for both AF_INET and AF_INET6 families, on all
available interfaces.
family=INET,socktype=STREAM #Full key name
f=INET,t=STREAM #Shortest unique string for keys
The special case of a field not in key value format (i.e. with out a '='), is
interpreted as the plack compatible listen switch argument.
HOST:PORT #INET/INET6 address and port
:PORT #wildcard address and port
PATH #UNIX socket path
The C<HOST> portion is assinged to the C<address> field. The C<PORT> portion is
assigned to the C<port> field. If a C<PORT> is specified without a C<HOST>,
then the C<address> field is set to C<["0.0.0.0", "::"]> which disables
interface matching, but will listen on all INET addresses.
B<NOTE> This behaviour may change in later versions, as "::" supports both INET
and INET6.
B<NOTE> to specify an IPv6 literal on the command line, it is contained in a pair
of [] and will need to be escaped or quoted in the shell
=head2 socket
socket $socket, $domain, $socktype, $proto
socket $socket, $hash
example:
die "$!" unless socket my $socket, AF_INET, SOCK_STREAM,0;
die "$!" unless socket my $socket, {family=>AF_INET, protocol=>0, socktype=>SOCK_STREAM};
A wrapper around C<CORE::socket>. It checks if the C<DOMAIN> is a number. If
so, it simply calls C<CORE::socket> with the supplied arguments.
Otherwise it assumes C<DOMAIN> is a packed sockaddr structure and extracts the
domain/family field using C<sockaddr_family>. This value is then used as the
C<DOMAIN> value in a call to C<CORE::socket>.
Return values are as per C<CORE::socket>. Please refer to L<perldoc -f socket>
for more.
=head2 has_IPv4_interface
has_IPv4_interface;
Returns true if at least one IPv4 interface was found. False otherwise.
=head2 has_IPv6_interface
has_IPv6_interface;
Returns true if at least one IPv6 interface was found. False otherwise.
=head2 reify_ports
reify_ports $specs, ...
example:
reify_ports {address=>"127.0.0.1", port=>0}
Iterates through list of specifications and replacing C<port> fields equal to 0
(any port), with a 'random' one supplied by the operating system. This performs
a C<sockaddr_passive> call to to 'flatten' any internal structures in the
specifications provided.
This works by taking the first entry which results in a 0 port number, creating a
socket and binding it. The 0 port will result in the OS choosing a port for
use. The resulting port is extracted from the socket (getsocketname) and
replaces the 0 port value in B<all> the specification entries. The socket has
C<SO_REUSEADDR> applied to ensure it can be bound again immediately.
If the specifications request two or more 0 ports in otherwise identical
specifications, it is up the user to choose how to handle any duplicate bind
complications (i.e C<SO_REUSEPORT>)
B<NOTE:> There is a chance that another program can use the port number
returned after a call to C<reify_ports>.
B<NOTE:> The interface/address tested to generate the random port might return
a port which is already in use on other interfaces.
=head2 reify_ports_unshared
reify_ports_chaos $specs, ...
example:
reify_ports {address=>"127.0.0.1", port=>[0,0]};
reify_ports {address=>"127.0.0.1", port=>0}, {port=>0};
Operates like C<reify_ports> with the exception that all 0 port entries in the
specifications cause a query to the OS. The port numbers are not explicitly
'shared' between specifications, thus returning potentially (most likely)
different port numbers for each entry.
=head1 EXAMPLES
Please checkout 'cli.pl' in the examples directory of this distribution. It
demonstrates many of the features of this module by using the
C<sockaddr_passive>, C<parse_passive_spec>, C<family_to_string> and
C<sock_to_string> functions. It requires C<Text::Table> in addition to this
module.
It takes user input from the command line using one or more C<-l> parameters
via L<Getopt::Long>. These are parsed into passive specifications, which are
then executed to generate list of passive structures matching the
specification. The results are converted into nice text table output.
The following shows the example outputs running this program with different
inputs.
=head2 Run1
Any interface, AF_INET6 only, stream or datagram on port 1000:
perl examples/cli.pl -l '[::]':1000
Interface Address Family Group Port Path Type Data
:: :: AF_INET6 UNSPECIFIED 1000 SOCK_STREAM
:: :: AF_INET6 UNSPECIFIED 1000 SOCK_DGRAM
=head2 Run2
Any interface, AF_INET only, stream or datagram on port 1000:
->perl examples/cli.pl -l 0.0.0.0:1000
Interface Address Family Group Port Path Type Data
0.0.0.0 0.0.0.0 AF_INET PRIVATE 1000 SOCK_STREAM
0.0.0.0 0.0.0.0 AF_INET PRIVATE 1000 SOCK_DGRAM
=head2 Run3
Any interface, AF_INET only, stream or datagram on port 1000, with data:
perl examples/cli.pl -l 0.0.0.0:1000,data='ca_path=ca_path.pem;key=key_path'
Interface Address Family Group Port Path Type Data
0.0.0.0 0.0.0.0 AF_INET PRIVATE 1000 SOCK_STREAM ca_path=ca_path.pem;key=key_path
0.0.0.0 0.0.0.0 AF_INET PRIVATE 1000 SOCK_DGRAM ca_path=ca_path.pem;key=key_path
=head2 Run4
On interface en0, port 1000, stream or datagram types and only private or link
local addresses:
perl examples/cli.pl -l interface=en0,port=1000,group='pri|link'
Interface Address Family Group Port Path Type Data
en0 192.168.1.103 AF_INET PRIVATE 1000 SOCK_STREAM
en0 192.168.1.103 AF_INET PRIVATE 1000 SOCK_DGRAM
en0 fe80::1086:a38e:8f5d:38e2 AF_INET6 LINK-LOCAL-UNICAST 1000 SOCK_STREAM
en0 fe80::1086:a38e:8f5d:38e2 AF_INET6 LINK-LOCAL-UNICAST 1000 SOCK_DGRAM
=head2 Run5
On interface en0,lo and unix, port 1000, path mypath.sock, and stream type only
perl examples/cli.pl -l interface='en0|lo|unix',port=1000,path=mypath.sock,socktype=stream
Interface Address Family Group Port Path Type Data
en0 192.168.1.103 AF_INET PRIVATE 1000 SOCK_STREAM
en0 fe80::1086:a38e:8f5d:38e2 AF_INET6 LINK-LOCAL-UNICAST 1000 SOCK_STREAM
lo0 fe80::1 AF_INET6 LINK-LOCAL-UNICAST 1000 SOCK_STREAM
unix mypath.sock_S AF_UNIX UNIX mypath.sock_S SOCK_STREAM
=head2 Run6
Shortened keys. Multiple listeners on command line:
First specification: Interface en0, port 1000, only AF_INET and stream
Second specification: Interface lo or unix, AF_INET or UNIX types, po 2000
for inet and path test.sock for unix, datagram type only
perl examples/cli.pl -l i='en0',po=1000,f='inet$',t=stream -l i='lo|unix',f='inet$|unix',po=2000,pa="test.sock",t=dgram
Interface Address Family Group Port Path Type Data
en0 192.168.1.103 AF_INET PRIVATE 1000 SOCK_STREAM
lo0 127.0.0.1 AF_INET LOOPBACK 2000 SOCK_DGRAM
unix test.sock_D AF_UNIX UNIX test.sock_D SOCK_DGRAM
=head2 RUN7
Interface en0 and lo, port 1010, private or link local group, multiple data keys
examples/cli.pl -l in=en0,in=lo,po=1010,gr='PRI|link',data=ca=test,data=key=path
Interface Address Family Group Port Path Type Data
en0 192.168.1.103 AF_INET PRIVATE 1010 SOCK_STREAM ca=test,key=path
en0 192.168.1.103 AF_INET PRIVATE 1010 SOCK_DGRAM ca=test,key=path
en0 fe80::1086:a38e:8f5d:38e2 AF_INET6 LINK-LOCAL-UNICAST 1010 SOCK_STREAM ca=test,key=path
en0 fe80::1086:a38e:8f5d:38e2 AF_INET6 LINK-LOCAL-UNICAST 1010 SOCK_DGRAM ca=test,key=path
lo0 fe80::1 AF_INET6 LINK-LOCAL-UNICAST 1010 SOCK_STREAM ca=test,key=path
lo0 fe80::1 AF_INET6 LINK-LOCAL-UNICAST 1010 SOCK_DGRAM ca=test,key=path
=head1 SEE ALSO
Other modules provide network interface queries:
L<Net::Interface> seems broken at the time of writing
L<IO::Interface> works with IPv4 addressing only?
=head1 AUTHOR
Ruben Westerberg, E<lt>drclaw@mac.com<gt>
=head1 REPOSITORTY and BUGS
Please report any bugs via git hub: L<http://github.com/drclaw1394/perl-socket-more>
=head1 COPYRIGHT AND LICENSE
Copyright (C) 2023 by Ruben Westerberg
This library is free software; you can redistribute it and/or modify it under
the same terms as Perl or the MIT license.
=head1 DISCLAIMER OF WARRANTIES
THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES,
INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS FOR A PARTICULAR PURPOSE.
=cut