@@ -355,7 +355,66 @@ def get_enrichment_data(self, resource: str, resource_type: str, explain: bool =
355355 raise DemistoException (f'Failed to fetch enrichment data for { resource_type } { resource } : { str (e )} ' )
356356
357357
358-
358+ def list_ip_information (self , ips : Union [str , List [str ]], explain : bool = False , scan_data : bool = False , sparse : Optional [str ] = None ) -> Dict :
359+ """
360+ Fetches information for both IPv4 and IPv6 addresses.
361+
362+ Args:
363+ ips: Either a single IP string or a list of IP strings
364+ explain: Whether to show details of data used to calculate scores
365+ scan_data: Whether to include scan data (IPv4 only)
366+ sparse: Optional specific data to return ('asn', 'asname', or 'sp_risk_score')
367+
368+ Returns:
369+ Dict: Combined results for all IP addresses
370+ """
371+ if isinstance (ips , str ):
372+ ip_list = [ip .strip () for ip in ips .split (',' )]
373+ else :
374+ ip_list = ips
375+
376+ if len (ip_list ) > 100 :
377+ raise DemistoException ("Maximum of 100 IPs can be submitted in a single request" )
378+
379+ results = []
380+
381+ for ip in ip_list :
382+ try :
383+ # Determine if IPv4 or IPv6 based on presence of colons
384+ is_ipv6 = ':' in ip
385+
386+ # Build parameters
387+ params = {
388+ 'explain' : 1 if explain else 0
389+ }
390+ if sparse :
391+ params ['sparse' ] = sparse
392+ if not is_ipv6 and scan_data :
393+ params ['scan_data' ] = 1
394+
395+ url_suffix = f"explore/{ 'ipv6' if is_ipv6 else 'ipv4' } /ipv{ 6 if is_ipv6 else 4 } info/{ ip } "
396+
397+ response = self ._http_request (
398+ method = 'GET' ,
399+ url_suffix = url_suffix ,
400+ params = params
401+ )
402+
403+ ip_info = response .get ('response' , {}).get ('ip2asn' , [{}])[0 ]
404+
405+ ip_info ['ip_type' ] = 'ipv6' if is_ipv6 else 'ipv4'
406+
407+ results .append (ip_info )
408+
409+ except Exception as e :
410+ demisto .error (f"Error fetching information for IP { ip } : { str (e )} " )
411+ results .append ({
412+ 'ip' : ip ,
413+ 'ip_type' : 'ipv6' if ':' in ip else 'ipv4' ,
414+ 'error' : str (e )
415+ })
416+
417+ return {'ips' : results }
359418
360419
361420
@@ -652,6 +711,87 @@ def get_enrichment_data_command(client: Client, args: Dict[str, Any]) -> Command
652711 except Exception as e :
653712 demisto .error (f'Failed to get enrichment data: { str (e )} ' )
654713 raise
714+
715+
716+ def list_ip_information_command (client : Client , args : Dict [str , Any ]) -> CommandResults :
717+ """
718+ Command handler for fetching IP information.
719+
720+ Args:
721+ client (Client): The client instance to fetch the data
722+ args (dict): Command arguments including:
723+ - ips (str): Comma-separated list of IP addresses
724+ - explain (bool): Whether to show calculation details
725+ - scan_data (bool): Whether to include scan data (IPv4 only)
726+ - sparse (str): Optional specific data to return
727+
728+ Returns:
729+ CommandResults: XSOAR command results
730+ """
731+
732+ ips = args .get ('ips' )
733+ if not ips :
734+ raise DemistoException ('No IPs provided. Please provide IPs using the "ips" argument.' )
735+
736+ explain = argToBoolean (args .get ('explain' , False ))
737+ scan_data = argToBoolean (args .get ('scan_data' , False ))
738+ sparse = args .get ('sparse' )
739+
740+ if sparse and sparse not in ['asn' , 'asname' , 'sp_risk_score' ]:
741+ raise DemistoException ('Invalid sparse value. Must be one of: asn, asname, sp_risk_score' )
742+
743+ try :
744+
745+ raw_response = client .list_ip_information (ips , explain , scan_data , sparse )
746+ ip_data = raw_response .get ('ips' , [])
747+
748+ markdown = ['### IP Information Results\n ' ]
749+
750+ for ip_info in ip_data :
751+ if 'error' in ip_info :
752+ markdown .append (f"#### IP: { ip_info .get ('ip' , 'N/A' )} (Error)\n " )
753+ markdown .append (f"Error: { ip_info ['error' ]} \n " )
754+ continue
755+
756+ markdown .append (f"#### IP: { ip_info .get ('ip' , 'N/A' )} ({ ip_info .get ('ip_type' , 'unknown' ).upper ()} )" )
757+
758+ basic_info = {
759+ 'ASN' : ip_info .get ('asn' , 'N/A' ),
760+ 'AS Name' : ip_info .get ('asname' , 'N/A' ),
761+ 'Risk Score' : ip_info .get ('sp_risk_score' , 'N/A' ),
762+ 'Subnet' : ip_info .get ('subnet' , 'N/A' )
763+ }
764+ markdown .append (tableToMarkdown ('Basic Information' , [basic_info ], headers = basic_info .keys ()))
765+
766+ if location_info := ip_info .get ('ip_location' , {}):
767+ location_data = {
768+ 'Country' : location_info .get ('country_name' , 'N/A' ),
769+ 'Continent' : location_info .get ('continent_name' , 'N/A' ),
770+ 'EU Member' : 'Yes' if location_info .get ('country_is_in_european_union' ) else 'No'
771+ }
772+ markdown .append (tableToMarkdown ('Location Information' , [location_data ], headers = location_data .keys ()))
773+
774+ if ip_info .get ('ip_type' ) == 'ipv4' :
775+ additional_info = {
776+ 'PTR Record' : ip_info .get ('ip_ptr' , 'N/A' ),
777+ 'Is TOR Exit Node' : 'Yes' if ip_info .get ('ip_is_tor_exit_node' ) else 'No' ,
778+ 'Is DSL/Dynamic' : 'Yes' if ip_info .get ('ip_is_dsl_dynamic' ) else 'No'
779+ }
780+ markdown .append (tableToMarkdown ('Additional Information' , [additional_info ], headers = additional_info .keys ()))
781+
782+ markdown .append ('\n ' )
783+
784+ return CommandResults (
785+ outputs_prefix = 'SilentPush.IP' ,
786+ outputs_key_field = 'ip' ,
787+ outputs = ip_data ,
788+ readable_output = '\n ' .join (markdown ),
789+ raw_response = raw_response
790+ )
791+
792+ except Exception as e :
793+ demisto .error (f"Error in list_ip_information_command: { str (e )} " )
794+ raise
655795
656796''' MAIN FUNCTION '''
657797
@@ -694,7 +834,8 @@ def main():
694834 'silentpush-get-domain-certificates' : get_domain_certificates_command ,
695835 'silentpush-search-domains' : search_domains_command ,
696836 'silentpush-list-domain-infratags' : list_domain_infratags_command ,
697- 'silentpush-get-enrichment-data' : get_enrichment_data_command
837+ 'silentpush-get-enrichment-data' : get_enrichment_data_command ,
838+ 'silentpush-list-ip-information' : list_ip_information_command
698839 }
699840
700841 if command in command_handlers :
0 commit comments