How Tengine Finds server Blocks

Summary

The target reader of this article is Tengine/Nginx R&D or Operations and Maintenance students. If you are very clear about this logic, you can skip it. If you have the following questions in the process of configuring or developing Tengine/Nginx, this article may be able to answer your questions for many years:

  1. Which server block does the request arrive at the match?
  2. Why is the server block explicitly configured, or is it not valid?
  3. Without the server block of this domain name, which server block is used for the request?
  4. If you want to match the server block by yourself, where should you start?
    ......

The problems related to such server blocks, such as Tengine, may often be encountered when using Tengine. It is easier to identify when there are fewer server blocks. But in the case of CDN or cloud platform access layer, there are usually many server blocks, dozens or hundreds of them are few, thousands or thousands of them are possible. So understanding how Tengine finds server blocks is very helpful for routine problem solving.

To configure

Let's start with a few configurations:

server {
    listen       10.101.192.91:80 default_server;
    listen       80 default_server;
    listen       8080 default_server;

    server_name  www.aa.com;

    default_type  text/plain;

    location / {
        return 200 "default-server: $server_name, host: $host";
    }
}


server {
    listen       10.101.192.91:80;

    server_name  www.bb.com;

    default_type  text/plain;

    location / {
        return 200 "80server: $server_name, host: $host";
    }
}

server {
    listen       10.101.192.91:8080;

    server_name  *.bb.com;

    default_type  text/plain;

    location / {
        return 200 "8080server: $server_name, host: $host";
    }
}

server {
    listen       10.101.192.91:8080;

    server_name  www.bb.com;

    default_type  text/plain;

    location / {
        return 200 "8080server: $server_name, host: $host";
    }
}

Four server blocks are configured, and the configuration is very simple. The first server block is configured with default_server parameter, which indicates that this is the meaning of default server block (to be exact, the IP of this listen: the request from Port is the default server block), listens on two ports 80 and 8080, matching the domain name www.a. A.com, the second is to listen to 10.101.192.91:80 and the server block matching the domain name www.bb.com, the third is to listen to 10.101.192.91:8080 and the server block matching the pan-domain name*.bb.com, and the fourth is to listen to 10.101.192.91:8080 and the server block matching the exact domain name www.bb.com. Let's verify that:

It can be seen that:

  1. 127.0.0.1:80 and 127.0.0.1:8080 have access to the first server block.

    • This is because the first server listens on ports: 80 and: 8080, other server blocks do not listen on the corresponding port of 127.0.0.1, and access to 127.0.0.1 can only match the first server block.
  2. 10.101.192.91:80 access, domain name and server block matching using the corresponding server block, mismatching using the first default server block

    • If IP:Port matches, the server block where the domain name is located is matched, and if the domain name does not match the server_name, the default server block is matched.
  3. 10.101.192.91:8080 access, the domain name first accurately matched to the server block of www.bb.com, and then matched to the server block of the pan-domain name*.bb.com. When mismatched, the third implicit default server block was used.

    • This involves pan-domain name and implicit default server block. It is easy to understand that pan-domain name matching is after exact domain name. Implicit default server block is a server block that does not specify default_server parameter after listen. Tengine/Nginx has a default server block for each IP:Port when parsing configuration. If the default_server parameter is explicitly specified after the listen, the server where the listen is located is the default server block of the IP: Port. If the default_server parameter is not explicitly specified, the first server block of the IP: Port is the implicit default server block.

These configurations can derive some debug techniques:

if ($http_x_alicdn_debug_get_server = "on") {
    return 200 "$server_addr:$server_port, server_name: $server_name";
}

Just bring the request header X-Alicdn-Debug-Get-Server: on to know which server block the request hit. This configuration is very useful for debug, a system with many server blocks. Note that this configuration needs to be placed in a configuration file and loaded with server_auto_include, and then tengine will automatically hit all s. The erver block takes effect (nginx does not have a similar configuration command).

data structure

Let's look at how the configuration of server block, the core module of http, is related to the data structure.

typedef struct {
    /* array of the ngx_http_server_name_t, "server_name" directive */
    ngx_array_t                 server_names;

    /* server ctx */
    ngx_http_conf_ctx_t        *ctx;

    u_char                     *file_name;
    ngx_uint_t                  line;

    ngx_str_t                   server_name;
#if (T_NGX_SERVER_INFO)
    ngx_str_t                   server_admin;
#endif

    size_t                      connection_pool_size;
    size_t                      request_pool_size;
    size_t                      client_header_buffer_size;

    ngx_bufs_t                  large_client_header_buffers;

    ngx_msec_t                  client_header_timeout;

    ngx_flag_t                  ignore_invalid_headers;
    ngx_flag_t                  merge_slashes;
    ngx_flag_t                  underscores_in_headers;

    unsigned                    listen:1;
#if (NGX_PCRE)
    unsigned                    captures:1;
#endif

    ngx_http_core_loc_conf_t  **named_locations;
} ngx_http_core_srv_conf_t;

I don't elaborate on what these fields are used for. It mainly depends on how ngx_http_core_srv_conf_t is associated with other data structures. From the configuration above, we can know that server is associated with IP:Port. The relationship in tengine/nginx is as follows:

typedef struct {
    ngx_http_listen_opt_t      opt;

    ngx_hash_t                 hash;
    ngx_hash_wildcard_t       *wc_head;
    ngx_hash_wildcard_t       *wc_tail;

#if (NGX_PCRE)
    ngx_uint_t                 nregex;
    ngx_http_server_name_t    *regex;
#endif

    /* the default server configuration for this address:port */
    ngx_http_core_srv_conf_t  *default_server;
    ngx_array_t                servers;  /* array of ngx_http_core_srv_conf_t */
} ngx_http_conf_addr_t;

As you can see, the default server block default_server and all server block array servers associated with IP:Port are included in the core data structure ngx_http_conf_addr_t. Several other fields are expanded in detail. tengine splits all IP:Port by Port and puts ngx_http_conf_addr_t into ngx_http_conf_port_t:

typedef struct {
    ngx_int_t                  family;
    in_port_t                  port;
    ngx_array_t                addrs;     /* array of ngx_http_conf_addr_t */
} ngx_http_conf_port_t;

Why split IP:Port? This is because if listen's Port does not specify ip, such as listen 80; then tengine/nginx's address when creating listen socket is 0.0.0; if there are other configurations with listen's exact ip and port, such as listen 10.101.192.91:80; then it is impossible to create this in the kernel. Section 2 configures several listeners in a socket to listen on the kernel like this:

Although listened to 80 and 10.101.192.91:80, the kernel is 0.0.0.0:80, so tengine needs to record all exact addresses of the port with ngx_http_conf_port_t. However, this structure is only used in the configuration phase, and converted into structures ngx_http_port_t and ngx_http_in_addr_t when listening to socket s (because ip:port and server blocks are many-to-many relationships and need to be reorganized and optimized):

typedef struct {
    /* ngx_http_in_addr_t or ngx_http_in6_addr_t */
    void                      *addrs;
    ngx_uint_t                 naddrs;
} ngx_http_port_t;

typedef struct {
    in_addr_t                  addr;
    ngx_http_addr_conf_t       conf;
} ngx_http_in_addr_t;

typdef  ngx_http_addr_conf_s ngx_http_addr_conf_t;

struct ngx_http_addr_conf_s {
    /* the default server configuration for this address:port */
    ngx_http_core_srv_conf_t  *default_server;

    ngx_http_virtual_names_t  *virtual_names;

    unsigned                   ssl:1;
    unsigned                   http2:1;
    unsigned                   proxy_protocol:1;
};

Among them, ngx_http_port_t records all the exact addresses of the port and the corresponding server blocks. And ngx_http_port_t is placed in the core structure ngx_listening_t of monitored socket:

typedef struct ngx_listening_s  ngx_listening_t;

struct ngx_listening_s {
    ngx_socket_t        fd;

    struct sockaddr    *sockaddr;
    socklen_t           socklen;    /* size of sockaddr */
    size_t              addr_text_max_len;
    ngx_str_t           addr_text;

    // Eliminate...

    /* handler of accepted connection */
    ngx_connection_handler_pt   handler;

    void               *servers;  /* array of ngx_http_in_addr_t, for example */

    // Eliminate...
};

struct ngx_connection_s {
    // Eliminate...

    ngx_listening_t    *listening;

    // Eliminate...
};

So a connection can find a matching server block from C - > listening - > servers.

The general relationship between ip:port and server in tengine is as follows:

(You can use this diagram to understand how tengine looks for server blocks)

From request to server block

This section describes the logic of tengine from processing requests to matching server s. ngx_http_init_connection is a function that initializes connections. In this function, we see the following logic:

void
ngx_http_init_connection(ngx_connection_t *c)
{
    // Eliminate...
    ngx_http_port_t        *port;
    ngx_http_in_addr_t     *addr;
    ngx_http_connection_t  *hc;
    // Eliminate...

    /* find the server configuration for the address:port */

    port = c->listening->servers;

    if (port->naddrs > 1) {
            // Eliminate...
            sin = (struct sockaddr_in *) c->local_sockaddr;

            addr = port->addrs;

            /* the last address is "*" */

            for (i = 0; i < port->naddrs - 1; i++) {
                if (addr[i].addr == sin->sin_addr.s_addr) {
                    break;
                }
            }

            hc->addr_conf = &addr[i].conf;
            // Eliminate...
    } else {
            // Eliminate...
            addr = port->addrs;
            hc->addr_conf = &addr[0].conf;
            // Eliminate...
    }

    /* the default server configuration for the address:port */
    hc->conf_ctx = hc->addr_conf->default_server->ctx;
    // Eliminate...
}

As you can see, when initializing, you get the ip:port of the socket and match the most appropriate configuration. It is stored in the hc-> addr_conf pointer, which is the data structure ngx_http_addr_conf_t pointer mentioned above. It stores all the core configuration of the server block associated with the ip:port, and then receives the HTTP request header. When processing the request line or the Host header, the real server block is matched in hc-> addr_conf according to the domain name:

static ngx_int_t
ngx_http_set_virtual_server(ngx_http_request_t *r, ngx_str_t *host)
{
    // Eliminate...
    ngx_http_connection_t     *hc;
    ngx_http_core_srv_conf_t  *cscf;

    // Eliminate...

    hc = r->http_connection;

    // Eliminate...

    rc = ngx_http_find_virtual_server(r->connection,
                                      hc->addr_conf->virtual_names,
                                      host, r, &cscf);

    //When r is created, r - > srv_conf and r - > loc_conf are the default configurations for HC - > conf_ctx
    //If the matching server block is not found, there is no need to set r->srv_conf and r->loc_conf
    if (rc == NGX_DECLINED) {
        return NGX_OK;
    }

    // Find the matching server and use the configuration of the real server block
    r->srv_conf = cscf->ctx->srv_conf;
    r->loc_conf = cscf->ctx->loc_conf;
    // Eliminate...
}

The function ngx_http_find_virtual_server is the server block interface for finding the domain name (another call to this function is when dealing with the SSL handshake encountering SNI, because it also needs to find the certificate configured in the matching server block when shaking hands).
At this point, the lookup logic of server block configuration ends, and the server/location block configuration of one's own module can be found from r-> srv_conf and r-> loc_conf in subsequent module processing.

(End of the text)

Keywords: Web Server Nginx socket SSL

Added by fisicx on Fri, 23 Aug 2019 07:36:20 +0300