WWW.DUMAIS.IO

Deploying a layer3 switch on my networkLast edited on Aug 20, 2012

Lately, I bought a Cisco 3550 switch with 48 ports. My goal was to have a switch that could do the inter-vlan routing instead of relying on the router. This way, if my router goes down, I only lose my internet connection but the voice network and data network on my LAN can still function properly. I was also using the cisco router as my DHCP server, but now it is running on my switch.

The first thing that needs to be configured is the SVI interface. SVI stands for Switch Virtual Interface. It represents a interface to a vlan. You need one SVI for each VLAN that you want to provide routing on. By creating an SVI, you assign it an IP address. This IP address is the address that you will use as the default gateway on all nodes on your VLAN.

! create SVI for VLAN 1
interface Vlan1
 ip address 192.168.1.1 255.255.255.0

! create SVI for VLAN 2
interface Vlan2
 ip address 192.168.2.1 255.255.255.0

Since I wanna host my DHCP server on the switch now instead of the router, I can take the same configuration I had on the router at apply it on the switch.

ip dhcp excluded-address 192.168.1.1 192.168.1.99
ip dhcp excluded-address 192.168.1.150 192.168.1.255
!
ip dhcp pool pool_vlan1
   import all
   network 192.168.1.0 255.255.255.0
   default-router 192.168.1.1
   dns-server 192.168.254.1
!
ip dhcp pool pool_vlan2
   import all
   network 192.168.2.0 255.255.255.0
   default-router 192.168.2.1
   dns-server 192.168.254.1

Notice that the DNS server (forwarder) is on the router and not on the switch. Usually, the DNS server in my setups was on the same device as the subnet's gateway. So on all the devices that were assigned a static IP on my network, I had to change the IP address of the DNS server.

Connecting the router and the switch

There are two ways of doing this: SVI or routed port.

SVI

With this method, I would create a VLAN 10 on my switch for subnet 192.168.10.0/24. I would assign an IP address from that subnet to my router's LAN interface and connect it to an access port that is part of VLAN 10 on the switch.

Routed port

A routed port is a port that behaves like a port on a router. It handles layer 3 protocols. Using this method, you need to create a small network between your router and the switch. You can set the port to become a router port by using the "no switchport" command when configuring your interface. Then, you assign an IP address to that interface. This small network only has two members in it, so it is a /30 subnet. I used 192.168.254/30 and used two IPs in there. So basically, with a routed port, we are creating a peer-to-peer network that will bridge the networks known by the router and the ones known by the router. On the switch, I would configure the routed port like this:

interface fast 0/1
 no switchport
 ip address 192.168.254.2 255.255.255.252

Either way, I need to create routes on the router because the router will not be aware of the other subnets on my switch. It will only be aware of the peer-to-peer network (if using a routed port) or the subnet for the vlan it is part of (if using SVI). But it will not know anything about the other subnets in other vlans. At first, I thought about assigning the router's LAN port an IP address in the VLAN1 subnet and connect it to an access port for VLAN1 on the switch. But then, since I have an ACL that prevents VLAN2 from talking to VLAN1, the VLAN2 wouldn't have access to the router, so no internet for VLAN2. So I thought about creating a VLAN 10 in which my router would be part of, in a new subnet 192.168.254.0/30. Only the SVI on the switch and the physical port on the router would belong to that subnet. But why do this when a routed port does exactly that anyway? So I chose to go with the routed port even though it does the same thing as the SVI method

Routes

The router only know about network 192.168.254.0/30. But because we are doing TCP/UDP port forwarding, it needs to know about 192.168.1.0, 192.168.2.0, 192.168.3.0 etc... So I tell the router: Whatever you have for 192.168.0.0/16, throw it on the FastEthernet0/0 interface. Whatever else you got, ship it on the WAN (my DSL modem). This is done like this:

ip route 192.168.0.0 255.255.0.0 Fast0/0
ip route 0.0.0.0 0.0.0.0 Dialer1

For the switch, things are really simple. The switch knows how to route between VLANs because we have created an SVI for each VLAN. We just need to tell it to send whatever does not belong to a known subnet to the router, through the routed port:

ip route 0.0.0.0 0.0.0.0 Fast0/1


C++ JSON libraryLast edited on Jul 9, 2012

After spending some time trying to find a good JSON library for C++, I realized that all the libraries out there are too heavy to use. Some of them look very good but their usage looks heavy. So I decided to write my own. My library is compliant with RFC 4627 except that it doesn't support unicode and numbers in exponential format .

Seriously, this library is really easy to use and has no dependencies (other than STL). I cannot find another C++ json library that is that simple to use.

Usage examples

The library exposes one object that is used to do everything you need. the "JSON" object. So there is no need to include a whole bunch of header files and use a whole bunch of class. You only need the JSON object to do everything you need. The object exposes these methods:

MethodDescription
JSON& operator[](int i);Access a list item
JSON& operator[](std::string str);Access an object member
std::string str();Get value of item
void parse(std::string json);Parse a JSON document
std::string stringify(bool formatted=false);serialize JSON object
JSON& addObject(const std::string& name="");Add object
JSON& addList(const std::string& name="");Add List
JSON& addValue(const std::string& val,const std::string& name="");Add string value
JSON& addValue(int val, const std::string& name="");Add integer value
JSON& addValue(double val, const std::string& name="");Add double precision FP value

Reading a JSON document

The library is very simple to use. Just compile it and it will output a "test" executable and a .a that you can link against. Then let's say you have the following JSON document:

{
    "obj1":{
        "member1":[
            "val5",
            "val4"
        ],
        "member2":"val3"
    },
    "list1":[
        "listItem1",
        "listeItem2",
        {
            "listObject1":"val2"
        }
    ],
    "value1":"val1"
}

The following code is an example on how to use the library:

JSON json;
std::string val;
std::string str = someFunctionThatReadsAJSONDocumentFromFileOrNetworkOrWhatever();
json.parse(str);
val= json["obj1"]["member1"][0].str(); // would give "val5"
val= json["list1"][1]["listObject1"].str(); // would give "val2"

Each access to a member will return a JSON object. So you only have 1 class to use at all time. So you can create a new variable each time or you can access all members by chaining the function calls.

val= json["obj1"]["member1"][0].str(); // would give "val5"
JSON& j1 = json["obj1"];
JSON& j2 = j1["member1"];
val = j2[0].str(); // would give "val5"
val = j2.str();    // would give "{list}"
val = j1.str();    // would give "{object}"

Invalid paths

The nice thing about this is that you don't need to worry about null objects. If you try to access an invalid member, you will get an invalid JSON object. But if you try to access another member from an invalid JSON object, you will also get an invalid JSON object. You will never get a NULL object that could crash your application.

val= json["obj1"][1].str(); // would give "{invalid}" because obj1 is an object, not a list
val= json["obj2"].str(); // would give "{invalid}"
val= json["obj2"]["member2"].str(); // would also give "{invalid}"
val= json["list"][100].str(); // would give "{invalid}"
val= json["list"][100]["something"].str(); // would also give "{invalid}"

Writing a JSON document

There are 3 methods provided to add items in the JSON document:

  • addObject(const std::string& keyName="")
  • addList(const std::string& keyName="")
  • addValue(const std::string& val, const std::string& keyName="")

All 3 functions have an optional keyName parameter. That is because if you add an item to an Object, you need to specify the key name that will be used in the parent. Again, I wanted to have a simple interface without having to force the programmer to use different classes if using a list or an object. So this here is the behavior of those function calls if you provide the key name or not.

ActionResult
addXXX on Object and provide key name item added and parent uses keyName
addXXX on Object and don't provide key name item added and a key name is auto generated
addXXX on List and provide key name item added and key name ignored
addXXX on List and don't provide key name item added
addXXX on Value and provide key name operation is ignored
addXXX on Value and don'tprovide key name operation is ignored

After adding items in the json object, you can then serialize it with stringing().

JSON json;
json.addObject("obj1");
json.addList("list1");
json.addValue("val1"); // key autogenerated because key name not provided
json["list1"].addValue("item1");
json["list1"].addValue("item2");
std::string val = json.stringify(true);

Would output:

{
    "obj1":{
    },
    "list1":[
        "item1",
        "item2"
    ],
    "key2":"val1"
}

Download

Project can be found on github



ACD Server with resiprocateLast edited on Jul 9, 2012

This is another project I've been working on using resiprocate. I'm using this ACD server with Asterisk. All devices, including the ACD server are talking with asterisk. The ACD server registers with asterisk just like any other phone would. When my gateway (FXO) sends a call to asterisk, asterisk routes it to the ACD server. When an agent logs on the ACD, the ACD server checks the contact header and establishes a presence subscription to that contact. In my case, this is always going to be an extension on Asterisk.

Features

Feature list:

  • multiple queues
  • Agents can be members of many queues
  • Queues are called with AOR (i.e: telemarkerqueue@acdserver.local)
  • most-idle agent routing (MIA)
  • redirect on no answer (to next MIA, with configurable timeout)
  • music while waiting
  • welcome message + periodic announcements while in queue
  • supports g.711 uLaw only
  • supports SIP info only for DTMF
  • REST api with JSON formatted responses
    • list of queues and calls with state, source/destination etc.
    • list of agents with state, idle time etc.

Unfortunately, there is too much I want to do so here is the list of other features I would like to add when I find the time

  • Adding agents & queues dynamically without restart
  • Ringall queues
  • Calling an agent directly (agent@acdserver.local)
  • Force a call out of the queue using REST api (unattended transfer to any device on same call server).
  • Use one thread only for all RTP sessions
  • Prevent agent to log on more than one phone and more than one agent on same phone whatever codec I use)

Using it

Here is a typical scenario:

  • Agent dials *44 (asterisk routes this to sip:acdlogin@acdserver.local as per dialplan)
  • ACD Server answers and prompts for agentID
  • ACD finds agent in internal list
  • ACD subscribe with calling phone (using contact header).
  • agent becomes available when a notify indicates IDLE.
  • agent dials *45 (asterisk routes this to sip:acdlogout@acdserver.local as per dialplan)
  • ACD unsubscribes and set Agent as unavailable

Asterisk

In sip.conf, under the profile for my acdserver, I set:

subscribecontext=acdpresence
context=acdserver

When phone1 calls the acdserver, the contact header will appear to be sip:phone1@asterisk.local to the ACD server (because asterisk is a B2BUA). So when the ACD server will try to subscribe to that device, it will need to have a corresponding entry in the dialplan. This is how I setup my dialplan:

[acdserver]
exten => _.,1,Dial(SIP/${EXTEN})

[acdpresence]
exten => _.,hint,SIP/${EXTEN}

It is discouraged to use "_." but currently, this is my only option. I'll try to find something. But this is because I don't have a naming convention for my devices. If all your devices are called phone1, phone2, phone3 etc. then you could use exten => _phone.,1,Dial(SIP/${EXTEN})

API

The server listens for incomming requests in the form of a RESTful API. The responses are sent as JSON data. I prefer JSON over xml since it is easier to parse with javascript and it also looks nicer in my opinion. I used my own json library which you can also find on this website. The server currently supports 2 requests.
curl -X GET "http://127.0.0.1:242/queues" would return

{
    "queues": [
        {
            "name": "queue1",
            "calls": [
                {
                    "id": "335099e931f09ad46ea75b8a451ad65d@192.168.0.3",
                    "state": "assigned",
                    "from": "gateway@192.168.0.3",
                    "to": "queue1@192.168.0.3:5071"
                }
            ]
        },
        {
            "name": "queue2",
            "calls": [ ]
        },
        {
            "name": "queue3",
            "calls": [ ]
        }
    ]
}

curl -X GET "http://127.0.0.1:242/agents" would return

{
    "agents": [
        {
            "id": "2771",
            "state": "idle",
            "idletime": "14",
            "device": "",
            "memberof": ["queue1","queue2","queue3"]
        },
        {
            "id": "2772",
            "state": "loggedout",
            "idletime": "0",
            "device": "",
            "memberof": ["queue2"]
        },
        {
            "id": "2773",
            "state": "loggedout",
            "idletime": "0",
            "device": "",
            "memberof": ["queue3"]
        }
    ]
}

Download

This code is experimental and is a mess right now. A lot of it can change at any time. The only libraries you will need is resiprocate and ortp.
download



Implementing your own mutex with cmpxchgLast edited on Jun 28, 2012

The cmpxchg instruction takes the form of "cmpxchg destination source" where the destination is a memory location and the source is a register. Before using this instruction, you need to load a value in the EAX register. The instruction will first compare the value in EAX to the value in memory pointed by the destination operand. If both values are equal, the value of the source operand will be loaded in memory where the destination operand points to. Note that this compare and store operation is done atomically. If, on the other hand, the destination and EAX do not match, then the destination will be loaded into eax. At first, it might not be clear why this instruction would be usefull. But consider this:

l2: mov eax,[mutex]
    cmp eax,1
    je l2
    mov eax,1
l3: mov [mutex],eax

This is an unsafe way of creating a mutex. You loop until its value is zero and then set a 1 in it. But what if another thread or another CPU changed the value between l2 and l3?

If you need to store the value of a lock in memory (let's say at location 0x12345678) then before attempting to lock a section of code, you would read the lock to see if it is free. So you would read location 0x12345678 and test if this value is zero. If it isn't, then keep on reading memory until it reads as zero (because some other thread cleared it). After that, you would need to store a "1" in this location to take ownership of the lock. But what if another thread takes ownership between the time you read the value and the time you wrote it? The CMPXCHG instruction will write a "1" in there only if a "0" was in memory first. EAX would be equal to "0" because we would first spin until the memory value is "0". So after that, we tell the CPU: "EAX is zero now, so compare value at 0x12345678 with EAX (thus 0) and change it to 1 if it is equal. Otherwise, if the value at 0x12345678 is not equal to 0 anymore, then load this value into EAX and I will go back to spinning until I get a zero". Simple enough? Here is a sample code that illustrates this.

    mov edx,1
l2: mov eax,[mutex]
    cmp eax,1
    je l2                   ; spin until we see that eax == 0
    lock cmpxchg [mutex],edx; At this point, eax=0 for sure. Now if memory location still equal to
                            ; eax, then store edx in there.
                            ; otherwise, eax will be loaded with changed value of mutex (should be 1)
                            ; if not equal to zero, it means it was modified. If it was modified,
    jnz l2                  ; it means cmpxchg has loaded the value of the mutex in it.
                            ; and if the value of mutex was loaded, it means it wasn't equal to zero
                            ; by the definition of the CMPXCHG instruction.
                            ; zf will have been set in that case, so we can just make a conditional jump

Now, notice how we used "lock" before using cmpxchg? This is because we want the CPU to lock the bus before doing the operation so that no other CPU will interfere with that memory location.



WakeupCall server using resiprocateLast edited on Jun 14, 2012

This is my first project I did with the resiprocate SIP stack. There's a lot of things left to do in this project but I wanted to post the code here right away in case someone needs more example on how to use resiprocate.

Dependencies and limitations

I chose to use resiprocate as the SIP stack and ortp as the RTP stack and libxml2 and the XML parser. The application only supports G.711 uLaw. The application only supports SIP info for receiving DTMF (inband and RFC2833 not supported).

Resiprocate

Resiprocate provides a Dialog Usage Manager (DUM). This engine is very useful for applications that don't want to deal with low level SIP messages. The DUM allows you to receive events such as onOffered, onAnswer, onTerminated (plus many more) by the use of an observer pattern. Using a class called AppDialogSet, it is possible to represent a "call" or a "dialog" and let the DUM manage it. For example, you could override the AppDialogSetFactory with your own CallFactory that would create "Call" objects derived from AppDialogSet. When receiving an event such as onOffered, the DUM will already have created a AppDialogSet with your factory class and you can then cast this AppDialogSet with your "Call". This is a good way to receive a "Call" reference on every events you get. And the beauty of this is that you never need to delete it becausr the DUM will take care of it. More information is available on the resiprocate website.

ortp

ortp is very easy to use but only provides basic functionalities. It won't bind to any sound cards or include encoding like other fancy stack do. This stack only allows you to open a stream and feed it data encoded with whatever codec you want. It is the developper's responsibility to make sure that the data that is fed is encoded with the proper codec.

Threading model

I chose to use 1 thread for general processing and 1 thread for each RTP session. The main thread is used to give cycles to the resiprocate DUM and to the WakeupCallService. A new thread is created for each RTP sessions. The RTP session only handles outgoing stream since we don't need the incomming stream. The ortp stack provides a way to read multiple streams from the same thread but I prefer to use different threads in order to leverage multi-cores CPUs.

Usage

The server is a user agent that registers with you PBX. Just call the server and enter the time at wich you want your wakeup call and the extension at which you wanna be notified. For example, you would enter 0,6,3,0 to get a wakeup call at 6h30 AM. I left out the prompts from the package so you'll want to replace them. The IVR is defined in the xml file. Just change the prompt names. There is no configuration file you can use right now. You will need to set the proper values that you need in config.h. To launch the application, run it and provide, as a command line argument, the ip address on which to bind on your computer.

Download

Download the source code



Pages:123456789101112