1) srandコマンドで乱数の数字を生成
2) FTPプロトコル内のデータを処理し、BIG-IP側で完全にレスポンスを返答
3) lindexコマンドでリスト内の値を利用

上記のテクニックは実際に運用で利用できますので、iRule作成に是非ご活用ください。

iRuleの使い方

このiRuleゲームの目的は「Wumpus」という魔物を探し出すことです。
FTPで接続したら、FTPの「cd」コマンド+北、東、西、南の方向を「N」、「E」、「W」、「S」の入力で指定します(例: 「cd n」)。
コマンドを入力したらゲーム内のキャラクタがその方向へ移動します。
ランダムで決められた場所へたどり着いたら、Wumpusが見つかりゲームが終了となります。

設定方法

1) FTP Virtual Serverに下記のiRuleを関連付け。
2) FTPクライアントで1)のVirtual Serverに接続。
3) anonymousとしてログイン
4) cd <方向> のコマンドで移動

when RULE_INIT {
  set ::DEBUG 1
  set ::DIRECTIONS [list "N" "E" "W" "S"]
}
when CLIENT_ACCEPTED {
    if { $::DEBUG } { log local0. "client accepted" }
   
    # build path to wumpus
    expr srand([clock clicks])
    set PATH [list \
      [lindex $::DIRECTIONS [expr int(4*rand())]] \
      [lindex $::DIRECTIONS [expr int(4*rand())]] \
      [lindex $::DIRECTIONS [expr int(4*rand())]] \
      [lindex $::DIRECTIONS [expr int(4*rand())]] \
      [lindex $::DIRECTIONS [expr int(4*rand())]] ]
   
    set LOCATION 0
   
    TCP::respond "220 (Wumpus Game)\r\n"
    TCP::release
    TCP::collect
}

when CLIENT_DATA {

  set client_data [string trim [TCP::payload]]
 
  if { $::DEBUG } { log local0. "---------------------------" }
  if { $::DEBUG } { log local0. "payload '$client_data'" }
 
  set CURPATH ""
  for {set i 0} {$i<$LOCATION} {incr i} {
    if { [string length $CURPATH] == 0 } {
      set CURPATH "[lindex $PATH $i]"
    } else {
      set CURPATH "$CURPATH/[lindex $PATH $i]"
    }
  }
  set NEXTPATH $CURPATH
  if { $LOCATION < [llength $PATH] } {
    if { [string length $NEXTPATH] == 0 } {
      set NEXTPATH "[lindex $PATH [expr $LOCATION]]"
    } else {
      set NEXTPATH "$NEXTPATH/[lindex $PATH [expr $LOCATION]]"
    }
  }
 
  set cmd [string toupper [lindex [split $client_data " "] 0]]
  set cmd_arg [string toupper [lindex [split $client_data " "] 1]]

  #---------------------------------------------------
  # Block or alert specific commands
  #---------------------------------------------------
  switch $cmd {
    "USER" {
      if { !($cmd_arg equals "ANONYMOUS") && !($cmd_arg equals "FTP") } {
        TCP::respond "550 Come on, do think the Wumpus requires authentication?
    Please login as Anonymous...\r\n"
      } else {
        TCP::respond "230 Login successful. Welcome to the Wumpus game.
    The Wumpus is hidden in a series of directions.
    CD a direction (N,E,W,S) to start the hunt!\r\n"
      }
    }
    "PORT" {
      TCP::respond "500 The Wumpus doesn't know how to reply to PORT requests.\r\n"
    }
    "PASV" {
      TCP::respond "502 The Wumpus doesn't know how to reply to PASV requests.\r\n"
    }
    "LIST" {
      if { $LOCATION < [llength $PATH] } {
        TCP::respond "200 ($CURPATH) : Current Path. Pick a direction (N,E,W,S)!\r\n"
      } else {
        TCP::respond "200 ($CURPATH) : YOU FOUND THE WUMPUS!!!\r\n"
      }
    }
    "CWD" {
      if { $::DEBUG } { log local0. "Requested path: '$cmd_arg'" }
      if { $::DEBUG } { log local0. "Expected Path: '[lindex $PATH $LOCATION]'" }
      if { $cmd_arg eq [lindex $PATH $LOCATION] } {
        incr LOCATION
        if { $LOCATION < [llength $PATH] } {
          TCP::respond "200 ($NEXTPATH) : '$cmd_arg' is Correct. Please pick the next direction (N,E,W,S)!\r\n"
        } else {
          TCP::respond "200 WOOHOO - YOU FOUND THE WUMPUS! Come back again soon.\r\n"
          TCP::release
          return
        }
      } else {
        TCP::respond "200 ($CURPATH) : '$cmd_arg' Wrong Choice. Please pick a direction (N,E,W,S).\r\n"
      }
    }
    "PWD" {
      TCP::respond "200 ($CURPATH) Please pick the next direction (N,E,W,S)!\r\n"
    }
    "QUIT" {
      TCP::respond "200 The Wumpus says Bye Bye!  Play again soon!\r\n"
      TCP::release
      return
    }
    "HELP" {
      TCP::respond "200 Welcome to the Wumpus game.  The Wumpus is hidden in a series of directions.
   CD a direction (N,E,W,S) to start the hunt!\r\n"
    }
    default {
      if { $::DEBUG } { log local0. "Unknown Command '$cmd!" }
      TCP::respond "550 UNKNOWN command '$cmd'\r\n"
    }
  }

  if { $::DEBUG } { log local0. "Returning control to client" }
  TCP::payload replace 0 [string length [TCP::payload]] ""
  TCP::collect
}

when CLIENT_CLOSED {
    if { $::DEBUG } { log local0. "client closed" }
}

※F5ネットワークスジャパンでは、サンプルコードについて検証を実施していますが、お客様の使用環境における動作を保証するものではありません。実際の使用にあたっては、必ず事前にテストを実施することを推奨します。