Skip to content

Align UDP #recvfrom usage with the stdlib#21562

Merged
sjanusz-r7 merged 7 commits into
rapid7:masterfrom
zeroSteiner:fix/udp/recvfrom-stdlib
Jun 24, 2026
Merged

Align UDP #recvfrom usage with the stdlib#21562
sjanusz-r7 merged 7 commits into
rapid7:masterfrom
zeroSteiner:fix/udp/recvfrom-stdlib

Conversation

@zeroSteiner

@zeroSteiner zeroSteiner commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Description

This requires the changes from rapid7/rex-socket#83 and incoporates them into the Metasploit framework to align the usage of #recvfrom on UDP sockets to behave the same as the standard library. This has the strong benefit of ensuring that developers do not need to worry about their socket being UDPSocket or Rex::Socket::Udp which by extension makes Metasploit compatible with libraries that need UDP sockets that have no knowledge of Rex.

There are three issues with Rex's #recvfrom vs the standard library

  1. The arguments are different; the size should should be a required argument and the second parameter should be flags, not a timeout.
  2. The shape of the return value is different, the standard library returns the address info as an array.
  3. The standard library's method is blocking and does not support a timeout, the Rex version took a timeout parameter where flags should have been.

Breaking Changes

rapid7/rex-socket#83 is a breaking change. This PR brings framework inline so that nothing breakes on this side. There are no user-facing breaking changes.

Reviewer Notes

It's not really practical to re-test every single module that uses UDP. That's the main reason that #timed_recvfrom was added, so methods could just be changed from #recvfrom -> #timed_recvfrom and not worry about points 1 and 3 from the list of issues above in the description section.

Verification Steps

    • Make sure the tests pass

I ran through multiple tests of the most important modules which covered the main patterns. The new #timed_recvfrom is used in place of the old #recvfrom where there was an expected timeout but the return value is aligned with the standard libraries #recvfrom.

Things I tested include

  • auxiliary/scanner/snmp/snmp_login
  • auxiliary/gather/enum_dns
  • auxiliary/scanner/ntp/timeroast

Test Evidence

msf auxiliary(gather/enum_dns) > use auxiliary/scanner/snmp/snmp_login 
msf auxiliary(scanner/snmp/snmp_login) > set RHOSTS 192.168.250.0/24
RHOSTS => 192.168.250.0/24
msf auxiliary(scanner/snmp/snmp_login) > set THREADS 10
THREADS => 10
msf auxiliary(scanner/snmp/snmp_login) > run
[+] 192.168.250.7:161 - Login Successful: internal (Access level: read-write); Proof (sysDescr.0): Brother NC-8800w, Firmware Ver.Z  ,MID 8C5-K8JFID 2
[+] 192.168.250.7:161 - Login Successful: public (Access level: read-write); Proof (sysDescr.0): Brother NC-8800w, Firmware Ver.Z  ,MID 8C5-K8JFID 2
[*] Scanned  29 of 256 hosts (11% complete)
[*] Scanned  52 of 256 hosts (20% complete)
[*] Scanned  77 of 256 hosts (30% complete)
[*] Scanned 103 of 256 hosts (40% complete)
[*] Error: 192.168.250.119: Errno::ECONNREFUSED Connection refused - recvfrom(2)
[*] Scanned 128 of 256 hosts (50% complete)
[*] Scanned 154 of 256 hosts (60% complete)
[*] Scanned 181 of 256 hosts (70% complete)
[*] Scanned 205 of 256 hosts (80% complete)
[*] Scanned 232 of 256 hosts (90% complete)
[*] Scanned 256 of 256 hosts (100% complete)
[*] Auxiliary module execution completed
msf auxiliary(scanner/snmp/snmp_login) > use auxiliary/gather/enum_dns 
msf auxiliary(gather/enum_dns) > show options 

Module options (auxiliary/gather/enum_dns):

   Name         Current Setting                                                            Required  Description
   ----         ---------------                                                            --------  -----------
   DOMAIN       home.lan                                                                   yes       The target domain
   ENUM_A       true                                                                       yes       Enumerate DNS A record
   ENUM_AXFR    true                                                                       yes       Initiate a zone transfer against each NS record
   ENUM_BRT     false                                                                      yes       Brute force subdomains and hostnames via the supplied wordlist
   ENUM_CNAME   true                                                                       yes       Enumerate DNS CNAME record
   ENUM_MX      true                                                                       yes       Enumerate DNS MX record
   ENUM_NS      true                                                                       yes       Enumerate DNS NS record
   ENUM_RVL     false                                                                      yes       Reverse lookup a range of IP addresses
   ENUM_SOA     true                                                                       yes       Enumerate DNS SOA record
   ENUM_SRV     true                                                                       yes       Enumerate the most common SRV records
   ENUM_TLD     false                                                                      yes       Perform a TLD expansion by replacing the TLD with the IANA TLD list
   ENUM_TXT     true                                                                       yes       Enumerate DNS TXT record
   IPRANGE                                                                                 no        The target address range or CIDR identifier
   NS           192.168.250.4                                                              no        Specify the nameservers to use for queries, space separated
   Proxies                                                                                 no        A proxy chain of format type:host:port[,type:host:port][...]. Supported proxies: http, sapni, socks4, socks5, socks5h
   RPORT        53                                                                         yes       The target port (TCP)
   SEARCHLIST                                                                              no        DNS domain search list, comma separated
   STOP_WLDCRD  false                                                                      yes       Stops bruteforce enumeration if wildcard resolution is detected
   THREADS      1                                                                          no        Threads for ENUM_BRT
   WORDLIST     /home/smcintyre/Projects/metasploit-framework/data/wordlists/namelist.txt  no        Wordlist of subdomains


View the full module info with the info, or info -d command.

msf auxiliary(gather/enum_dns) > run
[*] Attempting DNS AXFR for home.lan from 192.168.250.4
^C[-] Stopping running against current target...
[*] Control-C again to force quit all targets.
^C[-] Auxiliary interrupted by the console user
[*] Auxiliary module execution completed
msf auxiliary(gather/enum_dns) > est ENUM_AXFR false
[-] Unknown command: est. Run the help command for more details.
msf auxiliary(gather/enum_dns) > set ENUM_AXFR false
ENUM_AXFR => false
msf auxiliary(gather/enum_dns) > run
[*] Querying DNS CNAME records for home.lan
[*] Querying DNS NS records for home.lan
[+] home.lan NS: dns-aux.home.lan
[+] home.lan NS: dns-pri.home.lan
[*] Querying DNS MX records for home.lan
[*] Querying DNS SOA records for home.lan
[+] home.lan SOA: dns.home.lan
[*] Querying DNS TXT records for home.lan
[*] Querying DNS SRV records for home.lan
[*] Auxiliary module execution completed
msf auxiliary(gather/enum_dns) > use auxiliary/scanner/ntp/timeroast 
msf auxiliary(scanner/ntp/timeroast) > show options 

Module options (auxiliary/scanner/ntp/timeroast):

   Name     Current Setting  Required  Description
   ----     ---------------  --------  -----------
   DELAY    20               yes       The delay in milliseconds between attempts.
   RHOSTS                    yes       The target host(s), see https://docs.metasploit.com/docs/using-metasploit/basics/using-metasploit.html
   RIDS                      yes       The RIDs to enumerate (e.g. 1000-2000).
   RPORT    123              yes       The target port (UDP)
   THREADS  1                yes       The number of concurrent threads (max one per host)
   TIMEOUT  5                yes       The timeout in seconds to wait at the end for replies.


View the full module info with the info, or info -d command.

msf auxiliary(scanner/ntp/timeroast) > set RHOSTS 192.168.159.10
RHOSTS => 192.168.159.10
msf auxiliary(scanner/ntp/timeroast) > set RIDS 1000-2000
RIDS => 1000-2000
msf auxiliary(scanner/ntp/timeroast) > run
[+] Hash for RID: 1001 - 1001:$sntp-ms$2ce861b855afa3d3e7a1954e4806da26$1c0100e900000000000a00264c4f434ceddaa204c19641f60000000000000000eddaa237ad9e3864eddaa237ad9e8fa2

AI Usage Disclosure

I was assisted by Claude Opus during this development.

Pre-Submission Checklist

  • Ran rubocop on new files with no new offenses (net new files only)
  • Ran msftidy on changed module files with no new offenses (modules only)
  • Ran msftidy_docs on changed documentation files with no new offenses (documentation files only)
  • Included a corresponding documentation markdown file in documentation/modules (new modules only)
  • No sensitive information (IP addresses, credentials, API keys, hashes) in code or documentation
  • Tested on the target environment specified in the Environment section above
  • Included RSpec tests for library changes (encouraged for lib/ changes)
  • Read the CONTRIBUTING.md and module acceptance guidelines

@github-actions

Copy link
Copy Markdown

Thanks for your pull request! As part of our landing process, we manually verify that all modules work as expected.

We've added the additional-testing-required label to indicate that additional testing is required before this pull request can be merged.
For maintainers, this means visiting here.

@zeroSteiner zeroSteiner force-pushed the fix/udp/recvfrom-stdlib branch 7 times, most recently from a050b7f to e707971 Compare June 11, 2026 21:02
@smcintyre-r7 smcintyre-r7 marked this pull request as ready for review June 15, 2026 15:36
def recvfrom_nonblock(length, flags = 0)
data = super(length, flags)[0]
sockaddr = super(length, flags)[0]
sockaddr = super(MAX_SOCKADDR_LENGTH, flags)[0]

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This fixes a different bug. What was going on here is that the socket address was being read from the peer (the other end of the socket pair), and the length argument was used for it, which caused a failure when the length wasn't sufficient for the address. The second read should always be for enough data to get the whole address.

@smcintyre-r7 smcintyre-r7 moved this from Todo to Ready in Metasploit Kanban Jun 15, 2026
@jenkins-eks-metasploit

Copy link
Copy Markdown

Additional test pipeline started ⌛
Note: build results only accessible to maintainers.

@jenkins-eks-metasploit

Copy link
Copy Markdown

Pipeline results available

Slice summary:

  • Test slice 1 - 🟢
  • Test slice 2 - 🔴
  • Test slice 3 - 🟢
  • Test slice 4 - 🟢

Note: build results only accessible to maintainers.

Comment thread lib/msf/core/auxiliary/udp_scanner.rb Outdated
Comment thread lib/rex/proto/dns/resolver.rb
Comment thread modules/auxiliary/dos/scada/allen_bradley_pccc.rb
@sjanusz-r7

Copy link
Copy Markdown
Contributor

Looking at the test suite in Pro, this seems to have introduced some issues with services, where the UDP ports 137 and 161 are not discovered/in the DB:

| (-) 137  | (-) udp   | (-) open  | (-) netbios |
...
| (-) 161  | (-) udp   | (-) open  | (-) snmp    |

^ The (-) means the entry is missing.
Will do some more testing on this PR locally as well 👍

@sjanusz-r7

sjanusz-r7 commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

Seems like the issue is the auxiliary/scanner/discovery/udp_sweep module. On the main branch right now, scanning a Metasploitable 3 VM:

msf auxiliary(scanner/discovery/udp_sweep) > run rhosts=x
[*] Sending 13 probes to x->x (1 hosts)
[*] Discovered SNMP on x:161 (Hardware: Intel64 Family 6 Model 85 Stepping 7 AT/AT COMPATIBLE - Software: Windows Version 6.1 (Build 7601 Multiprocessor Free))
[*] Discovered NetBIOS on x:137 (VAGRANT-2008R2:<00>:U :WORKGROUP:<00>:G :VAGRANT-2008R2:<20>:U :02:8b:cb:6e:65:33)
[*] Scanned 1 of 1 hosts (100% complete)
[*] Auxiliary module execution completed

This PR:

msf auxiliary(scanner/discovery/udp_sweep) > run rhosts=x
[*] Sending 13 probes to x->x (1 hosts)
[*] Error: x-x: Connection refused - recvfrom(2)
[*] Scanned 1 of 1 hosts (100% complete)
[*] Auxiliary module execution completed

That's the only issue that I saw so far, and the only issue that Pro test suite has caught 👍

@sjanusz-r7

Copy link
Copy Markdown
Contributor

The latest commit fixed the udp_sweep issue:

msf auxiliary(scanner/discovery/udp_sweep) > run rhosts=x
[*] Sending 13 probes to x->x (1 hosts)
[*] Discovered NetBIOS on x:137 (VAGRANT-2008R2:<00>:U :WORKGROUP:<00>:G :VAGRANT-2008R2:<20>:U :02:8b:cb:6e:65:33)
[*] Discovered SNMP on x:161 (Hardware: Intel64 Family 6 Model 85 Stepping 7 AT/AT COMPATIBLE - Software: Windows Version 6.1 (Build 7601 Multiprocessor Free))
[*] Scanned 1 of 1 hosts (100% complete)
[*] Auxiliary module execution completed
...
services
...
host         port  proto  name     state  info          resource  parents
----         ----  -----  ----     -----  ----          --------  -------
10.140.10.8  137   udp    netbios  open   x             {}
10.140.10.8  161   udp    snmp     open   x             {}

@zeroSteiner zeroSteiner force-pushed the fix/udp/recvfrom-stdlib branch from 16aad65 to 9b587dd Compare June 17, 2026 20:22
@jenkins-eks-metasploit

Copy link
Copy Markdown

Additional test pipeline started ⌛
Note: build results only accessible to maintainers.

@jenkins-eks-metasploit

Copy link
Copy Markdown

Pipeline results available

Slice summary:

  • Test slice 1 - 🔴
  • Test slice 2 - 🟢

Note: build results only accessible to maintainers.

@jenkins-eks-metasploit

Copy link
Copy Markdown

Additional test pipeline started ⌛
Note: build results only accessible to maintainers.

@sjanusz-r7

Copy link
Copy Markdown
Contributor

Looks like something got past the changes again:
In Pro:

[*] [2026.06.18-11:14:30] [auxiliary/scanner/discovery/udp_sweep] Sending 13 probes to x->x (1 hosts)
[*] [2026.06.18-11:14:31] [auxiliary/scanner/discovery/udp_sweep] Discovered unknown on 1:0 ()
[*] [2026.06.18-11:14:31] [auxiliary/scanner/discovery/udp_sweep] Discovered unknown on 1:0 ()
[*] [2026.06.18-11:14:31] [auxiliary/scanner/discovery/udp_sweep] Discovered unknown on 1:0 ()

In Framework (this branch):

msf auxiliary(scanner/discovery/udp_sweep) > run rhosts=x
[*] Sending 13 probes to x->x (1 hosts)
[*] Error: x-x: Connection refused - recvfrom(2)
[*] Scanned 1 of 1 hosts (100% complete)
[*] Auxiliary module execution completed

Confirmed again that it works as expected on the main branch.

@jenkins-eks-metasploit

Copy link
Copy Markdown

Pipeline results available

Slice summary:

  • Test slice 1 - 🔴
  • Test slice 2 - 🟢

Note: build results only accessible to maintainers.

@jenkins-eks-metasploit

Copy link
Copy Markdown

Additional test pipeline started ⌛
Note: build results only accessible to maintainers.

@jenkins-eks-metasploit

Copy link
Copy Markdown

Pipeline results available

Slice summary:

  • Test slice 1 - 🔴
  • Test slice 2 - 🟢

Note: build results only accessible to maintainers.

@jenkins-eks-metasploit

Copy link
Copy Markdown

Additional test pipeline started ⌛
Note: build results only accessible to maintainers.

@jenkins-eks-metasploit

Copy link
Copy Markdown

Pipeline results available

Slice summary:

  • Test slice 1 - 🔴
  • Test slice 2 - 🟢
  • Test slice 3 - 🟢

Note: build results only accessible to maintainers.

@sjanusz-r7

sjanusz-r7 commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

Sorry for the series of iterations! It's a series of small mistakes on my part adding up;

The changes are good. The previously-failing test in Pro also works correctly after pinning Pro's Rex-Socket version to the git branch in this PR; this had to be done manually -> will have follow-up work on this internally.

1 scenario (1 passed)
28 steps (28 passed)
3m38.499s

This was the only blocker and has now been resolved.
~~There are some more Pro tests running/to run before this gets landed 👍 ~~
Pro tests completed, I can begin landing the rex-socket changes, then land this 👍

@zeroSteiner zeroSteiner force-pushed the fix/udp/recvfrom-stdlib branch 2 times, most recently from b883f4a to 1c8c08b Compare June 24, 2026 13:09
This pulls in changes from rapid7/rex-socket#83 which aligns #recvfrom
to match the signature and behavior of the standard library in regards
to it's arguments, return type and how it handles timeouts. The
historical usage of #recvfrom was a timed read and can now be replaced
with #timed_recvfrom.
@zeroSteiner zeroSteiner force-pushed the fix/udp/recvfrom-stdlib branch from 1c8c08b to b1ced41 Compare June 24, 2026 14:01
@github-project-automation github-project-automation Bot moved this from Ready to In Progress in Metasploit Kanban Jun 24, 2026
@sjanusz-r7 sjanusz-r7 merged commit 34db7e9 into rapid7:master Jun 24, 2026
66 of 67 checks passed
@github-project-automation github-project-automation Bot moved this from In Progress to Done in Metasploit Kanban Jun 24, 2026
@sjanusz-r7

Copy link
Copy Markdown
Contributor

Release Notes

Updated the usage of rex-socket's recvfrom method to align with the standard library implementation. This also allows rex-socket to now be used as a drop-in replacement for Ruby's UDPSocket.

@sjanusz-r7 sjanusz-r7 self-assigned this Jun 24, 2026
@sjanusz-r7 sjanusz-r7 added enhancement rn-enhancement release notes enhancement labels Jun 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Archived in project

Development

Successfully merging this pull request may close these issues.

4 participants