Camping: Going off
the Rails with Ruby
    Adventures in creative coding
  for people who should know better
The weasel words

This presentation contains code
That code is probably broken
If that bothers you - fix it
That’s called a...
Who are these lunatics?
     Eleanor McHugh
     eleanor@games-with-brains.com

     She does real-time systems


     Rom...
Alright, but what are
  they doing here?
Ruby
Pcap & BitStruct
WEBrick
Camping
but no Rails...
No Rails?

That’s right, we don’t use Rails
But we do use Ruby
And we do write web applications
So how is that possible?
Camping!!!
That’s right, we use Camping
It’s by Why The Lucky Stiff
It’s cool
It’s really cool
It’s so damn cool you’d hav...
It’s this simple!
%w[rubygems active_record markaby metaid ostruct].each {|lib| require lib}
module Camping;C=self;module ...
Why?
For fun
For profit
For the satisfaction of knowing exactly
how your application works
For the look on your boss’s face...
Earlier...

But let’s not get ahead of ourselves
First we want to take you on a journey
A journey back in time
A journey b...
January 3rd 2006
Location: The Secret Basement  Lair tm   of
Captain IP and The DNS avengers
Their task: to launch a new T...
Ruby to the Rescue

It’s easy to learn
It’s quick to code in
It’s pleasing to the eye
It’s fun!
You keep saying that
Yes!!!
Fun makes for better coders
Better coders write good code
Good code stands the test of time
If...
The console jockeys
let’s write a menu driven calculator
output: puts(), print()
input: gets(), termios library
old-fashio...
A simple calculator
 #!/usr/bin/env ruby -w
 require 'termios'

 $total = 0
 $menu_entries = [['+', quot;Addquot;], ['-', ...
A Ruby packet reader
The 7 layer IP model
What the heck?
We’re exploring the UDP layer




     We want to look at UDP and DNS traffic
     Our first implementation i...
UDP header in Ruby
udpip.rb
require 'bit-struct'                                                     class DNSQueryHeader ...
Capturing UDP packets
tcpdump.rb
#!/usr/local/bin/ruby
require 'pcaplet'
include Pcap
require 'udpip'

DIVIDER = quot;-quo...
A live UDP packet
A live DNS packet
Can we have that on
    Windows?
A GUI? You gotta be joking!!
Why do you think we use Macs?
How about we just turn it into...
The NDA kicks in
Here’s where we hit the brick wall on
what we can talk about
You might imagine a DNS-sniffing web
applicat...
Introducing WEBrick
WEBrick is an HTTP ser ver library
It’s part of the Ruby 1.8 release
It can ser ve static documents
It...
Static content
A standard HTTP ser ver
#!/usr/local/bin/ruby
require 'webrick'

server = WEBrick::HTTPServer.new(:Port => ...
Camping: Going off the Rails with Ruby
Camping: Going off the Rails with Ruby
Camping: Going off the Rails with Ruby
Camping: Going off the Rails with Ruby
Camping: Going off the Rails with Ruby
Camping: Going off the Rails with Ruby
Camping: Going off the Rails with Ruby
Camping: Going off the Rails with Ruby
Camping: Going off the Rails with Ruby
Camping: Going off the Rails with Ruby
Camping: Going off the Rails with Ruby
Camping: Going off the Rails with Ruby
Camping: Going off the Rails with Ruby
Camping: Going off the Rails with Ruby
Camping: Going off the Rails with Ruby
Camping: Going off the Rails with Ruby
Camping: Going off the Rails with Ruby
Camping: Going off the Rails with Ruby
Camping: Going off the Rails with Ruby
Camping: Going off the Rails with Ruby
Camping: Going off the Rails with Ruby
Camping: Going off the Rails with Ruby
Camping: Going off the Rails with Ruby
Camping: Going off the Rails with Ruby
Camping: Going off the Rails with Ruby
Camping: Going off the Rails with Ruby
Camping: Going off the Rails with Ruby
Camping: Going off the Rails with Ruby
Camping: Going off the Rails with Ruby
Camping: Going off the Rails with Ruby
Camping: Going off the Rails with Ruby
Camping: Going off the Rails with Ruby
Upcoming SlideShare
Loading in …5
×

Camping: Going off the Rails with Ruby

5,416 views
5,305 views

Published on

RailsConf Europe 2006 Presentation by myself and Romek Szczesniak. Ranges from Ruby pcap and roll-your-own WEBrick application servers to Why's brilliant Camping framework.
Some technical ability required.

Published in: Technology
2 Comments
5 Likes
Statistics
Notes
  • That's not a Ruby problem, it's an artefact of cutting and pasting from the slides (which being authored in Keynote may not always have the ASCII versions of printable characters). If you instead type the code in manually (or send me your email address so I can email you a copy) the errors should go away.
       Reply 
    Are you sure you want to  Yes  No
    Your message goes here
  • I was interested in trying out Ruby, so I thought I'd give it a shot with your simple calculator here. I pasted the source into a text file and ran it, but it says

    jritorto@Aedlinn~[22]12:49>./calc.ruby
    ./calc.ruby:11: Invalid char `\357' in expression
    ./calc.ruby:11: Invalid char `\254' in expression
    ./calc.ruby:11: Invalid char `\202' in expression
    jritorto@Aedlinn~[23]12:49>

    So I'm wondering about the simple and portable aspects, of course.

    Tried it on OpenSolaris 2009.06 with SUNWruby18 package.

    What did I do wrong?
       Reply 
    Are you sure you want to  Yes  No
    Your message goes here
No Downloads
Views
Total views
5,416
On SlideShare
0
From Embeds
0
Number of Embeds
56
Actions
Shares
0
Downloads
159
Comments
2
Likes
5
Embeds 0
No embeds

No notes for slide

Camping: Going off the Rails with Ruby

  1. 1. Camping: Going off the Rails with Ruby Adventures in creative coding for people who should know better
  2. 2. The weasel words This presentation contains code That code is probably broken If that bothers you - fix it That’s called a learning experience
  3. 3. Who are these lunatics? Eleanor McHugh eleanor@games-with-brains.com She does real-time systems Romek Szczesniak romek@spikyblackcat.co.uk He does security
  4. 4. Alright, but what are they doing here? Ruby Pcap & BitStruct WEBrick Camping but no Rails...
  5. 5. No Rails? That’s right, we don’t use Rails But we do use Ruby And we do write web applications So how is that possible?
  6. 6. Camping!!! That’s right, we use Camping It’s by Why The Lucky Stiff It’s cool It’s really cool It’s so damn cool you’d have to be mad not to use it!!!
  7. 7. It’s this simple! %w[rubygems active_record markaby metaid ostruct].each {|lib| require lib} module Camping;C=self;module Models;end;Models::Base=ActiveRecord::Base module Helpers;def R c,*args;p=/(.+?)/;args.inject(c.urls.detect{|x|x. scan(p).size==args.size}.dup){|str,a|str.gsub(p,(a.method(a.class.primary_key )[]rescue a).to_s)};end;def / p;File.join(@root,p) end;end;module Controllers module Base;include Helpers;attr_accessor :input,:cookies,:headers,:body, :status,:root;def method_missing(m,*args,&blk);str=m==:render ? markaview( *args,&blk):eval(quot;markaby.#{m}(*args,&blk)quot;);str=markaview(:layout){str }rescue nil;r(200,str.to_s);end;def r(s,b,h={});@status=s;@headers.merge!(h) @body=b;end;def redirect(c,*args);c=R(c,*args)if c.respond_to?:urls;r(302,'', 'Location'=>self/c);end;def service(r,e,m,a);@status,@headers,@root=200,{},e[ 'SCRIPT_NAME'];@cookies=C.cookie_parse(e['HTTP_COOKIE']||e['COOKIE']);cook= @cookies.marshal_dump.dup;if (quot;POSTquot;==e['REQUEST_METHOD'])and %r|Amultipart /form-data.*boundary=quot;?([^quot;;,]+)quot;?|n.match(e['CONTENT_TYPE']);return r(500, quot;No multipart/form-data supported.quot;)else;@input=C.qs_parse(e['REQUEST_METHOD' ]==quot;POSTquot;?r.read(e['CONTENT_LENGTH'].to_i):e['QUERY_STRING']);end;@body= method(m.downcase).call(*a);@headers[quot;Set-Cookiequot;]=@cookies.marshal_dump.map{ |k,v|quot;#{k}=#{C.escape(v)}; path=/quot;if v != cook[k]}.compact;self;end;def to_s quot;Status: #{@status}n#{{'Content-Type'=>'text/html'}.merge(@headers).map{|k,v| v.to_a.map{|v2|quot;#{k}: #{v2}quot;}}.flatten.join(quot;nquot;)}nn#{@body}quot;;end;def markaby;Class.new(Markaby::Builder){@root=@root;include Views;def tag!(*g,&b) [:href,:action].each{|a|(g.last[a]=self./(g.last[a]))rescue 0};super end}.new( instance_variables.map{|iv|[iv[1..-1].intern,instance_variable_get(iv)]},{}) end;def markaview(m,*args,&blk);markaby.instance_eval{Views.instance_method(m ).bind(self).call(*args, &blk);self}.to_s;end;end;class R;include Base end class NotFound<R;def get(p);r(404,div{h1(quot;#{C} Problem!quot;)+h2(quot;#{p} not foundquot;) });end end;class ServerError<R;def get(k,m,e);r(500,markaby.div{h1 quot;#{C} Prob lem!quot;;h2 quot;#{k}.#{m}quot;;h3 quot;#{e.class} #{e.message}:quot;;ul{e.backtrace.each{|bt|li( bt)}}})end end;class<<self;def R(*urls);Class.new(R){meta_def(:inherited){|c| c.meta_def(:urls){urls}}};end;def D(path);constants.each{|c|k=const_get(c) return k,$~[1..-1] if (k.urls rescue quot;/#{c.downcase}quot;).find {|x|path=~/^#{x} /?$/}};[NotFound,[path]];end end end;class<<self;def escape(s);s.to_s.gsub( /([^ a-zA-Z0-9_.-]+)/n){'%'+$1.unpack('H2'*$1.size).join('%').upcase}.tr(' ', '+') end;def unescape(s);s.tr('+', ' ').gsub(/((?:%[0-9a-fA-F]{2})+)/n){[$1. delete('%')].pack('H*')} end;def qs_parse(qs,d='&;');OpenStruct.new((qs||''). split(/[#{d}] */n).inject({}){|hsh,p|k,v=p.split('=',2).map{|v|unescape(v)} hsh[k]=v unless v.empty?;hsh}) end;def cookie_parse(s);c=qs_parse(s,';,') end def run(r=$stdin,w=$stdout);w<<begin;k,a=Controllers.D quot;/#{ENV['PATH_INFO']}quot;. gsub(%r!/+!,'/');m=ENV['REQUEST_METHOD']||quot;GETquot;;k.class_eval{include C include Controllers::Base;include Models};o=k.new;o.service(r,ENV,m,a);rescue =>e;Controllers::ServerError.new.service(r,ENV,quot;GETquot;,[k,m,e]);end;end;end module Views; include Controllers; include Helpers end;end
  8. 8. Why? For fun For profit For the satisfaction of knowing exactly how your application works For the look on your boss’s face when he reads the documentation
  9. 9. Earlier... But let’s not get ahead of ourselves First we want to take you on a journey A journey back in time A journey back to...
  10. 10. January 3rd 2006 Location: The Secret Basement Lair tm of Captain IP and The DNS avengers Their task: to launch a new Top Level Domain which DOESN’T RESOLVE MACHINE ADDRESSES?!?!?! Their resources? Hands to wave with and hit keyboards with!
  11. 11. Ruby to the Rescue It’s easy to learn It’s quick to code in It’s pleasing to the eye It’s fun!
  12. 12. You keep saying that Yes!!! Fun makes for better coders Better coders write good code Good code stands the test of time If coding isn’t fun YOU’RE USING THE WRONG TOOLS!!!!
  13. 13. The console jockeys let’s write a menu driven calculator output: puts(), print() input: gets(), termios library old-fashioned and unattractive termios is fiddly
  14. 14. A simple calculator #!/usr/bin/env ruby -w require 'termios' $total = 0 $menu_entries = [['+', quot;Addquot;], ['-', quot;Subtractquot;], ['*', quot;Multiplyquot;], ['/', quot;Dividequot;], ['c', 'Clear'], ['q',quot;Quitquot;]] $commands = $entries.inject([]) { | commands, entry | commands << entry[0] } $captions = $entries.inject([]) { | captions, entry | captions << entry[1] } loop do puts quot;nSimple Calculatornquot; entries.each { | entry | puts quot;#{entry[0]}. #{entry[1]}nquot; } t = Termios.tcgetattr(STDIN) t.lflag &= ~Termios::ICANON Termios.tcsetattr(STDIN,0,t) begin action = STDIN.getc.chr end until $commands.member?(action) exit() if action == $commands.last action = $commands.index(action) puts quot;n#{$captions[action]}nnquot; case action when 0 : $total += gets() when 1 : $total -= gets() when 2 : $total *= gets() when 3 : $total /= gets() when 4 : $total = 0 end puts quot;Total = #{$total}quot; end
  15. 15. A Ruby packet reader The 7 layer IP model
  16. 16. What the heck? We’re exploring the UDP layer We want to look at UDP and DNS traffic Our first implementation is console- based, so hold on to your hats...
  17. 17. UDP header in Ruby udpip.rb require 'bit-struct' class DNSQueryHeader < BitStruct unsigned :dns_id, 16, quot;IDquot; class IP < BitStruct unsigned :dns_qr, 1, quot;QRquot; unsigned :ip_v, 4, quot;Versionquot; unsigned :dns_opcode, 4, quot;OpCodequot; unsigned :ip_hl, 4, quot;Header lengthquot; unsigned :dns_aa, 1, quot;AAquot; unsigned :ip_tos, 8, quot;TOSquot; unsigned :dns_tc, 1, quot;TCquot; unsigned :ip_len, 16, quot;Lengthquot; unsigned :dns_rd, 1, quot;RDquot; unsigned :ip_id, 16, quot;IDquot; unsigned :dns_ra, 1, quot;RAquot; unsigned :ip_off, 16, quot;Frag offsetquot; unsigned :dns_z, 3, quot;Zquot; unsigned :ip_ttl, 8, quot;TTLquot; unsigned :dns_rcode, 4, quot;RCODEquot; unsigned :ip_p, 8, quot;Protocolquot; unsigned :dns_qdcount, 16, quot;QDCountquot; unsigned :ip_sum, 16, quot;Checksumquot; unsigned :dns_ancount, 16, quot;ANCountquot; octets :ip_src, 32, quot;Source addrquot; unsigned :dns_arcount, 16, quot;ARCountquot; octets :ip_dst, 32, quot;Dest addrquot; rest :data, quot;Dataquot; rest :body, quot;Body of messagequot; end note quot;rest is application defined message bodyquot; initial_value.ip_v = 4 class Time initial_value.ip_hl = 5 # tcpdump style format end def to_s sprintf quot;%0.2d:%0.2d:%0.2d.%0.6dquot;, hour, min, sec, tv_usec class UDP < BitStruct end unsigned :udp_srcport, 16, quot;Source Portquot; end unsigned :udp_dstport, 16, quot;Dest Portquot; unsigned :udp_len, 16, quot;UDP Lengthquot; unsigned :udp_chksum, 16, quot;UDP Checksumquot; rest :body, quot;Body of messagequot; note quot;rest is application defined message bodyquot; end
  18. 18. Capturing UDP packets tcpdump.rb #!/usr/local/bin/ruby require 'pcaplet' include Pcap require 'udpip' DIVIDER = quot;-quot; * 50 def print_details(section) puts DIVIDER, section, DIVIDER end pcaplet = Pcaplet.new('-s 1500') pcaplet.each_packet { |pkt| if pkt.udp? puts quot;Packet: #{pkt.time} #{pkt}quot; if (pkt.sport == 53) udp = UDP.new udp.udp_srcport = pkt.sport udp.udp_dstport = pkt.dport udp.udp_len = pkt.udp_len udp.udp_chksum = pkt.udp_sum udp.body = pkt.udp_data print_details udp.inspect_detailed # look for DNS request only dns = DNSQueryHeader.new(pkt.udp_data) bytearray = Array.new udp.body.each_byte { |c| bytearray.concat(c.to_s.to_a) print c.to_s(16), ' ' } print_details dns.inspect_detailed end end } pcaplet.close
  19. 19. A live UDP packet
  20. 20. A live DNS packet
  21. 21. Can we have that on Windows? A GUI? You gotta be joking!! Why do you think we use Macs? How about we just turn it into a web application instead? Sure, we can do that with Ruby [What have we let ourselves in for...]
  22. 22. The NDA kicks in Here’s where we hit the brick wall on what we can talk about You might imagine a DNS-sniffing web application, but we couldn’t possibly comment So lets get down to some web app basics And yes, we will be kicking it old-skool...
  23. 23. Introducing WEBrick WEBrick is an HTTP ser ver library It’s part of the Ruby 1.8 release It can ser ve static documents It can ser ve HTTPS using Ruby/OpenSSL It can ser ve arbitrary code blocks It can ser ve ser vlets
  24. 24. Static content A standard HTTP ser ver #!/usr/local/bin/ruby require 'webrick' server = WEBrick::HTTPServer.new(:Port => 8080, :DocumentRoot => Dir::pwd + quot;/htdocsquot;) # mount personal directory, generating directory indexes server.mount(quot;/~eleanorquot;, WEBrick::HTTPServlet::FileHandler, quot;/Users/eleanor/Sitesquot;, true) # catch keyboard interrupt signal to terminate server trap(quot;INTquot;){ server.shutdown } server.start An HTTPS ser ver #!/usr/local/bin/ruby # This requires Ruby/OpenSSL require 'webrick' require 'webrick/https' certificate_name = [ [quot;Cquot;,quot;UKquot;], [quot;Oquot;,quot;games-with-brains.orgquot;], [quot;CNquot;, quot;WWWquot;] ] server = WEBrick::HTTPServer.new(

×