From 25c046d73ab823a45f4b504d5cd700697147c108 Mon Sep 17 00:00:00 2001 From: Spencer McIntyre Date: Fri, 15 May 2026 16:45:18 -0400 Subject: [PATCH 1/3] Add the #send method to align with stdlib --- lib/rex/socket/udp.rb | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/lib/rex/socket/udp.rb b/lib/rex/socket/udp.rb index 58ba2c0..88b3809 100644 --- a/lib/rex/socket/udp.rb +++ b/lib/rex/socket/udp.rb @@ -116,6 +116,25 @@ def sendto(gram, peerhost, peerport, flags = 0) end + # + # Sends a datagram using the stdlib 4-arg form send(mesg, flags, host, port), + # delegating to sendto so that channel/pivoted sockets (which only implement + # sendto) work identically to local sockets. Callers can use this form + # uniformly without checking the socket type. + # + # Also handles the 3-arg sockaddr form used internally by sendto, forwarding + # it to BasicSocket#send which accepts a packed sockaddr as the third argument. + # + def send(mesg, flags, host_or_sockaddr = nil, port = nil) + if port + sendto(mesg, host_or_sockaddr, port, flags) + elsif host_or_sockaddr + super(mesg, flags, host_or_sockaddr) + else + super(mesg, flags) + end + end + # # Receives a datagram and returns the data and host:port of the requestor # as [ data, host, port ]. From 98d8639230eb4d32d136f12cdbff464118b2302a Mon Sep 17 00:00:00 2001 From: Spencer McIntyre Date: Fri, 15 May 2026 16:52:21 -0400 Subject: [PATCH 2/3] Mark #sendto as deprecated --- lib/rex/socket/udp.rb | 53 +++++++++++++++++++++---------------------- 1 file changed, 26 insertions(+), 27 deletions(-) diff --git a/lib/rex/socket/udp.rb b/lib/rex/socket/udp.rb index 88b3809..86960d0 100644 --- a/lib/rex/socket/udp.rb +++ b/lib/rex/socket/udp.rb @@ -97,39 +97,38 @@ def timed_read(length = 65535, timeout=def_read_timeout) # # Sends a datagram to the supplied host:port with optional flags. # + # @deprecated Use {#send} with the stdlib 4-arg form send(mesg, flags, host, port) instead. + # Note the argument order differs: sendto(gram, host, port, flags) vs send(mesg, flags, host, port). + # def sendto(gram, peerhost, peerport, flags = 0) - - # Catch unconnected IPv6 sockets talking to IPv4 addresses - peer = Rex::Socket.resolv_nbo(peerhost) - if (peer.length == 4 and self.ipv == 6) - peerhost = Rex::Socket.getaddress(peerhost, true) - if peerhost[0,7].downcase != '::ffff:' - peerhost = '::ffff:' + peerhost - end - end - - begin - send(gram, flags, Rex::Socket.to_sockaddr(peerhost, peerport)) - rescue ::Errno::EHOSTUNREACH,::Errno::ENETDOWN,::Errno::ENETUNREACH,::Errno::ENETRESET,::Errno::EHOSTDOWN,::Errno::EACCES,::Errno::EINVAL,::Errno::EADDRNOTAVAIL - return nil - end - + warn "#{self.class}#sendto is deprecated; use send(mesg, flags, host, port) instead", uplevel: 1 + send(gram, flags, peerhost, peerport) end # - # Sends a datagram using the stdlib 4-arg form send(mesg, flags, host, port), - # delegating to sendto so that channel/pivoted sockets (which only implement - # sendto) work identically to local sockets. Callers can use this form - # uniformly without checking the socket type. + # Sends a datagram using the stdlib 4-arg form send(mesg, flags, host, port). # - # Also handles the 3-arg sockaddr form used internally by sendto, forwarding - # it to BasicSocket#send which accepts a packed sockaddr as the third argument. + # The 4-arg form handles IPv6/IPv4 address mapping and dispatches via + # BasicSocket#send with a packed sockaddr, so channel/pivoted sockets that + # override sendto are not involved. Also accepts the 3-arg sockaddr form used + # by lower-level callers, and the 2-arg connected-socket form. # - def send(mesg, flags, host_or_sockaddr = nil, port = nil) - if port - sendto(mesg, host_or_sockaddr, port, flags) - elsif host_or_sockaddr - super(mesg, flags, host_or_sockaddr) + def send(mesg, flags, host = nil, port = nil) + if host && port + # Catch unconnected IPv6 sockets talking to IPv4 addresses + peer = Rex::Socket.resolv_nbo(host) + if peer.length == 4 && self.ipv == 6 + host = Rex::Socket.getaddress(host, true) + host = '::ffff:' + host unless host[0, 7].downcase == '::ffff:' + end + begin + super(mesg, flags, Rex::Socket.to_sockaddr(host, port)) + rescue ::Errno::EHOSTUNREACH, ::Errno::ENETDOWN, ::Errno::ENETUNREACH, ::Errno::ENETRESET, + ::Errno::EHOSTDOWN, ::Errno::EACCES, ::Errno::EINVAL, ::Errno::EADDRNOTAVAIL + nil + end + elsif host + super(mesg, flags, host) else super(mesg, flags) end From 9820c0c6597bd89a544899fb38f9385e4642bc93 Mon Sep 17 00:00:00 2001 From: Spencer McIntyre Date: Fri, 15 May 2026 17:12:18 -0400 Subject: [PATCH 3/3] Add some basic UDP specs --- spec/rex/socket/udp_spec.rb | 41 +++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 spec/rex/socket/udp_spec.rb diff --git a/spec/rex/socket/udp_spec.rb b/spec/rex/socket/udp_spec.rb new file mode 100644 index 0000000..1f8cab6 --- /dev/null +++ b/spec/rex/socket/udp_spec.rb @@ -0,0 +1,41 @@ +# -*- coding: binary -*- +require 'spec_helper' + +RSpec.describe Rex::Socket::Udp do + let(:receiver) { UDPSocket.new.tap { |s| s.bind('127.0.0.1', 0) } } + let(:recv_port) { receiver.addr[1] } + let(:socket) { Rex::Socket::Udp.create('LocalHost' => '127.0.0.1') } + + after do + socket.close rescue nil + receiver.close rescue nil + end + + describe '#send' do + it 'delivers a datagram with the 4-arg (host, port) form' do + socket.send('hello', 0, '127.0.0.1', recv_port) + expect(IO.select([receiver], nil, nil, 1)).not_to be_nil + expect(receiver.recvfrom(16).first).to eq('hello') + end + + it 'delivers a datagram with the 3-arg (packed sockaddr) form' do + sockaddr = Socket.pack_sockaddr_in(recv_port, '127.0.0.1') + socket.send('world', 0, sockaddr) + expect(IO.select([receiver], nil, nil, 1)).not_to be_nil + expect(receiver.recvfrom(16).first).to eq('world') + end + end + + describe '#sendto' do + it 'emits a deprecation warning' do + expect { socket.sendto('hi', '127.0.0.1', recv_port) } + .to output(/sendto.*deprecated/i).to_stderr + end + + it 'still delivers the datagram' do + socket.sendto('hi', '127.0.0.1', recv_port) + expect(IO.select([receiver], nil, nil, 1)).not_to be_nil + expect(receiver.recvfrom(16).first).to eq('hi') + end + end +end