Coverage for mfutil/net.py: 71%

87 statements  

« prev     ^ index     » next       coverage.py v7.6.1, created at 2025-05-27 03:19 +0000

1"""Utility functions around network.""" 

2 

3import logging 

4import socket 

5import netifaces 

6 

7 

8def __get_logger(): 

9 return logging.getLogger("mfutil.net") 

10 

11 

12def get_ip_for_hostname(hostname, ignore_hostnames_list=["AUTO"]): 

13 """Get the IP of a given hostname. 

14 

15 Args: 

16 hostname (string): hostname to find. 

17 ignore_hostnames_list (list of strings): special hostname values which 

18 won't be lookup ip (if the given hostname is in this list, this 

19 function will return the given hostname without any modification). 

20 Returns: 

21 (string) IP of the given hostname (or None if we can't find it). 

22 

23 """ 

24 if hostname in ignore_hostnames_list: 

25 return hostname 

26 if hostname in ("127.0.0.1", "localhost", "localhost.localdomain"): 

27 return "127.0.0.1" 

28 try: 

29 infos = socket.getaddrinfo(hostname, 80, 0, 0, socket.IPPROTO_TCP) 

30 except Exception: 

31 return None 

32 for (family, sockettype, proto, canonname, sockaddr) in infos: 

33 if sockaddr is None or len(sockaddr) != 2: 

34 continue 

35 tmp = sockaddr[0] 

36 if '.' not in tmp: 

37 # we don't want ipv6 

38 continue 

39 return tmp 

40 return None 

41 

42 

43def get_simple_hostname(): 

44 """Return the "simple" hostname of the server. 

45 

46 "simple" hostname means "without network domain", so without any dot 

47 in the hostname. 

48 

49 Returns: 

50 string: the "simple" hostname of the server. 

51 

52 """ 

53 return socket.gethostname().split('.')[0] 

54 

55 

56def _get_domainname_from_resolv_conf(resolv_conf_file="/etc/resolv.conf"): 

57 with open(resolv_conf_file, "r") as f: 

58 lines = f.readlines() 

59 for line in lines: 

60 tmp = line.strip() 

61 if tmp.startswith("domain"): 

62 cols = tmp.split() 

63 if len(cols) >= 2: 

64 return cols[1] 

65 return None 

66 

67 

68def get_domainname(use_resolv_conf=True, resolv_conf_file="/etc/resolv.conf"): 

69 """Get the domain name of the server. 

70 

71 The domain name does not include the hostname. 

72 

73 We try first with the getfqdn method then with the resolv.conf file because 

74 the domain can be found here. 

75 

76 Args: 

77 use_resolv_conf (boolean): if set to True, we use the resolv.conf file. 

78 resolv_conf_file (string): full path of the resolv.conf file (useful 

79 for unit testing). 

80 

81 Returns: 

82 string: the domain name of the server (or None if we can't find it) 

83 

84 """ 

85 tmp = socket.getfqdn(get_simple_hostname()) 

86 if '.' in tmp: 

87 return ".".join(tmp.split('.')[1:]) 

88 if use_resolv_conf: 

89 return _get_domainname_from_resolv_conf(resolv_conf_file) 

90 return None 

91 

92 

93def get_full_hostname(use_resolv_conf=True, 

94 resolv_conf_file="/etc/resolv.conf"): 

95 """Return the "full" hostname of the server. 

96 

97 "full" hostname means "with network domain" appended. 

98 

99 Args: 

100 use_resolv_conf (boolean): if set to True, we use the resolv.conf file 

101 to find the domain name. 

102 resolv_conf_file (string): full path of the resolv.conf file (useful 

103 for unit testing). 

104 

105 Returns: 

106 string: the "full" hostname of the server. 

107 

108 """ 

109 tmp = socket.getfqdn(get_simple_hostname()) 

110 if '.' in tmp or not use_resolv_conf: 

111 return tmp 

112 domainname = get_domainname(use_resolv_conf, resolv_conf_file) 

113 if domainname is not None: 

114 return "%s.%s" % (get_simple_hostname(), domainname) 

115 return get_simple_hostname() 

116 

117 

118def _get_real_ip_netifaces(): 

119 # pylint: disable=E1101 

120 ip = "127.0.0.1" 

121 try: 

122 # we try first with the default gateway 

123 if 'default_gateway' in dir(netifaces): 

124 # netifaces2 

125 def_gw_device = \ 

126 netifaces.default_gateway(old_api=True)[netifaces.AF_INET][1] 

127 else: 

128 # original netifaces 

129 def_gw_device = \ 

130 netifaces.gateways()['default'][netifaces.AF_INET][1] 

131 infos = netifaces.ifaddresses(def_gw_device) 

132 ip = str(infos[netifaces.AF_INET][0]['addr']) 

133 except Exception: 

134 pass 

135 if ip != "127.0.0.1": 

136 return ip 

137 # we try every interface 

138 for interface in netifaces.interfaces(): 

139 try: 

140 infos = netifaces.ifaddresses(interface) 

141 ip = str(infos[netifaces.AF_INET][0]['addr']) 

142 except Exception: 

143 pass 

144 if ip != "127.0.0.1": 

145 return ip 

146 return ip 

147 

148 

149def get_real_ip(): 

150 """Try to find and return the real IP of the server. 

151 

152 We try to avoid to return 127.0.0.1 by examining network interfaces. 

153 

154 Returns: 

155 string: the real IP of the server. 

156 

157 """ 

158 hostname = get_simple_hostname() 

159 ip = get_ip_for_hostname(hostname) 

160 if ip != "127.0.0.1": 

161 return ip 

162 # try to find the real ip (not 127.0.0.1) 

163 return _get_real_ip_netifaces() 

164 

165 

166def ping_tcp_port(host, port, timeout=5): 

167 """Ping a TCP host/port with a configurable timeout. 

168 

169 It's not really a ping but a little connection attempt to see if the 

170 port is open and listened. The timeout is useful when there is a kind 

171 of firewall between. 

172 

173 No Exception are raised in any case. 

174 

175 Args: 

176 host (string): the hostname/ip to ping. 

177 port (int): the TCP port to ping. 

178 timeout (int): timeout in seconds. 

179 

180 Returns: 

181 boolean: True if the port is open and listened. 

182 

183 """ 

184 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 

185 sock.settimeout(timeout) 

186 try: 

187 result = sock.connect_ex((host, port)) 

188 if result == 0: 

189 sock.close() 

190 return True 

191 except Exception: 

192 pass 

193 return False