Configuring ISCSI Targets and Initiators on Fedora 16


Configuring ISCSI is simple, but the configuration options can be complex.  This is a simple illustration of how you can configure an ISCSI target (storage space on a server) and connect to it using and initiator (client that will use the space).  Everything will be as trim as possible, with basic IP restriction and simple CHAP security.  If you want to get a bit fancier just look up the options for the various commands that are used most things that I’ve wanted to do have been simple modifications to this basic pattern.

For this example I will be using a virtual machine dev01 as the target that hosts the storage and a virtual machine dev02 as the initiator that connects to and uses the storage on dev01.  Everything done in this example is done while logged in as root on the respective system.


Configuring the Target

Setting up the local firewall

If you are using netfilter (note the change to the firewalld service in fedora 16 from the iptables service in fedora 15) then you will need to allow incoming TCP connections to the port configured for ISCSI.  The default port is 3260 and used throughout this post.  You can change it, but if you do you will need to specify the port in all connection commands and change the port opened in netfilter.  I used the following commands to log inbound connections, open the port and save it so that it will persist through reboots.

[root@dev01 ~]# iptables -I INPUT 3 -p tcp --dport 3260 -m state --state NEW,ESTABLISHED -j ACCEPT
[root@dev01 ~]# iptables -I INPUT 3 -p tcp --dport 3260 -m limit --limit 5/m --limit-burst 7 -j LOG --log-prefix="iptables-iscsi: "
[root@dev01 ~]# iptables-save > /etc/sysconfig/iptables

Using SELinux

If you are using SELinux then you may have to configure SELinux to allow tgtd access to the file.  To do this the simplest way is to set SELinux to permissive complete the installation and testing then run audit2allow to create a new SELinux module based on the tgtd alerts in the audit file.  So start by setting your SELinux status to permissive.

[root@dev01 ~]# setenforce 0

Once you have completed all of the configuration items in the following sections you should then run the audit2allow script for the tgtd service entries. If you are using files as storage rather than devices then you could just set the labels on the files to ‘system_u:object_r:tgtd_var_lib_t:s0′. However if you still have trouble then I suggest that you perform the following anyway.

[root@dev01 ~]# cat /var/log/audit/audit.log | audit2allow -M iscsi-disk1.pol
[root@dev01 ~]# semodule -i iscsi-disk1.pol.pp

Creating the space

In ISCSI terms the target is the server on which the storage space actually resides.  The storage space can be anything from a file on a disk system, a dedicated physical device, a RAM disk or even another network mounted storage device.  In this case to prepare the device I will be creating a file for the storage space.

[root@dev01 ~]# mkdir /iscsi-disks
[root@dev01 ~]# dd if=/dev/zero of=/iscsi-disks/iscsi-disk1 bs=1M count=2000

Installing the SCSI tools and services

First you need to install the tools and services required with their associated dependencies.  Once the service has been installed it needs to be turned on and set to turn on at start up.

[root@dev01 ~]# yum -y install scsi-target-utils
[root@dev01 ~]# service tgtd start
[root@dev01 ~]# chkconfig tgtd on

Creating the ISCSI device

Next we get to actually configure the resource itself.  This involves creating the ISCSI device and adding logical units (LUNs) to it.  In this case we only add 1LUN but you could add more. The details for the ISCSI device are held in a database in ‘/var/lib/iscsi/nodes/’ and are manipulated and viewed using the ‘tgtadm’ tool. In the listing below the last command illustrates how to view the changes that you have just made.

We also create a name for the ISCSI device.  The name could be anything but there is a standard to be applied that keeps us out of name collision difficulties.  The ISCSI standard specifies ‘iqn.yyyy-mm.<reversed domain name>[:identifier]‘ as the standard naming convention.  So in this case our device is named ‘iqn.2012-01.nz.net.chesterproductions:dev01′.

[root@dev01 ~]# tgtadm --lld iscsi --mode target --op new --tid=1 --targetname iqn.2012-01.nz.net.chesterproductions:dev01
[root@dev01 ~]# tgtadm --lld iscsi --mode logicalunit --op new --tid 1 --lun 1 -b /iscsi-disks/iscsi-disk1
[root@dev01 ~]# tgtadm --lld iscsi --mode target --op show

So that was the way to do it manually. If you want to have tgtd automatically load your device when it starts then you need to add an entry in to the ‘/etc/tgt/targets.conf’ file. The file installed by default is full of examples that are commented out. Given that there is so much fine information I’ll be brief and just provide an example that works for me. The one warning that I would give though is that this will be executed when tgtd starts, if you are like me and prefer to control when this appears then you may like to script the manual solution above somewhere more convenient for you.

<target iqn.2012-01.nz.net.chesterproductions:dev01>
    backing-store /iscsi-disks/iscsi-disk1
    incominguser auser secret
#    outgoinguser anotheruser anothersecret
</target>

 Configuring access to the device

Finally we configure the user details (CHAP target authentication in this case) and allow connections from any IP address opening it up to be accessed by the initiator (client that uses the storage space).  Once this is done we are ready to configure and hook up our initiator. Note that this configuration will not persist between machine restarts but can be scripted to do so.

[root@dev01 ~]# tgtadm --lld iscsi --mode account --op new --user auser --password secret
[root@dev01 ~]# tgtadm --lld iscsi --mode account --op bind --tid 1 --user auser
[root@dev01 ~]# tgtadm --lld iscsi --mode target --op bind --tid 1 -I ALL
[root@dev01 ~]# tgtadm --lld iscsi --mode account --op show


Configuring the Initiator (client)

Installing the ISCSI tools and services

First you need to install the tools and services required with their associated dependencies.  Once the service has been installed it needs to be turned on and set to turn on at start up.

[root@dev02 ~]# yum -y install iscsi-initiator-utils
[root@dev02 ~]# service iscsid start
[root@dev02 ~]# chkconfig iscsid on

Creating the local ISCSI device

Once the services and tools have been installed you can create the ISCSI local device.  This is all done through the ‘iscsiadm’ tool and like the ‘tgtadm’ tool on the target the ‘iscsiadm’ tool manipulates the database in ‘/var/lib/iscsi/nodes/’.  You can use the command ‘iscsiadm -m node’ to view the discovered nodes in the database. There are a number of steps to the configuration, but basically you just need to discover the target – which puts its details in the database, configure the authentication details and then log in.  At the end of this process ‘sfdisk’ should recognise a new ‘/dev/sdX’ device provided by ISCSI that it can format and use like any other.

[root@dev02 ~]# iscsiadm -m discovery -t st -p dev01
Starting iscsid:                                           [  OK  ]
192.168.122.11:3260,1 iqn.2012-01.nz.net.chesterproductions:dev01
[root@dev02 ~]# iscsiadm -m node
192.168.122.11:3260,1 iqn.2012-01.nz.net.chesterproductions:dev01
[root@dev02 ~]# iscsiadm -m node --targetname "iqn.2012-01.nz.net.chesterproductions:dev01" --portal dev01 --op update --name node.session.auth.authmethod --value CHAP
[root@dev02 ~]# iscsiadm -m node --targetname "iqn.2012-01.nz.net.chesterproductions:dev01" --portal dev01 --op update --name node.session.auth.username --value auser
[root@dev02 ~]# iscsiadm -m node --targetname "iqn.2012-01.nz.net.chesterproductions:dev01" --portal dev01 --op update --name node.session.auth.password --value password
[root@dev02 ~]# iscsiadm -m node --targetname "iqn.2012-01.nz.net.chesterproductions:dev01" --portal dev01 --op update --name node.session.auth.password --value secret
[root@dev02 ~]# iscsiadm -m node --targetname "iqn.2012-01.nz.net.chesterproductions:dev01" --portal dev01 --login

Format and mount the new disk

Next we make the device usable in just the same way as you would any other device.  There’s no rocket science here.

[root@dev02 ~]# fdisk /dev/sda
[root@dev02 ~]# mkfs.ext4 /dev/sda1
[root@dev02 ~]# mkdir /data
[root@dev02 ~]# mount /dev/sda1 /data

Configure the disk to auto mount at boot

To make the device auto mount at boot all you need to do is add a standard entry to ‘/etc/fstab’ with the parameters of ‘defaults,auto,_netdev’.  That’s it you can now use your ISCSI storage just like the on board disk physically in your machine.

If you have more than one disk then the ‘/dev/sdX/’ link is not guaranteed to be assigned to the same disk each time. Each disk takes the first available device name as it is connected. To mount specific disks to specific mount points you need to use the UUID. The UUID is a unique identifier assigned tot he disk whent he file system is created. To locate the UUID you can use the ‘blkid’ command as seen below.

Alternatively you can use the logical volume manager and it will take care of all of this for you.

[root@dev02 ~]# blkid | grep sda1
/dev/sda1: UUID="d894a751-93a4-4739-806b-b1f5fd30b4e1" TYPE="ext4"
[root@dev02 ~]# vim /etc/fstab
...
UUID=d894a751-93a4-4739-806b-b1f5fd30b4e1 /data ext4 defaults,auto,_netdev 0 0


Other Places to Look for Help

There are several other places that I used to look for help.  These include the local man pages and the help pages that were installed with the software.  The locally installed help pages have similar useful examples but go way beyond the scope of this simple introductory example.  The following references were especially useful.

http://fedoraproject.org/wiki/Scsi-target-utils_Quickstart_Guide

http://www.howtoforge.com/using-iscsi-on-fedora-10-initiator-and-target

http://linux.die.net/man/8/tgtadm

http://linux.die.net/man/8/iscsiadm

http://www.open-iscsi.org/

http://iscsitarget.sourceforge.net/


Logging iptables (netfilter) to a Separate File

It’s been a while.

Here’s a little example of how to make iptables log to a separate log file on Redhat systems. I will let you look up the switches and things.

First add the log command, in this case I want to log every thing that is dropped by netfilter.  But I still want to limit it so that I can see what is going on without my log being flooded.  Note also the prefix that I’m using to identify netfilter log entries.

iptables -I INPUT 7 -m limit --limit 5/m --limit-burst 7 -j LOG --log-prefix="iptables: "

We should now be able to see log entries in /var/messages that are prefixed by ‘iptables: ‘.  Assuming of course that you are creating the traffic to generate such entries!

Now to get this puppy logging to where we want it to go.  To do this we add a ‘conf’ file to ‘/etc/rsyslog.d/’ in this case we name it ‘iptables.conf’.

vim /etc/rsyslog.d/iptables.conf

… and give it a little content

:msg, startswith, "iptables: " -/var/log/iptables.log
&~

In this case I also want to make the logs roll nicely.  To do this we wake up our old friend logrotate with a suitable file in ‘/etc/logrotate.d/’ called ‘iptables’.

vim /etc/logrotate.d/iptables

 … and give it a little content …

/var/log/iptables.log
{
	rotate 7
	daily
	missingok
	notifempty
	delaycompress
	compress
	postrotate
		invoke-rc.d rsyslog reload < /dev/null
	endscript
}

The rules in ‘/etc/rsyslog.conf’ pick up all files in the ‘/etc/rsyslog.d/’ directory when rsyslog is loaded. A simple service restart should do it.

service rsyslog restart

So that’s it. Not much to it. Naturally you could have multiple log prefixes in netfilter for different logs and send them to different files. In which case you just repeat the process above.


Parallel and Concurrent Processing with Sudoku: Part 1

One of the more fascinating areas of modern computing is that of parallel and concurrent (PAC) computing.  Of course it has only been in the last couple of years that I’ve had access to the equipment and environment in which to play.  However apart from the obvious droll exercises there was still the lack of a suitable fodder to use for demonstrating anything more than a contrived task.

So along came Sudoku.  Its a perfect example of a problem set that can be broken down in multiple ways.  More than that it is really simple and also flexible enough to investigate the intricacies of PAC.  Naturally it is a very small problem.  Most often the basic game takes place within an 81 (9×9) cell square board.  However it can be scaled up to any square number of cells.  So its a scalable problem too.

Now at this point the place to start is with the analysis of the game itself.  So for the rest of this article a simple method for solving trivial Sudoku puzzles will be developed.  For this series of articles only simple puzzles will be used to simplify the code used to investigate PAC.

Introducing the Game

Sudoku is played on a on a board made up of a grid of cells that can contain a number from 1 to 9.  For this example we will limit it to 9×9 or 81 cells but it is possible to scale up.  Logically then the board is also composed of 9 non overlapping squares of 3×3 cells each.  An empty board looks something like the following.

Starting with a set of known values in specific cells determine the values in the remaining cells currently of unknown value.  There are only two real rules:

  • A number can only appear once in any given row or column, and,
  • A number can only appear once in any given square.

Given these rules the empty grid has the following possibilities:

Which is all very simple.  Actually, as with many number games and puzzles, the whole puzzle is very simple.  Where it becomes a game is when it taxes the discipline and observation of the puzzler.  To assist with terminology as we get nested items there are three terms that I will attempt to be consistent with:

  • Board shall mean the whole 9×9 board,
  • Square shall mean a 3×3 square within the board, and
  • Cell shall mean the smallest part of the board which can only contain a single value.

Puzzle Play

So the first thing that is provided is a grid with a number of values already provided on it.

Which we use to set the known cells in our grid of possibilities.

Now for each value we can remove all of the same possibilities in the square that it sits in excluding the known cell.  Observe that for each cell of a square where the ultimate value is yet to be determined they all have the same set of possibilities within that square.  This is not important for this article but may be referenced in a future article.

Next for each known value we can remove all of the same possibilities in the row and column that the known cell is in except of course for the known cell itself.

At this point a quick review of the possibilities reveals that there are some:

  • Squares where a possibility only occurs in one cell, and
  • Cells that only have one possibility.

This means that those are the know values for those cells and the process can start again: setting the cells, clearing the possibility from the squares, and clearing the possibility from the lines.  In the example this means that the following cells are now known (shown in yellowish).

Repeating the steps to reduce the possibilities in the the possibility grid we get the following.

If you look closely at the grid of possibilities while you apply the reduction rules you will see that more values become known before you have completely applied the reduction steps.  For example I observed the 5 in the centre bottom square before completing the reduction steps.

While these revelations are distracting, enticing the observer to pursue the new value, it is important to note that if you apply the steps sequentially looping through them all of these will be picked up in time.  The reason that this is important is to demonstrate that this whole process of reduction can be performed sequentially in a single threaded fashion before investigating the use of a PAC approach.

Closing the Loop

In time the loop will naturally halt because either all of the values have been discovered or because no further single values are found in an iteration of the reduction loop.  Because the identifying logic is very simple this will only satisfy very basic Sudoku puzzles.  It could be simply extended to include more complicated cases but for our example this would potentially introduce obscuring factors to the overall purpose for this investigation, which is to explore PAC.

For our example used in this post the loop eventually discovers all of the known values as shown in the probability grid.

Giving us the completed puzzle.

Next Time

In this article we simply looked at the mechanics of solving a simple Sudoku puzzle.  It gave us the basic procedure for solving the puzzle:

  1. Identify values,
  2. Reduce possibilities,
  3. Loop until no new values are found in one iteration.

Also presented was the idea of using a possibilities table for the reduction process.  This will become important in helping keep all of the PAC threads singing from the same hymn sheet.

In the next article we will look at presenting the basic data structures and getting a single threaded, non-PAC, version of this process up and running.  With any luck it should prove my hand work above!


How to Code Comment

Its been a while since I last posted, primarily because of my health, but also because I am working on something that I don’t wish to post on until I have it all together.  However last week at work I was helping a friend with his code, specifically logging and how it should be written for the reader which got me to thinking about code commenting too.  Writing comments is a lot like writing log entries, you need to dump the technical and get with the intentional.  It’s definitely more art than science but there are some basic practical rules of thumb that should help.

Thumb Number 1 – Do not repeat the code, it’s already been said

So let’s start with a classic.

/* c is an integer */
int c = 0;

I just love this, it reminds me so much of grading first year papers at university.  Yes my friend c is an integer, and if you look really closely that’s just what the code says.  So why say it again?  Why not?  The first question is rhetorical the second deserves an answer.  In general do not repeat what is said in the code as if you change it you make a messy maintenance trap for yourself.  If you come back and change the code you need to change the comment.

Thumb Number 2 – Do not state your intentions, they may change

Our next example is little better than the first.

/* c is a counter */
int c = 0;

Surprisingly the comment is a lie. The variable c is not a counter, not yet anyway, it is just an integer. It might be used as an counter later, but that might change and stating intentions like this might be very noble but ultimately it could be very misleading and cause grief. So rule of thumb is if it is obvious then just don’t do it. In fact it is difficult to justify the commenting of variables of built in type. Even commenting complex types or imported types such as the following are somewhat hard to justify.

/* f is a pointer to a file */
FILE *f = null;

Thumb Number 3 – Talk to your audience, they want to know your thoughts

Write your comments so that your reader knows what you were up to when you wrote the code. I recently have the privilege of reading some nice looking C code. Unfortunately it was completely uncommented. So when I looked at a function called trim_field there was nothing to tell me what the code was actually doing. As it turns out it also replaced the unprintable characters in the field with printable characters.

void trim_field(char * f, int l)
{
    int c = l;
    for (c = l; c > 0; c--)
        ...;
}

Naturally I’ve changed just about everything to protect the guilty but the essence is there. All you need to consider is that an unprintable character is ‘\0′. Now read it again with a comment.

void trim_field(char * f, int l)
{
    int c = l;
    /* Replace all of the non-printable characters in the field
       with a space. */
    for (c = l; c > 0; c--)
        ...;
}

Now the first example is completely valid even though it does not operate on the first character of the field. That is because there was no statement of what the code should do. Note that the comment does not break principle number 2 as it is a statement of what the code should do at this point, not a good intentions statement of what something might be used for in the future. So from the comment we see that the code does not conform because the comment says all but the code does everything but the first one. If there had have been a comment like this in the code that I was reviewing I would have saved about half an hour of time. It takes less than 16 of such issues to soak up a day’s work.

Thumb Number 4 – Explain yourself, give the reader some context

Sticking with our little trim_field example for a while, it would also have been very helpful to know what the function was supposed to do without having to work through the code. If you look at the Java coding standards generally adopted because of automated documentation tools, for example Javadoc, you will see a fascination with the fields in and out. Which is not a bad start. However because we human animals tend to like filling out forms we seem often to forget that there is more to life than just forms and hence a lot of Java documentation stops with the enumeration of fields. So lets take this approach with our trim_field example.

/*
 * function: trim_field
 * input: char * f - a pre-populated string of characters to trim
 * input: int l - the number of characters in the string f
 * output: none
 */
void trim_field(char * f, int l)
{
        ...;
}

Lovely. But really unhelpful and it does break thumb number 1. This sort of commenting came about not to help the reader but to help the documenter. In reality its just a form to be submitted to a tool that will produce a library of code interfaces or API. However its a mistake to think that this is all that is necessary. To be fair I’m probably being overly harsh on the Javadoc guys, its not their fault, they actually do allow for a great deal of excellent commenting. But the human creature seems to be a lazy one and so frequently this is all I see when presented with code for review. So lets move on.

/*
 * function: trim_field
 *
 * description: This function takes a pointer string of characters and
 * changes it in place it so that:
 *  1. The string is terminated with a \'0' after the last printable
 *     character if the last printable character is not at the end of
 *     the string.
 *  2. All non-printing characters once the trim has been performed are
 *     changed to a space character.
 *
 * input: char * f - a pre-populated string of characters to trim
 * input: int l - the number of characters in the string f
 * output: none
 */
void trim_field(char * f, int l)
{
        ...;
}

Of course this could be dramatically improved, but that is where the art bit comes in. You see the function has been explained and now if you see the code doing something different you can quite happily question its validity. It also says to the reader that this dangerous little piece of code is going to reach back into the context of whoever set up f and monkey with it’s internals. This is quite a valid thing to do but now at lest your reader is aware of it.

Thumb Number 5 – Don’t drown your code, your audience may not be lifesavers.

Now it seems a little odd but there are those amongst us who swing to the other end of the commenting pendulum and go absolutely berserk. This at first seems good. I’m firmly in the camp that says if you remove all of your code you should be left with a commented architecture. But at the same time if you end up with a lot of words to explain your mind, give it more thought.

The objective of commenting is to enhance your code. Get stuck in, be brief, get out. For example the following snippet from above could easily be grossly over embellished.

/*
 ...
 *  1. The string is terminated with a \'0' after the last printable
 *     character if the last printable character is not at the end of
 *     the string.
 ...
 */

It might be tempting to explain why it was decided to terminate the string with a ‘\0′ instead of lets say returning a new length for the string. This could make it look more like the following.

/*
 ...
 *  1. The string is terminated with a \'0' after the last printable
 *     character if the last printable character is not at the end of
 *     the string.  We could alternatively have simply returned a
 *     new string length but doing so may have created errors for
 *     boundary checking in code elsewhere.
 ...
 */

Which is very nice and descriptive, but this is code and not a novel. Such comments belong in other documents based around requirements perhaps referring to coding standards. If you were to go berserk and include comments like this consistently it would eventually obscure the code during review.

When writing anything, especially in communicating thought, the following quote is worth remembering.

If you can’t write your idea on the back of my calling card, you don’t have a clear idea. ~David Belasco

Thumb Number 6 – Consider your audience’s desires, lest they surprise you

One of the great bug bears of my life as a software engineer/architect has been the misinformed idea that coders write comments primarily for themselves. This is not the case. There is a lovely quote that I like which illustrates this quite well.

The past is a foreign country; they do things differently there. ~Lesley P. Hartley, The Go-Between, 1953

What we all fail to remember is that we are not the same person tomorrow as we are today. Yes the tense is right. Those two people will look at things differently and be approaching the matter for different reasons. Any comment written should be for a stranger, so what do you want them to see? How will they be looking at your code?

A number of different people will be looking at you code, but generally they can be classified as three distinct groups; hunters, renovators and free loaders. You, as a coder, will probably be all three.

  • Hunters are looking to kill some thing. Normally a bug. Sometimes they need to simply find out why a feature behaves the way it does. To help this creature comment infrequently but cover the essence as shown in thumb 2.
  • Renovators are looking to extend your work. Make sure that maintaining your comments are easy. Be brief, be concise and be consistent.
  • Free loaders just want to use your code. Summarise so that they don’t have to look to far into the code itself. Make sure that automated document schemes can pick up the relevant comments that make an API useful.

When you think of your code from these perspectives it will help identify areas of weakness before you submit it for the inevitable code review. Hopefully it will help the quality of your code overall.

In practical terms you should treat each page of code as a written work. Once you have completed the framework (code) and embellished it (commented) review it as a single piece of work from each of the three perspectives to help those who come along after you. You will be helping yourself.

Finally

I guess the real key to commenting well is not the rules, the coding standards, the automatic documentation tools, the IDE automated cross referencing or even grammar and spelling. All of these things are very useful and I recommend all of them, the secret to good commenting is writing for people so they can use what you have written.


An SSL Client Using OpenSSL

Recently I wrote about how to use OpenSSL to connect to a plain data server, this time I’m modifying the same code to perform encrypted connections.  Naturally this is more of an example for how to use the API than production ready code.  It’s main purpose is to show the very small difference between using the library as I did last time and how that example can be altered to create a basic SSL client.

The essential changes to the code below are the replacement of the connection function ‘connect_unencrypted(host_and_port)‘ with ‘connect_encrypted(host_and_port, store_path, store_type, &ctx, &ssl)‘ and the introduction of the SSL cleanup step ‘SSL_CTX_free(ctx)‘.  All other changes are purely cosmetic; which really shows how simple adding SSL to your application connections can be.  Externally you need to provide the root CA certificate for the connection to be verified by.  That’s it.

At this point I could warble through the connection function, but you should just read through it yourself and consult the SSL man pages.  Note that there is a dreadful buffer overflow possibility in this code and no real error handling, just a bit of logging.  This is to keep the example short and also because only you will know what valid handling should take place for each situation when you write your own code.

So take a look and enjoy.  To try this out yourself:

  1. Make sure that you have Firefox, GCC and OpenSSL (development sources and libraries) installed.
  2. Copy the following code to a file called ‘main.c‘ in a directory that you will be playing around in.
  3. Compile the code using ‘gcc main.c -o sslclient -lssl‘ if you are on Linux or ‘gcc main.c -o sslclient -lssl-lcrypto‘ if you are on OSX.
  4. Select an SSL (https) web site to connect to and find the Root CA’s certificate name in the site’s certificate.
  5. Either export the appropriate root CA from Firefox or obtain it directly from the CA online in pem format and copy it to a file ‘certificate.pem‘ in the same directory as the ‘sslclient‘ file.
  6. Run the following command:


'./sslclient servername:443 "GET / \r\n\r\n" certificate.pem f e'


/*
 * File:   main.cpp
 *
 * Licence: GPL2
 */

/* Standard headers */
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

/* OpenSSL headers */
#include <openssl/bio.h>
#include <openssl/ssl.h>
#include <openssl/err.h>

/**
 * Simple log function
 */
void slog(char* message) {
    fprintf(stdout, message);
}

/**
 * Print SSL error details
 */
void print_ssl_error(char* message, FILE* out) {

    fprintf(out, message);
    fprintf(out, "Error: %s\n", ERR_reason_error_string(ERR_get_error()));
    fprintf(out, "%s\n", ERR_error_string(ERR_get_error(), NULL));
    ERR_print_errors_fp(out);
}

/**
 * Print SSL error details with inserted content
 */
void print_ssl_error_2(char* message, char* content, FILE* out) {

    fprintf(out, message, content);
    fprintf(out, "Error: %s\n", ERR_reason_error_string(ERR_get_error()));
    fprintf(out, "%s\n", ERR_error_string(ERR_get_error(), NULL));
    ERR_print_errors_fp(out);
}

/**
 * Initialise OpenSSL
 */
void init_openssl() {

    /* call the standard SSL init functions */
    SSL_load_error_strings();
    SSL_library_init();
    ERR_load_BIO_strings();
    OpenSSL_add_all_algorithms();

    /* seed the random number system - only really nessecary for systems without '/dev/random' */
    /* RAND_add(?,?,?); need to work out a cryptographically significant way of generating the seed */
}

/**
 * Close an unencrypted connection gracefully
 */
int close_connection(BIO* bio) {

    int r = 0;

    r = BIO_free(bio);
    if (r == 0) {
        /* Error unable to free BIO */
    }

    return r;
}

/**
 * Connect to a host using an unencrypted stream
 */
BIO* connect_unencrypted(char* host_and_port) {

    BIO* bio = NULL;

    /* Create a new connection */
    bio = BIO_new_connect(host_and_port);
    if (bio == NULL) {

        print_ssl_error("Unable to create a new unencrypted BIO object.\n", stdout);
        return NULL;
    }

    /* Verify successful connection */
    if (BIO_do_connect(bio) != 1) {

        print_ssl_error("Unable to connect unencrypted.\n", stdout);
        close_connection(bio);
        return NULL;
    }

    return bio;
}

/**
 * Connect to a host using an encrypted stream
 */
BIO* connect_encrypted(char* host_and_port, char* store_path, char store_type, SSL_CTX** ctx, SSL** ssl) {

    BIO* bio = NULL;
    int r = 0;

    /* Set up the SSL pointers */
    *ctx = SSL_CTX_new(SSLv23_client_method());
    *ssl = NULL;

    /* Load the trust store from the pem location in argv[2] */
    if (store_type == 'f')
        r = SSL_CTX_load_verify_locations(*ctx, store_path, NULL);
    else
        r = SSL_CTX_load_verify_locations(*ctx, NULL, store_path);
    if (r == 0) {

        print_ssl_error_2("Unable to load the trust store from %s.\n", store_path, stdout);
        return NULL;
    }

    /* Setting up the BIO SSL object */
    bio = BIO_new_ssl_connect(*ctx);
    BIO_get_ssl(bio, ssl);
    if (!(*ssl)) {

        print_ssl_error("Unable to allocate SSL pointer.\n", stdout);
        return NULL;
    }
    SSL_set_mode(*ssl, SSL_MODE_AUTO_RETRY);

    /* Attempt to connect */
    BIO_set_conn_hostname(bio, host_and_port);

    /* Verify the connection opened and perform the handshake */
    if (BIO_do_connect(bio) < 1) {

        print_ssl_error_2("Unable to connect BIO.%s\n", host_and_port, stdout);
        return NULL;
    }

    if (SSL_get_verify_result(*ssl) != X509_V_OK) {

        print_ssl_error("Unable to verify connection result.\n", stdout);
    }

    return bio;
}

/**
 * Read a from a stream and handle restarts if nessecary
 */
ssize_t read_from_stream(BIO* bio, char* buffer, ssize_t length) {

    ssize_t r = -1;

    while (r < 0) {

        r = BIO_read(bio, buffer, length);
        if (r == 0) {

            print_ssl_error("Reached the end of the data stream.\n", stdout);
            continue;

        } else if (r < 0) {

            if (!BIO_should_retry(bio)) {

                print_ssl_error("BIO_read should retry test failed.\n", stdout);
                continue;
            }

            /* It would be prudent to check the reason for the retry and handle
             * it appropriately here */
        }

    };

    return r;
}

/**
 * Write to a stream and handle restarts if nessecary
 */
int write_to_stream(BIO* bio, char* buffer, ssize_t length) {

    ssize_t r = -1;

    while (r < 0) {

        r = BIO_write(bio, buffer, length);
        if (r <= 0) {

            if (!BIO_should_retry(bio)) {

                print_ssl_error("BIO_read should retry test failed.\n", stdout);
                continue;
            }

            /* It would be prudent to check the reason for the retry and handle
             * it appropriately here */
        }

    }

    return r;
}

/**
 * Main SSL demonstration code entry point
 */
int main(int argc, char** argv) {

    char* host_and_port = argv[1]; /* localhost:4422 */
    char* server_request = argv[2]; /* "GET / \r\n\r\n" */
    char* store_path = argv[3]; /* /home/user/projects/sslclient/certificate.pem */
    char store_type = argv[4][0]; /* f = file, anything else is a directory structure */
    char connection_type = argv[5][0]; /* e = encrypted, anything else is unencrypted */

    char buffer[4096];
    buffer[0] = 0;

    BIO* bio;
    SSL_CTX* ctx = NULL;
    SSL* ssl = NULL;

    /* initilise the OpenSSL library */
    init_openssl();

    /* encrypted link */
    if (connection_type == 'e') {

        if ((bio = connect_encrypted(host_and_port, store_path, store_type, &ctx, &ssl)) == NULL)
            return (EXIT_FAILURE);
    }
        /* unencrypted link */
    else if ((bio = connect_unencrypted(host_and_port)) == NULL)
        return (EXIT_FAILURE);

    write_to_stream(bio, server_request, strlen(server_request));
    read_from_stream(bio, buffer, 4096);
    printf("%s\r\n", buffer);

    if (close_connection(bio) == 0)
        return (EXIT_FAILURE);

    /* clean up the SSL context resources for the encrypted link */
    if (connection_type == 'e')
        SSL_CTX_free(ctx);

    return (EXIT_SUCCESS);
}

Non SSL Connections Using OpenSSL

Strange as it seems but you can make non-SSL connections using the OpenSSL libraries.  The following code is a brief example.  I like it because it appears to simplify (arguably) the rather more involved Berkley sockets.  It still lets you get at the socket if you want to mangle it yourself.  But ultimately its kind of handy to have a library that does SSL and plain transfers without too much of a code change.

Here is a simple example of using the OpenSSL BIO interface to retrieve a root web page.  Naturally most of the exception handling is not included so don’t going using it for production purposes as is.  Not that it’d be a lot of use as anything other than an exaple of the OpenSSL BIO API usage.

Have fun.


/*
 * File:   main.cpp
 *
 * Licence: GPL2
 */

/* Standard headers */
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

/* OpenSSL headers */
#include <openssl/bio.h>
#include <openssl/ssl.h>
#include <openssl/err.h>

/**
 * Initialise OpenSSL
 */
void init_openssl() {

    SSL_load_error_strings();
    ERR_load_BIO_strings();
    OpenSSL_add_all_algorithms();
}

/**
 * Close a BIO connection gracefully
 */
int close_connection(BIO* bio) {

    int r = 0;

    r = BIO_free(bio);
    if (r == 0) {
        /* Error unable to free BIO */
    }

    return r;
}

/**
 * Connect to a host using an unencrypted stream
 */
BIO* connect_unencrypted(char* host_and_port) {

    BIO* bio = NULL;

    /* Create a new connection */
    bio = BIO_new_connect(host_and_port);
    if (bio == NULL) {

        printf("Error: %s\n", ERR_reason_error_string(ERR_get_error()));
        printf("%s\n", ERR_error_string(ERR_get_error(), NULL));

        return NULL;
    }

    /* Verify successful connection */
    if (BIO_do_connect(bio) != 1) {

        printf("Error: %s\n", ERR_reason_error_string(ERR_get_error()));
        printf("%s\n", ERR_error_string(ERR_get_error(), NULL));

        close_connection(bio);
        return NULL;
    }

    return bio;
}

/**
 * Read a from a stream and handle restarts if nessecary
 */
ssize_t read_from_stream(BIO* bio, char* buffer, ssize_t length) {

    ssize_t r = -1;

    while (r < 0) {

        r = BIO_read(bio, buffer, length);
        if (r == 0) {

            /* Handle closed connection */
            continue;

        } else if (r < 0) {

            if (!BIO_should_retry(bio)) {

                /* Handle failed read here */

                printf("Error: %s\n", ERR_reason_error_string(ERR_get_error()));
                printf("%s\n", ERR_error_string(ERR_get_error(), NULL));

                continue;
            }

            /* It would be prudent to check the reason for the retry and handle
             * it appropriately here */
        }

    };

    return r;
}

/**
 * Write to a stream and handle restarts if nessecary
 */
int write_to_stream(BIO* bio, char* buffer, ssize_t length) {

    ssize_t r = -1;

    while (r < 0) {

        r = BIO_write(bio, buffer, length);
        if (r <= 0) {

            if (!BIO_should_retry(bio)) {

                /* Handle failed write here */
                printf("Error: %s\n", ERR_reason_error_string(ERR_get_error()));
                printf("%s\n", ERR_error_string(ERR_get_error(), NULL));

                continue;
            }

            /* It would be prudent to check the reason for the retry and handle
             * it appropriately here */
        }

    }

    return r;
}

/**
 * Main SSL demonstration code entry point
 */
int main(int argc, char** argv) {

    BIO * bio;
    char *addr = "GET / HTTP/1.1\r\nHost: www.home.com\r\n\r\n";
    char buffer[4096];
    buffer[0] = 0;

    init_openssl();

    if ((bio = connect_unencrypted(argv[1])) == NULL)
        return (EXIT_FAILURE);

    write_to_stream(bio, addr, strlen(addr));
    read_from_stream(bio, buffer, 4096);
    printf("%s", buffer);

    if (close_connection(bio) == 0)
        return (EXIT_FAILURE);

    return (EXIT_SUCCESS);
}

Child’s Toys and JQuery

In late January I suffered a rather nasty medical event which had me on my back for a few weeks and will probably not have me back to full health for some time.  One of the things that helped me was Chess and programming.  Help meant proving to myself that my mental faculties were completely intact.  My results in Chess were no more rubbish than usual so that proved a little but programming has helped a bit more.

Any way we have been trying different things to encourage our children to learn maths so I wrote a little maths testing tool to help us.  Its plain, not fancy, just asks questions and keeps count of the results.  The idea is that we can get one or other of the urchins to run a few tests when they ask for something.  So we get them to get 200 questions correct and then they can play their game, watch TV or whatever.  It has no rules so you can make them up as you want.

So it’s here, if you want to play.  Real simple.  An example of what you can do with a little HTML, CSS and some simple JQuery.


SELinux and audit2allow

Using SELinux is reasonably straight forward once you get used to it.  It protects OS objects and configuration from potentially dangerous manipulation.  If an exception to its current policy is found then it is logged and either denied or allowed.  The reasons that it may be allowed depend on the policy’s construction.

For me I use a targeted, as opposed to blanket, policy and set the SELinux level to enforcing.  This means that not everything unplanned will be blocked, but it does log and alert me when something crosses the policy line and allows me to block it if I feel it is necessary.  Naturally it also can block services that I want to use too.  So in this case I will look at setting up vsftpd to access home directories.

If you run this command (you need the selinuxtools package installed) you will generate a new policy straight to the console to allow all of the exceptions found in the SELinux log ‘audit2allow < /var/log/audit/audit.log’.  Naturally this is not what we want as it will generate a policy that will allow all previously caught exceptions to proceed.  We just want to allow ftp; which is what the following statement does: ‘grep ftp /var/log/audit/audit.log | audit2allow -R’.

The result:

[foo@bar ~]# grep ftp /var/log/audit/audit.log | audit2allow -R
require {
type ftpd_t;
type home_root_t;
class capability net_raw;
class dir search;
}
#============= ftpd_t ==============
#!!!! This avc can be allowed using one of the these booleans:
#     allow_ftpd_full_access, allow_ftpd_full_access
allow ftpd_t home_root_t:dir search;
allow ftpd_t self:capability net_raw;

What is interesting here is that the audit2allow application alerts us to the fact that the same thing being achieved through the creation of this policy could also be accomplished using SELinux booleans; in this case  ‘setsebool -P allow_ftpd_full_access true’ would do the trick.  However we’re going to do this the long way as booleans are not always available, this is just the first step.

Now that we have inspected the policy we need to build it and integrate it into the active policy.  First we need to construct the new policy file using ‘grep ftp /var/log/audit/audit.log | audit2allow -M ftp’.  This will create a ‘ftp.te’ policy file and a ‘ftp.pp’ compiled policy file.  To make the new policy active simply run ‘semodule -i ftp.pp’ and its done.  This of course may reveal another denial or other OS configuration issue so you just need to perform an allow then test to see if there is anything else that needs sorting out.


A Brief Linux Sockets Connect Illustration

For a little while I’ve been playing with sockets in C now and have come up with the following succinct example of connecting.  Note that the connection is fairly flexible with regards to protocol and transport type.  It really is simply there to make the connection to somewhere else with as few questions asked.  It will involve DNS and service lookups if you provide names instead of the network address and port.  If you want to catch exceptions you need to clear the standard error variable and check it yourself after receiving a fail return value.  It won’t, or at least shouldn’t, report any exceptions because for my current purposes failures are not really exceptions just dead ends on some of many paths.  I tried to keep it simple too, have fun.


/* connect_to_socket
 *
 * connect to a socket using an initialised addrinfo structure,
 *
 * info is an initialised addrinfo structure
 * sock is a pointer to the location to store the socket descriptor when opened
 *
 * returns 0 if successful
 */
int connect_to_socket(const struct addrinfo *info, int* sock) {

    /* open socket */
    *sock = socket(info->ai_family, info->ai_socktype, info->ai_protocol);
    if (*sock < 1) return -1;

    /* connect to the server*/
    if (connect(*sock, info->ai_addr, info->ai_addrlen) == 0)
        return 0;

    /* clean up on failure */
    close(*sock);
    return -1;
}

/* connect_to_server
 *
 * connect to a server using the server name or adress and port or service name
 *
 * server is a string containing either the name or address of the server
 * port is a string containing either the service name or port number to use
 * sock is a pointer to the location to store the socket descriptor when opened
 *
 *  returns 0 if successful
 */
int connect_to_server(const char* server, const char* port, int* sock) {

    struct addrinfo hints, *info = NULL, *list = NULL;
    int e = 0;

    /* initialise the hints for retrieving the address details */
    memset(&hints, 0, sizeof (hints));
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_flags = AI_PASSIVE;
    hints.ai_protocol = 0;

    /* get the address if the server to connect to */
    e = getaddrinfo(server, port, &hints, &list);
    if (e != 0) return -1;

    /* connect to the first socket in the list */
    for (info = list; info != NULL; info = info->ai_next)
        if (connect_to_socket(info, sock) == 0)
            break;

    /* clean up and return */
    if (list != NULL) freeaddrinfo(list);
    if (sock > 0) return 0;
    else return -1;
}

When there’s Nothing in your Sock

Normally I do most of my network programming in Java; life is just easier that way. But some times, *very* rarely, you need to carve up a little C.  So I’ve not touched socket networking for a while and all its foibles.  Hence a few problems with reading from a socket.

When socket programming with the ‘read’ function it does not always return the length read into the buffer.  In fact if you are talking to an HTTP server a ‘GET /‘ will nearly always end with read returning 0.  This is because the socket will have been closed on your last read.  This of course begs the question, how do you know how much was read in the last read?  For me this was simple; I just zero filled the buffer prior to reading.


ssize_t r_read(int sock, void* buf, size_t size)
{
        ssize_t ret;
        bzero(buf, size);
        while (ret = read(sock, buf, size - 1), ret == -1 && errno == EINTR);
        return ret;
}


Which does work because now its a simple matter of counting until the first 0 shows up, but I’d still like to know how to tell how many characters were read without having to do the count. This becomes more important when transferring binary files as you won’t know if the 0 is validly part of the file or not.


Copyright © 1996-2010 Code Snips. All rights reserved.
iDream theme by Templates Next | Powered by WordPress