1#!/usr/bin/env python3
2
3r"""
4Network generic functions.
5
6"""
7
8import collections
9import ipaddress
10import json
11import re
12import socket
13import subprocess
14
15import bmc_ssh_utils as bsu
16import gen_cmd as gc
17import gen_misc as gm
18import gen_print as gp
19import var_funcs as vf
20from robot.libraries.BuiltIn import BuiltIn
21
22ip_regex = r"\d{1,3}.\d{1,3}.\d{1,3}.\d{1,3}"
23
24
25def get_running_system_ip():
26    r"""
27    Get the IP address of server from which robot code is running.
28
29    """
30
31    ip_list = list()
32    stdout = subprocess.check_output(["hostname", "--all-fqdns"], shell=True)
33    host_fqdns = stdout.decode("utf-8").strip()
34    ip_address = socket.gethostbyname(str(host_fqdns))
35    ip_list.append(ip_address)
36
37    return ip_list
38
39
40def netmask_prefix_length(netmask):
41    r"""
42    Return the netmask prefix length.
43
44    Description of argument(s):
45    netmask     Netmask value (e.g. "255.255.0.0", "255.255.255.0",
46                                    "255.252.0.0", etc.).
47    """
48
49    # IP address netmask format: '0.0.0.0/255.255.252.0'
50    return ipaddress.ip_network("0.0.0.0/" + netmask).prefixlen
51
52
53def get_netmask_address(prefix_len):
54    r"""
55    Return the netmask address.
56
57    Description of argument(s):
58    prefix_len     Prefix length value (e.g. "24", "23", "22", etc.).
59    """
60
61    # IP address netmask format: '0.0.0.0/24'
62    return ipaddress.ip_network("0.0.0.0/" + prefix_len).netmask
63
64
65def parse_nping_output(output):
66    r"""
67    Parse the output from the nping command and return as a dictionary.
68
69    Example of output value:
70
71    Starting Nping 0.6.47 ( http://nmap.org/nping ) at 2019-08-07 22:05 IST
72    SENT (0.0181s) TCP Source IP:37577 >
73      Destination IP:80 S ttl=64 id=39113 iplen=40  seq=629782493 win=1480
74    SENT (0.2189s) TCP Source IP:37577 >
75      Destination IP:80 S ttl=64 id=39113 iplen=40  seq=629782493 win=1480
76    RCVD (0.4120s) TCP Destination IP:80 >
77      Source IP:37577 SA ttl=49 id=0 iplen=44  seq=1078301364 win=5840 <mss 1380>
78    Max rtt: 193.010ms | Min rtt: 193.010ms | Avg rtt: 193.010ms
79    Raw packets sent: 2 (80B) | Rcvd: 1 (46B) | Lost: 1 (50.00%)
80    TCP connection attempts: 5 | Successful connections: 5 | Failed: 0 (0.00%)
81    Nping done: 1 IP address pinged in 0.43 seconds
82
83    Example of data returned by this function:
84
85    nping_result:
86      [max_rtt]:                 193.010ms
87      [min_rtt]:                 193.010ms
88      [avg_rtt]:                 193.010ms
89      [raw_packets_sent]:        2 (80B)
90      [rcvd]:                    1 (46B)
91      [lost]:                    1 (50.00%)
92      [percent_lost]:            50.00
93      [tcp_connection_attempts]: 5
94      [successful_connections]:  5
95      [failed]:                  0 (0.00%)
96      [percent_failed]:          0.00
97
98    Description of argument(s):
99    output                          The output obtained by running an nping
100                                    command.
101    """
102
103    lines = output.split("\n")
104    # Obtain only the lines of interest.
105    lines = list(
106        filter(
107            lambda x: re.match(r"(Max rtt|Raw packets|TCP connection)", x),
108            lines,
109        )
110    )
111
112    key_value_list = []
113    for line in lines:
114        key_value_list += line.split("|")
115    nping_result = vf.key_value_list_to_dict(key_value_list)
116    # Extract percent_lost/percent_failed value from lost/failed field.
117    if "lost" in nping_result:
118        nping_result["percent_lost"] = float(
119            nping_result["lost"].split(" ")[-1].strip("()%")
120        )
121    else:
122        nping_result["percent_failed"] = float(
123            nping_result["failed"].split(" ")[-1].strip("()%")
124        )
125    return nping_result
126
127
128openbmc_host = BuiltIn().get_variable_value("${OPENBMC_HOST}")
129
130
131def nping(host=openbmc_host, parse_results=1, **options):
132    r"""
133    Run the nping command and return the results either as a string or as a dictionary.
134
135    Do a 'man nping' for a complete description of the nping utility.
136
137    Note that any valid nping argument may be specified as a function argument.
138
139    Example robot code:
140
141    ${nping_result}=  Nping  delay=${delay}  count=${count}  icmp=${None}  icmp-type=echo
142    Rprint Vars  nping_result
143
144    Resulting output:
145
146    nping_result:
147      [max_rtt]:                                      0.534ms
148      [min_rtt]:                                      0.441ms
149      [avg_rtt]:                                      0.487ms
150      [raw_packets_sent]:                             4 (112B)
151      [rcvd]:                                         2 (92B)
152      [lost]:                                         2 (50.00%)
153      [percent_lost]:                                 50.0
154
155    Description of argument(s):
156    host                            The host name or IP of the target of the
157                                    nping command.
158    parse_results                   1 or True indicates that this function
159                                    should parse the nping results and return
160                                    a dictionary rather than the raw nping
161                                    output.  See the parse_nping_output()
162                                    function for details on the dictionary
163                                    structure.
164    options                         Zero or more options accepted by the nping
165                                    command.  Do a 'man nping' for details.
166    """
167
168    command_string = gc.create_command_string("nping", host, options)
169    rc, output = gc.shell_cmd(command_string, print_output=0, ignore_err=0)
170    if parse_results:
171        return parse_nping_output(output)
172
173    return output
174
175
176def get_channel_config():
177    r"""
178    Get the channel config data and return as a dictionary.
179
180    Example:
181    channel_config = get_channel_config()
182    print_vars(channel_config)
183
184    channel_config:
185      [0]:
186        [name]:                  IPMB
187        [is_valid]:              True
188        [active_sessions]:       0
189        [channel_info]:
190          [medium_type]:         ipmb
191          [protocol_type]:       ipmb-1.0
192          [session_supported]:   session-less
193          [is_ipmi]:             True
194      [1]:
195        [name]:                  eth0
196        [is_valid]:              True
197        [active_sessions]:       0
198        [channel_info]:
199          [medium_type]:         other-lan
200          [protocol_type]:       ipmb-1.0
201          [session_supported]:   multi-session
202          [is_ipmi]:             True
203      [2]:
204        [name]:                  eth1
205        [is_valid]:              True
206        [active_sessions]:       0
207        [channel_info]:
208          [medium_type]:         lan-802.3
209          [protocol_type]:       ipmb-1.0
210          [session_supported]:   multi-session
211          [is_ipmi]:             True
212    (etc.)
213    """
214
215    stdout, stderr, rc = bsu.bmc_execute_command(
216        "cat /usr/share/ipmi-providers/channel_config.json"
217    )
218    return json.loads(stdout)
219
220
221def get_active_channel_config():
222    r"""
223    Channel configs which medium_type are 'other-lan' or 'lan-802.3' returned by
224     this function.
225    """
226
227    return vf.filter_struct(
228        get_channel_config(),
229        "[('medium_type', 'other-lan|lan-802.3')]",
230        regex=1,
231    )
232
233
234def get_channel_access_config(file_name):
235    r"""
236    Get the channel access config data and return as a dictionary.
237
238    Description of argument:
239    file_name     File name for channel access settings (e.g. '/run/ipmi/channel_access_volatile.json',
240                 '/var/lib/ipmi/channel_access_nv.json'.).
241
242    Example:
243
244    channel_access_config =  get_channel_access_config()
245    print_vars(channel_access_config)
246
247    channel_access_config:
248        [1]:
249            [priv_limit]:                                 priv-admin
250            [per_msg_auth_disabled]:                      False
251            [access_mode]:                                always_available
252            [alerting_disabled]:                          False
253            [user_auth_disabled]:                         False
254        [2]:
255            [priv_limit]:                                 priv-admin
256            [per_msg_auth_disabled]:                      False
257            [access_mode]:                                always_available
258            [alerting_disabled]:                          False
259            [user_auth_disabled]:                         False
260    """
261    stdout, stderr, rc = bsu.bmc_execute_command("cat " + file_name)
262
263    return json.loads(stdout)
264