Sunday, December 1, 2013

Connecting Cable Modem to Two routers

Sometimes we want to split our home private network to two separate subnets, but want to maintain connectivity to the Internet/outside world. The following article tries to explain the basics and internals of most home WiFi routers in the market.

First, let see what are the basic components of a Wi-Fi router:



The following is an example of topology and connection of typical home network:





Router-1: Linksys WRT-54G running DD-WRT firmware
Router-2: NETGEAR Genie WNDR3400v2

The cable modem (DOCSIS modem) is connected to cable provider thru coax cable and its main function is to modulate/demodulate DOCSIS signal to regular ethernet frames (it might bind multiple channels [channel bonding] to increase bandwidth).  The cable provider assigns a public IP address to us to use.  This single public IP cannot be shared if we don't use router.

Router-1 and Router-2 see packets coming from modem, but they don't know how to route them to our devices at home yet.  Assume router-1 is the router that assigns DHCP IP address (running DHCP server), while DHCP server on router-1 is set to forwarder to router-2.  Router-2 is chosen as the main router because it has more recent hardware, supports 11n Wi-fi and supports IPv6 (PS3 is actually better to be connected to router-2 to lower the latency).

Other clients such as as Ooma, Roku and PS3 don't support IPv6 yet, so it's Ok to connect them to Router-2.

On Router-1:  
  • Set DHCP to DHCP forwarder (forward DHCP requests) to router-2 IP address
  • Set router IP address to 192.168.0.1/24
  • Set DNS to either public DNS (such as Google: 8.8.8.8, opendns's IPs or our own local DNS server)
  • Operation mode: router
  • Set wireless to bridged mode (so router-1's wifi is like another L2 ethernet device in our private LAN)
  • WAN type: Static IP.  If it is set to DHCP and the WAN port of router-1 is connected to the ethernet bridge, DHCP server at the ISP site may deny the request or even worse, shutdown the connection completely (thus require modem reboot to fix the problem).
  • Set WAN static IP to router-2 ip address.  This way, we assume router-2 is the gateway and delegates NAT work to router-2 (that's why we set the operation mode of router-1 to "router")


Router-2:
  • Enable AP mode
  • Set router IP address to 192.168.1.1/24
  • Internet IP: Get dynamically from ISP
  • Set DMZ to Router-1

How it works:

A tablet is trying to connect to the Internet via router-2.  All Wi-fi transactions have been done and now it sends DHCP request to router-2.  If this is an initial request made to router-2, router-2 doesn't learn any IP yet so it first learns for the tablet's MAC. Because router-2 runs local dhcp server, it snoops any DHCP request and serves it.  In this case, router-2 then assigns an address in the subnet 192.168.1.0/24 along with DNS addresses and gateway IP (in this case, an ISP-assigned IP address) back to the tablet as DHCP RESP packet.

Now the tablet has a valid private address, now it can connect to the Internet via router-2.  All requests from the tablet is network-translated to public address and vice-versa.  So, if the public IP is a.b.c.d, tablet' IP address is 192.168.1.5 and it is requesting access to www.google.com (http www.google.com:80) from a local tcp port xxxx, the actual packet in the public wire is "a.b.c.d:yyyy", where xxxx is a the original tcp port and yyyy is the translated tcp port by NAT (router-2 maps local to public IP via port) [ Click this for more info about NAT/PAT mechanism].

OK, everything seems to work.  Wait...what about all other devices connected to router-1? What happens if we want to watch Netflix on Roku?

When Roku device is turned on, it sends DHCP REQ similar to tablet above.  The sequence is the same, but the difference is, because router-1 doesn't run DHCP server, all DHCP packets are forwarded to router-2.  So, it is assigned an IP address in the same subnet (sorry, the diagram above is wrong, the subnet should be the same).  When the Roku starts sending tcp packets, the packets not-intended to the private subnet and coming to router-1 are assumed to be forwarded to its gateway (router-2).  Router-1 sees these packets coming from its DMZ and do the same translation to public IP.

An alternative is to subtend router-1 to router-2 directly, not via ethernet switch.  The rest is the same.



Sunday, November 10, 2013

AT&T U-Verse with external wifi router running DD-WRT software



I don't like the way wi-fi connection is handled by CPE (Customer Premise Equipment, such as the provider's home gateway/router), so I want to use the Wi-Fi capability of Linksys.  DD-WRT gives features in handling L2 connections (MAC filtering etc.),  but I still want the CPE to handle DHCP and NAT services.  Basically, I just want to make the Linksys router acts like a Wi-Fi and Wired switch, as an extender of the existing CPE.

 The objects in yellow box represents component in the Wi-Fi router (in this case, a Linksys WRT54G running DD-WRT firmware).

DHCP server on U-Verse CPE is configured to give IPs in 192.168.0.x subnet.  The CPE address is set manually to 192.168.0.1

Here's what I want:
CPE internal IP address = 192.168.0.1
Linksys Internal IP address = 192.168.0.2
IP range for Clients = 192.168.0.3 - 192.168.0.254

CPE setting:
  • Wireless  disabled
  • Configure DHCP to assign IP range: 192.168.0.3 - 192.168.254

DD-WRT settings:
  • WAN connection type = disable
  • Local IP = 192.168.0.2/24
  • DHCP server = forwarding to 19.168.0.1 (CPE)
  • Check option box to assign WAN port to switch (NAT is thus disabled; it is now acting like a pass-thru to switch)
  • Wiress network configuration = bridged (so all Wi-Fi clients are seen by CPE as they're directly connected)
  • Wiress Tx Power = 250 mW
  • Advanced routing = router (doesn't matter actually)
  • Disable CPI firewall
  • Routing = disable
This way, all Wi-Fi is handled by Linksys router/switch, but only its L1-L2 layers.  Everything else is handled by the CPE.  We can also relocate the Linksys somewhere else, no need to be close to CPE as long as we have long ethernet cable or by using Powerline extender.

Monday, October 21, 2013

Which PC/gadget has the highest pixel resolution?

Here I have collected and calculated PPI (Pixels per Inch) for some laptops and tablets.



Screen Diameter (inch)Screen Ratio (Width/Height)Angle (rad)Screen widthScreen HeightNumber Of Pixels (Horizontal)Number Of Pixels (Vertical)PPI
15.61.780.5113.607.65128072094.14HP Pavilion 2000T
15.61.780.5113.607.651366768100.44
17.31.780.5115.088.481600900106.11HP Pavilion 17z Laptop
141.780.5112.206.861366768111.92HP Pavilion 14 Chromebook
15.61.780.5113.607.651600900117.68
18.41.780.5116.049.0219201080119.72Alienware 18
15.61.600.5613.238.2716801050127.00
17.31.780.5115.088.4819201080127.34HP ENVY 17t 1080p
15.61.780.5113.607.6519201080141.21HP ENVY 15t Quad 1080p
15.61.780.5113.607.6519201080141.21Dell XPS 15
8.91.600.567.554.721280800169.60Amazon Kindle HD
17.31.780.5115.088.4825601440169.78
15.41.600.5613.068.1628801800220.53Apple Macbook 15.4 with Retina Display
13.31.780.5111.596.5225601440220.84Toshiba KIRAbook™ 13 Ultrabook
13.31.600.5611.287.0525601600226.98Apple Macbook 15.4 with Retina Display
141.780.5112.206.8632001800262.25HP TouchSmart 14 Ultrabook
8.91.330.647.125.3420481536287.64iPad 8.9" with Retina Display
71.600.565.943.7119201200323.45Amazon Kindle HDX 7"
8.91.780.517.764.3625601440330.02Amazon Kindle HDX 8.9"


So far, nothing can beat Amazon Kindle HDX, not HP not even Apple iPad with Retina display.  Because the price of a laptop is significantly higher with higher PPI, the affordable price of Kindle plus its highest PPI makes it the best gadget for eyes and pocket!

Saturday, October 12, 2013

Distance Measurement

Last week my order of ultrasonic ranging device arrived.  With excitement I connected it to my Pic18 protoboard.  With my existing code framework I added driver to access this thing and display it to LCD (as well as logging it via rs232 to laptop pc).

I took a short video of  the board with my new iPhone5s and edited it using iMovie which is now available for free on iTunes store.

Here is the link:




Sunday, September 1, 2013

XML-RPC Client/Server in Python

This demo shows how to have RPC connection in python.  The server collects CPU information (Linux).

Server-side:

 #!/usr/bin/python
import re
import os
import sys
import xmlrpclib

from SimpleXMLRPCServer import SimpleXMLRPCServer

def is_even(n):
    return n%2 == 0

def get_mhz():
    try:
        #return 0
        f = open("/proc/cpuinfo", 'r')
    except IOError:
        print "Unable to access /proc/cpuinfo"
        exit
    except:
        print "Other unhandled error (why?)"
      
    else:
        f.seek(0)
        line = f.readline()
        while line:
            line = line.strip()
            print "line = %s" % line
            m = re.match("cpu MHz[\t ]+: (.*)", line)
            if m:
                print m.group(1)
                f.close()
                return m.group(1)
            line = f.readline()
      
#    finally:
#        print "Sorry, still fails"


def xmlsrv_exit():
    print "trying to exit now..."
    try:
        print "try sys.exit(0)"
        #sys.exit(0)
    except:
        print "Unable to exit"
  
server = SimpleXMLRPCServer(("localhost", 8000))
print "Listening on port 8000..."
server.register_function(is_even, "xmlsrv_is_even")
server.register_function(get_mhz, "xmlsrv_get_mhz")
server.register_function(xmlsrv_exit, "xmlsrv_exit")
server.serve_forever()





Client side:

#!/usr/bin/python

import xmlrpclib

try:
    proxy = xmlrpclib.ServerProxy("http://localhost:8000/")
    #print proxy.system.listMethods()
    print "3 is even: %s" % str(proxy.xmlsrv_is_even(3))
    print "100 is even: %s" % str(proxy.xmlsrv_is_even(100))

    print "CPU Clock: %s" % str(proxy.xmlsrv_get_mhz())

except xmlrpclib.Fault, err:
    print "A fault occurred"
    print "Fault code: %d" % err.faultCode
    print "Fault string: %s" % err.faultString
   
#proxy.xmlsrv_exit()


 

Multithreaded IGMP Query

Example how to access raw socket in Python:


#!/usr/bin/python

from socket import *
from struct import *
from time import *
import sys
import IN
import threading
import signal


src = '192.168.2.2'
dst = '224.0.0.1'
dev = "eth1.100" + "\0"

if len(sys.argv) > 1:
    dev = sys.argv[1]
    print "device = %s" % dev

src = gethostbyname(gethostname())
                  
def ichecksum(data, sum=0):
    """ Compute the Internet Checksum of the supplied data.  The checksum is
    initialized to zero.  Place the return value in the checksum field of a
    packet.  When the packet is received, check the checksum, by passing
    in the checksum field of the packet and the data.  If the result is zero,
    then the checksum has not detected an error.
    """
    # make 16 bit words out of every two adjacent 8 bit words in the packet
    # and add them up
    for i in range(0,len(data),2):
        if i + 1 >= len(data):
            sum += ord(data[i]) & 0xFF
        else:
            w = ((ord(data[i]) <> 16) > 0)
        sum = (sum & 0xFFFF) + (sum >> 16)

    # one's complement the result
    sum = ~sum

    return sum & 0xFFFF


def dump( data ):
    i = 0
    for x in data:
        if i == 4:
            print ''
            i = 0
        i += 1
        sys.stdout.write( ' %0.2x' % ord(x) )
    print ''


# ip header generation

def create_ip_hdr(id, type):
    ip_ihl = 5
    ip_ver = 4
    ip_tos = 0
    ip_tot_len = 0  # kernel will fill the correct total length
    ip_frag_off = 0
    ip_ttl = 255
    ip_proto = type #IPPROTO_IGMP
    ip_check = 0    # kernel will fill the correct checksum
    isrc = inet_aton( src )
    idst = inet_aton( dst )
    ip_ihl_ver = (ip_ver << 4) + ip_ihl
    router_alert = int( '1001010000000100', 2 ) << 16

    # the ! in the pack format string means network order
    ip_hdr = pack('!BBHHHBBH4s4sI',
        ip_ihl_ver, ip_tos, ip_tot_len, id, ip_frag_off, ip_ttl, ip_proto,
        ip_check,
        isrc, idst,
        router_alert)

    crc = pack( '!H', ichecksum( ip_hdr ) )
    ip_hdr = ip_hdr[:10] + crc + ip_hdr[12:]
    return ip_hdr


# IGMP header:
# type (octet), max resp time (octet), checksum (octet), group (4-octets)
IGMP_QUERY = 0x11
IGMP_REPORT = 0x16
IGMP_LEAVE = 0x17
igmp_type = IGMP_QUERY
IGMP_RESP_TIME = 120


def create_igmp_packet(id, type, group_addr='224.0.0.1'):
    igmp = pack( '!BBH4s', type, IGMP_RESP_TIME, 0, inet_aton(group_addr))
    crc = pack( '!H', ichecksum( igmp ) )
    igmp = igmp[0:2] + crc + igmp[4:]
    packet = create_ip_hdr(id, IPPROTO_IGMP) + igmp
    print 'packet:'
    dump( packet )
    return packet

def create_non_igmp_packet(id, type, group_addr='224.0.0.1'):
    igmp = pack( '!BBH4s', type, IGMP_RESP_TIME, 0, inet_aton(group_addr))
    crc = pack( '!H', ichecksum( igmp ) )
    igmp = igmp[0:2] + crc + igmp[4:]
    packet = create_ip_hdr(id, IPPROTO_UDP) + igmp
    print 'packet:'
    dump( packet )
    return packet


group = '224.0.0.1'
id = 1

s = socket( AF_INET, SOCK_RAW, IPPROTO_RAW )
s.setsockopt( IPPROTO_IP, IP_HDRINCL, 1 )
s.setsockopt( IPPROTO_IP, IP_MULTICAST_TTL, 2)
s.setsockopt( SOL_SOCKET, IN.SO_BINDTODEVICE, dev)

socksema = threading.Semaphore()
stop = False

def signal_handler(signal, frame):
    global stop
    print 'You pressed Ctrl+C!'
    stop = True
    #th1.join()
    #th2.join()
    #sys.exit(0)

class IgmpQueryThread(threading.Thread):
    def run(self):
        global stop,id
        while (not stop):
            socksema.acquire()
            print "Sending IGMP query"
            igmp_q = create_igmp_packet(id, IGMP_QUERY, group)
            print s.sendto( igmp_q, (dst, 0) )
            socksema.release()
            dump( igmp_q)
            id += 1
            sleep(1)


class IgmpReportThread(threading.Thread):       
    def run(self):
        global stop,id
        while (not stop):
            socksema.acquire()
            print "Sending IGMP report"
            igmp_r = create_igmp_packet(id, IGMP_REPORT, group)
            print s.sendto( igmp_r, (dst, 0) )
            id += 1
            socksema.release()
            sleep(1)


i = 0
th1 = IgmpQueryThread()
th2 = IgmpReportThread()

th1.start()
th2.start()

signal.signal(signal.SIGINT, signal_handler)
print 'Press Ctrl+C to quit'
#signal.pause()


while(not stop):
    if (i % 5 == 0):
        print "Sending NON-IGMP (%d)" % i
        false_igmp = create_non_igmp_packet(id, IGMP_QUERY, group)
        print s.sendto( false_igmp, (dst, 0) )
        #stop = True
        id += 1
    sleep( 1 )
    i += 1
   
th1.join()
th2.join()

s.close()