Hey ChatGPT, can you write iRules?

Hey ChatGPT, can you write iRules?

iRules allow you to parse the payload of the data passing through the BIG-IP and, at wire speed, execute an entire script of commands on that traffic.
They allow you to log and redirecting traffic, modify the URI or port or to rewrite the payload itself.
iRules and their almost endless capabilities to customize the behavior of F5 Networks’ BIG-IP are one the unique selling point of BIG-IP.
Many users of devcentral ask for help with their iRules, consultants who can write or troubleshoot iRules are sought after by many of F5s’ customers.
I was wondering how well the new cool kid in town, ChatGPT, performs at writing iRules and how many of the most seen iRules questions on devcentral it can actually answer.
Find the most requested iRules
I tried to figure out what challenges devcentral users most often look for help with and came up with a list of usual iRule code questions.

iRule challenges I posed to ChatPGT.

  1. Redirect HTTP to HTTPS.
  2. Rewrite the Host header in the request and response.
  3. Do a loadbalancing decission based on the Host header.
  4. Do a loadbalancing decission based on the URI.
  5. Different ASM blocking pages based on the Host header.

A recent example: https://community.f5.com/t5/technical-forum/need-help-for-irule/m-p/310444
Another recent example: https://community.f5.com/t5/technical-forum/irule-redirect-irule-help-required/m-p/304943
Below are ChatGPTs solutions with my comments.

Challenge 1: Redirect HTTP to HTTPS

Forwarding HTTP requests to HTTPS is a feature that every load balancer can handle and that is often asked for in the forum. The BIG-IP can meet this requirement with both iRules and Local Traffic Policies. Each BIG-IP comes with a collection of pre-installed iRules, an iRule that redirects HTTP to HTTPS is also included (IMHO not a great example, because it sends a 302 Found redirect response that indicates that the resource requested has been temporarily moved to a different URL, I prefer the 301 Moved Permanently redirect status).

The iRule ChatGPT suggests as a solution is the following:

when HTTP_REQUEST {
  if { [HTTP::header exists "X-Forwarded-Proto"] && [HTTP::header value "X-Forwarded-Proto"] eq "http" }
  {
    HTTP::respond 301 Location "https://[HTTP::host][HTTP::uri]"
  }
}

This iRule executes on the HTTP_REQUEST event and checks for the presence of an X-Forwarded-Proto header in the request. Such header would only be inserted from another upstream device in front of the BIG-IP, for example another loadbalancer or a CDN. I like that it uses HTTP status 301 (Moved Permanently) instead of HTTP::redirect. However, I would add the options version auto and noserver.

Challenge 2: Rewrite the Host header in the request and response

A common use case for this functionality is when your application servers only accept specific Host headers, but you are publishing your application with a differen FQDN. You want to rewrite the Host header for incoming requests and the Location header for responses, so that the application continues to work with the public FQDN.

ChatGPTs answer:

when HTTP_REQUEST {
  HTTP::header replace "Host" "newhost.example.com"
}

when HTTP_RESPONSE {
  if { [HTTP::header exists "Location"] } {
    HTTP::header replace "Location" [string map {"oldhost.example.com" "newhost.example.com"} [HTTP::header value "Location"]]
  }
}

No remarks here. Challenge completed.

Challenge 3: Loadbalancing based on the Host header

This function is commonly referred to as Layer 7 loadbalancing or content switching. Behind a VIP there are different web applications, the loadbalancing decision is made either on the basis of the host name or the path portion of the URL. The latter is Challenge 4.

when HTTP_REQUEST {
  switch -glob [HTTP::host] {
    "www.example.com" {
      pool my_pool1
    }
    "api.example.com" {
      pool my_pool2
    }
    default {
      pool my_default_pool
    }
  }
}

No remarks here either.

Challenge 4: Loadbalancing based on the URI

when HTTP_REQUEST {
  if { [HTTP::uri] starts_with "/app1/" } 
  {
    pool my_pool1
  }
  elseif { [HTTP::uri] starts_with "/app2/" }
  {
    pool my_pool2
  }
  else {
    pool my_default_pool
  }
}
 

Looking at the solution for example 4, I’d say that you should know, from experience, that a switch statement will be more performant than an if/else clause at a certain scale. If you are just having 3 or 4 alternatives, the performance gain from using a switch statement won’t be significant. However, I asked ChatGPT to rewrite the same iRule using datagroups to map URLs to pools. This way the solution could easily scale. It did so and even gave a tmsh example to create a datagroup and populate some sample data.

tmsh create ltm data-group internal my_datagroup type string
tmsh modify ltm data-group my_datagroup records add { /path1 pool1 /path2 pool2 /path3 pool3 }

Challenge 5: Different ASM blocking pages based on the Host header.

Sometimes you want to show a custom blocking page, for example to add extra information like the URL or the client IP. Or to add some corporate identity to your blocking page.

when ASM_REQUEST_DONE {
  if { [ASM::blocking_active] } 
  {
    switch -glob [HTTP::host] {
      "www.example.com" {
    HTTP::respond 403 content "<html><body><h1>Access to this site is blocked!</h1></body></html>"
      }
      "www.example2.com" {
    HTTP::respond 403 content "<html><body><h1>Sorry, this site is not available!</h1></body></html>"
      }
      default {
    HTTP::respond 403 content "<html><body><h1>Access to this site is blocked!</h1></body></html>"
      }
    }
  }
}

I have a few comments on this proposed solution. The command [ASM::blocking_active] does not exist. You could probably fix the iRule by using [ASM::status] equals “blocked” or when ASM_REQUEST_BLOCKING instead. Besides that, the usual HTTP response code ASM uses to send a blocking page is 200, also this solution does not include the support ID. I recommend taking a look at Support Solution K22017023 and combining the best of solutions. https://my.f5.com/manage/s/article/K22017023

I have of course asked ChatGPT to adjust the iRule to take my suggestions into account. The new iRule that ChatGPT proposed was even worse.

when ASM_REQUEST_BLOCKED {
  set block_type [ASM::block type]
  set host_header [HTTP::header Host]
  
  switch -glob $host_header {
    "www.example.com" {
      ASM::respond $block_type "<html><body><h1>Access blocked by ASM</h1><p>Your request has been blocked by ASM. Please contact support with the following ID:</p><p>ASM Block ID: [ASM::block id]</p></body></html>"
    }
    "www.anotherexample.com" {
      ASM::respond $block_type "<html><body><h1>Access blocked by ASM</h1><p>Your request has been blocked by ASM. Please contact support with the following ID:</p><p>ASM Block ID: [ASM::block id]</p></body></html>"
    }
    default {
      ASM::respond $block_type "<html><body><h1>Access blocked by ASM</h1><p>Your request has been blocked by ASM. Please contact support with the following ID:</p><p>ASM Block ID: [ASM::block id]</p></body></html>"
    }
  }
}

This iRule is supposed to execute on the ASM_REQUEST_BLOCKED event, which doesn’t exist. Also the commands [ASM::respond] and the [ASM::block] don’t exist. I think the AI got a little bit too creative here 🙂

Challenge 6: Redirect HTTP request matching a specific path and add a query parameter

The author of this thread asks for an iRule that will redirect the user to a new URL and uses the subdomain portion of the FQDN a query parameter in the new URI. The thread is not marked as “solved”, so I cannot say whether this satisfies the requirements of the user. For the sake of brevity I removed all comments that ChatGTP added. It is worth mentioning, that the comments ChatGPT adds are indeed helpful.

Here is what ChatGPT suggests.

when HTTP_REQUEST {
  if {[string starts_with [HTTP::path] "/age/signup"]} 
  {
    set xyz_value [string range [HTTP::host] 0 [expr {[regexp -indices "\\.test\\.com" [HTTP::host]]-1}]]
    set redirect_url "https://abc.test.com/#/inSign/invite?account=$xyz_value&[HTTP::query]"
    HTTP::redirect $redirect_url 302
  }
}

Here is again a lot that goes wrong. The if condition if {[string starts_with [HTTP::path] “/age/signup”]} is just wrong syntax. Correct syntax would be if {[HTTP::path] starts_with “/age/signup”}. Also the next line set xyz_value [string range [HTTP::host] 0 [expr {[regexp -indices “\.test\.com” [HTTP::host]]-1}]] won’t do what you’d expect it to do. If the FQDN is aaa.test.com this won’t extract the full subdomain portion of the FQDN, it will just take the first letter. And the [HTTP::redirect] command does not accept 302 as a flag. Furthermore, what might be missing here, is a check for the value of [HTTP::host] to ensure both conditions of matching the hostname and the URL path are matched. I already mentioned, I am not a fan of the HTTP::redirect command because it uses a 302 Found redirect response rather than a 301 Moved Permanently response. But what I like about this

Here is my working solution.

when HTTP_REQUEST {
  if {[string tolower [HTTP::path]] starts_with "/age/signup"}
  {
    HTTP::redirect "https://abc.test.com/#/inSign/invite?account=[getfield [HTTP::host] ".test.com" 1]&[HTTP::query]"
  }
}

Notice that I use string tolower here. I often use it to make sure my iRule can compare strings case-insensitive.

Challenge 7: Redirect users from one URL to a new URL

Here the is quite similar, the author of this thread asks for an iRule that redirects from https://abc.xyz.com/Projectsite/* to https://bbb.xyz.com/page/projectdocs-users/.

when HTTP_REQUEST {
  if {[string starts_with [HTTP::path] "/Projectsite/"]} 
  {
    set redirect_url "https://bbb.xyz.com/page/projectdocs-users/"
    append redirect_url [string range [HTTP::path] 11 end]
    HTTP::redirect $redirect_url 302
  }
}

ChatGPT makes the same syntax mistake here, the if condition will cause an error when saving the iRule. if {[string starts_with [HTTP::path] “/age/signup”]} is not valid tcl code, correct is if {[HTTP::path] starts_with “/Projectsite/“}. The string range is off by two characters. In order to remove the string “/Projectsite/“ from the HTTP::path you need to count 13 characters, not 11. I wonder how an AI can be bad at counting… Also here second check for matching the hostname as well as the URL Path would be useful, the use a variable for the redirect URL seems unnecessary and the HTTP::redirect command… enough said about that.

Here is my quick and dirty solution that will work.

when HTTP_REQUEST {
  if {[HTTP::path] starts_with "/Projectsite/"}
  {
    HTTP::redirect "http://bbb.xyz.com/page/projectdocs-users/[string range [HTTP::path] 13 end]&[HTTP::query]"
  }
}

Conclusion

After ChatGPT had solved the first tasks I it quite convincingly, I was quite optimistic that it could also correctly answer the more complex tasks or questions from the devcentral forum. Unfortunately this was not the case, the AI was quite creative in finding solutions but often with wrong syntax and with wasteful use of variables. To correct the syntax errors, you need an F5 specialist again. He will then also have enough experience to develop iRules not only correctly but also in a performant and scalable way. Of course we can expect that the AI will improve over time and hopefully it will soon be able to solve more complex iRule questions for us - I will for sure test it again.

Update

JRahm hinted me to take a another look at the second solution ChatGPT provided. The AI made another mistake here, it reversed the order of the string map. Apologies, I should have noticed that.

ChatGPT:

[string map {"oldhost.example.com" "newhost.example.com"} [HTTP::header value "Location"]

Correct:

[string map {"newhost.example.com" "oldhost.example.com"} [HTTP::header value "Location"]