ネットワークエンジニアのためのIPアドレス計算 for Ruby

Rubyと[ipaddress](https://rubygems.org/gems/ipaddress)ライブラリで、ネットワークエンジニアがよく使うIPアドレスの計算する方法を簡単に解説します。

より詳細なドキュメントはライブラリの[ドキュメント](http://www.rubydoc.info/github/bluemonk/ipaddress/master/toplevel)を参照してください。
ライブラリはIPv4アドレスとIPv6アドレスに対応していますが、今回はIPv4アドレスのみ取り扱います。
試した環境は下記のとおりです。RubyGemsでライブラリをインストールして使用します。

* Ruby 2.3.1
* [ipaddress](https://rubygems.org/gems/ipaddress) 0.8.3

```ruby:Gemfile
source "https://rubygems.org"
gem 'ipaddress'
```

ちなみに、標準ライブラリの[ipaddr](https://docs.ruby-lang.org/ja/latest/class/IPAddr.html)では、ネットワークアドレスやブロードキャストアドレスを直接求められないため、ネットワークエンジニアが利用するにはちょっと向きません。

## ipaddressの基本的な機能

### 色々なIPアドレスの読み込み

```ruby
require 'ipaddress'

# プレフィックス長を指定したIPアドレス
ipaddr = IPAddress('192.168.1.124/24')
ipaddr.to_string
=> "192.168.1.124/24"

# プレフィックス長を省略すると/32になる
ipaddr = IPAddress('192.168.1.124') 
ipaddr.to_string
=> "192.168.1.124/32"

# サブネットマスクで指定も可能
ipaddr = IPAddress('192.168.1.124/255.255.255.0')
ipaddr.to_string
=> "192.168.1.124/24"

# 10進数からIPアドレスに変換
ipaddr = IPAddress(0)
ipaddr.to_string
=> "0.0.0.0/32"

# 16進数からIPアドレスに変換
ipaddr = IPAddress(0xffffffff) 
ipaddr.to_string                                                 
=> "255.255.255.255/32"
```

### よく使うIPアドレスの表現

```ruby
require 'ipaddress'

ipaddr = IPAddress('192.168.1.124/24')
=> #<IPAddress::IPv4:0x0055c4134bccb0
 @address="192.168.1.124",
 @octets=[192, 168, 1, 124],
 @prefix=24,
 @u32=3232235900>

# プレフィックス表記
ipaddr.to_string
=> "192.168.1.124/24"

# ホストアドレス
ipaddr.to_s
=> "192.168.1.124"
ipaddr.address
=> "192.168.1.124"

# サブネットマスク
ipaddr.netmask
=> "255.255.255.0"

# ネットワークアドレス
ipaddr.network.to_s
=> "192.168.1.0"

# ブロードキャストアドレス
ipaddr.broadcast.to_s
=> "192.168.1.255"

IPAddress("0.0.0.0/0").broadcast.to_s
=> "255.255.255.255"

# プレフィックス長
ipaddr.prefix
=> 24

# ワイルドカードマスク
netmask = IPAddress(ipaddr.netmask)
wildmask = IPAddress(2**32 - netmask.to_u32 - 1)
=> #<IPAddress::IPv4:0x0055c412d1b470
 @address="0.0.0.255",
 @octets=[0, 0, 0, 255],
 @prefix=32,
 @u32=255>
wildmask.to_s
=> "0.0.0.255"

# サブネットマスク（プレフィックス）を指定
ipaddr = IPAddress(0)                                           
=> #<IPAddress::IPv4:0x0055afb75b7478
 @address="0.0.0.0",
 @octets=[0, 0, 0, 0],
 @prefix=32,
 @u32=0>
ipaddr.to_string
=> "0.0.0.0/32"
ipaddr.prefix = 0                                               
=> 0
ipaddr.to_string                                                
=> "0.0.0.0/0"

```

### IPアドレスの分割と集約
```ruby
require 'ipaddress'

# 計算対象のIPアドレス
ipaddr = IPAddress('192.168.0.0/16')
=> #<IPAddress::IPv4:0x0055c412eff778
 @address="192.168.0.0",
 @octets=[192, 168, 0, 0],
 @prefix=16,
 @u32=3232235520>


# /16を2分割
## 192.168.0.0/16
## => 192.168.0.0/17, 192.168.128.0/17
subnets = ipaddr.split(2)
=> [#<IPAddress::IPv4:0x0055c412a73010
  @address="192.168.0.0",
  @octets=[192, 168, 0, 0],
  @prefix=17,
  @u32=3232235520>,
 #<IPAddress::IPv4:0x0055c412a72958
  @address="192.168.128.0",
  @octets=[192, 168, 128, 0],
  @prefix=17,
  @u32=3232268288>]


# subnets配列を集約（サマライズ）する
## 192.168.0.0/17
## 192.168.128.0/17
## => 192.168.0.0/16
IPAddress::IPv4.summarize(*subnets)
=> [#<IPAddress::IPv4:0x0055c4133ce7e0
  @address="192.168.0.0",
  @octets=[192, 168, 0, 0],
  @prefix=16,
  @u32=3232235520>]


# ipaddrs配列を可能な限り集約（サマライズ）する
## 192.168.0.0/24
## 192.168.1.0/24
## 192.168.2.0/24
## 192.168.3.0/24
## 192.168.4.0/24
## => 192.168.0.0/22, 192.168.4.0/24
ipaddrs = %w|192.168.0.0/24 192.168.1.0/24 192.168.2.0/24 192.168.3.0/24 192.168.4.0/24|
=> ["192.168.0.0/24", "192.168.1.0/24", "192.168.2.0/24", "192.168.3.0/24", "192.168.4.0/24"]
ipaddrs = ipaddrs.map{|ipaddr| IPAddress(ipaddr)}
=> [#<IPAddress::IPv4:0x0055c412b9cf40
  @address="192.168.0.0",
  @octets=[192, 168, 0, 0],
  @prefix=24,
  @u32=3232235520>,
 #<IPAddress::IPv4:0x0055c412b9ca40
  @address="192.168.1.0",
  @octets=[192, 168, 1, 0],
  @prefix=24,
  @u32=3232235776>,
 #<IPAddress::IPv4:0x0055c412b9c590
  @address="192.168.2.0",
  @octets=[192, 168, 2, 0],
  @prefix=24,
  @u32=3232236032>,
 #<IPAddress::IPv4:0x0055c412b9c0e0
  @address="192.168.3.0",
  @octets=[192, 168, 3, 0],
  @prefix=24,
  @u32=3232236288>,
 #<IPAddress::IPv4:0x0055c412b7bbd8
  @address="192.168.4.0",
  @octets=[192, 168, 4, 0],
  @prefix=24,
  @u32=3232236544>]
supernets = IPAddress::IPv4.summarize(*ipaddrs)
=> [#<IPAddress::IPv4:0x0055c4125bf9d8
  @address="192.168.0.0",
  @octets=[192, 168, 0, 0],
  @prefix=22,
  @u32=3232235520>,
 #<IPAddress::IPv4:0x0055c4125beb78
  @address="192.168.4.0",
  @octets=[192, 168, 4, 0],
  @prefix=24,
  @u32=3232236544>]
supernets.map(&:to_string)
=> ["192.168.0.0/22", "192.168.4.0/24"]

# 集約アドレスを指定のプレフィックス長で分割する
## /24を/26に分割する
ipaddr = IPAddress('192.168.1.0/24')                            
=> #<IPAddress::IPv4:0x0055afb8324de0
 @address="192.168.1.0",
 @octets=[192, 168, 1, 0],
 @prefix=24,
 @u32=3232235776>
subnets = ipaddr.subnet(26)                                               
=> [#<IPAddress::IPv4:0x0055afb84032e8
  @address="192.168.1.0",
  @octets=[192, 168, 1, 0],
  @prefix=26,
  @u32=3232235776>,
 #<IPAddress::IPv4:0x0055afb8402c30
  @address="192.168.1.64",
  @octets=[192, 168, 1, 64],
  @prefix=26,
  @u32=3232235840>,
 #<IPAddress::IPv4:0x0055afb84025c8
  @address="192.168.1.128",
  @octets=[192, 168, 1, 128],
  @prefix=26,
  @u32=3232235904>,
 #<IPAddress::IPv4:0x0055afb8401f60
  @address="192.168.1.192",
  @octets=[192, 168, 1, 192],
  @prefix=26,
  @u32=3232235968>]
> subnets.size                                                    
=> 4
```

### その他の表記

```ruby
require 'ipaddress'

# オクテット配列
ipaddr.octets 
=> [192, 168, 1, 124]

# 2進数
ipaddr.bits
=> "11000000101010000000000101111100"

# 10進数
ipaddr.to_u32
=> 3232235900
ipaddr.u32
=> 3232235900
ipaddr.to_i
=> 3232235900

# 16進数
ipaddr.hex
=> "c0a8017c"

# DNS逆引きレコード
ipaddr.reverse
=> "124.1.168.192.in-addr.arpa"


# ホストアドレスの配列（ネットワークアドレスとブロードキャストを除く）
ipaddr.hosts                                                                          
=> [#<IPAddress::IPv4:0x0055c412b92ef0
  @address="192.168.1.1",
  @octets=[192, 168, 1, 1],
  @prefix=24,
  @u32=3232235777>,
 #<IPAddress::IPv4:0x0055c412b92860
  @address="192.168.1.2",
  @octets=[192, 168, 1, 2],
  @prefix=24,
  @u32=3232235778>,
〜略〜
 #<IPAddress::IPv4:0x0055c411ea6660
  @address="192.168.1.254",
  @octets=[192, 168, 1, 254],
  @prefix=24,
  @u32=3232236030>]

```


## よくある例

### 複数のIPアドレスから集約アドレスを計算する

```ruby:summarize.rb
require 'ipaddress'

# サマライズ対象のIPアドレス（重複しててもOK）
networks = <<EOL.lines
192.168.0.0/25
192.168.0.128/25
192.168.1.0/24
192.168.3.0/24
192.168.4.0/24
192.168.5.0/26
192.168.128.0/22
192.168.132.0/22
192.168.128.0/21
EOL

# サマライズ計算
ipaddresses = networks.map{|net| IPAddress(net)}
summarized_networks = IPAddress::IPv4.summarize(*ipaddresses)

# サマライズ結果を出力
puts summarized_networks.map(&:to_string)

```

```bash:実行結果
$ ruby summarize.rb
192.168.0.0/23
192.168.3.0/24
192.168.4.0/24
192.168.5.0/26
192.168.128.0/21
```



### シスコルータでよくあるコンフィグ生成の例
シスコルータのインタフェースにIPアドレスを割り振り、OSPFを有効化する例です。

```ruby:config.rb
require 'ipaddress'

# IPアドレスからワイルドカードマスクを計算
def wildmask(ipaddr)
  netmask = IPAddress(ipaddr.netmask)
  IPAddress(2**32 - netmask.to_u32 - 1)
end

# パラメータ
interface = 'FastEthernet0'
ipaddr = IPAddress('192.168.1.124/24')

# コンフィグ生成
puts <<EOL
interface #{interface}
 ip address #{ipaddr} #{ipaddr.netmask}
 no shutdown

router ospf 1
 no passive-interface #{interface}
 network #{ipaddr.network} #{wildmask(ipaddr)}
EOL
```

```bash:出力結果
$ ruby config.rb
interface FastEthernet0
 ip address 192.168.1.124 255.255.255.0
 no shutdown

router ospf 1
 no passive-interface FastEthernet0
 network 192.168.1.0 0.0.0.255
```