For more information regarding the security incident at F5, the actions we are taking to address it, and our ongoing efforts to protect our customers, click here.

Forum Discussion

ukstin's avatar
ukstin
Icon for Nimbostratus rankNimbostratus
Jun 09, 2008

conditional redirect (404)

Hi people,

I´m trying to wrote a i-rule to redirect a 404 error based on the url. The i-rule I made is this one:

when HTTP_REQUEST { 
 set host [HTTP::host] 
 set url  [HTTP::uri] 
 } 
 when HTTP_RESPONSE { 
 set status_code [HTTP::status] 
 if { $status_code equals 404 } { 
 if { $host contains "abcd" } { 
 HTTP::redirect "http://abcd.otherdomain.com/$url" 
 } 
 elseif { $host contains "efg" } { 
 HTTP::redirect "http://efg.otherdomain.com/$url" 
 } 
 else {  
 HTTP::redirect "http://anotherone.com/xyz.html" 
 } 
   }   
 }

it seens ok to me and it works almost every time, but sometimes I received the following error:

Jun  9 19:25:07 tmm tmm[1133]: 01220001:3: TCL error: Rule irule_redirect_404  - can't read "host": no such variable     while executing "if { $host contains ..."

any ideas why the host variable doesn´t work sometimes?

4 Replies

  • Looks like the variable can't be used in another part of the irule.

    have you tried the following

      
      when HTTP_REQUEST {   
       set ::host [HTTP::host]   
       set ::url  [HTTP::uri]   
       }   
        
       when HTTP_RESPONSE {   
        if { [HTTP::status] == 404 } {   
       if { $::host contains "abcd" } {   
       HTTP::redirect "http://abcd.otherdomain.com/$::url"   
       } elseif { $::host contains "efg" } {   
       HTTP::redirect "http://efg.otherdomain.com/$::url"   
       } else {    
      HTTP::redirect "http://anotherone.com/xyz.html"   
       }   
         }     
       }  
      

    If you noticed I am changing your variable from local to global. I have not tested this, but give that a shot.

    CB

  • Patrick_Chang_7's avatar
    Patrick_Chang_7
    Historic F5 Account
    Some HTTP requests do not contain a host header. Therefore, [HTTP::host] will be empty. You could change the when HTTP_REQUEST logic to read the host from the first line of the raw HTTP::request if [HTTP::host] is an empty string. I would log the value of [HTTP::host] when you set the host variable, just to make sure though. You can remove the logging when the rule is fully debugged.
  • hoolio's avatar
    hoolio
    Icon for Cirrostratus rankCirrostratus
    The host header is only required for HTTP 1.1. If a client sends an HTTP 1.0 (or even 0.9) request, the Host header value wouldn't necessarily be present, but regardless the $host variable should be set to a zero length string. I'm not sure how the runtime error could be triggered for $host not being defined, as it's set on every HTTP request (even if it's to a null value).

    Using the rule below, I did some tests on 9.2.4 using netcat. In every test, checking if the $host value contained a string succeeded without a TCL error. Between this post and a related one on the ProxyPass rule (Click here), I'd suggest opening a case with F5 Support. It seems like this might be a bug.

    As a workaround, you could add a check to see if $host exists before trying to check if it contains a string:

      
      when HTTP_REQUEST {   
         set host [HTTP::host]  
         set url  [HTTP::uri]   
      }  
      when HTTP_RESPONSE {   
         if { [HTTP::status] == 404 } {  
            if {[info exists host]}{  
               if {$host contains "abcd" } {  
                  HTTP::redirect "http://abcd.otherdomain.com/$url"  
               } elseif {[info exists host] && $host contains "efg" } {  
                  HTTP::redirect "http://efg.otherdomain.com/$url"  
               } else {  
                  HTTP::redirect "http://anotherone.com/xyz.html"  
               }  
            } else {  
                $host doesn't exist!  
               log local0. "[IP::client_addr]:[TCP::client_port]: \$host was not set!?"  
            }  
         }  
      }  
      

    Aaron

    Example rule which saves the HTTP::host output and checks the value in HTTP_RESPONSE:

      
      when HTTP_REQUEST {  
        
         set host [HTTP::host]  
           
         log local0. "HTTP version: [HTTP::version], HTTP host: $host, HTTP uri: [HTTP::uri]"  
      }  
      when HTTP_RESPONSE {  
        
         if { $host contains "www" } {  
            log local0. "matched $host"  
         } else {    
            log local0. "didn't match $host"  
         }    
      }  
      

    Request with a Host header:

    $ nc 192.168.101.40 80

    GET /withhost HTTP/1.1

    Connection: close

    Host: www.example.com

    Log output:

    Rule log_http_host_rule : HTTP version: 1.1, HTTP host: www.example.com, HTTP uri: /withhost

    Rule log_http_host_rule : matched www.example.com

    Request without a Host header:

    $ nc 192.168.101.40 80

    GET /withouthost HTTP/1.0

    Log output:

    Rule log_http_host_rule : HTTP version: 1.0, HTTP host: , HTTP uri: /withouthost

    Rule log_http_host_rule : didn't match

    0.9 formatted request:

    $ nc 192.168.101.40 80

    GET /

    Log output:

    Rule log_http_host_rule : HTTP version: 0.9, HTTP host: , HTTP uri: /

    Rule log_http_host_rule : didn't match

    Request with a Host header, but nothing after the colon:

    nc 192.168.101.40 80

    GET / HTTP/1.0

    Host:

    Test: value

    Log output:

    Rule log_http_host_rule : HTTP version: 1.1, HTTP host: Connection: close, HTTP uri: /

    Rule log_http_host_rule : didn't match Connection: close

    This last one could be considered a bug as well. Apparently the HTTP parser doesn't stop parsing the Host header value at the line terminating CRLF if there isn't a value for the header.

    'HTTP::header Host' seems to be more accurate:

     
     when HTTP_REQUEST { 
      
        set http_host [HTTP::host] 
        set http_header_host [HTTP::header Host] 
         
        log local0. "HTTP version: [HTTP::version], HTTP host: $http_host, HTTP header Host: $http_header_host, HTTP uri: [HTTP::uri]" 
     } 
     when HTTP_RESPONSE { 
      
        if { $http_host contains "www" } { 
           log local0. "http_host matched $http_host" 
        } else {   
           log local0. "http_host didn't match $http_host" 
        }   
        if { $http_header_host contains "www" } { 
           log local0. "http_header_host matched $http_header_host" 
        } else {   
           log local0. "http_header_host didn't match $http_header_host" 
        }   
     } 
     

    Request:

    $ nc 192.168.101.40 80

    GET / HTTP/1.0

    Host:

    Test: value

    Log output shows HTTP::host errantly returns the next header name and value, while HTTP::header Host correctly returns nothing:

    Rule log_http_host_rule : HTTP version: 1.0, HTTP host: Test: value, HTTP header Host: , HTTP uri: /

    Rule log_http_host_rule : http_host didn't match Test: value

    Rule log_http_host_rule : http_header_host didn't match

  • Yea, this looks really similar to the ProxyPass issue from a couple days ago. They were setting a variable in HTTP_REQUEST but occasionally they'd get a runtime error accessing it in HTTP_RESPONSE.

     

     

    I wonder if there's a bug or some weird situation where HTTP_RESPONSE is being triggered without a corresponding HTTP_REQUEST.

     

     

    Out of curiosity, do you have OneConnect enabled?