postgresql源码学习(46)—— PostmasterMain(3) 监听套接字与客户端认证

​​​​​​​继续学习后面的部分

postgresql源码学习(46)—— PostmasterMain(3) 监听套接字与客户端认证_第1张图片

 

一、 创建监听套接字

1. 预备知识

  • ListenAddresses:字符串,用于存储服务器IP地址,以逗号分隔
extern char *ListenAddresses;
  • ListenSocket[MAXLISTEN]:整型数组,保存与服务器上某个IP地址绑定的套接字描述符。初始时全为-1,MAXLISTEN为64。
/* The socket(s) we're listening to. */
#define MAXLISTEN   64
static pgsocket ListenSocket[MAXLISTEN];

2. 相关代码

/*
     * Establish input sockets.
     *
     * First, mark them all closed, and set up an on_proc_exit function that's charged with closing the sockets again at postmaster shutdown.
     */
    for (i = 0; i < MAXLISTEN; i++)
        ListenSocket[i] = PGINVALID_SOCKET;

    on_proc_exit(CloseServerPorts, 0);

    /* 如果ListenAddresses非空 */
    if (ListenAddresses)
    {
        char       *rawstring;
        List       *elemlist;
        ListCell   *l;
        int         success = 0;

        /* Need a modifiable copy of ListenAddresses */
        rawstring = pstrdup(ListenAddresses);

        /* Parse string into list of hostnames,解析ListenAddresses获取ip地址或主机名 */
        if (!SplitGUCList(rawstring, ',', &elemlist))
        {
            /* syntax error in list */
            ereport(FATAL,
                    (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
                     errmsg("invalid list syntax in parameter \"%s\"",
                            "listen_addresses")));
        }

/* 循环处理解析出来的每个ip */
        foreach(l, elemlist)
        {
            char       *curhost = (char *) lfirst(l);

/* 如果不为*,调用StreamServerPort函数创建套接字,注意第二个参数为NULL */
            if (strcmp(curhost, "*") == 0)
                status = StreamServerPort(AF_UNSPEC, NULL,
                                          (unsigned short) PostPortNumber,
                                          NULL,
                                          ListenSocket, MAXLISTEN);
            else
/* 否则,也是调用StreamServerPort函数创建套接字,但第二个参数为curhost */
                status = StreamServerPort(AF_UNSPEC, curhost,
                                          (unsigned short) PostPortNumber,
                                          NULL,
                                          ListenSocket, MAXLISTEN);

/* 创建成功 */
            if (status == STATUS_OK)
            {
                success++;
                /* record the first successful host addr in lockfile,在lockfile中记录第一个成功的地址 */
                if (!listen_addr_saved)
                {
                    AddToDataDirLockFile(LOCK_FILE_LINE_LISTEN_ADDR, curhost);
                    listen_addr_saved = true;
                }
            }
            else
                ereport(WARNING,
                        (errmsg("could not create listen socket for \"%s\"",
                                curhost)));
        }

        if (!success && elemlist != NIL)
            ereport(FATAL,
                    (errmsg("could not create any TCP/IP sockets")));

        list_free(elemlist);
        pfree(rawstring);
    }

二、 读取客户端认证的配置文件

调用load_hba()函数和load_ident()函数读取客户端认证文件pg_hba.conf和pg_ident.conf。

/*
     * Load configuration files for client authentication.
     */
    if (!load_hba())
    {
        /*
         * It makes no sense to continue if we fail to load the HBA file,
         * since there is no way to connect to the database in this case.
         */
        ereport(FATAL,
                (errmsg("could not load pg_hba.conf")));
    }
    if (!load_ident())
    {
        /*
         * We can start up without the IDENT file, although it means that you
         * cannot log in using any of the authentication methods that need a
         * user name mapping. load_ident() already logged the details of error
         * to the log.
         */
    }

1. load_hba函数

/*
 * Read the config file and create a List of HbaLine records for the contents.
 *
 * The configuration is read into a temporary list, and if any parse error
 * occurs the old list is kept in place and false is returned.  Only if the
 * whole file parses OK is the list replaced, and the function returns true.
 *
 * On a false result, caller will take care of reporting a FATAL error in case
 * this is the initial startup.  If it happens on reload, we just keep running
 * with the old data.
 */
bool
load_hba(void)
{
    FILE       *file;
    List       *hba_lines = NIL;
    ListCell   *line;
    List       *new_parsed_lines = NIL;
    bool        ok = true;
    MemoryContext linecxt;
    MemoryContext oldcxt;
    MemoryContext hbacxt;

    file = AllocateFile(HbaFileName, "r");
    if (file == NULL)
    {
        ereport(LOG,
                (errcode_for_file_access(),
                 errmsg("could not open configuration file \"%s\": %m",
                        HbaFileName)));
        return false;
    }

    linecxt = tokenize_file(HbaFileName, file, &hba_lines, LOG);
    FreeFile(file);

    /* Now parse all the lines */
    Assert(PostmasterContext);
    hbacxt = AllocSetContextCreate(PostmasterContext,
                                   "hba parser context",
                                   ALLOCSET_SMALL_SIZES);
    oldcxt = MemoryContextSwitchTo(hbacxt);
    foreach(line, hba_lines)
    {
        TokenizedLine *tok_line = (TokenizedLine *) lfirst(line);
        HbaLine    *newline;

        /* don't parse lines that already have errors */
        if (tok_line->err_msg != NULL)
        {
            ok = false;
            continue;
        }

        if ((newline = parse_hba_line(tok_line, LOG)) == NULL)
        {
            /* Parse error; remember there's trouble */
            ok = false;

            /*
             * Keep parsing the rest of the file so we can report errors on
             * more than the first line.  Error has already been logged, no
             * need for more chatter here.
             */
            continue;
        }

        new_parsed_lines = lappend(new_parsed_lines, newline);
    }

    /*
     * A valid HBA file must have at least one entry; else there's no way to
     * connect to the postmaster.  But only complain about this if we didn't
     * already have parsing errors.
     */
    if (ok && new_parsed_lines == NIL)
    {
        ereport(LOG,
                (errcode(ERRCODE_CONFIG_FILE_ERROR),
                 errmsg("configuration file \"%s\" contains no entries",
                        HbaFileName)));
        ok = false;
    }

    /* Free tokenizer memory */
    MemoryContextDelete(linecxt);
    MemoryContextSwitchTo(oldcxt);

    if (!ok)
    {
        /* File contained one or more errors, so bail out */
        MemoryContextDelete(hbacxt);
        return false;
    }

    /* Loaded new file successfully, replace the one we use */
    if (parsed_hba_context != NULL)
        MemoryContextDelete(parsed_hba_context);
    parsed_hba_context = hbacxt;
    parsed_hba_lines = new_parsed_lines;

    return true;
}

2. load_ident函数

这个文件没有不影响启动,代码跟前一个函数有点像

/*
 * Read the ident config file and create a List of IdentLine records for
 * the contents.
 *
 * This works the same as load_hba(), but for the user config file.
 */
bool
load_ident(void)
{
    FILE       *file;
    List       *ident_lines = NIL;
    ListCell   *line_cell,
               *parsed_line_cell;
    List       *new_parsed_lines = NIL;
    bool        ok = true;
    MemoryContext linecxt;
    MemoryContext oldcxt;
    MemoryContext ident_context;
    IdentLine  *newline;

    file = AllocateFile(IdentFileName, "r");
    if (file == NULL)
    {
        /* not fatal ... we just won't do any special ident maps */
        ereport(LOG,
                (errcode_for_file_access(),
                 errmsg("could not open usermap file \"%s\": %m",
                        IdentFileName)));
        return false;
    }

    linecxt = tokenize_file(IdentFileName, file, &ident_lines, LOG);
    FreeFile(file);

    /* Now parse all the lines */
    Assert(PostmasterContext);
    ident_context = AllocSetContextCreate(PostmasterContext,
                                          "ident parser context",
                                          ALLOCSET_SMALL_SIZES);
    oldcxt = MemoryContextSwitchTo(ident_context);
    foreach(line_cell, ident_lines)
    {
        TokenizedLine *tok_line = (TokenizedLine *) lfirst(line_cell);

        /* don't parse lines that already have errors */
        if (tok_line->err_msg != NULL)
        {
            ok = false;
            continue;
        }

        if ((newline = parse_ident_line(tok_line)) == NULL)
        {
            /* Parse error; remember there's trouble */
            ok = false;

            /*
             * Keep parsing the rest of the file so we can report errors on
             * more than the first line.  Error has already been logged, no
             * need for more chatter here.
             */
            continue;
        }

        new_parsed_lines = lappend(new_parsed_lines, newline);
    }

    /* Free tokenizer memory */
    MemoryContextDelete(linecxt);
    MemoryContextSwitchTo(oldcxt);

    if (!ok)
    {
        /*
         * File contained one or more errors, so bail out, first being careful
         * to clean up whatever we allocated.  Most stuff will go away via
         * MemoryContextDelete, but we have to clean up regexes explicitly.
         */
        foreach(parsed_line_cell, new_parsed_lines)
        {
            newline = (IdentLine *) lfirst(parsed_line_cell);
            if (newline->ident_user[0] == '/')
                pg_regfree(&newline->re);
        }
        MemoryContextDelete(ident_context);
        return false;
    }

    /* Loaded new file successfully, replace the one we use */
    if (parsed_ident_lines != NIL)
    {
        foreach(parsed_line_cell, parsed_ident_lines)
        {
            newline = (IdentLine *) lfirst(parsed_line_cell);
            if (newline->ident_user[0] == '/')
                pg_regfree(&newline->re);
        }
    }
    if (parsed_ident_context != NULL)
        MemoryContextDelete(parsed_ident_context);

    parsed_ident_context = ident_context;
    parsed_ident_lines = new_parsed_lines;

    return true;
}

参考

《PostgreSQL数据库内核分析》第二章

Postgres中postmaster代码解析(上) - JavaShuo

你可能感兴趣的:(源码学习,PostgreSQL,源码学习,postgresql,监听,套接字,客户端认证)