Writing things down so I can remember them later

WaltCo Tech

Archives Posts

Working on the Edge can be frustrating and amazing at the same time

February 24th, 2008 by Walter Wilfinger

I’m messing around with the edge of the Merb 0.9 developer release and Haml. I have very little experience in Haml and I’ve never used Merb before, so it’s probably a bad idea to start off with a developer release but hey whatever. I could not get the form_tag function from merb_helpers to work. This…

%p
  This will be the most basic DNS lookup ever

- form_tag(:action => url(:controller => :dns, :action => :index)) do
  = text_control "target", :label => "Domain/IP"

Gave me this…

undefined local variable or method `_buf' for # - (NameError)

I wasn’t sure if I was using Haml wrong, Merb wrong, or both. Google indexed an error log of the exact same problem and a few minutes later the bug entry which was entered just 45 minutes before I found it.

That’s just crazy cool.

Filed under Haml, Merb having No Comments »

Archives Posts

Use SWIG to Build a Ruby Extension to Wrap a Windows DLL

February 23rd, 2008 by Walter Wilfinger

I had a need to do some automation in Windows. AutoIt is an excellent tool for the job. It comes with its own scripting language and, more importantly, a well documented DLL interface called AutoItX. Why settle for learning a new, limited-scope, scripting language when you can build a Ruby Extension to wrap the API? On top of getting all of AutoIt’s automation functions, I get everything that comes with Ruby: Modules, classes, functions, mix-ins, inheritance, and access to hundreds of pre-written libraries.

I’m going to be using SWIG to semi-automatically build the C extension code that will wrap the AutoItX DLL. SWIG can take a chunk of C/C++ code and generate the C/C++ code necessary to build support in Ruby. It has support for several other dynamic languages as well and it is actually rather useful.

Pre-requisites

Remember that this is going to be built on Windows. We’re going to run into some snags because of this. Setting up the build environment is actually going to take more time than writing the extension with SWIG. Download and install the following…

SWIG’s Windows installer isn’t really an installer at all. I extracted it into C:\Program Files\SWIG. I and then added that to my PATH in my Makefile, which we’ll do shortly.

Set up the project directory

In my project directory at My Documents\Projects\Autoit I have the following.

AutoIt.h # Copy from C:\Program Files\AutoIt3\AutoItX\StandardDLL\VC6
AutoItX3.lib # Copy from C:\Program Files\AutoIt3\AutoItX\StandardDLL\VC6
AutoItX3.dll # Copy from C:\Program Files\AutoIt3\AutoItX
# We will make the following three files
autoit.i # SWIG configuration file
extconf.rb # Ruby file to create our Makefile
Makefile # Will be generated by extconf.rb

Modify Ruby’s config.h

The one-click install of ruby is compiled with Visual Studio 6.0 which I would be surprised if you were using (it was released 10 years ago). I do not know why the One-click Windows Rubyists decided to go with Visual Studio 6.0 and I really don’t care. I’ve seen pretty dumb compatibility arguments from Rubyists before and I don’t feel like reading through another one.

This won’t hurt us until we try to compile our extension, but let’s take care of it now. Open up C:\ruby\lib\ruby\1.8\i386-mswin32\config.h. Right at the top you see this.

#if _MSC_VER != 1200
#error MSC version unmatch
#endif

This says, “If you’re not using VC 6.0 bail.” Why? I don’t know. Comment it out.

Many thanks to anelson and his blog The Totally Bullshit Ruby Extension Experience on Windows. He helped me with the config.h problem and the writing of much of this guide.

Create the SWIG configuration file

You can check the SWIG documentation for more details, but the syntax is rather easy. Put this in a file called autoit.i in your project directory.

%module autoit
%include typemaps.i

%inline %{
#include "AutoIt3.h"
%}

#define WINAPI // Only needed for AutoIt3.h

long WINAPI AU3_Run(const char *INPUT, const char *INPUT, long nShowFlags);

%include "AutoIt3.h"

Here’s a quick run down of the SWIG source.

%module% autoit defines the name of the resultant module. This means that eventually we will include our module with a call of require 'autoit'.

%include typemaps.i is required for the INPUT macros you see below in the AU3_Run line. In C-style programs you will often see pointers used for function input and output. The typemaps.i macros will take care of making this work in Ruby for you. In very simple terms, you will want to do the following.

  • If you see a pointer used for function input, change the function prototype to use INPUT
  • If you see a pointer used for function output, change the function prototype to use OUTPUT
  • If you see a pointer used for function input and output, change the function prototype to use INOUT

You can read more about these macros in the SWIG documentation for Ruby.

The %inline block of code will insert everything between %{ and %} verbatim into the resultant extension code. Here, we’re making sure that the extension code includes AutoIt3.h.

#define WINAPI was included because there are WINAPI macros all over the AutoIt3.h file. This lets us %include "AutoIt3.h" later without modifying it.

The AU3_Run line is an example of how to use the INPUT SWIG macro. The first two parameters are input pointers, so I have changed the name of the variable to be INPUT.

Finally, %include "AutoIt3.h" copies the contents of AutoIt3.h into our SWIG file verbatim, much like how #include works in C. This saves us from having to manually type out all the function prototypes in AutoIt3.h.

Run SWIG part of our build by running this in the Platform SDK command prompt that we modified previously.

swig -c++ -ruby autoit.i

This should not report any errors and create a file called autoit_wrap.cxx in your project directory. You must do this before you create and run the Makefile we generate below. If you don’t have any C/C++ files in your project when you generate the Makefile, your project will be very boring and very empty.

Set up mkmf configuration file

Create a file called extconf.rb in your project directory and put in the following. I got this code from Peter Cooper’s guide to creating a Ruby C Extension and then modified it.

extconf.rb will create a file Makefile in the project directory. You should be able to just run nmake from here to build the Ruby extension, but because we’re working in Windows and not *nix we have to take some extra steps. Again, you can read all about Ruby’s problems with building extensions in Windows in the blog post The Totally Bullshit Ruby Extension Experience on Windows. Basically, the Makefile generated by mkmf does NOT work on Windows. The documentation for mkmf isn’t very helpful either. For Windows, we need to add a .manifest file into the final autoit.so output. For my project, we need to add the SWIG program directory to our build path, add a dependency line for autoit_wrap.cxx, and add AutoItX3.lib as library dependency.

I found that the best way to embed the .manifest file is to edit the mkmf source code. You can modify the Makefile that is generated, but if you change the source code you’ll never have to worry about it again. If you used the Ruby One-click installer, mkmf.rb can be found at C:\ruby\lib\ruby\1.8\mkmf.rb.

# Line 1324
  mfile.print "$(RUBYARCHDIR)/" if $extout
  mfile.print "$(DLLIB): ", (makedef ? "$(DEFFILE) " : ""), "$(OBJS)\n"
  mfile.print "\t@-$(RM) $@\n"
  mfile.print "\t@-$(MAKEDIRS) $(@D)\n" if $extout
  # link_so = LINK_SO.gsub(/^/, "\t")
  # mfile.print link_so, "\n\n"
  # Add the lines below
  link_so = LINK_SO.gsub(/^/, "\t")
  mfile.print link_so, "\n"
  mfile.print "\tmt.exe -manifest $(DLLIB).manifest -outputresource:$(DLLIB);2\n" if $mswin
  mfile.print "\n\n"

This will add mt.exe -manifest $(DLLIB).manifest -outputresource:$(DLLIB);2 to the Makefile dependency for our output .so file if we’re building for Windows.

Below is the code for extconf.rb.

# Loads mkmf which is used to make makefiles for Ruby extensions
require 'mkmf'

# Give it a name
extension_name = 'autoit'

# The destination
dir_config(extension_name)

# Add autoitx.lib
$libs = append_library($libs,"AutoItX3")

# Do the work
create_makefile(extension_name)

# For SWIG
File.open("Makefile", "a") do |mf|
  mf.puts "PATH=$(PATH);C:\\Program Files\\SWIG"
  mf.puts "\n\n"
  mf.puts "autoit_wrap.cxx: autoit.i\n"
  mf.puts "\tswig -c++ -ruby autoit.i\n"
end

The lines I modified are just for my project. Explanations follow.

The line $libs = append_library($libs,"AutoItX3") is something I gleaned off of the SWIG Ruby documentation. This tells the Makefile to compile AutoItX3.lib into our project.

The last chunk of code after # For SWIG both adds the SWIG directory to the build PATH and adds a dependency for autoit_wrap.cxx. This way if I modify autoit.i the file autoit_wrap.cxx will automatically be regenerated by nmake.

Generate Makefile using mkmf

Run the command line shortcut for Windows XP 32-bit that was installed into the Start menu by the Platform SDK. This is in Microsoft Windows SDK 6.1 -> CMD Shell. Run the following to generate the Makefile.

ruby extconf.rb

Build the extension library

Now in the Platform SDK command prompt run this.

nmake

If you crossed your fingers hard enough you will end up with a autoit.so file in your project directory. You WILL get errors from the compiler about depreciated and unknown options. Blame this on mkmf, but the build should still work.

Install the library

Cross your fingers again and run this from the command prompt.

nmake install

This copies autoit.so to Ruby’s Windows library path. With the one click install defaults that would be c:/ruby/lib/ruby/site_ruby/1.8/i386-msvcrt/autoit.so

Copy AutoitX3.dll into a system path

Because our Ruby library depends on AutoItX3.dll, Windows needs to be able to find AutoItX3.dll. It can either be somewhere in your PATH or in the current directory of your Ruby script. I didn’t really care about dirtying up my system, so I copied it into C:\windows\system32 and forgot about it.

Test the extension with irb

C:\>irb
irb(main):001:0> require 'autoit'
=> true
irb(main):002:0> Autoit.AU3_Run "notepad.exe", "", 1
=> 912
irb(main):003:0>

This should open up Notepad. Magic!

Troubleshooting

If Windows cannot find AutoItX3.dll, Ruby will error out with the following uninformative error.

c:/ruby/lib/ruby/site_ruby/1.8/i386-msvcrt/autoit.so: 126: The specified module could not be found.   - c:/ruby/lib/ruby/site_ruby/1.8/i386-msvcrt/autoit.so (LoadError)
    from c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `require'
    from MyTest/autoit.rb:1

Make sure AutoItX3.dll is in a system path or in the current directory.

If the manifest was not correctly embedded you will get errors about not being able to find MSVC80.dll when we finally run our extension. Ruby itself will error out with a LoadError exception. If you run the script from a command prompt, Windows will popup complaining about missing MSVC80.dll. This happens even if you do have the MFC 8.0 run times installed.

If running nmake ends without compiling anything, you more than likely generated the Makefile before any source files were in your project directory. Run swig -c++ -ruby autoit.i to generate autoit_wrap.cxx and then run nmake again.

Currently, I cannot get OUTPUT parameters to work. The solution to this lies in SWIG typemaps. I’m going to write up another post about that.

Wrap the wrapper (optional)

You may not like the interface that the dll gives you. You can of course write wrappers around the extension to make things easier on yourself. For example.

module Autoit
  def Autoit.run(file,directory="",flag=1)
    Autoit.AU3_Run(file,directory,flag)
  end
end

# These two are now equivalent
Autoit.AU3_Run("notepad.exe", "", 1)
Autoit.run "notepad.exe"

A whole bunch of convenience functions and error handling can all be handled in Ruby itself. You barely have to know any C/C++ to make working with the third party library much more convenient. For example, the AutoIt API will set an error flag to 1 if a function fails. You can retrieve the value of the error flag by calling AU3_Error. I can write wrappers that will check this error code and throw a Ruby exception if it is set. This saves me from having to manually check the error code every time. Neato.

Conclusion

I hope this helps anyone that might be trying to wrap third party DLLs into a Ruby extension. This is not a very simple process and there is a lot of work to be done just to get to the point of creating the Ruby module library. You’ll have to deal with several things not being written with Windows in mind: SWIG not having a real installer, mkmf not creating a very useful Makefile, and the manifest issue. If you have any problems getting this to work, please leave a comment. I’ll see if I can help and, in turn, hopefully make this guide better.

Filed under AutoIt, ruby having No Comments »

Archives Posts

SSH slow to connect in Ubuntu 7.10 Gutsy Gibbon

February 2nd, 2008 by Walter Wilfinger

I have a laptop and desktop “server” on a local network both running Ubuntu 7.10. Today, I decided to get an SSH server running on both of them. To my dismay, it took SSH 15-20 seconds to prompt me for a password. After the delay, I didn’t run into any further speed issues. The problem ended up being with the way the server machine was looking up the reverse DNS of the client machine.

The OpenSSH server on the server attempts to look up the reverse DNS of the connecting machine. In my case, as would be the case in most local networks I suspect, the client machine does not have a host name (i.e. reverse DNS) set up. The quick solution is to edit /etc/nsswitch.conf on the server machine

...
# Change this line
# hosts:          files mdns4_minimal [NOTFOUND=return] dns mdns4
# To this (removed mdns4)
hosts:          files mdns4_minimal [NOTFOUND=return] dns
...

Explanation

nsswitch.conf defines how dns host lookups are performed. The system will try every method in turn until it finds one that succeeds or until it runs out of options. All of the lookup options in the default configuration are lightning quick except for mdns4. I’m not exactly sure what mdns4 is or in what situations I would absolutely need it enabled. Wikipedia says it’s some sort of zero configuration network protocol that spawned from Apple. Great. I’m not using it, so I’m ok with removing it from the configuration. If for whatever reason you are not OK with ditching it, I see two other options for you.

Configure sshd to not do any rDNS lookups

On the server machine, do the following

sudo echo "UseDNS no" >> /etc/ssh/sshd_config
sudo /etc/init.d/ssh restart

The OpenSSH daemon will now not attempt any reverse DNS lookups when a client connects. If you are using host names to authorize connections to the server, this is obviously not an option for you.

Add your client machine’s ip to the hosts file on the server machine

On the server machine, do the following

sudo echo "[your_client_ip] [your_client_hostname]" >> /etc/hosts

This is, of course, dependent on your client machine having a static IP. If the IP of the client changes, the server won’t have a host name to go on again, and you’ll be back to slow SSH connections.

Debugging SSH

To figure out the above I had to do a lot of sifting through debug logs. For reference, here is how to set up verbose logging for SSH on both the client and server side.

Server Side SSH Debug Logs

/etc/ssh/sshd_config

# Logging
SyslogFacility AUTH
LogLevel DEBUG3 # DEBUG3 is the most verbose, default is INFO

Then do tail -f /var/log/auth.log

Client Side SSH Debug

ssh -vvv [normal connection string] will output big logs on the client side.

File Versions

$ ssh -V
OpenSSH_4.6p1 Debian-5build1, OpenSSL 0.9.8e 23 Feb 2007
$ dpkg-query --search nsswitch.conf
manpages: /usr/share/man/man5/nsswitch.conf.5.gz
base-files: /usr/share/base-files/nsswitch.conf
$ dpkg-query --show base-files
base-files      4.0.0ubuntu5
$ dpkg-query --search /usr/sbin/sshd
openssh-server: /usr/sbin/sshd
$ dpkg-query --show openssh-server
openssh-server  1:4.6p1-5ubuntu0.1

Sources:

Ubuntu Bug report on slow SSH blaming it on GSSAPIAuthentication setting on client

Another Bug report about how mdns4 in nsswitch is really slow

Ubuntu Wiki on SSH

Format for nsswitch.conf