File: udp.rb

package info (click to toggle)
ruby-packetfu 2.0.0-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 3,520 kB
  • sloc: ruby: 8,344; makefile: 2
file content (179 lines) | stat: -rw-r--r-- 5,501 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
# -*- coding: binary -*-
require 'packetfu/protos/eth/header'
require 'packetfu/protos/eth/mixin'

require 'packetfu/protos/ip/header'
require 'packetfu/protos/ip/mixin'

require 'packetfu/protos/ipv6/header'
require 'packetfu/protos/ipv6/mixin'

require 'packetfu/protos/udp/header'
require 'packetfu/protos/udp/mixin'

module PacketFu

  # UDPPacket is used to construct UDP Packets. They contain an EthHeader, an IPHeader, and a UDPHeader.
  #
  # == Example
  #
  #   udp_pkt = PacketFu::UDPPacket.new
  #   udp_pkt.udp_src=rand(0xffff-1024) + 1024
  #   udp_pkt.udp_dst=53
  #   udp_pkt.ip_saddr="1.2.3.4"
  #   udp_pkt.ip_daddr="10.20.30.40"
  #   udp_pkt.recalc
  #   udp_pkt.to_f('/tmp/udp.pcap')
  #
  #   udp6_pkt = PacketFu::UDPPacket.new(:on_ipv6 => true)
  #   udp6_pkt.udp_src=rand(0xffff-1024) + 1024
  #   udp6_pkt.udp_dst=53
  #   udp6_pkt.ip6_saddr="4::1"
  #   udp6_pkt.ip6_daddr="12:3::4567"
  #   udp6_pkt.recalc
  #   udp6_pkt.to_f('/tmp/udp.pcap')
  #
  # == Parameters
  #
  #  :eth
  #    A pre-generated EthHeader object.
  #  :ip
  #    A pre-generated IPHeader object.
  #  :flavor
  #    TODO: Sets the "flavor" of the UDP packet. UDP packets don't tend have a lot of
  #    flavor, but their underlying ip headers do.
  #  :config
  #   A hash of return address details, often the output of Utils.whoami?
  class UDPPacket < Packet
    include ::PacketFu::EthHeaderMixin
    include ::PacketFu::IPHeaderMixin
    include ::PacketFu::IPv6HeaderMixin
    include ::PacketFu::UDPHeaderMixin

    attr_accessor :eth_header, :ip_header, :ipv6_header, :udp_header

    def self.can_parse?(str)
      return false unless str.size >= 28
      return false unless EthPacket.can_parse? str
      if IPPacket.can_parse? str
        return true if str[23,1] == "\x11"
      elsif IPv6Packet.can_parse? str
        return true if str[20,1] == "\x11"
      end
      false
    end

    def read(str=nil, args={})
      super
      if args[:strip]
        udp_body_len = self.ip_len - self.ip_hlen - 8
        @udp_header.body.read(@udp_header.body.to_s[0,udp_body_len])
        udp_calc_sum
        @ip_header.ip_recalc unless ipv6?
      end
      self
    end

    def initialize(args={})
      if args[:on_ipv6] or args[:ipv6]
        @eth_header = EthHeader.new(args.merge(:eth_proto => 0x86dd)).read(args[:eth])
        @ipv6_header = IPv6Header.new(args).read(args[:ipv6])
        @ipv6_header.ipv6_next=0x11
      else
        @eth_header = EthHeader.new(args).read(args[:eth])
        @ip_header = IPHeader.new(args).read(args[:ip])
        @ip_header.ip_proto=0x11
      end
      @udp_header = UDPHeader.new(args).read(args[:udp])
      if args[:on_ipv6] or args[:ipv6]
        @ipv6_header.body = @udp_header
        @eth_header.body = @ipv6_header
        @headers = [@eth_header, @ipv6_header, @udp_header]
      else
        @ip_header.body = @udp_header
        @eth_header.body = @ip_header
        @headers = [@eth_header, @ip_header, @udp_header]
      end
      super
      udp_calc_sum
    end

    # udp_calc_sum() computes the UDP checksum, and is called upon intialization. 
    # It usually should be called just prior to dropping packets to a file or on the wire. 
    def udp_calc_sum
      # This is /not/ delegated down to @udp_header since we need info
      # from the IP header, too.
      if @ipv6_header
        checksum = ipv6_calc_sum_on_addr
      else
        checksum = ip_calc_sum_on_addr
      end

      checksum += 0x11
      checksum += udp_len.to_i
      checksum += udp_src.to_i
      checksum += udp_dst.to_i
      checksum += udp_len.to_i
      if udp_len.to_i >= 8
        # For IP trailers. This isn't very reliable. :/
        real_udp_payload = payload.to_s[0,(udp_len.to_i-8)] 
      else
        # I'm not going to mess with this right now.
        real_udp_payload = payload 
      end
      chk_payload = (real_udp_payload.size % 2 == 0 ? real_udp_payload : real_udp_payload + "\x00")
      chk_payload.unpack("n*").each {|x| checksum = checksum+x}
      checksum = checksum % 0xffff
      checksum = 0xffff - checksum
      checksum == 0 ? 0xffff : checksum
      @udp_header.udp_sum = checksum
    end

    # udp_recalc() recalculates various fields of the UDP packet. Valid arguments are:
    #
    #   :all
    #     Recomputes all calculated fields.
    #   :udp_sum
    #     Recomputes the UDP checksum.
    #   :udp_len
    #     Recomputes the UDP length.
    def udp_recalc(args=:all)
      case args
      when :udp_len
        @udp_header.udp_recalc
      when :udp_sum
        udp_calc_sum
      when :all
        @udp_header.udp_recalc
        udp_calc_sum
      else
        raise ArgumentError, "No such field `#{arg}'"
      end
    end

    # Peek provides summary data on packet contents.
    def peek_format
      if self.ipv6?
        peek_data = ["6U "]
        peek_data << "%-5d" % self.to_s.size
        peek_data << "%-31s" % "#{self.ipv6_saddr}:#{self.udp_sport}"
        peek_data << "->"
        peek_data << "%31s" % "#{self.ipv6_daddr}:#{self.udp_dport}"
        peek_data.join
      else
        peek_data = ["U  "]
        peek_data << "%-5d" % self.to_s.size
        peek_data << "%-21s" % "#{self.ip_saddr}:#{self.udp_sport}"
        peek_data << "->"
        peek_data << "%21s" % "#{self.ip_daddr}:#{self.udp_dport}"
        peek_data << "%23s" % "I:"
        peek_data << "%04x" % self.ip_id
        peek_data.join
      end
    end

  end

end

# vim: nowrap sw=2 sts=0 ts=2 ff=unix ft=ruby