2
0
mirror of https://github.com/fhem/fhem-mirror.git synced 2025-04-07 19:04:20 +00:00

TcpServerUtils.pm: 126290

git-svn-id: https://svn.fhem.de/fhem/trunk@25831 2b470e98-0d58-463d-a4d8-8e2adae1ed80
This commit is contained in:
rudolfkoenig 2022-03-14 09:07:12 +00:00
parent a0361e8164
commit c40dcf7822

View File

@ -8,11 +8,14 @@ use IO::Socket;
use vars qw($SSL_ERROR);
sub
TcpServer_Open($$$)
TcpServer_Open($$$;$)
{
my ($hash, $port, $global) = @_;
my ($hash, $port, $global, $multicast) = @_;
my $name = $hash->{NAME};
return 'multicast not supported without SO_REUSEPORT'
if($multicast && !defined(&SO_REUSEPORT));
if($port =~ m/^IPV6:(\d+)$/i) {
$port = $1;
eval "require IO::Socket::INET6; use Socket6;";
@ -26,7 +29,7 @@ TcpServer_Open($$$)
my $lh = ($global ? ($global eq "global"? undef : $global) :
($hash->{IPV6} ? "::1" : "127.0.0.1"));
my @opts = (
my %opts = (
Domain => ($hash->{IPV6} ? AF_INET6() : AF_UNSPEC), # Linux bug / #126448
LocalHost => $lh,
LocalPort => $port,
@ -34,10 +37,18 @@ TcpServer_Open($$$)
Blocking => ($^O =~ /Win/ ? 1 : 0), # Needed for .WRITEBUFFER@darwin
ReuseAddr => 1
);
if($multicast) {
$opts{ReusePort} = 1;
$opts{Proto} = 'udp';
delete($opts{Listen});
$hash->{MULTICAST} = $multicast;
}
readingsSingleUpdate($hash, "state", "Initialized", 0);
$hash->{SERVERSOCKET} = $hash->{IPV6} ?
IO::Socket::INET6->new(@opts) :
IO::Socket::INET->new(@opts);
IO::Socket::INET6->new(%opts) :
IO::Socket::INET->new(%opts);
if(!$hash->{SERVERSOCKET}) {
return "$name: Can't open server port at $port: $!";
@ -51,12 +62,192 @@ TcpServer_Open($$$)
return undef;
}
sub
TcpServer_SetLoopbackMode($$)
{
my ($hash, $loopback) = @_;
my $name = $hash->{NAME};
my $sock = $hash->{SERVERSOCKET};
my $old;
if( !$hash->{IPV6} && $sock->sockdomain() == AF_INET ) {
my $packed = getsockopt($sock, Socket::IPPROTO_IP,
Socket::IP_MULTICAST_LOOP);
if( !$packed ) {
Log3 $name, 1, "$name: failed to get loopback mode: $!";
return undef;
}
$old = unpack("I", $packed);
if( !setsockopt($sock, Socket::IPPROTO_IP,
Socket::IP_MULTICAST_LOOP, pack("I", $loopback ) ) ) {
Log3 $name, 1, "$name: could not set loopback mode: $!";
return undef;
}
} elsif( !$hash->{IPV6} && $sock->sockdomain() == AF_INET6 ) {
my $packed = getsockopt($sock, Socket::IPPROTO_IPV6,
Socket::IPV6_MULTICAST_LOOP);
if( !$packed ) {
Log3 $name, 1, "$name: failed to get loopback mode: $!";
return undef;
}
$old = unpack("I", $packed);
if( setsockopt($sock, Socket::IPPROTO_IPV6,
Socket::IPV6_MULTICAST_LOOP, pack("I", $loopback ) ) ) {
Log3 $name, 1, "$name: could not set loopback mode: $!";
return undef;
}
} else {
Log3 $name, 1,
"$name: TcpServer_SetLoopbackMode failed: unsupported socket family";
return undef;
}
return $old;
}
sub
TcpServer_MCastAdd($$)
{
my ($hash, $addr) = @_;
my $name = $hash->{NAME};
my $sock = $hash->{SERVERSOCKET};
$hash->{ADDR} = $addr;
# disable loopback
TcpServer_SetLoopbackMode($hash, 0);
# add multicast address
if(!$hash->{IPV6} && $sock->sockdomain() == AF_INET) {
# should we allow to specify the interfache instead of using any?
my $ip_mreq = Socket::pack_ip_mreq( inet_aton( $addr ), INADDR_ANY );
setsockopt($sock, Socket::IPPROTO_IP, Socket::IP_ADD_MEMBERSHIP, $ip_mreq )
or return "$name: could not set IP_ADD_MEMBERSHIP socket option: $!";
} elsif($hash->{IPV6} && $sock->sockdomain() == AF_INET6) {
# should we allow to specify the interfache instead of using any?
my $ipv6_mreq = Socket::pack_ipv6_mreq( inet_pton( AF_INET6, $addr ), 0 );
setsockopt($sock, Socket::IPPROTO_IPV6, Socket::IPV6_JOIN_GROUP, $ipv6_mreq )
or return "$name: could not set IPV6_JOIN_GROUP socket option: $!";
} else {
return("$name: TcpServer_MCastAdd failed: unsupported socket family" );
}
readingsSingleUpdate($hash, "state", "Multicast listen", 0);
return undef;
}
sub
TcpServer_MCastRecv($$$;$)
{
my ($hash, undef, $length, $flags) = @_;
my $name = $hash->{NAME};
my $sock = $hash->{SERVERSOCKET};
my $sockaddr = $sock->recv($_[1], $length, $flags);
if(!$hash->{IPV6} && $sock->sockdomain() == AF_INET) {
my ($peer_port, $addr) = Socket::unpack_sockaddr_in($sockaddr);
my $peer_host = inet_ntoa($addr);
return $peer_host, $peer_port;
} elsif($hash->{IPV6} && $sock->sockdomain() == AF_INET6) {
my ($peer_port, $addr) = Socket::unpack_sockaddr_in6($sockaddr);
my $peer_host = inet_ntop(AF_INET6(),$addr);
return $peer_host, $peer_port;
} else {
Log3 $name, 1, "$name: TcpServer_MCastRecv failed: unsupported socket family";
return undef;
}
}
sub
TcpServer_MCastSend($$;$$)
{
my ($hash, $data, $addr, $port) = @_;
my $name = $hash->{NAME};
my $sock = $hash->{SERVERSOCKET};
$addr = $hash->{ADDR} if( !$addr );
$port = $hash->{PORT} if( !$port );
if( !$addr ) {
Log3 $name, 1, "$name: TcpServer_MCastSend failed: address unknown";
return undef;
}
if( !$port ) {
Log3 $name, 1, "$name: TcpServer_MCastSend failed: port unknown";
return undef;
}
if(!$hash->{IPV6} && $sock->sockdomain() == AF_INET) {
my $sockaddr = Socket::pack_sockaddr_in($port, inet_aton($addr));
return $sock->send($data,0,$sockaddr);
} elsif($hash->{IPV6} && $sock->sockdomain() == AF_INET6) {
my $sockaddr = Socket::pack_sockaddr_in6($port, inet_pton($addr));
return $sock->send($data,0,$sockaddr);
} else {
Log3 $name, 1,"$name: TcpServer_MCastSend failed: unsupported socket family";
return undef;
}
}
sub
TcpServer_MCastRemove($$)
{
my ($hash, $addr) = @_;
my $name = $hash->{NAME};
my $sock = $hash->{SERVERSOCKET};
delete $hash->{ADDR};
if(!$hash->{IPV6} && $sock->sockdomain() == AF_INET) {
# should we allow to specify the interfache instead of using any?
my $ip_mreq = Socket::pack_ip_mreq( inet_aton( $addr ), INADDR_ANY );
setsockopt($sock, Socket::IPPROTO_IP, Socket::IP_DROP_MEMBERSHIP, $ip_mreq )
or return "$name: could not set IP_DROP_MEMBERSHIP socket option: $!";
} elsif($hash->{IPV6} && $sock->sockdomain() == AF_INET6) {
# should we allow to specify the interfache instead of using any?
my $ipv6_mreq = Socket::pack_ipv6_mreq( inet_pton( AF_INET6, $addr ), 0 );
setsockopt($sock, Socket::IPPROTO_IPV6, Socket::IPV6_LEAVE_GROUP, $ipv6_mreq)
or return "$name: could not set IPV6_LEAVE_GROUP socket option: $!";
} else {
return("$name: TcpServer_MCastRemove failed: unsupported socket family" );
}
readingsSingleUpdate($hash, "state", "Multicast listen stopped", 0);
return undef;
}
sub
TcpServer_Accept($$)
{
my ($hash, $type) = @_;
my $name = $hash->{NAME};
if($hash->{MULTICAST}) {
Log3 $name, 1, "$name: can't accept on a mutlicast socket";
return undef;
}
my @clientinfo = $hash->{SERVERSOCKET}->accept();
if(!@clientinfo) {
Log3 $name, 1, "Accept failed ($name: $!)" if($! != EAGAIN);
@ -383,4 +574,49 @@ TcpServer_WriteBlocking($$)
return 1; # success
}
=pod
Multicast:
(https://forum.fhem.de/index.php/topic,126290.msg1209591.html#msg1209591)
verwendet wird es so:
- das socket mit gesetztem optionalen vierten paramter von TcpServer_Open
erzeugen: d.h. im define oder sonst wo mit my $ret = TcpServer_Open($hash,
'5353', '0.0.0.0', 1); initialisieren. statt der 0.0.0.0 kann man auch die
multicast adresse angeben, ich bin mir nicht sicher was richtiger ist und ob
es einen unterschied macht. funktionieren tut bei mir beides.
- TcpServer_Open schaltet automatisch den loopback mode aus. d.h. man empfängt
seine eigenen daten nicht. ich vermute das ist der normalfall. mit
TcpServer_SetLoopbackMode($hash,[0|1]); kann man das ändern wenn man es
braucht.
- mit TcpServer_MCastAdd die multicast adresse zum socket explizit hinzufügen:
z.b.: TcpServer_MCastAdd($hash,'224.0.0.251'); erst ab jetzt bekommt man auch
tatsächlich daten.
- im (device) hash wird für jedes udp packet die ReadFn aufgerufen. dort kann
man mit TcpServer_MCastRecv dann die empfangenen daten abholen:
my($peer_host, $peer_port) = TcpServer_MCastRecv($hash,$data,$length);
alternativ kann man auch die low leven routinen direkt aufrufen:
- my $sockaddr = $hash->{SERVERSOCKET}->recv($data, 4096);
der empfang geht über SERVERSOCKET, nicht wie bei tcp über CD, es wird auch
kein accept verwendet
- aus $sockaddr kann man sich die gegenstelle zum udp packet holen falls man
die braucht:
my ($peer_port, $addr) = Socket::sockaddr_in($sockaddr);
my $peer_host = inet_ntoa($addr);
- eigene daten sendet man mit TcpServer_MCastSend:
TcpServer_MCastSend($hash,$data[,$host[,$port]]); hier bei wird der port aus
dem TcpServer_Open und die adresse aus TcpServer_MCastAdd verwendet.
alternativ kann man beides auch beim aufruf mitgeben.
- mit TcpServer_MCastRemove kann man die multicast adresse auch wieder vom
socket entfernen:
z.b.: TcpServer_MCastRemove($hash,'224.0.0.251');
das socket empfängt dann keine daten für diese adresse mehr.
- mit wechselweisem TcpServer_MCastAdd und TcpServer_MCastRemove kann man auch
zeitweise zwischen empfangen und ignorieren hin und her wechseln. z.b. als
reaktion auf disable.
- mit TcpServer_Close($hash); das ganze am ende z.b. in der UndefFn wieder zu
machen.
=cut
1;