1// Copyright 2003-2005 Arthur van Hoff, Rick Blair
2// Licensed under Apache License version 2.0
3// Original license LGPL
4
5package javax.jmdns.impl;
6
7import java.io.IOException;
8import java.net.DatagramPacket;
9import java.net.Inet4Address;
10import java.net.Inet6Address;
11import java.net.InetAddress;
12import java.net.NetworkInterface;
13import java.net.UnknownHostException;
14import java.util.ArrayList;
15import java.util.Collection;
16import java.util.List;
17import java.util.logging.Level;
18import java.util.logging.Logger;
19
20import javax.jmdns.NetworkTopologyDiscovery;
21import javax.jmdns.impl.constants.DNSConstants;
22import javax.jmdns.impl.constants.DNSRecordClass;
23import javax.jmdns.impl.constants.DNSRecordType;
24import javax.jmdns.impl.constants.DNSState;
25import javax.jmdns.impl.tasks.DNSTask;
26
27/**
28 * HostInfo information on the local host to be able to cope with change of addresses.
29 *
30 * @author Pierre Frisch, Werner Randelshofer
31 */
32public class HostInfo implements DNSStatefulObject {
33    private static Logger       logger = Logger.getLogger(HostInfo.class.getName());
34
35    protected String            _name;
36
37    protected InetAddress       _address;
38
39    protected NetworkInterface  _interfaze;
40
41    private final HostInfoState _state;
42
43    private final static class HostInfoState extends DNSStatefulObject.DefaultImplementation {
44
45        private static final long serialVersionUID = -8191476803620402088L;
46
47        /**
48         * @param dns
49         */
50        public HostInfoState(JmDNSImpl dns) {
51            super();
52            this.setDns(dns);
53        }
54
55    }
56
57    /**
58     * @param address
59     *            IP address to bind
60     * @param dns
61     *            JmDNS instance
62     * @param jmdnsName
63     *            JmDNS name
64     * @return new HostInfo
65     */
66    public static HostInfo newHostInfo(InetAddress address, JmDNSImpl dns, String jmdnsName) {
67        HostInfo localhost = null;
68        String aName = "";
69        InetAddress addr = address;
70        try {
71            if (addr == null) {
72                String ip = System.getProperty("net.mdns.interface");
73                if (ip != null) {
74                    addr = InetAddress.getByName(ip);
75                } else {
76                    addr = InetAddress.getLocalHost();
77                    if (addr.isLoopbackAddress()) {
78                        // Find local address that isn't a loopback address
79                        InetAddress[] addresses = NetworkTopologyDiscovery.Factory.getInstance().getInetAddresses();
80                        if (addresses.length > 0) {
81                            addr = addresses[0];
82                        }
83                    }
84                }
85                aName = addr.getHostName();
86                if (addr.isLoopbackAddress()) {
87                    logger.warning("Could not find any address beside the loopback.");
88                }
89            } else {
90                aName = addr.getHostName();
91            }
92            if (aName.contains("in-addr.arpa") || (aName.equals(addr.getHostAddress()))) {
93                aName = ((jmdnsName != null) && (jmdnsName.length() > 0) ? jmdnsName : addr.getHostAddress());
94            }
95        } catch (final IOException e) {
96            logger.log(Level.WARNING, "Could not intialize the host network interface on " + address + "because of an error: " + e.getMessage(), e);
97            // This is only used for running unit test on Debian / Ubuntu
98            addr = loopbackAddress();
99            aName = ((jmdnsName != null) && (jmdnsName.length() > 0) ? jmdnsName : "computer");
100        }
101        // A host name with "." is illegal. so strip off everything and append .local.
102        aName = aName.replace('.', '-');
103        aName += ".local.";
104        localhost = new HostInfo(addr, aName, dns);
105        return localhost;
106    }
107
108    private static InetAddress loopbackAddress() {
109        try {
110            return InetAddress.getByName(null);
111        } catch (UnknownHostException exception) {
112            return null;
113        }
114    }
115
116    /**
117     * This is used to create a unique name for the host name.
118     */
119    private int hostNameCount;
120
121    private HostInfo(final InetAddress address, final String name, final JmDNSImpl dns) {
122        super();
123        this._state = new HostInfoState(dns);
124        this._address = address;
125        this._name = name;
126        if (address != null) {
127            try {
128                _interfaze = NetworkInterface.getByInetAddress(address);
129            } catch (Exception exception) {
130                logger.log(Level.SEVERE, "LocalHostInfo() exception ", exception);
131            }
132        }
133    }
134
135    public String getName() {
136        return _name;
137    }
138
139    public InetAddress getInetAddress() {
140        return _address;
141    }
142
143    Inet4Address getInet4Address() {
144        if (this.getInetAddress() instanceof Inet4Address) {
145            return (Inet4Address) _address;
146        }
147        return null;
148    }
149
150    Inet6Address getInet6Address() {
151        if (this.getInetAddress() instanceof Inet6Address) {
152            return (Inet6Address) _address;
153        }
154        return null;
155    }
156
157    public NetworkInterface getInterface() {
158        return _interfaze;
159    }
160
161    public boolean conflictWithRecord(DNSRecord.Address record) {
162        DNSRecord.Address hostAddress = this.getDNSAddressRecord(record.getRecordType(), record.isUnique(), DNSConstants.DNS_TTL);
163        if (hostAddress != null) {
164            return hostAddress.sameType(record) && hostAddress.sameName(record) && (!hostAddress.sameValue(record));
165        }
166        return false;
167    }
168
169    synchronized String incrementHostName() {
170        hostNameCount++;
171        int plocal = _name.indexOf(".local.");
172        int punder = _name.lastIndexOf('-');
173        _name = _name.substring(0, (punder == -1 ? plocal : punder)) + "-" + hostNameCount + ".local.";
174        return _name;
175    }
176
177    boolean shouldIgnorePacket(DatagramPacket packet) {
178        boolean result = false;
179        if (this.getInetAddress() != null) {
180            InetAddress from = packet.getAddress();
181            if (from != null) {
182                if (from.isLinkLocalAddress() && (!this.getInetAddress().isLinkLocalAddress())) {
183                    // Ignore linklocal packets on regular interfaces, unless this is
184                    // also a linklocal interface. This is to avoid duplicates. This is
185                    // a terrible hack caused by the lack of an API to get the address
186                    // of the interface on which the packet was received.
187                    result = true;
188                }
189                if (from.isLoopbackAddress() && (!this.getInetAddress().isLoopbackAddress())) {
190                    // Ignore loopback packets on a regular interface unless this is also a loopback interface.
191                    result = true;
192                }
193            }
194        }
195        return result;
196    }
197
198    DNSRecord.Address getDNSAddressRecord(DNSRecordType type, boolean unique, int ttl) {
199        switch (type) {
200            case TYPE_A:
201                return this.getDNS4AddressRecord(unique, ttl);
202            case TYPE_A6:
203            case TYPE_AAAA:
204                return this.getDNS6AddressRecord(unique, ttl);
205            default:
206        }
207        return null;
208    }
209
210    private DNSRecord.Address getDNS4AddressRecord(boolean unique, int ttl) {
211        if ((this.getInetAddress() instanceof Inet4Address) || ((this.getInetAddress() instanceof Inet6Address) && (((Inet6Address) this.getInetAddress()).isIPv4CompatibleAddress()))) {
212            return new DNSRecord.IPv4Address(this.getName(), DNSRecordClass.CLASS_IN, unique, ttl, this.getInetAddress());
213        }
214        return null;
215    }
216
217    private DNSRecord.Address getDNS6AddressRecord(boolean unique, int ttl) {
218        if (this.getInetAddress() instanceof Inet6Address) {
219            return new DNSRecord.IPv6Address(this.getName(), DNSRecordClass.CLASS_IN, unique, ttl, this.getInetAddress());
220        }
221        return null;
222    }
223
224    DNSRecord.Pointer getDNSReverseAddressRecord(DNSRecordType type, boolean unique, int ttl) {
225        switch (type) {
226            case TYPE_A:
227                return this.getDNS4ReverseAddressRecord(unique, ttl);
228            case TYPE_A6:
229            case TYPE_AAAA:
230                return this.getDNS6ReverseAddressRecord(unique, ttl);
231            default:
232        }
233        return null;
234    }
235
236    private DNSRecord.Pointer getDNS4ReverseAddressRecord(boolean unique, int ttl) {
237        if (this.getInetAddress() instanceof Inet4Address) {
238            return new DNSRecord.Pointer(this.getInetAddress().getHostAddress() + ".in-addr.arpa.", DNSRecordClass.CLASS_IN, unique, ttl, this.getName());
239        }
240        if ((this.getInetAddress() instanceof Inet6Address) && (((Inet6Address) this.getInetAddress()).isIPv4CompatibleAddress())) {
241            byte[] rawAddress = this.getInetAddress().getAddress();
242            String address = (rawAddress[12] & 0xff) + "." + (rawAddress[13] & 0xff) + "." + (rawAddress[14] & 0xff) + "." + (rawAddress[15] & 0xff);
243            return new DNSRecord.Pointer(address + ".in-addr.arpa.", DNSRecordClass.CLASS_IN, unique, ttl, this.getName());
244        }
245        return null;
246    }
247
248    private DNSRecord.Pointer getDNS6ReverseAddressRecord(boolean unique, int ttl) {
249        if (this.getInetAddress() instanceof Inet6Address) {
250            return new DNSRecord.Pointer(this.getInetAddress().getHostAddress() + ".ip6.arpa.", DNSRecordClass.CLASS_IN, unique, ttl, this.getName());
251        }
252        return null;
253    }
254
255    @Override
256    public String toString() {
257        StringBuilder buf = new StringBuilder(1024);
258        buf.append("local host info[");
259        buf.append(getName() != null ? getName() : "no name");
260        buf.append(", ");
261        buf.append(getInterface() != null ? getInterface().getDisplayName() : "???");
262        buf.append(":");
263        buf.append(getInetAddress() != null ? getInetAddress().getHostAddress() : "no address");
264        buf.append(", ");
265        buf.append(_state);
266        buf.append("]");
267        return buf.toString();
268    }
269
270    public Collection<DNSRecord> answers(boolean unique, int ttl) {
271        List<DNSRecord> list = new ArrayList<DNSRecord>();
272        DNSRecord answer = this.getDNS4AddressRecord(unique, ttl);
273        if (answer != null) {
274            list.add(answer);
275        }
276        answer = this.getDNS6AddressRecord(unique, ttl);
277        if (answer != null) {
278            list.add(answer);
279        }
280        return list;
281    }
282
283    /**
284     * {@inheritDoc}
285     */
286    @Override
287    public JmDNSImpl getDns() {
288        return this._state.getDns();
289    }
290
291    /**
292     * {@inheritDoc}
293     */
294    @Override
295    public boolean advanceState(DNSTask task) {
296        return this._state.advanceState(task);
297    }
298
299    /**
300     * {@inheritDoc}
301     */
302    @Override
303    public void removeAssociationWithTask(DNSTask task) {
304        this._state.removeAssociationWithTask(task);
305    }
306
307    /**
308     * {@inheritDoc}
309     */
310    @Override
311    public boolean revertState() {
312        return this._state.revertState();
313    }
314
315    /**
316     * {@inheritDoc}
317     */
318    @Override
319    public void associateWithTask(DNSTask task, DNSState state) {
320        this._state.associateWithTask(task, state);
321    }
322
323    /**
324     * {@inheritDoc}
325     */
326    @Override
327    public boolean isAssociatedWithTask(DNSTask task, DNSState state) {
328        return this._state.isAssociatedWithTask(task, state);
329    }
330
331    /**
332     * {@inheritDoc}
333     */
334    @Override
335    public boolean cancelState() {
336        return this._state.cancelState();
337    }
338
339    /**
340     * {@inheritDoc}
341     */
342    @Override
343    public boolean closeState() {
344        return this._state.closeState();
345    }
346
347    /**
348     * {@inheritDoc}
349     */
350    @Override
351    public boolean recoverState() {
352        return this._state.recoverState();
353    }
354
355    /**
356     * {@inheritDoc}
357     */
358    @Override
359    public boolean isProbing() {
360        return this._state.isProbing();
361    }
362
363    /**
364     * {@inheritDoc}
365     */
366    @Override
367    public boolean isAnnouncing() {
368        return this._state.isAnnouncing();
369    }
370
371    /**
372     * {@inheritDoc}
373     */
374    @Override
375    public boolean isAnnounced() {
376        return this._state.isAnnounced();
377    }
378
379    /**
380     * {@inheritDoc}
381     */
382    @Override
383    public boolean isCanceling() {
384        return this._state.isCanceling();
385    }
386
387    /**
388     * {@inheritDoc}
389     */
390    @Override
391    public boolean isCanceled() {
392        return this._state.isCanceled();
393    }
394
395    /**
396     * {@inheritDoc}
397     */
398    @Override
399    public boolean isClosing() {
400        return this._state.isClosing();
401    }
402
403    /**
404     * {@inheritDoc}
405     */
406    @Override
407    public boolean isClosed() {
408        return this._state.isClosed();
409    }
410
411    /**
412     * {@inheritDoc}
413     */
414    @Override
415    public boolean waitForAnnounced(long timeout) {
416        return _state.waitForAnnounced(timeout);
417    }
418
419    /**
420     * {@inheritDoc}
421     */
422    @Override
423    public boolean waitForCanceled(long timeout) {
424        if (_address == null) {
425            // No need to wait this was never announced.
426            return true;
427        }
428        return _state.waitForCanceled(timeout);
429    }
430
431}
432