diff --git a/lib/rex/socket/udp.rb b/lib/rex/socket/udp.rb index 58ba2c0..86960d0 100644 --- a/lib/rex/socket/udp.rb +++ b/lib/rex/socket/udp.rb @@ -97,23 +97,41 @@ 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) + warn "#{self.class}#sendto is deprecated; use send(mesg, flags, host, port) instead", uplevel: 1 + send(gram, flags, peerhost, peerport) + end - # 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 + # + # Sends a datagram using the stdlib 4-arg form send(mesg, flags, host, port). + # + # 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 = 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 - - 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 - end # 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