001package net.gdface.utils;
002import java.io.BufferedReader;
003import java.io.IOException;
004import java.io.InputStream;
005import java.io.InputStreamReader;
006import java.net.HttpURLConnection;
007import java.net.InetAddress;
008import java.net.NetworkInterface;
009import java.net.Socket;
010import java.net.SocketException;
011import java.net.URL;
012import java.net.UnknownHostException;
013import java.util.Iterator;
014import java.util.List;
015import java.util.Set;
016
017import static com.google.common.base.Preconditions.*;
018
019import com.google.common.collect.ImmutableSet;
020import com.google.common.collect.Iterators;
021
022import com.google.common.base.Predicates;
023
024import com.google.common.base.Joiner;
025import com.google.common.base.MoreObjects;
026import com.google.common.base.Predicate;
027import com.google.common.collect.Lists;
028import com.google.common.primitives.Bytes;
029
030import com.google.common.base.Function;
031
032/**
033 * 网络管理工具类
034 * @author guyadong
035 * @since 1.0.7
036 *
037 */
038public class NetworkUtil {
039    public static final String DEFAULT_HOST = "localhost";
040    public static enum Radix{
041        /** 二进制 */BIN(2),
042        /** 十进制 */DEC(10),
043        /** 十六进制 */HEX(16);
044        public final int value;
045        Radix(int radix){
046            this.value = radix;
047        }
048    }
049    public static enum Filter implements Predicate<NetworkInterface>{
050        /** 过滤器: 所有网卡 */ALL,
051        /** 过滤器: 在线设备,see also {@link NetworkInterface#isUp()} */UP,
052        /** 过滤器: 虚拟接口,see also {@link NetworkInterface#isVirtual()} */VIRTUAL,
053        /** 过滤器:LOOPBACK, see also {@link NetworkInterface#isLoopback()} */LOOPBACK,
054        /** 过滤器:物理网卡 */PHYICAL_ONLY;
055
056        @Override
057        public boolean apply(NetworkInterface input) {
058            if(null == input ){
059                return false;
060            }
061            try{
062                switch(this){
063                case UP:
064                    return input.isUp();
065                case VIRTUAL:
066                    return input.isVirtual();
067                case LOOPBACK:
068                    return input.isLoopback();
069                case PHYICAL_ONLY :{
070                    byte[] hardwareAddress = input.getHardwareAddress();
071                    return null != hardwareAddress 
072                            && hardwareAddress.length > 0 
073                            && !input.isVirtual() 
074                            && !isVMMac(hardwareAddress);
075                }
076                case ALL:
077                default :
078                    return true;
079                }
080            } catch (SocketException e) {
081                throw new RuntimeException(e);
082            }
083        }
084    }
085    /**
086     * 根据过滤器{@code filters}指定的条件(AND)返回网卡设备对象
087     * @param filters
088     * @return
089     */
090    @SafeVarargs
091    @SuppressWarnings("unchecked")
092    public static Set<NetworkInterface> getNICs(Predicate<NetworkInterface> ...filters) {
093        if(null == filters){
094            filters = new Predicate[]{Filter.ALL};
095        }
096        try {
097            Iterator<NetworkInterface> filtered = Iterators.filter(
098                    Iterators.forEnumeration(NetworkInterface.getNetworkInterfaces()),
099                    Predicates.and(filters));
100            return ImmutableSet.copyOf(filtered);
101        } catch (SocketException e) {
102            throw new RuntimeException(e);
103        } 
104    }
105    /**
106     * 返回所有物理网卡
107     * @return
108     */
109    public static Set<NetworkInterface> getPhysicalNICs() {
110        return getNICs(Filter.PHYICAL_ONLY,Filter.UP);
111    }
112    /**
113     * 将{@code byte[]} 转换为{@code radix}指定格式的字符串
114     * 
115     * @param source 
116     * @param separator 分隔符
117     * @param radix 进制基数
118     * @return {@code source}为{@code null}时返回空字符串
119     */
120    public static final String format(byte[] source,String separator, final Radix radix) {
121        if (null == source){
122            return "";
123        }
124        if(null == separator){
125            separator = "";
126        }
127        List<String> hex = Lists.transform(Bytes.asList(source),new Function<Byte,String>(){
128            @Override
129            public String apply(Byte input) {
130                switch (radix) {
131                                case HEX:
132                                        return String.format("%02x", input & 0xff);
133                                default:
134                        return Integer.toString(input & 0xff, radix.value);
135                                }
136            }});
137        return Joiner.on(separator).join(hex);
138    }
139    /** 
140     * MAC地址格式(16进制)格式化{@code source}指定的字节数组 
141     * @see #format(byte[], String, Radix)
142     */
143    public static final String formatMac(byte[] source,String separator) {
144        return format(source,separator,Radix.HEX);
145    }
146    /** 
147     * 以IP地址格式(点分位)格式化{@code source}指定的字节数组<br>
148     * @see #format(byte[], String, Radix) 
149     */
150    public static final String formatIp(byte[] source) {
151        return format(source,".",Radix.DEC);
152    }
153    /**
154     * 返回指定{@code address}绑定的网卡的物理地址(MAC)
155     * @param address
156     * @return 指定的{@code address}没有绑定在任何网卡上返回{@code null}
157     * @see NetworkInterface#getByInetAddress(InetAddress)
158     * @see NetworkInterface#getHardwareAddress()
159     */
160    public static byte[] getMacAddress(InetAddress address) {
161        try {
162            NetworkInterface nic = NetworkInterface.getByInetAddress(address);
163            return null == nic ? null  : nic.getHardwareAddress();
164        } catch (SocketException e) {
165            throw new RuntimeException(e);
166        }
167    }
168    /**
169     * @param nic 网卡对象
170     * @param separator 格式化分隔符
171     * @return 表示MAC地址的字符串
172     */
173    public static String getMacAddress(NetworkInterface nic,String separator) {
174        try {
175            return format(nic.getHardwareAddress(),separator, Radix.HEX);
176        } catch (SocketException e) {
177            throw new RuntimeException(e);
178        }
179    }
180    /**
181     * 参见 {@link #getMacAddress(InetAddress)}
182     * @param address
183     * @param separator 格式化分隔符
184     * @return 表示MAC地址的字符串
185     */
186    public static String getMacAddress(InetAddress address,String separator) {
187        return format(getMacAddress(address),separator, Radix.HEX);        
188    }
189    private static byte invalidMacs[][] = {
190            /** VMWare */{0x00, 0x05, 0x69},             
191            /** VMWare */{0x00, 0x1C, 0x14},             
192            /** VMWare */{0x00, 0x0C, 0x29},             
193            /** VMWare */{0x00, 0x50, 0x56},             
194            /** Virtualbox */{0x08, 0x00, 0x27},         
195            /** Virtualbox */{0x0A, 0x00, 0x27},         
196            /** Virtual-PC */{0x00, 0x03, (byte)0xFF},   
197            /** Hyper-V */{0x00, 0x15, 0x5D}             
198    };
199    private static boolean isVMMac(byte[] mac) {
200        if(null == mac) {
201            return false;
202        }
203        
204        for (byte[] invalid: invalidMacs){
205            if (invalid[0] == mac[0] && invalid[1] == mac[1] && invalid[2] == mac[2]) {
206                return true;
207            }
208        }
209        return false;
210    }
211    /** 判断{@code host}是否为localhost */
212    public static final boolean isLoopbackAddress(String host) {
213        return "127.0.0.1".equals(host) 
214                || "::1".equals(host) 
215                || DEFAULT_HOST.equals(host);
216    }
217    /** 判断{@code address}是否为本机地址 */
218    public static final boolean isLocalhost(InetAddress address) {
219        try {
220            return address.isLoopbackAddress() 
221                    || InetAddress.getLocalHost().getHostAddress().equals( address.getHostAddress()) ;
222        } catch (UnknownHostException e) {
223            throw new RuntimeException(e);
224        }
225    }
226    /** 判断{@code address}是否为本机地址 */
227    public static final boolean isLocalhost(String host) {
228        try {
229            return isLoopbackAddress(host) || isLocalhost(InetAddress.getByName(checkNotNull(host)));
230        } catch (UnknownHostException e) {
231            return false;
232        }
233    }
234    /** 如果{@code host}为localhost转换为{@value #DEFAULT_HOST} */
235    public  static final String convertHost(String host) {
236        return isLoopbackAddress(host)? DEFAULT_HOST : host;
237    }
238    
239    /** 遍历所有物理网上绑定的地址,判断{@code address}是否为本机网卡绑定的地址 */
240    public static boolean selfBind(final InetAddress address){
241        if(isLocalhost(address)){
242            return true;
243        }
244        final Predicate<InetAddress> filter = new Predicate<InetAddress>(){
245            @Override
246            public boolean apply(InetAddress input) {
247                return input.getHostAddress().equals(address.getHostAddress());
248        }};
249        return Iterators.tryFind(getPhysicalNICs().iterator(),new Predicate<NetworkInterface>(){
250            @Override
251            public boolean apply(NetworkInterface input) {
252                return Iterators.tryFind(
253                        Iterators.forEnumeration(input.getInetAddresses()), 
254                        filter).isPresent();
255            }}).isPresent();
256    }
257    /** see also {@link #selfBind(InetAddress)} */
258    public static boolean selfBind(String host){
259        try {
260            return selfBind(InetAddress.getByName(host));
261        } catch (UnknownHostException e) {
262            return false;
263        }
264    }        
265        /**
266         * 获取访问指定host的当前网卡物理地址
267         * @param host
268         * @param port
269         * @return
270         * @throws IOException
271         */
272        public static byte[] getCurrentMac(String host,int port) throws IOException {
273                Socket socket = null;
274                try {
275                        socket = new Socket(host,port);
276                        InetAddress address = socket.getLocalAddress();
277                        NetworkInterface nic = NetworkInterface.getByInetAddress(address);
278                        return nic.getHardwareAddress();
279                } finally{
280                        if(socket != null){
281                                socket.close();
282                        }
283                }
284        }
285        /**
286         * 向指定的url发送http请求
287         * @param url
288         * @param requestType 请求类型,see {@link HttpURLConnection#setRequestMethod(String)}
289         * @return 返回响应数据,请求失败返回{@code null}
290         */
291        public static String sendHttpRequest(URL url,String requestType) {
292        
293            HttpURLConnection con = null;  
294        
295            BufferedReader buffer = null; 
296            StringBuffer resultBuffer = null;  
297        
298            try {
299                //得到连接对象
300                con = (HttpURLConnection) url.openConnection(); 
301                //设置请求类型
302                con.setRequestMethod(requestType);  
303                //设置请求需要返回的数据类型和字符集类型
304                con.setRequestProperty("Content-Type", "application/json;charset=UTF-8");  
305                //允许写出
306                con.setDoOutput(true);
307                //允许读入
308                con.setDoInput(true);
309                //不使用缓存
310                con.setUseCaches(false);
311                //得到响应码
312                int responseCode = con.getResponseCode();
313        
314                if(responseCode == HttpURLConnection.HTTP_OK){
315                    //得到响应流
316                    InputStream inputStream = con.getInputStream();
317                    //将响应流转换成字符串
318                    resultBuffer = new StringBuffer();
319                    String line;
320                    buffer = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"));
321                    while ((line = buffer.readLine()) != null) {
322                        resultBuffer.append(line);
323                    }
324                    return resultBuffer.toString();
325                }
326        
327            }catch(Exception e) {
328            }finally {
329                        if (con != null){
330                                con.disconnect();
331                        }
332                }
333            return null;
334        }
335        /**
336         * 连接测试返回状态
337         * @author guyadong
338         *
339         */
340        public static enum ConnectStatus{               
341                /** 可连接,http响应有效 */CONNECTABLE,
342                /** 可连接,http响应无效 */INVALID_RESPONE,
343                /** 连接失败 */FAIL
344        }
345        /**
346         * 测试http连接是否可连接<br>
347         * 连接失败返回{@link ConnectStatus#FAIL},
348         * 建立连接后用
349         * {@code responseValidator}验证响应数据,{@code responseValidator}返回{@code true}则连接有效返回{@link ConnectStatus#CONNECTABLE},
350         * {@code responseValidator}返回{@code false}则连接无效返回{@link ConnectStatus#INVALID_RESPONE} ,
351         * 
352         * @param url 测试的url
353         * @param responseValidator 用于验证响应数据是否有效的验证器,
354         * 为{@code null}时,只要连接成功就返回{@link ConnectStatus#CONNECTABLE}
355         * @return 连接状态{@link ConnectStatus}
356         */
357        public static ConnectStatus testHttpConnect(URL url,Predicate<String> responseValidator){
358                String reponse = sendHttpRequest(url,"GET");
359                responseValidator = MoreObjects.firstNonNull(responseValidator, Predicates.<String>alwaysTrue());
360                return reponse == null 
361                                ? ConnectStatus.FAIL : 
362                                        (responseValidator.apply(reponse) 
363                                                ? ConnectStatus.CONNECTABLE : ConnectStatus.INVALID_RESPONE);
364        }
365        /**
366         * 测试http连接是否可连接
367         * @param url 测试的url
368         * @param responseValidator
369         * @return 连接状态{@link ConnectStatus}
370         * @see #testHttpConnect(URL, Predicate)
371         */
372        public static ConnectStatus testHttpConnect(String url,Predicate<String> responseValidator){
373                try {
374                        return testHttpConnect(new URL(url),responseValidator);
375                } catch (Exception e) {
376                        return ConnectStatus.FAIL;
377                }
378        }
379        /**
380         * 测试http连接是否可连接
381         * @param url 测试的url
382         * @param responseValidator
383         * @return 连接状态{@link ConnectStatus}
384         * @see #testHttpConnect(URL, Predicate)
385         */
386        public static ConnectStatus testHttpConnect(String host,int port,Predicate<String> responseValidator){
387                try {
388                        return testHttpConnect(new URL("http",host,port,""), responseValidator);
389                } catch (Exception e) {
390                        return ConnectStatus.FAIL;
391                }
392        }
393        /**
394         * 测试http连接是否可连接
395         * @param url
396         * @param responseValidator
397         * @return 连接成功{@link ConnectStatus#CONNECTABLE}返回{@code true},
398         * 连接失败{@link ConnectStatus#FAIL}返回{@code false}
399         * 响应无效{@link ConnectStatus#INVALID_RESPONE}抛出异常
400         * @see #testHttpConnect(URL, Predicate)
401         * @throws IllegalStateException 连接响应无效
402         */
403        public static boolean testHttpConnectChecked(URL url,Predicate<String> responseValidator){
404                ConnectStatus status = testHttpConnect(url,responseValidator);
405                checkState(status != ConnectStatus.INVALID_RESPONE,"INVALID INVALID_RESPONE from %s",url);
406                return status == ConnectStatus.CONNECTABLE;
407        }
408        /**
409         * 测试http连接是否可连接
410         * @param url
411         * @param responseValidator
412         * @return
413         * @see #testHttpConnectChecked(URL, Predicate)
414         * @throws IllegalStateException 连接响应无效,连接状态为 {@link ConnectStatus#INVALID_RESPONE}时
415         */
416        public static boolean testHttpConnectChecked(String url,Predicate<String> responseValidator){
417                try {
418                        return testHttpConnectChecked(new URL(url),responseValidator);
419                } catch (Exception e) {
420                        return false;
421                }
422        }
423        /**
424         * 测试http连接是否可连接
425         * @param host
426         * @param port
427         * @param responseValidator
428         * @return
429         * @see #testHttpConnectChecked(URL, Predicate)
430         * @throws IllegalStateException 连接响应无效,连接状态为 {@link ConnectStatus#INVALID_RESPONE}时
431         */
432        public static boolean testHttpConnectChecked(String host,int port,Predicate<String> responseValidator){
433                try {
434                        return testHttpConnectChecked(new URL("http",host,port,""), responseValidator);
435                } catch (Exception e) {
436                        return false;
437                }
438        }
439}