TL; DR


A Smarter IP Change Detector

This is an free API by me. It allows you to check the change of your IP address, with a bit more elegance. And you are not letting go any privacy, too.

Here’s the usage:

https://ip.via.moe/me?id=<IDENTIFIER>

where <IDENTIFIER> is created by yourself. It can be any string between length 8, 32 (inclusive). When the API detects an <IDENTIFIER> is accessed with a different IP than the previous one, it returns true (text/plain). You should have guessed, <IDENTIFIER> is supposed to be unique. Simple enough!

Example code:

Python (3, with requests):

import requests, time, string, random
my_identifier = ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(32))
while True:
    if requests.get('https://ip.via.moe/me?id=' + my_identifier).text == 'true':
        print('IP Changed!')
    time.sleep(300)

Shell script (with curl):

ID=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 32 | head -n 1)
while true
do
    if [ "true" = $(/usr/bin/curl -s "https://ip.via.moe/me?id=$ID") ]; then
        echo "IP Changed!"
    fi  
    /bin/sleep 300 
done


Just want Plain IP Echo?

Simply use:

https://ip.via.moe/

For more information on this IP Echo, see this post.


More Details

HTTP Method

All requests should be sent with GET method.


Query Parameters Documentation

  • id
    • Required. The key which IP change detection is based on. Length must be >= 8 and <= 32. On receiving requests, the server compares the source IP of request with the previous IP in the database associated with the same id. If they are different, return true. If they are the same, return false. If there is no such key in the database, return <on_new_id> (see below). Then the server updates the corresponding record in the database. Note that true is returned only once when new IP is detected.
    • TTL: An id and its associated IP address will be removed from the database if there is no new request with it, after 1 week.
  • on_new_id
    • Optional. If present, the only value allowed for it is true. If not present, the server will take the default value new_id. This value is returned to the client when the server sees a new id which is not in the database.

The above are the available parameters for now.


Availability

This service is totally free… and it may disappear someday, if I really want it to disappear. Also, please do wait for some time between requests.


Privacy

This service is hosted by AWS Lambda, and DynamoDB is used for data storage. There is no log enabled. Identifiers and IP addresses in the database are hashed. No other data is stored. An item in the database looks like:

db_item


Source Code

Well… I thought for a while about this, because the code is… too simple.

import boto3
import time
import hashlib

dynamo = boto3.client('dynamodb')
table_name = 'ip_via_moe'


def respond(res, status=200, err=None):
    return {
        'statusCode': '400' if err else status,
        'body': err if err else res,
        'headers': {
            'Content-Type': 'text/plain',
        },
    }


def wrap_str(string):
    return {'S': string}

def wrap_num(n):
    return {'N': str(n)}


def lambda_handler(event, context):
    params = event['queryStringParameters']
    id = params.get('id')
        
    if len(id) < 8:
        return respond(None, err='id too short; minimum: 8')
        
    if len(id) > 32:
        return respond(None, err='id too long; maximum: 32')
        
    id = hashlib.sha1(bytes(id, 'utf-8')).hexdigest()[:16]
    
    current_ip = event['requestContext']['identity']['sourceIp']
    current_ip = hashlib.sha1(bytes(current_ip, 'utf-8')).hexdigest()[:16]
    
    on_new_id = params.get('on_new_id')
    if on_new_id != 'true':
        on_new_id = 'new_id'
        
    db_item = dynamo.put_item(TableName=table_name,
        ReturnValues='ALL_OLD',
        Item={
            'identifier': wrap_str(id),
            'ip': wrap_str(current_ip),
            'expire': wrap_num(int(time.time()) + 604800)  # 1 week
        })
    
    if db_item.get('Attributes'):
        if db_item['Attributes']['ip']['S'] != current_ip:
            return respond('true')
        else:
            return respond('false')
    else:
        return respond(on_new_id)

Less than 60 lines!