Community discussions

MikroTik App
 
excession
Member Candidate
Member Candidate
Topic Author
Posts: 122
Joined: Mon May 11, 2015 8:16 pm

Enhanced IP Scan with Vendor and Additional Name Sources

Sun Oct 06, 2024 11:48 pm

I often find myself wanting more from the standard IP-Scan tool.

This script will perform a normal IP-Scan then augment it with vendor names pulled from api.maclookup.app.
(The script is rate limited to not exceed the API limits, you can register for a free API key if you want to go faster and remove the :delay.)

In addition to the normal DNS, SNMP and Netbios name detection, it will also scrape names from the Neighbours table as well as Hostname, ClassID and Comment from the DHCP leases table.

*Updated 16-10-24* - Now sorts results, in as efficient a manor as I could manage. :lol:
*Updated 17-10-24* - Now also lists interface matched from the ARP table. This is especially useful when you're scanning a bridge with multiple VLANs on it.
*Updated 25-10-24* - Updated to selectively send additional octets to maclookup api for unusual vendors using larger mac address blocks.
*Updated 10-11-24* - Now supports optional API key use - Free registration here (I have no affiliation): https://my.maclookup.app/login

Tested down to ROS 7.10. Requires v7 minimum.

You should modify "scanInterface" and "scanDuration" to suit your network.

Add as a script named "ip-scan-vendor" then run from the CLI with:
/system/script/run ip-scan-vendor
*Last Update 10-11-24*
#ip-scan-vendor v1.6
#ROS v7 and above

###---------------------

:local scanInterface "bridge"; #Interface to run scan on
:local scanDuration 30; #Seconds to run scan, 20 as a rough minimum, consider 60-120 for larger networks 
:local addressRange ""; #Set here to scan a specific range
:local saveLog "no"; #Set yes to export a log file as well

:local apiKey ""; #Optional api key
#Speeds up vendor resolution slightly. Register for free here: https://my.maclookup.app/login

###---------------------

:if ([:len $apiKey] > 10) do={:put ("API key present: Rate limit disabled")}
:put ("Interface: ".$scanInterface)
:put ("Duration: ".$scanDuration."sec")
:put "Starting Scan, please wait.."
:put ""

:local scanResult
:if ([:len $addressRange] > 8) do={
    :put ("Address Range: ".$addressRange)
    :set scanResult [/tool/ip-scan interface=$scanInterface address-range=$addressRange as-value duration=$scanDuration]
} else={
    :set scanResult [/tool/ip-scan interface=$scanInterface as-value duration=$scanDuration]
}

:local neighbours [/ip/neighbor/print as-value]
:local dhcpLeases [/ip/dhcp-server/lease/print detail as-value]
#Two calls to get dynamic and reservation lists, annoying it's not present in the above as-value results!
:local dhcpLeasesDynamic [/ip dhcp-server/lease/print as-value where dynamic=yes]
:local dhcpLeasesReservations [/ip dhcp-server/lease/print as-value where dynamic=no]
:local arpTable [/ip/arp/print as-value]
:local countTotal 0
:local countDynamic 0
:local countReservations 0
:local logString 

#strPadTrim function
:local strPadTrim do={
    :local str $string
    :local tar $targetLen
    :if ([:len $str] > $tar) do={:set str [:pick $str 0 $tar]}
    :local cont [:len $str]
        :while ($cont < $tar)  do={
            :set str ($str." ")
            :set cont [:len $str]
        }
	:return $str
}

#char replace function
:local strReplace do={
	:local encode
	:for i from=0 to=([:len $string]-1) do={ 
		:local char [:pick $string $i]
		:if ($char=$find) do={
			:set char $replace
		}
		:set encode ($encode.$char)
	}
	:return $encode
}

#Build multi array from each IP octet, let default array sorting organise entries.
:local multiSorted [ :toarray "" ]
:foreach line in=$scanResult do={
    :local ipParts [:toarray [$strReplace string=[:tostr [($line->"address")]] find="." replace=","]]
    :local pt0 ($ipParts->0); :if ([:len $pt0]=1) do={:set pt0 ("0".$pt0)}; :if ([:len $pt0]=2) do={:set pt0 ("0".$pt0)};
    :local pt1 ($ipParts->1); :if ([:len $pt1]=1) do={:set pt1 ("0".$pt1)}; :if ([:len $pt1]=2) do={:set pt1 ("0".$pt1)};
    :local pt2 ($ipParts->2); :if ([:len $pt2]=1) do={:set pt2 ("0".$pt2)}; :if ([:len $pt2]=2) do={:set pt2 ("0".$pt2)};
    :local pt3 ($ipParts->3); :if ([:len $pt3]=1) do={:set pt3 ("0".$pt3)}; :if ([:len $pt3]=2) do={:set pt3 ("0".$pt3)};
    :set ($multiSorted->$pt0->$pt1->$pt2->$pt3) $line
}

#Break sorted multi array back down to simple array
:local scanSorted [ :toarray "" ]
:local cont 0
:foreach line0 in=$multiSorted do={
    :foreach line1 in=$line0 do={
        :foreach line2 in=$line1 do={
            :foreach line3 in=$line2 do={
                :set ($scanSorted->$cont) "$line3"
                :set cont ($cont+1)
            }
        }
    }
}

#Display Loop
:foreach line in=$scanSorted do={

    :local address ($line->"address")
    :local addressTarLen 15
    :local macAddress ($line->"mac-address")
    :local macAddressTarLen 17
    :local dns ($line->"dns")
    :local netbios ($line->"netbios")
    :local snmp ($line->"snmp")
    :local time ($line->"time")
    :local url ""
    :local vendor ""
    :local vendorTarLen 30
    :local identity ""
    :local dhcp ""
    :local dhcpState " "
    :local interface
    :local interfaceTarLen 20
    :local vendor
    :local vmax 1
    :local vend 8
    :local keyParam
    :if ([:len $apiKey] > 10) do={:set keyParam "?apiKey=$apiKey"}

    :if ([:len $macAddress] = 17) do={

        #Get Vendor
        :local secBit [:pick $macAddress 1 2 ]
        :if ( ($secBit = "2") or ($secBit = "6") or ($secBit = "A") or ($secBit = "E") ) do={:set vendor "*LOCAL PRIVATE*"}  else={

            :set vendor "IEEE Registration Authority"

            :while ( ($vendor="IEEE Registration Authority") and ($vmax < 4) ) do={
            
                :set url ("https://api.maclookup.app/v2/macs/".[:pick $macAddress 0 $vend]."/company/name".$keyParam)
                :local vendorResult; :do {:set vendorResult [/tool fetch url=$url as-value output=user]} on-error={}
                :if (($vendorResult->"status")="finished") do={:set vendor ($vendorResult->"data")}
                :if ([:len $apiKey] < 10) do={:delay 0.5;}; #keep inside api rate limit
                :set vend ($vend+3)
                :set vmax ($vmax+1)

            }
            
        }

    } 

    :if ($vendor="IEEE Registration Authority") do={:set $vendor ""}
    
    :if ([:len $address] > 6) do={
        #Neighbour Name
        :foreach  neighbour in=$neighbours do={
            :if ( ($macAddress=($neighbour->"mac-address")) or ($address=($neighbour->"address")) ) do={
                :if ($macAddress=($neighbour->"mac-address")) do={:set identity ($neighbour->"identity")}
            }
        }
        #ARP interfaces
        :foreach  arp in=$arpTable do={
            :if ( ($macAddress=($arp->"mac-address")) or ($address=($arp->"address")) ) do={
                :if ([:len ($arp->"interface")] > 0) do={:set interface ($arp->"interface")}
            }
        }
        #DHCP Names
        :foreach lease in=$dhcpLeases do={
            :if ( ($macAddress=($lease->"mac-address")) or ($address=($lease->"address")) ) do={
                :if ([:len ($lease->"host-name")] > 0) do={:set dhcp ($dhcp.($lease->"host-name")." ")}
                :if ([:len ($lease->"class-id")] > 0) do={:set dhcp ($dhcp.($lease->"class-id")." ")}
                :if ([:len ($lease->"comment")] > 0) do={:set dhcp ($dhcp.($lease->"comment")." ")}
            }
        }
        #DHCP Dynamic
        :foreach lease in=$dhcpLeasesDynamic do={
            :if ( ($macAddress=($lease->"mac-address")) or ($address=($lease->"address")) ) do={
                :set dhcpState "D"
                :set countDynamic ($countDynamic + 1)
            }
        }
        #DHCP Reservations
        :foreach lease in=$dhcpLeasesReservations do={
            :if ( ($macAddress=($lease->"mac-address")) or ($address=($lease->"address")) ) do={
                :set dhcpState "R"
                :set countReservations ($countReservations + 1)
            }
        }

    }

    :set countTotal ($countTotal + 1)

    :local space1 ""; :if ([:len $snmp] > 0) do={:set space1 " "}
    :local space2 ""; :if ([:len $dns] > 0) do={:set space2 " "}
    :local space3 ""; :if ([:len $netbios] > 0) do={:set space3 " "}
    :local space4 ""; :if ([:len $identity] > 0) do={:set space4 " "}
    :local names ($snmp.$space1.$dns.$space2.$netbios.$space3.$identity.$space4.$dhcp)

    :set address [$strPadTrim string=$address targetLen=$addressTarLen]
    :set macAddress [$strPadTrim string=$macAddress targetLen=$macAddressTarLen]
    :set interface [$strPadTrim string=$interface targetLen=$interfaceTarLen]
    :set vendor [$strPadTrim string=$vendor targetLen=$vendorTarLen]
    
    :put ("IP: $address   $dhcpState| MAC: $macAddress   | ARP: $interface   | Vendor: $vendor   | Names: $names");
    :if ($saveLog = "yes") do={:set logString ($logString."IP: $address   $dhcpState| MAC: $macAddress   | ARP: $interface   | Vendor: $vendor   | Names: $names\r\n")}

}

:put ""
:put ("Found Total: $countTotal | DHCP Dynamic: $countDynamic | DHCP Reservations: $countReservations")

#Export Log
:if ($saveLog = "yes") do={
    # get time
    :local ts [/system clock get time]
    :set ts ([:pick $ts 0 2]."-".[:pick $ts 3 5]."-".[:pick $ts 6 8])

    # get Date
    :local ds [/system clock get date]

    #file
    :local filename ("IP-Scan_".[/system/identity get value-name=name]."_".$scanInterface."_".$ds."_".$ts)
    :global logExport $logString
    :local writeScript ":put (\$logExport)"
    /execute $writeScript file=($filename)

    #clean global var
    /system/script/environment remove logExport
    :put ("Log File: $filename")
}
Last edited by excession on Sun Nov 10, 2024 7:58 pm, edited 13 times in total.
 
User avatar
rextended
Forum Guru
Forum Guru
Posts: 12521
Joined: Tue Feb 25, 2014 12:49 pm
Location: Italy
Contact:

Re: Enhanced IP Scan with Vendor and Additional Name Sources

Sat Oct 12, 2024 3:26 am

EDIT: This post is still valid but is referred to first version of the OP.

I see "on the fly" two big mistakes:

the first is that the entire mac-address is sent to the site (uselessly), instead of just the vendor part, screwing up privacy,

the second is that the invented/random or local-use mac-addresses are also requested (bit 2 and 1 of the first byte),
which are a waste of resources for those who host the service for free.


then I see a lack of coherence in variable names (example scanInterface, scanDuration, scanresult),
and also a fundamental lack of "/" where it is needed (example [tool and [ip )
excess of . and " where it is not needed (for example :put ("IP: ".$address."\t | MAC: ".$mac[...] must be like :put "IP: $address\t | MAC: $mac[...] )

and I'll stop here.
Last edited by rextended on Thu Oct 17, 2024 2:53 pm, edited 1 time in total.
 
excession
Member Candidate
Member Candidate
Topic Author
Posts: 122
Joined: Mon May 11, 2015 8:16 pm

Re: Enhanced IP Scan with Vendor and Additional Name Sources

Sat Oct 12, 2024 7:42 pm

Ahh rextended, always a pleasure.

Good point about the mac addresses, I'll give that some thought, thank you.

Don't suppose you could lend some of your expertise to my question about sorting multidimensional arrays in Mikrotik script?
Perhaps an example that would work in my script and allow me to sort by IP?

Grazie mille bello :D
 
excession
Member Candidate
Member Candidate
Topic Author
Posts: 122
Joined: Mon May 11, 2015 8:16 pm

Re: Enhanced IP Scan with Vendor and Additional Name Sources

Tue Oct 15, 2024 11:44 pm

Updated 15-10-24

Modified above to include rextended's suggestions as well as those of a colleague.

Script now only sends required octets to api.
Random MAC addresses are shown as *LOCAL PRIVATE*. https://www.oasys.net/fragments/identify-private-macs/
Added a flag for DHCP state.
Added the ability to save a logfile.
General tidying up and remembering how to write a function in Mikrotik script. :D

Looks a bit like this:
Capture.PNG
You do not have the required permissions to view the files attached to this post.
 
User avatar
diamuxin
Member
Member
Posts: 335
Joined: Thu Sep 09, 2021 5:46 pm

Re: Enhanced IP Scan with Vendor and Additional Name Sources

Wed Oct 16, 2024 12:57 pm

Very useful your script, thank you!

BR.
 
excession
Member Candidate
Member Candidate
Topic Author
Posts: 122
Joined: Mon May 11, 2015 8:16 pm

Re: Enhanced IP Scan with Vendor and Additional Name Sources

Wed Oct 16, 2024 3:03 pm

Thanks for letting me know you found it useful!

Though I’d much rather Mikrotik would add this kind of metadata to the regular scan function. Preferably with the vendor data but even just the DHCP and Neighbours table info that’s already present in the system would be very welcome.
 
excession
Member Candidate
Member Candidate
Topic Author
Posts: 122
Joined: Mon May 11, 2015 8:16 pm

Re: Enhanced IP Scan with Vendor and Additional Name Sources

Wed Oct 16, 2024 7:33 pm

Updated 16-10-24

Code above is updated, now sorts by IP address!
I'm sure my sorting method is horrific, please do chime in if you can tidy it up / make 1000% better by re-writing it.
 
User avatar
diamuxin
Member
Member
Posts: 335
Joined: Thu Sep 09, 2021 5:46 pm

Re: Enhanced IP Scan with Vendor and Additional Name Sources

Wed Oct 16, 2024 8:09 pm

Updated 16-10-24

Code above is updated, now sorts by IP address!
I'm sure my sorting method is horrific, please do chime in if you can tidy it up / make 1000% better by re-writing it.
Hi,

Could your script be adapted to get some data from the DHCP-Server/Leases screen only?

I need to list the hosts that are in both “bound” and “waiting” status. I would then dump it into a file every night.

EDIT: Ready! :D

Thanks!
 
excession
Member Candidate
Member Candidate
Topic Author
Posts: 122
Joined: Mon May 11, 2015 8:16 pm

Re: Enhanced IP Scan with Vendor and Additional Name Sources

Thu Oct 17, 2024 1:41 am

Could your script be adapted to get some data from the DHCP-Server/Leases screen only?
You can run this in the scheduler if you just need the same style output as you get from the print command:
/ip/dhcp-server/lease/print file=dhcp.txt where status=bound or status=waiting
 
User avatar
diamuxin
Member
Member
Posts: 335
Joined: Thu Sep 09, 2021 5:46 pm

Re: Enhanced IP Scan with Vendor and Additional Name Sources

Thu Oct 17, 2024 3:10 pm

off-topic.
Last edited by diamuxin on Sun Oct 20, 2024 2:31 pm, edited 2 times in total.
 
excession
Member Candidate
Member Candidate
Topic Author
Posts: 122
Joined: Mon May 11, 2015 8:16 pm

Re: Enhanced IP Scan with Vendor and Additional Name Sources

Thu Oct 17, 2024 4:12 pm

Nice, happy you could adapt it to your needs. :)
 
excession
Member Candidate
Member Candidate
Topic Author
Posts: 122
Joined: Mon May 11, 2015 8:16 pm

Re: Enhanced IP Scan with Vendor and Additional Name Sources

Thu Oct 17, 2024 4:17 pm

Updated 17-10-24

Modified script in OP.
Now also lists interface, matched from the ARP table.
This is especially useful when you're scanning a bridge with multiple VLANs on it.
Moved the Neighbours table matching around slightly to also match if there's only an IP detected in the scan.
 
User avatar
diamuxin
Member
Member
Posts: 335
Joined: Thu Sep 09, 2021 5:46 pm

Re: Enhanced IP Scan with Vendor and Additional Name Sources

Sat Oct 19, 2024 11:05 am

Hi!

@excession could you please help me?

In the following code snippet, it does not work when the MAC address does not exist in the Bridge Host table, it gives me an empty result, I should see *NOT AVAILABLE*.

If the MAC address does exist it reports your interface correctly.

Where is the error?

Thanks.
{
:local iFace ""
:local macAddress "54:B7:BD:70:0F:B8" ; # Does not exist in the bridge host table
# :local macAddress "E0:CC:F8:E7:90:4A" ; # Yes, it does exist

:local bridHost [/interface/bridge/host/print as-value]
:foreach i in=$bridHost do={
    :if ($macAddress=($i->"mac-address")) do={
        :if ([:len ($i->"on-interface")] > 0) do={
            :set iFace ($i->"on-interface")
        } else={:set iFace "*NOT AVAILABLE*"}
    } 
}
:put $iFace
}
BR.
 
User avatar
rextended
Forum Guru
Forum Guru
Posts: 12521
Joined: Tue Feb 25, 2014 12:49 pm
Location: Italy
Contact:

Re: Enhanced IP Scan with Vendor and Additional Name Sources

Sat Oct 19, 2024 2:18 pm

...
 
excession
Member Candidate
Member Candidate
Topic Author
Posts: 122
Joined: Mon May 11, 2015 8:16 pm

Re: Enhanced IP Scan with Vendor and Additional Name Sources

Sat Oct 19, 2024 10:36 pm


In the following code snippet, it does not work when the MAC address does not exist in the Bridge Host table, it gives me an empty result, I should see *NOT AVAILABLE*.

If the MAC address does exist it reports your interface correctly.
Might be better to start a new thread to work on your scripts, but in the interest of good spirit.

Looks like your "else" is on the wrong "if" statement, seems to me you could remove the else entirely and do something like this:
{
:local iFace "*NOT AVAILABLE*"
:local macAddress "54:B7:BD:70:0F:B8" ; # Does not exist in the bridge host table
# :local macAddress "E0:CC:F8:E7:90:4A" ; # Yes, it does exist

:local bridHost [/interface/bridge/host/print as-value]
:foreach i in=$bridHost do={
    :if ( ($macAddress = ($i->"mac-address") ) and ([:len ($i->"on-interface")] > 0) ) do={
        :set iFace ($i->"on-interface")
    } 
}
:put $iFace
}
 
User avatar
diamuxin
Member
Member
Posts: 335
Joined: Thu Sep 09, 2021 5:46 pm

Re: Enhanced IP Scan with Vendor and Additional Name Sources

Sat Oct 19, 2024 10:50 pm

Might be better to start a new thread to work on your scripts, but in the interest of good spirit.
It works correctly, thank you.

You are right, I should have opened a new post, sorry.
 
excession
Member Candidate
Member Candidate
Topic Author
Posts: 122
Joined: Mon May 11, 2015 8:16 pm

Re: Enhanced IP Scan with Vendor and Additional Name Sources

Sat Oct 19, 2024 10:52 pm

I wrote a version that downloaded the wireshark oui database to do local matching.
Borrowing from the great work here: viewtopic.php?t=152632 to work through a large list in parts.

Unfortunately I couldn't find a way (even after slimming down the dataset to just the required parts) to match against that dataset fast enough to be useable. Even after loading the 52k entries into a global var it was still way too slow.

I was testing on a 951 (the idea being if it worked there it would work on any hardware), I'm sure it would have been faster on a better box. But I'm wondering if I'm just asking too much of ROS?
 
Josephny
Forum Veteran
Forum Veteran
Posts: 750
Joined: Tue Sep 20, 2022 12:11 am

Re: Enhanced IP Scan with Vendor and Additional Name Sources

Sun Oct 20, 2024 3:53 am

Beautiful and useful script!

Thank you for writing this.
 
excession
Member Candidate
Member Candidate
Topic Author
Posts: 122
Joined: Mon May 11, 2015 8:16 pm

Re: Enhanced IP Scan with Vendor and Additional Name Sources

Sun Oct 20, 2024 8:42 pm

Beautiful and useful script!

Thank you for writing this.
Thank you for letting me know you found it useful! :D
I got lots of inspiration from other peoples great work on the forum and some really useful feedback.
 
hammer185
newbie
Posts: 27
Joined: Wed Sep 13, 2006 8:28 am

Re: Enhanced IP Scan with Vendor and Additional Name Sources

Fri Oct 25, 2024 4:01 am

With the concern of not sharing private information along with not forgetting about allocations that are not the block large size; with a quick review, it appears the particular API you are using to get vendor information returns “IEEE Registration Authority” until it has a vendor match as long as it is passed 6 characters that are interpreted as representing a hexadecimal number. So, for example, if I query with “00:50:C2” the result is “IEEE Registration Authority”. If I get that for a block large size query instead of a different result that would be a vendor I can either keep adding characters such as “00:50:C2:C”, which returns also “IEEE Registration Authority”, then next “00:50:C2:CC”, which returns also “IEEE Registration Authority”, then next “00:50:C2:CC:1”, which doesn’t return “IEEE Registration Authority” so would be the vendor. It appears likely the allocation sizes are all MAC-L, MAC-M or MAC-S size so potentially the logic could just skip from the first 8 characters to the first 10 characters for the MAC-M size and if that still returns “IEEE Registry Authority” then could skip to the first 13 for the MAC-S size such as with “70:B3:D5:6A:2”. I haven’t tested yet what happens if a query were to be done with an unallocated block.
 
User avatar
rextended
Forum Guru
Forum Guru
Posts: 12521
Joined: Tue Feb 25, 2014 12:49 pm
Location: Italy
Contact:

Re: Enhanced IP Scan with Vendor and Additional Name Sources

Fri Oct 25, 2024 8:51 am

[...]
if vendor = "IEEE Registration Authority"
[...]
:set url ("https://api.maclookup.app/v2/macs/".[:pick $macAddress 0 11]."/company/name")
[...]
if vendor = "IEEE Registration Authority"
[...]
:set url ("https://api.maclookup.app/v2/macs/".[:pick $macAddress 0 13]."0/company/name")
[...]
 
excession
Member Candidate
Member Candidate
Topic Author
Posts: 122
Joined: Mon May 11, 2015 8:16 pm

Re: Enhanced IP Scan with Vendor and Additional Name Sources

Fri Oct 25, 2024 6:55 pm

With the concern of not sharing private information along with not forgetting about allocations that are not the block large size; with a quick review, it appears the particular API you are using to get vendor information returns “IEEE Registration Authority”
Updated 25-10-24

Hammer185 rightly pointed out that some vendors use larger Mac Address blocks and that only the first 3 octets are insufficient to match vendor for these devices. Taking some inspiration from rextended's suggested solution I've updated the script above to now recursively send additional octets to the maclookup api until a match is found but still stopping short of sending the final octet.

This also has the side benefit of re-sending any match requests that for some reason time out or get interrupted, limited to 3 retries.

Thanks everyone. :)
 
User avatar
rextended
Forum Guru
Forum Guru
Posts: 12521
Joined: Tue Feb 25, 2014 12:49 pm
Location: Italy
Contact:

Re: Enhanced IP Scan with Vendor and Additional Name Sources

Mon Nov 04, 2024 10:29 am

You do the 99% of works...

Just change
/name")
to
/name\3FapiKey=$apiKey")

(untested, i do not have a key)
 
excession
Member Candidate
Member Candidate
Topic Author
Posts: 122
Joined: Mon May 11, 2015 8:16 pm

Re: Enhanced IP Scan with Vendor and Additional Name Sources

Sun Nov 10, 2024 7:22 pm

Can someone add an apikey to the script?
Updated 10-11-24 - Hi wfburton, I finally got around to adding optional api key support.

(Free registration is here, if anyone else is interested: https://my.maclookup.app/login)
I have no affiliation with the api developers, registration is optional.

Their usage policy also means the above script will not rate limit calls when there is a key present.
It does not however check your key is valid, please check yourself before adding it in the script parameters..
 
User avatar
rextended
Forum Guru
Forum Guru
Posts: 12521
Joined: Tue Feb 25, 2014 12:49 pm
Location: Italy
Contact:

Re: Enhanced IP Scan with Vendor and Additional Name Sources

Mon Nov 11, 2024 2:15 pm

 # ARP Fixup table
:put "### ARP Fixup table"
:local dumplist [/ip arp find complete=no ]
:foreach i in=$dumplist do={
    /ip arp remove $i
} 

Completly useless... simply
/ip arp remove [find where complete=no]

Who is online

Users browsing this forum: No registered users and 10 guests