Software Engineering

A colleague of mine sent me an article by Tom DeMarco, one of the pioneers of structured analysis and also a strong believer in software engineering processes. Previously advocating metrics, metrics and more metrics in the past, he has come to realise that software engineering is rather a misnomer. In one sense it is engineering but in another sense, it is not. After decades of invaluable experience, he has come to one conclusion:

For the past 40 years, for example, we’ve tortured ourselves over our inability to finish a software project on time and on budget. But as I hinted earlier, this never should have been the supreme goal. The more important goal is transformation, creating software that changes the world or that transforms a company or how it does business.

Since I have also been writing code for a couple of decades since my humble beginnings with LOGO and BASIC, I have to say that I am of the opinion that it is very difficult to control the process of software creation – and it is a creative process, no doubt about it. However, as an engineer, I do believe that metrics is useful but only after the fact. What I mean to say is that metrics are only useful in documenting failures.

It is silly to try to control software creation during its inception and conception. You just need to hire the best people, give them the best tools and then hope for the best. Project managers who try to micro-manage the project will invariably fail because of the nature of the metrics used – they try to attribute success to certain values. Unfortunately, the success of a software rarely depends on the number of lines of code maintained, nor does it depend on the number of faults found.

After a project has come to a finish – and when I say finished, I mean that the people involved have come to a unanimous decision that they are happy with the state of the product at the time – that is when software metrics can be used to measure certain things. For example, it could be useful to measure individual contributions to the code base and identify good managers. It could also possibly measure the number of significant changes made to base code.

Anyway, I think that the video below is as good a metric as any for measuring software quality. I like the fact that the contributors seem to come in waves. I wonder if it correlates with real-world events in any way.

Network Disk Cloning

I had occasion to clone a hard-disk over the network today. I wanted to replicate a VM setup that I already had running in one machine, over onto another, which will save me time from setting it up again. So, I checked the internet about using dd with nc to clone over the network. Most places gave the following info:

Target:

# nc -l -p 9000 | dd of=/dev/sda2

Source:

# dd if=/dev/sda2 | nc serverip 9000

However, trying that set of commands would fail. So, checking the man pages for NetCat revealed that you cannot use both the -l and -p parameters together. So, the correct set of commands should be as follows:

Target:

# nc -l 9000 | dd of=/dev/sda2

Source:

# dd if=/dev/sda2 | nc serverip 9000

Which worked out just fine in the end. Thought I’d share it.

Broadcast Storm

Learning about networking is not very fun unless you actually get to see it in action, which is the opportunity that I gave my apprentice today. A few months ago, the corporate network at the place where I am doing public service and paying my dues, went down when a low-level tech accidentally connected the network in a loop.

When that happens, it becomes possible to send packets round and round in a loop. This is not so much a problem with proper network connections because the packets will be terminated at the destination. However, it is a problem for broadcast packets as they could possibly be broadcast in circles – ad infinitum.

First, I got two computers with two network ports each. Then, I bridged the ports together so that br0 on each computer bridged eth0 and eth1 together. This is the example configuration for Debian networking:

auto br0
iface br0 inet static
bridge_ports eth0 eth1
bridge_stp off
....

Next, I connected one network port from each PC together with a LAN cable. I asked my apprentice to observe the lights on the network ports. They blinked a few times when the cables were first attached due to hand-shaking of the network capabilities. Then, all activity stopped.

So, I initiated a broadcast packet by performing a ping to a random IP address. Because the PC does not know the destination machine, it will send an Address Resolution Protocol (ARP) broadcast packet asking for the destination machine for a reply. We could have just as easily done this with an arping command instead.

The lights on the network ports began to blink with activity and we managed to observe things. After my apprentice got a good look, I stopped the pinging and we confirmed that all activity stopped.

Then, we connected the other two network ports from each PC together with a second LAN cable. Now, we have got ourselves a network loop. Again the lights blinked a few times during hand-shaking and then all activity stopped. I initiated another broadcast and the lights began to blink again. So far so good.

Now, it was time for the broadcast storm. I stopped the pinging like before but this time around, the lights did not stop blinking. The broadcast packet was moving round the network in a loop. My apprentice got all excited that the network ports were blinking rapidly even when there was no application sending out any activity.

Then, I disconnected one port and the blinking stopped – magic!

Finally, I showed my apprentice how the network would work with STP enabled. I changed the settings above and set bridge_stp on instead, restarted the network stack and reconnected the network cable. With STP in place, there would be periodic network activity when the STP packets were sent out but these were easily distinguished from the crazy blinking broadcast storm.

I repeated the whole experiment again, but this time, the broadcast packets stopped after the pinging application was stopped – success!

I think that this would make networking subjects more interesting. This experiment was fairly simple to conduct and took nary five minutes. It would demonstrate networking concepts in a very visual and visceral manner. It does away with the whole idea of imagining how network packets moved through a network. Beautiful simplicity.

Software Layers

I had a talk with my apprentice today, on software application architecture. In our case, it had to do with an application that had to interface with a back-end database. I was reviewing her code when I realised that she was replicating a lot of code everywhere – a definite case for code factoring. Everyone likes to talk about factoring but I had to find an easy way to explain it to my apprentice.

I explained to her that it is a good practice to design software in three layers – primitives, middleware and application – and proceeded to describe the layers.

Primitives are low-level functions. This did not seem to make sense to her and so I elaborated that primitives should only do one thing and one thing only. It did not elicit any extra clarity from her and so I decided to put it in context. For our application, the database operations could be made into primitives – insert, update, delete and select. Once I said that, things became obvious to her. This layer would be tied very closely to the low-level architecture, the back-end database in our case.

Middleware serves as glue between application and primitives. This makes sense in context. Our application only supports one database back-end today but we may need to support more database back-ends in the future. This is where middleware comes in. The middleware provides a standard application programming interface (API) that can be used by the application regardless of database back-end. We may have multiple primitives used to access different database back-ends but we can have a single middleware layer that abstracts it all away from the application.

In the case of embedded software, this would be any architecture specific code that need to access the hardware registers and functionality directly. Similarly to databases, these would usually include code that set and get values. A typical example of this would be code that would set, clear and toggle bits in a register. The middleware would be any code that defines processes and functionality by wrapping around the bare-metal primitive code. This could include code that reset, initialised and activated certain functionality.

Applications should be pretty obvious. The application contains all the business processes and logic that is entirely dependent on the application used. Ideally, if everything was done properly, we would be able to use the same set of middleware and primitives across multiple different applications. This should be the idea that we strive for when architecting software applications. I hope that she will always remember this simple rule-of-thumb in her future code.

IPv6 Stress

I was reading this article on seven IPv6 myths today and a random thought entered my head. The article seemed to stress that the only real difference that IPv6 makes in this world is extending the address space from 32-bits to 128-bits. This is a 4x increase in the address space currently used.

This instantly triggered a yellow-flag in my head. This also means that there will be a marked increase in the memory space on networking devices, coupled with an increase in memory bandwidth consumption on the said devices.

What many people may not know is that on a networking device like a router, routes need to be maintained for the duration of a connection. This is usually maintained as some sort of table in the memory. When a network packet comes into a router, its header is examined for the destination address. This is then used to look-up the route to take in the routing table. Then, the network packet is forwarded onto the route on its way to the destination.

So, an IPv6 routing table would need to quadruple its memory space requirements. This would not be so bad if we didn’t take the real-world performance of memory into account. It would also mean that the amount of memory transfers would be quadrupled as well, assuming similar table and lookup patterns.

While I have not researched into this, I do think that there is room for innovation here. We could possibly change the routing table structure by dropping off some of the bits or to partition it up into directories indexed by the significant bits of the address or to hash the IP addresses in such a way as to reduce memory requirements.

This may not mean much to a lot of people but if the route look-up process slows down by a factor of four, it will significantly affect the latency of the network even if its effects on throughput may be minimal. Latency is very important for certain applications, anything that requires near real-time feedback like first-person shooters.

Interesting…

Seeded Hash

For the sake of security, I had to implement a seeded-hash system to secure passwords in a database. While straight-forward hashes are good for ensuring that passwords are not stores in clear text, they are still vulnerable to rainbow-table attacks. A seeded hash helps to reduce this risk.

However, the question occurred on how to actually do a seeded hash. I got my apprentice to look around and we finally found a useful scheme. The seed and password are usually concatenated before being hashed.

hash = Hash(seed + password);

However, if the seed was fixed, then it does not really help much because it would still be susceptible to rainbow attacks if the secret seed ever got out. So, we had to use a random seed. However, a random seed would generate all manners of rubbish unless we could somehow embed the seed in the hash.

Since this was part of a password storage scheme, it would be perfectly alright to embed the seed with the hash because the size of the hash result is fixed. So, any extra data stored with the hash result would be the seed. We could convert everything to Base64 to store it in clear-text on the server. This was the scheme that we used in the end:

seededhash = Base64(Hash(seed + password) + seed);

This way, to do a password match, the application would need to decode the Base64, separate the seed from the hash and then perform the hash operation on the supplied password with the seed to see if it matched the hash.

Caveat: this solution only works in the situation of password matching – where we already know exactly which record to match against and merely need to verify that the information provided is accurate. This would not be useful for indexing purposes, like that used in Git. In such a scenario, straight-forward hashing would still need to be done.