1 module myip.public_; 2 3 import std.datetime : Clock, dur; 4 import std.file : tempDir, exists, isFile, read, write; 5 import std.path : buildPath; 6 import std.socket : Socket, SocketException, TcpSocket, Address, InternetAddress, Internet6Address, AddressFamily, SocketOption, SocketOptionLevel; 7 import std.string : indexOf, strip; 8 9 private immutable string cache; 10 11 shared static this() { 12 cache = buildPath(tempDir(), ".dub.my-ip"); 13 } 14 15 struct Service { 16 17 enum ipify = Service("api.ipify.org", "/"); /// https://www.ipify.org/ 18 enum plain_text_ip = Service("plain-text-ip.com", "/"); /// http://about.plain-text-ip.com/ 19 enum icanhazip = Service("icanhazip.com", "/"); /// http://icanhazip.com/ 20 enum whatismyipaddress = Service("bot.whatismyipaddress.com", "/"); /// https://whatismyipaddress.com/api 21 enum amazonws = Service("checkip.amazonaws.com", "/"); /// http://checkip.amazonaws.com/ 22 23 string host; 24 string path; 25 26 } 27 28 @safe string publicAddressImpl(Service service, AddressFamily addressFamily) { 29 30 Address address = { 31 switch(addressFamily) { 32 case AddressFamily.INET: return cast(Address)new InternetAddress(service.host, 80); 33 case AddressFamily.INET6: return cast(Address)new Internet6Address(service.host, 80); 34 default: throw new SocketException("Invalid address family"); 35 } 36 }(); 37 38 Socket socket = new TcpSocket(addressFamily); 39 socket.blocking = true; 40 socket.setOption(SocketOptionLevel.SOCKET, SocketOption.SNDTIMEO, dur!"seconds"(5)); 41 socket.setOption(SocketOptionLevel.SOCKET, SocketOption.SNDTIMEO, dur!"seconds"(5)); 42 socket.connect(address); 43 scope(exit) socket.close(); 44 45 if(socket.send("GET " ~ service.path ~ " HTTP/1.1\r\nHost: " ~ service.host ~ "\r\nAccept: text/plain\r\n\r\n") != Socket.ERROR) { 46 47 char[] buffer = new char[512]; 48 ptrdiff_t recv, body_; 49 50 if((recv = socket.receive(buffer)) != Socket.ERROR && (body_ = buffer[0..recv].indexOf("\r\n\r\n")) != -1) return buffer[body_+4..recv].idup.strip; 51 52 } 53 54 throw new SocketException("Could not send or receive data"); 55 56 } 57 58 @trusted string publicAddress(Service service=Service.ipify, AddressFamily addressFamily=AddressFamily.INET) { 59 if(exists(cache) && isFile(cache)) { 60 void[] data = read(cache); 61 if(data.length > 4) { 62 if((cast(int[])data[0..4])[0] + 60 * 60 > Clock.currTime.toUnixTime!int) { 63 // cached less that one hour ago 64 return cast(string)data[4..$]; 65 } 66 } 67 } 68 try { 69 string ret = publicAddressImpl(service, addressFamily); 70 write(cache, cast(void[])[Clock.currTime.toUnixTime!int] ~ cast(void[])ret); 71 return ret; 72 } catch(SocketException) { 73 return ""; 74 } 75 } 76 77 @safe string publicAddress(AddressFamily addressFamily) { 78 return publicAddress(addressFamily); 79 } 80 81 @safe string publicAddress4(Service service=Service.ipify) { 82 return publicAddress(service, AddressFamily.INET); 83 } 84 85 @safe string publicAddress6(Service service=Service.ipify) { 86 return publicAddress(service, AddressFamily.INET6); 87 }