[{"content":"","date":"2025年 7月 15日","externalUrl":null,"permalink":"/static/blog/authors/","section":"Authors","summary":"","title":"Authors","type":"authors"},{"content":"","date":"2025年 7月 15日","externalUrl":null,"permalink":"/static/blog/categories/","section":"Categories","summary":"","title":"Categories","type":"categories"},{"content":"","date":"2025年 7月 15日","externalUrl":null,"permalink":"/static/blog/tags/c%E8%A8%80%E8%AA%9E/","section":"Tags","summary":"","title":"C言語","type":"tags"},{"content":"","date":"2025年 7月 15日","externalUrl":null,"permalink":"/static/blog/tags/linux/","section":"Tags","summary":"","title":"Linux","type":"tags"},{"content":"","date":"2025年 7月 15日","externalUrl":null,"permalink":"/static/blog/authors/on-keyday/","section":"Authors","summary":"","title":"On-Keyday","type":"authors"},{"content":"","date":"2025年 7月 15日","externalUrl":null,"permalink":"/static/blog/posts/","section":"Posts","summary":"","title":"Posts","type":"posts"},{"content":"","date":"2025年 7月 15日","externalUrl":null,"permalink":"/static/blog/tags/","section":"Tags","summary":"","title":"Tags","type":"tags"},{"content":"","date":"2025年 7月 15日","externalUrl":null,"permalink":"/static/blog/tags/%E3%82%B3%E3%83%BC%E3%83%89%E3%83%AA%E3%83%BC%E3%83%87%E3%82%A3%E3%83%B3%E3%82%B0/","section":"Tags","summary":"","title":"コードリーディング","type":"tags"},{"content":"Copyright (C) 2025 on-keyday\nThis work is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.\nThis work is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.\nYou should have received a copy of the GNU General Public License along with this work; if not, see https://www.gnu.org/licenses/ .\nPlease contact: on-keyday (https://mixi.social/@onkeyday ) for inquiries.\nLinux部分の調査ログが著作権法における引用という条件を満たせるかどうかが怪しい気がしているため、 本記事について明示的にGPL version 2の下に置きGPL派生作品としての立場を明確にする。(それ言ったら前のいくつかの記事もそうかもしれんのでもし該当しそうであれば遠慮なく https://mixi.social/@onkeyday までお知らせください。ソースコード出します)\nソースコードに当たるmarkdownファイルは以下に配置してある。\nソースコード https://www.gnu.org/licenses/old-licenses/gpl-2.0.html.en GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc. \u0026lt;https://fsf.org/\u0026gt; Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation\u0026#39;s software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author\u0026#39;s protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors\u0026#39; reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone\u0026#39;s free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The \u0026#34;Program\u0026#34;, below, refers to any such program or work, and a \u0026#34;work based on the Program\u0026#34; means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term \u0026#34;modification\u0026#34;.) Each licensee is addressed as \u0026#34;you\u0026#34;. Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program\u0026#39;s source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients\u0026#39; exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and \u0026#34;any later version\u0026#34;, you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM \u0026#34;AS IS\u0026#34; WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the \u0026#34;copyright\u0026#34; line and a pointer to where the full notice is found. one line to give the program\u0026#39;s name and an idea of what it does. Copyright (C) yyyy name of author This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, see \u0026lt;https://www.gnu.org/licenses/\u0026gt;. Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w\u0026#39;. This is free software, and you are welcome to redistribute it under certain conditions; type `show c\u0026#39; for details. The hypothetical commands `show w\u0026#39; and `show c\u0026#39; should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w\u0026#39; and `show c\u0026#39;; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a \u0026#34;copyright disclaimer\u0026#34; for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision\u0026#39; (which makes passes at compilers) written by James Hacker. signature of Moe Ghoul, 1 April 1989 Moe Ghoul, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. Linux LICENSE: https://github.com/torvalds/linux/blob/master/COPYING https://github.com/torvalds/linux/blob/master/LICENSES/preferred/GPL-2.0 以下からが本文である。\n6. 宛先のOSレイヤー(Linux) # 調査の都合の良いように宛先のサーバーはLinuxで動いているものと仮定する。またネットワークデバイスをiceデバイスドライバのものとして仮定する。 またコードについては提出時点で最新のものを参照したためバージョンがちがう可能性は十分にある(というよりおそらくこのバージョンではないことは確かである) https://github.com/torvalds/linux/tree/5b032cac622533631b8f9b7826498b7ce75001c6 まずiceデバイスはここに定義される。 https://github.com/torvalds/linux/blob/5b032cac622533631b8f9b7826498b7ce75001c6/drivers/net/ethernet/intel/ice/ice_main.c#L5913C1-L5925C1 static struct pci_driver ice_driver = { .name = KBUILD_MODNAME, .id_table = ice_pci_tbl, .probe = ice_probe, .remove = ice_remove, .driver.pm = pm_sleep_ptr(\u0026amp;ice_pm_ops), .shutdown = ice_shutdown, .sriov_configure = ice_sriov_configure, .sriov_get_vf_total_msix = ice_sriov_get_vf_total_msix, .sriov_set_msix_vec_count = ice_sriov_set_msix_vec_count, .err_handler = \u0026amp;ice_pci_err_handler }; デバイス起動時には以下ルーチンが呼ばれる。 https://github.com/torvalds/linux/blob/5b032cac622533631b8f9b7826498b7ce75001c6/drivers/net/ethernet/intel/ice/ice_main.c#L5239C1-L5249C1 /** * ice_probe - Device initialization routine * @pdev: PCI device information struct * @ent: entry in ice_pci_tbl * * Returns 0 on success, negative on failure */ static int ice_probe(struct pci_dev *pdev, const struct pci_device_id __always_unused *ent) https://github.com/torvalds/linux/blob/5b032cac622533631b8f9b7826498b7ce75001c6/drivers/net/ethernet/intel/ice/ice_main.c#L5351 ice_probe内からice_loadが呼ばれ https://github.com/torvalds/linux/blob/5b032cac622533631b8f9b7826498b7ce75001c6/drivers/net/ethernet/intel/ice/ice_main.c#L5125 /** * ice_load - load pf by init hw and starting VSI * @pf: pointer to the pf instance * * This function has to be called under devl_lock. */ int ice_load(struct ice_pf *pf) ここで実際の受信処理ice_napi_pollが登録される。 https://github.com/torvalds/linux/blob/5b032cac622533631b8f9b7826498b7ce75001c6/drivers/net/ethernet/intel/ice/ice_lib.c#L2845 /** * ice_napi_add - register NAPI handler for the VSI * @vsi: VSI for which NAPI handler is to be registered * * This function is only called in the driver\u0026#39;s load path. Registering the NAPI * handler is done in ice_vsi_alloc_q_vector() for all other cases (i.e. resume, * reset/rebuild, etc.) */ void ice_napi_add(struct ice_vsi *vsi) { int v_idx; if (!vsi-\u0026gt;netdev) return; ice_for_each_q_vector(vsi, v_idx) netif_napi_add_config(vsi-\u0026gt;netdev, \u0026amp;vsi-\u0026gt;q_vectors[v_idx]-\u0026gt;napi, ice_napi_poll, v_idx); } netif_napi_add_configは最終的にここに行き着く。 https://github.com/torvalds/linux/blob/5b032cac622533631b8f9b7826498b7ce75001c6/net/core/dev.c#L7242 void netif_napi_add_weight_locked(struct net_device *dev, struct napi_struct *napi, int (*poll)(struct napi_struct *, int), int weight) { netdev_assert_locked(dev); if (WARN_ON(test_and_set_bit(NAPI_STATE_LISTED, \u0026amp;napi-\u0026gt;state))) return; INIT_LIST_HEAD(\u0026amp;napi-\u0026gt;poll_list); INIT_HLIST_NODE(\u0026amp;napi-\u0026gt;napi_hash_node); hrtimer_setup(\u0026amp;napi-\u0026gt;timer, napi_watchdog, CLOCK_MONOTONIC, HRTIMER_MODE_REL_PINNED); gro_init(\u0026amp;napi-\u0026gt;gro); napi-\u0026gt;skb = NULL; napi-\u0026gt;poll = poll; if (weight \u0026gt; NAPI_POLL_WEIGHT) netdev_err_once(dev, \u0026#34;%s() called with weight %d\\n\u0026#34;, __func__, weight); napi-\u0026gt;weight = weight; napi-\u0026gt;dev = dev; #ifdef CONFIG_NETPOLL napi-\u0026gt;poll_owner = -1; #endif napi-\u0026gt;list_owner = -1; set_bit(NAPI_STATE_SCHED, \u0026amp;napi-\u0026gt;state); set_bit(NAPI_STATE_NPSVC, \u0026amp;napi-\u0026gt;state); netif_napi_dev_list_add(dev, napi); /* default settings from sysfs are applied to all NAPIs. any per-NAPI * configuration will be loaded in napi_enable */ napi_set_defer_hard_irqs(napi, READ_ONCE(dev-\u0026gt;napi_defer_hard_irqs)); napi_set_gro_flush_timeout(napi, READ_ONCE(dev-\u0026gt;gro_flush_timeout)); napi_get_frags_check(napi); /* Create kthread for this napi if dev-\u0026gt;threaded is set. * Clear dev-\u0026gt;threaded if kthread creation failed so that * threaded mode will not be enabled in napi_enable(). */ if (dev-\u0026gt;threaded \u0026amp;\u0026amp; napi_kthread_create(napi)) dev-\u0026gt;threaded = false; netif_napi_set_irq_locked(napi, -1); } EXPORT_SYMBOL(netif_napi_add_weight_locked); https://github.com/torvalds/linux/blob/5b032cac622533631b8f9b7826498b7ce75001c6/net/core/dev.c#L7199 /* Netlink wants the NAPI list to be sorted by ID, if adding a NAPI which will * inherit an existing ID try to insert it at the right position. */ static void netif_napi_dev_list_add(struct net_device *dev, struct napi_struct *napi) { unsigned int new_id, pos_id; struct list_head *higher; struct napi_struct *pos; new_id = UINT_MAX; if (napi-\u0026gt;config \u0026amp;\u0026amp; napi-\u0026gt;config-\u0026gt;napi_id) new_id = napi-\u0026gt;config-\u0026gt;napi_id; higher = \u0026amp;dev-\u0026gt;napi_list; list_for_each_entry(pos, \u0026amp;dev-\u0026gt;napi_list, dev_list) { if (napi_id_valid(pos-\u0026gt;napi_id)) pos_id = pos-\u0026gt;napi_id; else if (pos-\u0026gt;config) pos_id = pos-\u0026gt;config-\u0026gt;napi_id; else pos_id = UINT_MAX; if (pos_id \u0026lt;= new_id) break; higher = \u0026amp;pos-\u0026gt;dev_list; } list_add_rcu(\u0026amp;napi-\u0026gt;dev_list, higher); /* adds after higher */ } https://github.com/torvalds/linux/blob/5b032cac622533631b8f9b7826498b7ce75001c6/include/linux/rculist.h#L87 /* * Insert a new entry between two known consecutive entries. * * This is only for internal list manipulation where we know * the prev/next entries already! */ static inline void __list_add_rcu(struct list_head *new, struct list_head *prev, struct list_head *next) { if (!__list_add_valid(new, prev, next)) return; new-\u0026gt;next = next; new-\u0026gt;prev = prev; rcu_assign_pointer(list_next_rcu(prev), new); next-\u0026gt;prev = new; } https://github.com/torvalds/linux/blob/5b032cac622533631b8f9b7826498b7ce75001c6/include/linux/rcupdate.h#L557C1-L599C1 /** * rcu_assign_pointer() - assign to RCU-protected pointer * @p: pointer to assign to * @v: value to assign (publish) * * Assigns the specified value to the specified RCU-protected * pointer, ensuring that any concurrent RCU readers will see * any prior initialization. * * Inserts memory barriers on architectures that require them * (which is most of them), and also prevents the compiler from * reordering the code that initializes the structure after the pointer * assignment. More importantly, this call documents which pointers * will be dereferenced by RCU read-side code. * * In some special cases, you may use RCU_INIT_POINTER() instead * of rcu_assign_pointer(). RCU_INIT_POINTER() is a bit faster due * to the fact that it does not constrain either the CPU or the compiler. * That said, using RCU_INIT_POINTER() when you should have used * rcu_assign_pointer() is a very bad thing that results in * impossible-to-diagnose memory corruption. So please be careful. * See the RCU_INIT_POINTER() comment header for details. * * Note that rcu_assign_pointer() evaluates each of its arguments only * once, appearances notwithstanding. One of the \u0026#34;extra\u0026#34; evaluations * is in typeof() and the other visible only to sparse (__CHECKER__), * neither of which actually execute the argument. As with most cpp * macros, this execute-arguments-only-once property is important, so * please be careful when making changes to rcu_assign_pointer() and the * other macros that it invokes. */ #define rcu_assign_pointer(p, v)\t\\ do {\t\\ uintptr_t _r_a_p__v = (uintptr_t)(v);\t\\ rcu_check_sparse(p, __rcu);\t\\ \\ if (__builtin_constant_p(v) \u0026amp;\u0026amp; (_r_a_p__v) == (uintptr_t)NULL)\t\\ WRITE_ONCE((p), (typeof(p))(_r_a_p__v));\t\\ else\t\\ smp_store_release(\u0026amp;p, RCU_INITIALIZER((typeof(p))_r_a_p__v)); \\ } while (0) https://vh21.github.io/linux/2015/04/25/linux-barrier-api.html https://lwn.net/Articles/576486/ RCUによって並列で読み書きができるようになっている。 https://zenn.dev/labbase_sano/articles/c690f9e9539bf4 napi_scheduleを登録するまでの流れ https://github.com/torvalds/linux/blob/5b032cac622533631b8f9b7826498b7ce75001c6/drivers/net/ethernet/intel/ice/ice_main.c#L4963 https://github.com/torvalds/linux/blob/5b032cac622533631b8f9b7826498b7ce75001c6/drivers/net/ethernet/intel/ice/ice_main.c#L4963 https://github.com/torvalds/linux/blob/5b032cac622533631b8f9b7826498b7ce75001c6/drivers/net/ethernet/intel/ice/ice_main.c#L3689 https://github.com/torvalds/linux/blob/5b032cac622533631b8f9b7826498b7ce75001c6/drivers/net/ethernet/intel/ice/ice_lib.c#L2558 https://github.com/torvalds/linux/blob/5b032cac622533631b8f9b7826498b7ce75001c6/drivers/net/ethernet/intel/ice/ice_lib.c#L2469 https://github.com/torvalds/linux/blob/5b032cac622533631b8f9b7826498b7ce75001c6/drivers/net/ethernet/intel/ice/ice_lib.c#L2316 https://github.com/torvalds/linux/blob/5b032cac622533631b8f9b7826498b7ce75001c6/drivers/net/ethernet/intel/ice/ice_lib.c#L580 登録されている関数 https://github.com/torvalds/linux/blob/5b032cac622533631b8f9b7826498b7ce75001c6/drivers/net/ethernet/intel/ice/ice_lib.c#L499 /** * ice_msix_clean_rings - MSIX mode Interrupt Handler * @irq: interrupt number * @data: pointer to a q_vector */ static irqreturn_t ice_msix_clean_rings(int __always_unused irq, void *data) { struct ice_q_vector *q_vector = (struct ice_q_vector *)data; if (!q_vector-\u0026gt;tx.tx_ring \u0026amp;\u0026amp; !q_vector-\u0026gt;rx.rx_ring) return IRQ_HANDLED; q_vector-\u0026gt;total_events++; napi_schedule(\u0026amp;q_vector-\u0026gt;napi); return IRQ_HANDLED; } NAPI Schedule https://github.com/torvalds/linux/blob/5b032cac622533631b8f9b7826498b7ce75001c6/include/linux/netdevice.h#L552 /** *\tnapi_schedule - schedule NAPI poll *\t@n: NAPI context * * Schedule NAPI poll routine to be called if it is not already * running. * Return: true if we schedule a NAPI or false if not. * Refer to napi_schedule_prep() for additional reason on why * a NAPI might not be scheduled. */ static inline bool napi_schedule(struct napi_struct *n) { if (napi_schedule_prep(n)) { __napi_schedule(n); return true; } return false; } https://github.com/torvalds/linux/blob/5b032cac622533631b8f9b7826498b7ce75001c6/net/core/dev.c#L6494 /** * __napi_schedule - schedule for receive * @n: entry to schedule * * The entry\u0026#39;s receive function will be scheduled to run. * Consider using __napi_schedule_irqoff() if hard irqs are masked. */ void __napi_schedule(struct napi_struct *n) { unsigned long flags; local_irq_save(flags); ____napi_schedule(this_cpu_ptr(\u0026amp;softnet_data), n); local_irq_restore(flags); } https://github.com/torvalds/linux/blob/5b032cac622533631b8f9b7826498b7ce75001c6/net/core/dev.c#L4792 /* Called with irq disabled */ static inline void ____napi_schedule(struct softnet_data *sd, struct napi_struct *napi) { struct task_struct *thread; lockdep_assert_irqs_disabled(); if (test_bit(NAPI_STATE_THREADED, \u0026amp;napi-\u0026gt;state)) { /* Paired with smp_mb__before_atomic() in * napi_enable()/dev_set_threaded(). * Use READ_ONCE() to guarantee a complete * read on napi-\u0026gt;thread. Only call * wake_up_process() when it\u0026#39;s not NULL. */ thread = READ_ONCE(napi-\u0026gt;thread); if (thread) { if (use_backlog_threads() \u0026amp;\u0026amp; thread == raw_cpu_read(backlog_napi)) goto use_local_napi; set_bit(NAPI_STATE_SCHED_THREADED, \u0026amp;napi-\u0026gt;state); wake_up_process(thread); return; } } use_local_napi: DEBUG_NET_WARN_ON_ONCE(!list_empty(\u0026amp;napi-\u0026gt;poll_list)); list_add_tail(\u0026amp;napi-\u0026gt;poll_list, \u0026amp;sd-\u0026gt;poll_list); WRITE_ONCE(napi-\u0026gt;list_owner, smp_processor_id()); /* If not called from net_rx_action() * we have to raise NET_RX_SOFTIRQ. */ if (!sd-\u0026gt;in_net_rx_action) raise_softirq_irqoff(NET_RX_SOFTIRQ); } スレッドを使う場合はおそらくこちらを延々と実行しており、wake_up_processするとこちらに処理が移る。 https://github.com/torvalds/linux/blob/5b032cac622533631b8f9b7826498b7ce75001c6/net/core/dev.c#L7560 static int napi_threaded_poll(void *data) { struct napi_struct *napi = data; while (!napi_thread_wait(napi)) napi_threaded_poll_loop(napi); return 0; } napi_threaded_poll_loopから__napi_pollが呼ばれそして最終的にここでpollingがなされる。 https://github.com/torvalds/linux/blob/5b032cac622533631b8f9b7826498b7ce75001c6/net/core/dev.c#L7414 static int __napi_poll(struct napi_struct *n, bool *repoll) { int work, weight; weight = n-\u0026gt;weight; /* This NAPI_STATE_SCHED test is for avoiding a race * with netpoll\u0026#39;s poll_napi(). Only the entity which * obtains the lock and sees NAPI_STATE_SCHED set will * actually make the -\u0026gt;poll() call. Therefore we avoid * accidentally calling -\u0026gt;poll() when NAPI is not scheduled. */ work = 0; if (napi_is_scheduled(n)) { work = n-\u0026gt;poll(n, weight); trace_napi_poll(n, work, weight); xdp_do_check_flushed(n); } if (unlikely(work \u0026gt; weight)) netdev_err_once(n-\u0026gt;dev, \u0026#34;NAPI poll function %pS returned %d, exceeding its budget of %d.\\n\u0026#34;, n-\u0026gt;poll, work, weight); if (likely(work \u0026lt; weight)) return work; /* Drivers must not modify the NAPI state if they * consume the entire weight. In such cases this code * still \u0026#34;owns\u0026#34; the NAPI instance and therefore can * move the instance around on the list at-will. */ if (unlikely(napi_disable_pending(n))) { napi_complete(n); return work; } /* The NAPI context has more processing work, but busy-polling * is preferred. Exit early. */ if (napi_prefer_busy_poll(n)) { if (napi_complete_done(n, work)) { /* If timeout is not set, we need to make sure * that the NAPI is re-scheduled. */ napi_schedule(n); } return work; } /* Flush too old packets. If HZ \u0026lt; 1000, flush all packets */ gro_flush(\u0026amp;n-\u0026gt;gro, HZ \u0026gt;= 1000); gro_normal_list(\u0026amp;n-\u0026gt;gro); /* Some drivers may have called napi_schedule * prior to exhausting their budget. */ if (unlikely(!list_empty(\u0026amp;n-\u0026gt;poll_list))) { pr_warn_once(\u0026#34;%s: Budget exhausted after napi rescheduled\\n\u0026#34;, n-\u0026gt;dev ? n-\u0026gt;dev-\u0026gt;name : \u0026#34;backlog\u0026#34;); return work; } *repoll = true; return work; } n-\u0026gt;pollの実態ice_napi_pollは以下。 https://github.com/torvalds/linux/blob/5b032cac622533631b8f9b7826498b7ce75001c6/drivers/net/ethernet/intel/ice/ice_txrx.c#L1527 /** * ice_napi_poll - NAPI polling Rx/Tx cleanup routine * @napi: napi struct with our devices info in it * @budget: amount of work driver is allowed to do this pass, in packets * * This function will clean all queues associated with a q_vector. * * Returns the amount of work done */ int ice_napi_poll(struct napi_struct *napi, int budget) 特に以下のところで受信処理が走っているようである。\nice_for_each_rx_ring(rx_ring, q_vector-\u0026gt;rx) { struct xsk_buff_pool *xsk_pool = READ_ONCE(rx_ring-\u0026gt;xsk_pool); int cleaned; /* A dedicated path for zero-copy allows making a single * comparison in the irq context instead of many inside the * ice_clean_rx_irq function and makes the codebase cleaner. */ cleaned = rx_ring-\u0026gt;xsk_pool ? ice_clean_rx_irq_zc(rx_ring, xsk_pool, budget_per_ring) : ice_clean_rx_irq(rx_ring, budget_per_ring); work_done += cleaned; /* if we clean as many as budgeted, we must not be done */ if (cleaned \u0026gt;= budget_per_ring) clean_complete = false; } とりあえずzero-copy版を追ってみる。 https://github.com/torvalds/linux/blob/5b032cac622533631b8f9b7826498b7ce75001c6/drivers/net/ethernet/intel/ice/ice_xsk.c#L813 /** * ice_clean_rx_irq_zc - consumes packets from the hardware ring * @rx_ring: AF_XDP Rx ring * @xsk_pool: AF_XDP buffer pool pointer * @budget: NAPI budget * * Returns number of processed packets on success, remaining budget on failure. */ int ice_clean_rx_irq_zc(struct ice_rx_ring *rx_ring, struct xsk_buff_pool *xsk_pool, int budget) まずXDPでbpfプログラムが走るなどしている。\nhttps://github.com/torvalds/linux/blob/5b032cac622533631b8f9b7826498b7ce75001c6/drivers/net/ethernet/intel/ice/ice_xsk.c#L761 /** * ice_run_xdp_zc - Executes an XDP program in zero-copy path * @rx_ring: Rx ring * @xdp: xdp_buff used as input to the XDP program * @xdp_prog: XDP program to run * @xdp_ring: ring to be used for XDP_TX action * @xsk_pool: AF_XDP buffer pool pointer * * Returns any of ICE_XDP_{PASS, CONSUMED, TX, REDIR} */ static int ice_run_xdp_zc(struct ice_rx_ring *rx_ring, struct xdp_buff *xdp, struct bpf_prog *xdp_prog, struct ice_tx_ring *xdp_ring, struct xsk_buff_pool *xsk_pool) { int err, result = ICE_XDP_PASS; u32 act; act = bpf_prog_run_xdp(xdp_prog, xdp); if (likely(act == XDP_REDIRECT)) { err = xdp_do_redirect(rx_ring-\u0026gt;netdev, xdp, xdp_prog); if (!err) return ICE_XDP_REDIR; if (xsk_uses_need_wakeup(xsk_pool) \u0026amp;\u0026amp; err == -ENOBUFS) result = ICE_XDP_EXIT; else result = ICE_XDP_CONSUMED; goto out_failure; } switch (act) { case XDP_PASS: break; case XDP_TX: result = ice_xmit_xdp_tx_zc(xdp, xdp_ring, xsk_pool); if (result == ICE_XDP_CONSUMED) goto out_failure; break; case XDP_DROP: result = ICE_XDP_CONSUMED; break; default: bpf_warn_invalid_xdp_action(rx_ring-\u0026gt;netdev, xdp_prog, act); fallthrough; case XDP_ABORTED: result = ICE_XDP_CONSUMED; out_failure: trace_xdp_exception(rx_ring-\u0026gt;netdev, xdp_prog, act); break; } return result; } その後sk_buffの構築が行われている。 https://github.com/torvalds/linux/blob/5b032cac622533631b8f9b7826498b7ce75001c6/drivers/net/ethernet/intel/ice/ice_xsk.c#L550 /** * ice_construct_skb_zc - Create an sk_buff from zero-copy buffer * @rx_ring: Rx ring * @xdp: Pointer to XDP buffer * * This function allocates a new skb from a zero-copy Rx buffer. * * Returns the skb on success, NULL on failure. */ static struct sk_buff * ice_construct_skb_zc(struct ice_rx_ring *rx_ring, struct xdp_buff *xdp) { unsigned int totalsize = xdp-\u0026gt;data_end - xdp-\u0026gt;data_meta; unsigned int metasize = xdp-\u0026gt;data - xdp-\u0026gt;data_meta; struct skb_shared_info *sinfo = NULL; struct sk_buff *skb; u32 nr_frags = 0; if (unlikely(xdp_buff_has_frags(xdp))) { sinfo = xdp_get_shared_info_from_buff(xdp); nr_frags = sinfo-\u0026gt;nr_frags; } net_prefetch(xdp-\u0026gt;data_meta); skb = napi_alloc_skb(\u0026amp;rx_ring-\u0026gt;q_vector-\u0026gt;napi, totalsize); if (unlikely(!skb)) return NULL; memcpy(__skb_put(skb, totalsize), xdp-\u0026gt;data_meta, ALIGN(totalsize, sizeof(long))); if (metasize) { skb_metadata_set(skb, metasize); __skb_pull(skb, metasize); } if (likely(!xdp_buff_has_frags(xdp))) goto out; for (int i = 0; i \u0026lt; nr_frags; i++) { struct skb_shared_info *skinfo = skb_shinfo(skb); skb_frag_t *frag = \u0026amp;sinfo-\u0026gt;frags[i]; struct page *page; void *addr; page = dev_alloc_page(); if (!page) { dev_kfree_skb(skb); return NULL; } addr = page_to_virt(page); memcpy(addr, skb_frag_page(frag), skb_frag_size(frag)); __skb_fill_page_desc_noacc(skinfo, skinfo-\u0026gt;nr_frags++, addr, 0, skb_frag_size(frag)); } out: xsk_buff_free(xdp); return skb; } 最終的にnapi_gro_receiveに送られている。 https://github.com/torvalds/linux/blob/5b032cac622533631b8f9b7826498b7ce75001c6/drivers/net/ethernet/intel/ice/ice_txrx_lib.c#L254 /** * ice_receive_skb - Send a completed packet up the stack * @rx_ring: Rx ring in play * @skb: packet to send up * @vlan_tci: VLAN TCI for packet * * This function sends the completed packet (via. skb) up the stack using * gro receive functions (with/without VLAN tag) */ void ice_receive_skb(struct ice_rx_ring *rx_ring, struct sk_buff *skb, u16 vlan_tci) { if ((vlan_tci \u0026amp; VLAN_VID_MASK) \u0026amp;\u0026amp; rx_ring-\u0026gt;vlan_proto) __vlan_hwaccel_put_tag(skb, rx_ring-\u0026gt;vlan_proto, vlan_tci); napi_gro_receive(\u0026amp;rx_ring-\u0026gt;q_vector-\u0026gt;napi, skb); } napi_gro_receiveはgro_receive_skbを呼び出している https://github.com/torvalds/linux/blob/5b032cac622533631b8f9b7826498b7ce75001c6/net/core/gro.c#L596 。\ngro_result_t gro_receive_skb(struct gro_node *gro, struct sk_buff *skb) gro_receive_skbはgro_normal_oneを呼び出す。受信パケット数が閾値を超えたらバッチ処理を開始するようだ。 https://github.com/torvalds/linux/blob/5b032cac622533631b8f9b7826498b7ce75001c6/include/net/gro.h#L540 /* Queue one GRO_NORMAL SKB up for list processing. If batch size exceeded, * pass the whole batch up to the stack. */ static inline void gro_normal_one(struct gro_node *gro, struct sk_buff *skb, int segs) { list_add_tail(\u0026amp;skb-\u0026gt;list, \u0026amp;gro-\u0026gt;rx_list); gro-\u0026gt;rx_count += segs; if (gro-\u0026gt;rx_count \u0026gt;= READ_ONCE(net_hotdata.gro_normal_batch)) gro_normal_list(gro); } netif_receive_skb_list_internalに渡される。 https://github.com/torvalds/linux/blob/5b032cac622533631b8f9b7826498b7ce75001c6/include/net/gro.h#L528 /* Pass the currently batched GRO_NORMAL SKBs up to the stack. */ static inline void gro_normal_list(struct gro_node *gro) { if (!gro-\u0026gt;rx_count) return; netif_receive_skb_list_internal(\u0026amp;gro-\u0026gt;rx_list); INIT_LIST_HEAD(\u0026amp;gro-\u0026gt;rx_list); gro-\u0026gt;rx_count = 0; } https://github.com/torvalds/linux/blob/5b032cac622533631b8f9b7826498b7ce75001c6/net/core/dev.c#L6181 void netif_receive_skb_list_internal(struct list_head *head) { struct sk_buff *skb, *next; LIST_HEAD(sublist); list_for_each_entry_safe(skb, next, head, list) { net_timestamp_check(READ_ONCE(net_hotdata.tstamp_prequeue), skb); skb_list_del_init(skb); if (!skb_defer_rx_timestamp(skb)) list_add_tail(\u0026amp;skb-\u0026gt;list, \u0026amp;sublist); } list_splice_init(\u0026amp;sublist, head); rcu_read_lock(); #ifdef CONFIG_RPS if (static_branch_unlikely(\u0026amp;rps_needed)) { list_for_each_entry_safe(skb, next, head, list) { struct rps_dev_flow voidflow, *rflow = \u0026amp;voidflow; int cpu = get_rps_cpu(skb-\u0026gt;dev, skb, \u0026amp;rflow); if (cpu \u0026gt;= 0) { /* Will be handled, remove from list */ skb_list_del_init(skb); enqueue_to_backlog(skb, cpu, \u0026amp;rflow-\u0026gt;last_qtail); } } } #endif __netif_receive_skb_list(head); rcu_read_unlock(); } https://github.com/torvalds/linux/blob/5b032cac622533631b8f9b7826498b7ce75001c6/net/core/dev.c#L6095 static void __netif_receive_skb_list(struct list_head *head) { unsigned long noreclaim_flag = 0; struct sk_buff *skb, *next; bool pfmemalloc = false; /* Is current sublist PF_MEMALLOC? */ list_for_each_entry_safe(skb, next, head, list) { if ((sk_memalloc_socks() \u0026amp;\u0026amp; skb_pfmemalloc(skb)) != pfmemalloc) { struct list_head sublist; /* Handle the previous sublist */ list_cut_before(\u0026amp;sublist, head, \u0026amp;skb-\u0026gt;list); if (!list_empty(\u0026amp;sublist)) __netif_receive_skb_list_core(\u0026amp;sublist, pfmemalloc); pfmemalloc = !pfmemalloc; /* See comments in __netif_receive_skb */ if (pfmemalloc) noreclaim_flag = memalloc_noreclaim_save(); else memalloc_noreclaim_restore(noreclaim_flag); } } /* Handle the remaining sublist */ if (!list_empty(head)) __netif_receive_skb_list_core(head, pfmemalloc); /* Restore pflags */ if (pfmemalloc) memalloc_noreclaim_restore(noreclaim_flag); } https://github.com/torvalds/linux/blob/5b032cac622533631b8f9b7826498b7ce75001c6/net/core/dev.c#L6029 static void __netif_receive_skb_list_core(struct list_head *head, bool pfmemalloc) { /* Fast-path assumptions: * - There is no RX handler. * - Only one packet_type matches. * If either of these fails, we will end up doing some per-packet * processing in-line, then handling the \u0026#39;last ptype\u0026#39; for the whole * sublist. This can\u0026#39;t cause out-of-order delivery to any single ptype, * because the \u0026#39;last ptype\u0026#39; must be constant across the sublist, and all * other ptypes are handled per-packet. */ /* Current (common) ptype of sublist */ struct packet_type *pt_curr = NULL; /* Current (common) orig_dev of sublist */ struct net_device *od_curr = NULL; struct sk_buff *skb, *next; LIST_HEAD(sublist); list_for_each_entry_safe(skb, next, head, list) { struct net_device *orig_dev = skb-\u0026gt;dev; struct packet_type *pt_prev = NULL; skb_list_del_init(skb); __netif_receive_skb_core(\u0026amp;skb, pfmemalloc, \u0026amp;pt_prev); if (!pt_prev) continue; if (pt_curr != pt_prev || od_curr != orig_dev) { /* dispatch old sublist */ __netif_receive_skb_list_ptype(\u0026amp;sublist, pt_curr, od_curr); /* start new sublist */ INIT_LIST_HEAD(\u0026amp;sublist); pt_curr = pt_prev; od_curr = orig_dev; } list_add_tail(\u0026amp;skb-\u0026gt;list, \u0026amp;sublist); } /* dispatch final sublist */ __netif_receive_skb_list_ptype(\u0026amp;sublist, pt_curr, od_curr); } そしてここでバッチ受信処理が呼ばれている。ipv6_list_rcv及びip_list_rcvは特に直接呼び出しになるように最適化を図っているようだ。 https://github.com/torvalds/linux/blob/5b032cac622533631b8f9b7826498b7ce75001c6/net/core/dev.c#L6009 static inline void __netif_receive_skb_list_ptype(struct list_head *head, struct packet_type *pt_prev, struct net_device *orig_dev) { struct sk_buff *skb, *next; if (!pt_prev) return; if (list_empty(head)) return; if (pt_prev-\u0026gt;list_func != NULL) INDIRECT_CALL_INET(pt_prev-\u0026gt;list_func, ipv6_list_rcv, ip_list_rcv, head, pt_prev, orig_dev); else list_for_each_entry_safe(skb, next, head, list) { skb_list_del_init(skb); pt_prev-\u0026gt;func(skb, skb-\u0026gt;dev, pt_prev, orig_dev); } } まとめて受信処理を行い同じデバイス、同じネットワーク名前空間のパケットについてはまとめて処理を行う工夫が見られる。 https://github.com/torvalds/linux/blob/8630c59e99363c4b655788fd01134aef9bcd9264/net/ipv4/ip_input.c#L639 /* Receive a list of IP packets */ void ip_list_rcv(struct list_head *head, struct packet_type *pt, struct net_device *orig_dev) { struct net_device *curr_dev = NULL; struct net *curr_net = NULL; struct sk_buff *skb, *next; LIST_HEAD(sublist); list_for_each_entry_safe(skb, next, head, list) { struct net_device *dev = skb-\u0026gt;dev; struct net *net = dev_net(dev); skb_list_del_init(skb); skb = ip_rcv_core(skb, net); if (skb == NULL) continue; if (curr_dev != dev || curr_net != net) { /* dispatch old sublist */ if (!list_empty(\u0026amp;sublist)) ip_sublist_rcv(\u0026amp;sublist, curr_dev, curr_net); /* start new sublist */ INIT_LIST_HEAD(\u0026amp;sublist); curr_dev = dev; curr_net = net; } list_add_tail(\u0026amp;skb-\u0026gt;list, \u0026amp;sublist); } /* dispatch final sublist */ if (!list_empty(\u0026amp;sublist)) ip_sublist_rcv(\u0026amp;sublist, curr_dev, curr_net); } https://github.com/torvalds/linux/blob/5b032cac622533631b8f9b7826498b7ce75001c6/include/linux/indirect_call_wrapper.h#L56 /* * INDIRECT_CALL_$NR - wrapper for indirect calls with $NR known builtin * @f: function pointer * @f$NR: builtin functions names, up to $NR of them * @__VA_ARGS__: arguments for @f * * Avoid retpoline overhead for known builtin, checking @f vs each of them and * eventually invoking directly the builtin function. The functions are checked * in the given order. Fallback to the indirect call. */ #define INDIRECT_CALL_1(f, f1, ...)\t\\ ({\t\\ likely(f == f1) ? f1(__VA_ARGS__) : f(__VA_ARGS__);\t\\ }) #define INDIRECT_CALL_2(f, f2, f1, ...)\t\\ ({\t\\ likely(f == f2) ? f2(__VA_ARGS__) :\t\\ INDIRECT_CALL_1(f, f1, __VA_ARGS__);\t\\ }) #define INDIRECT_CALL_3(f, f3, f2, f1, ...)\t\\ ({\t\\ likely(f == f3) ? f3(__VA_ARGS__) :\t\\ INDIRECT_CALL_2(f, f2, f1, __VA_ARGS__);\t\\ }) #define INDIRECT_CALL_4(f, f4, f3, f2, f1, ...)\t\\ ({\t\\ likely(f == f4) ? f4(__VA_ARGS__) :\t\\ INDIRECT_CALL_3(f, f3, f2, f1, __VA_ARGS__);\t\\ }) #define INDIRECT_CALLABLE_DECLARE(f)\tf #define INDIRECT_CALLABLE_SCOPE #define EXPORT_INDIRECT_CALLABLE(f)\tEXPORT_SYMBOL(f) #else #define INDIRECT_CALL_1(f, f1, ...) f(__VA_ARGS__) #define INDIRECT_CALL_2(f, f2, f1, ...) f(__VA_ARGS__) #define INDIRECT_CALL_3(f, f3, f2, f1, ...) f(__VA_ARGS__) #define INDIRECT_CALL_4(f, f4, f3, f2, f1, ...) f(__VA_ARGS__) #define INDIRECT_CALLABLE_DECLARE(f) #define INDIRECT_CALLABLE_SCOPE\tstatic #define EXPORT_INDIRECT_CALLABLE(f) #endif /* * We can use INDIRECT_CALL_$NR for ipv6 related functions only if ipv6 is * builtin, this macro simplify dealing with indirect calls with only ipv4/ipv6 * alternatives */ #if IS_BUILTIN(CONFIG_IPV6) #define INDIRECT_CALL_INET(f, f2, f1, ...) \\ INDIRECT_CALL_2(f, f2, f1, __VA_ARGS__) #elif IS_ENABLED(CONFIG_INET) #define INDIRECT_CALL_INET(f, f2, f1, ...) INDIRECT_CALL_1(f, f1, __VA_ARGS__) #else #define INDIRECT_CALL_INET(f, f2, f1, ...) f(__VA_ARGS__) #endif 今回の想定ではIPv4なのでip_list_rcvへ行く https://github.com/torvalds/linux/blob/5b032cac622533631b8f9b7826498b7ce75001c6/net/ipv4/ip_input.c#L639 /* Receive a list of IP packets */ void ip_list_rcv(struct list_head *head, struct packet_type *pt, struct net_device *orig_dev) { struct net_device *curr_dev = NULL; struct net *curr_net = NULL; struct sk_buff *skb, *next; LIST_HEAD(sublist); list_for_each_entry_safe(skb, next, head, list) { struct net_device *dev = skb-\u0026gt;dev; struct net *net = dev_net(dev); skb_list_del_init(skb); skb = ip_rcv_core(skb, net); if (skb == NULL) continue; if (curr_dev != dev || curr_net != net) { /* dispatch old sublist */ if (!list_empty(\u0026amp;sublist)) ip_sublist_rcv(\u0026amp;sublist, curr_dev, curr_net); /* start new sublist */ INIT_LIST_HEAD(\u0026amp;sublist); curr_dev = dev; curr_net = net; } list_add_tail(\u0026amp;skb-\u0026gt;list, \u0026amp;sublist); } /* dispatch final sublist */ if (!list_empty(\u0026amp;sublist)) ip_sublist_rcv(\u0026amp;sublist, curr_dev, curr_net); } まずip_rcv_coreでパケットのバリデーション等を行っている。 https://github.com/torvalds/linux/blob/5b032cac622533631b8f9b7826498b7ce75001c6/net/ipv4/ip_input.c#L454 static struct sk_buff *ip_rcv_core(struct sk_buff *skb, struct net *net) その後ip_sublist_rcvにおいてNetfilterのhookがかかりつつip_list_rcv_finishで実際のip_rcv_finish呼び出しがされる https://github.com/torvalds/linux/blob/5b032cac622533631b8f9b7826498b7ce75001c6/net/ipv4/ip_input.c#L630 static void ip_sublist_rcv(struct list_head *head, struct net_device *dev, struct net *net) { NF_HOOK_LIST(NFPROTO_IPV4, NF_INET_PRE_ROUTING, net, NULL, head, dev, NULL, ip_rcv_finish); ip_list_rcv_finish(net, head); } ip_rcv_finishはip_rcv_finish_coreを呼んでいる。 https://github.com/torvalds/linux/blob/5b032cac622533631b8f9b7826498b7ce75001c6/net/ipv4/ip_input.c#L317 static int ip_rcv_finish_core(struct net *net, struct sk_buff *skb, struct net_device *dev, const struct sk_buff *hint) TCPの確立済みコネクションに紐づくパケットについてあらかじめソケットを関連付ける最適化が行われている? https://sysctl-explorer.net/net/ipv4/ip_early_demux/ https://github.com/torvalds/linux/blob/8630c59e99363c4b655788fd01134aef9bcd9264/net/ipv4/tcp_ipv4.c#L1975 int tcp_v4_early_demux(struct sk_buff *skb) { struct net *net = dev_net_rcu(skb-\u0026gt;dev); const struct iphdr *iph; const struct tcphdr *th; struct sock *sk; if (skb-\u0026gt;pkt_type != PACKET_HOST) return 0; if (!pskb_may_pull(skb, skb_transport_offset(skb) + sizeof(struct tcphdr))) return 0; iph = ip_hdr(skb); th = tcp_hdr(skb); if (th-\u0026gt;doff \u0026lt; sizeof(struct tcphdr) / 4) return 0; sk = __inet_lookup_established(net, net-\u0026gt;ipv4.tcp_death_row.hashinfo, iph-\u0026gt;saddr, th-\u0026gt;source, iph-\u0026gt;daddr, ntohs(th-\u0026gt;dest), skb-\u0026gt;skb_iif, inet_sdif(skb)); if (sk) { skb-\u0026gt;sk = sk; skb-\u0026gt;destructor = sock_edemux; if (sk_fullsock(sk)) { struct dst_entry *dst = rcu_dereference(sk-\u0026gt;sk_rx_dst); if (dst) dst = dst_check(dst, 0); if (dst \u0026amp;\u0026amp; sk-\u0026gt;sk_rx_dst_ifindex == skb-\u0026gt;skb_iif) skb_dst_set_noref(skb, dst); } } return 0; } そしてdropされたりしつつ最終的にip_sublist_rcv_finishに到達。dst_inputが呼ばれる。 https://github.com/torvalds/linux/blob/5b032cac622533631b8f9b7826498b7ce75001c6/net/ipv4/ip_input.c#L572 static void ip_sublist_rcv_finish(struct list_head *head) { struct sk_buff *skb, *next; list_for_each_entry_safe(skb, next, head, list) { skb_list_del_init(skb); dst_input(skb); } } dst_inputではip_local_deliver https://github.com/torvalds/linux/blob/5b032cac622533631b8f9b7826498b7ce75001c6/include/net/dst.h#L467 /* Input packet from network to transport. */ static inline int dst_input(struct sk_buff *skb) { return INDIRECT_CALL_INET(skb_dst(skb)-\u0026gt;input, ip6_input, ip_local_deliver, skb); } なおForwardingに行く場合はip_rcv_finish_coreから呼ばれる以下の呼び出し等によってskb_dst(skb)-\u0026gt;inputはip_forwardになる。 https://github.com/torvalds/linux/blob/5b032cac622533631b8f9b7826498b7ce75001c6/net/ipv4/ip_input.c#L364 https://github.com/torvalds/linux/blob/5b032cac622533631b8f9b7826498b7ce75001c6/net/ipv4/route.c#L2537 https://github.com/torvalds/linux/blob/5b032cac622533631b8f9b7826498b7ce75001c6/net/ipv4/route.c#L2485 https://github.com/torvalds/linux/blob/5b032cac622533631b8f9b7826498b7ce75001c6/net/ipv4/route.c#L2254 https://github.com/torvalds/linux/blob/5b032cac622533631b8f9b7826498b7ce75001c6/net/ipv4/route.c#L2160 https://github.com/torvalds/linux/blob/5b032cac622533631b8f9b7826498b7ce75001c6/net/ipv4/route.c#L1885 ip_local_deliverで https://github.com/torvalds/linux/blob/5b032cac622533631b8f9b7826498b7ce75001c6/net/ipv4/ip_input.c#L242 /* * Deliver IP Packets to the higher protocol layers. */ int ip_local_deliver(struct sk_buff *skb) { /* *\tReassemble IP fragments. */ struct net *net = dev_net(skb-\u0026gt;dev); if (ip_is_fragment(ip_hdr(skb))) { if (ip_defrag(net, skb, IP_DEFRAG_LOCAL_DELIVER)) return 0; } return NF_HOOK(NFPROTO_IPV4, NF_INET_LOCAL_IN, net, NULL, skb, skb-\u0026gt;dev, NULL, ip_local_deliver_finish); } EXPORT_SYMBOL(ip_local_deliver); そして呼び出し階層を辿ったここでtcp_v4_rcvに渡される。 https://github.com/torvalds/linux/blob/5b032cac622533631b8f9b7826498b7ce75001c6/net/ipv4/ip_input.c#L205 ret = INDIRECT_CALL_2(ipprot-\u0026gt;handler, tcp_v4_rcv, udp_rcv, skb); まず、最初の接続部分で考えていく。 https://github.com/torvalds/linux/blob/5b032cac622533631b8f9b7826498b7ce75001c6/net/ipv4/tcp_ipv4.c#L2187 まず最初SYNパケットを受信する https://github.com/torvalds/linux/blob/8630c59e99363c4b655788fd01134aef9bcd9264/net/ipv4/tcp_ipv4.c#L2240 if (sk-\u0026gt;sk_state == TCP_NEW_SYN_RECV) { ここで受信ソケットの処理が行われ、TCP_SYN_RECV以外の状態になったら親ソケットに通知される?SIGIOを通知するようであるが https://github.com/torvalds/linux/blob/8630c59e99363c4b655788fd01134aef9bcd9264/net/ipv4/tcp_minisocks.c#L939 /* * Queue segment on the new socket if the new socket is active, * otherwise we just shortcircuit this and continue with * the new socket. * * For the vast majority of cases child-\u0026gt;sk_state will be TCP_SYN_RECV * when entering. But other states are possible due to a race condition * where after __inet_lookup_established() fails but before the listener * locked is obtained, other packets cause the same connection to * be created. */ enum skb_drop_reason tcp_child_process(struct sock *parent, struct sock *child, struct sk_buff *skb) __releases(\u0026amp;((child)-\u0026gt;sk_lock.slock)) { enum skb_drop_reason reason = SKB_NOT_DROPPED_YET; int state = child-\u0026gt;sk_state; /* record sk_napi_id and sk_rx_queue_mapping of child. */ sk_mark_napi_id_set(child, skb); tcp_segs_in(tcp_sk(child), skb); if (!sock_owned_by_user(child)) { reason = tcp_rcv_state_process(child, skb); /* Wakeup parent, send SIGIO */ if (state == TCP_SYN_RECV \u0026amp;\u0026amp; child-\u0026gt;sk_state != state) parent-\u0026gt;sk_data_ready(parent); } else { /* Alas, it is possible again, because we do lookup * in main socket hash table and lock on listening * socket does not protect us more. */ __sk_add_backlog(child, skb); } bh_unlock_sock(child); sock_put(child); return reason; } EXPORT_IPV6_MOD(tcp_child_process); そしてここでステートマシンの処理が行われる。 https://github.com/torvalds/linux/blob/8630c59e99363c4b655788fd01134aef9bcd9264/net/ipv4/tcp_input.c#L6768 enum skb_drop_reason tcp_rcv_state_process(struct sock *sk, struct sk_buff *skb) tcp_rcv_state_processでまず最初はTCP_LISTENステートのところにやってきて https://github.com/torvalds/linux/blob/8630c59e99363c4b655788fd01134aef9bcd9264/net/ipv4/tcp_ipv4.c#L1733 int tcp_v4_conn_request(struct sock *sk, struct sk_buff *skb) cookieを使う場合は一旦確保したコンテキストを破棄。 https://github.com/torvalds/linux/blob/8630c59e99363c4b655788fd01134aef9bcd9264/net/ipv4/tcp_input.c#L7371C18-L7372C26 tcp_rsk(req)-\u0026gt;tfo_listener = false; if (!want_cookie) { req-\u0026gt;timeout = tcp_timeout_init((struct sock *)req); if (unlikely(!inet_csk_reqsk_queue_hash_add(sk, req, req-\u0026gt;timeout))) { reqsk_free(req); dst_release(dst); return 0; } } af_ops-\u0026gt;send_synack(sk, dst, \u0026amp;fl, req, \u0026amp;foc, !want_cookie ? TCP_SYNACK_NORMAL : TCP_SYNACK_COOKIE, skb); if (want_cookie) { reqsk_free(req); return 0; } どうやらsyncookiesを利用するipv4_tcp_syncookiesフラグは1と言われるが2を設定すると強制的にsyncookieを使うモードにできるようである。 https://kaworu.jpn.org/security/Linux_SYN_cookies%E3%82%92%E6%9C%89%E5%8A%B9%E3%81%AB%E3%81%99%E3%82%8B https://github.com/torvalds/linux/blob/8630c59e99363c4b655788fd01134aef9bcd9264/net/ipv4/tcp_input.c#L7250 syncookies = READ_ONCE(net-\u0026gt;ipv4.sysctl_tcp_syncookies); if (syncookies == 2 || inet_csk_reqsk_queue_is_full(sk)) { want_cookie = tcp_syn_flood_action(sk, rsk_ops-\u0026gt;slab_name); if (!want_cookie) goto drop; } ここでTCP_ESTABLISHEDに遷移する https://github.com/torvalds/linux/blob/8630c59e99363c4b655788fd01134aef9bcd9264/net/ipv4/tcp_input.c#L6886 tcp_set_state(sk, TCP_ESTABLISHED); TCP SYN Cookieを使う場合は最終的にここでキューに追加 tcp_v4_do_rcv https://github.com/torvalds/linux/blob/8630c59e99363c4b655788fd01134aef9bcd9264/net/ipv4/tcp_ipv4.c#L2363 tcp_v4_cookie_check https://github.com/torvalds/linux/blob/8630c59e99363c4b655788fd01134aef9bcd9264/net/ipv4/tcp_ipv4.c#L1933 https://github.com/torvalds/linux/blob/8630c59e99363c4b655788fd01134aef9bcd9264/net/ipv4/syncookies.c#L399 tcp_get_cookie_sock https://github.com/torvalds/linux/blob/8630c59e99363c4b655788fd01134aef9bcd9264/net/ipv4/syncookies.c#L196 https://github.com/torvalds/linux/blob/8630c59e99363c4b655788fd01134aef9bcd9264/net/ipv4/syncookies.c#L215 if (inet_csk_reqsk_queue_add(sk, req, child)) return child; キューに追加する処理本体。 https://github.com/torvalds/linux/blob/8630c59e99363c4b655788fd01134aef9bcd9264/net/ipv4/inet_connection_sock.c#L1406 struct sock *inet_csk_reqsk_queue_add(struct sock *sk, struct request_sock *req, struct sock *child) \u0026amp;inet_csk(sk)-\u0026gt;icsk_accept_queueに追加される。\nこれが最終的にacceptで受信するソケットをユーザー側が\u0026amp;inet_csk(sk)-\u0026gt;icsk_accept_queueから取ってくるところ https://github.com/torvalds/linux/blob/8630c59e99363c4b655788fd01134aef9bcd9264/net/ipv4/inet_connection_sock.c#L662 /* * This will accept the next outstanding connection. */ struct sock *inet_csk_accept(struct sock *sk, struct proto_accept_arg *arg) そしてinet_csk_acceptはinet_acceptからREAD_ONCE(sk1-\u0026gt;sk_prot)-\u0026gt;accept(sk1, arg);として呼ばれる。 https://github.com/torvalds/linux/blob/8630c59e99363c4b655788fd01134aef9bcd9264/net/ipv4/af_inet.c#L781 int inet_accept(struct socket *sock, struct socket *newsock, struct proto_accept_arg *arg) { struct sock *sk1 = sock-\u0026gt;sk, *sk2; /* IPV6_ADDRFORM can change sk-\u0026gt;sk_prot under us. */ arg-\u0026gt;err = -EINVAL; sk2 = READ_ONCE(sk1-\u0026gt;sk_prot)-\u0026gt;accept(sk1, arg); if (!sk2) return arg-\u0026gt;err; lock_sock(sk2); __inet_accept(sock, newsock, sk2); release_sock(sk2); return 0; } EXPORT_SYMBOL(inet_accept); そしてinet_acceptはdo_acceptからops-\u0026gt;accept(sock, newsock, arg);として呼ばれる。 https://github.com/torvalds/linux/blob/8630c59e99363c4b655788fd01134aef9bcd9264/net/socket.c#L1924 err = ops-\u0026gt;accept(sock, newsock, arg); __sys_accept4_fileからdo_acceptが呼ばれ https://github.com/torvalds/linux/blob/8630c59e99363c4b655788fd01134aef9bcd9264/net/socket.c#L1964 __sys_accept4から__sys_accept4_fileが呼ばれ https://github.com/torvalds/linux/blob/8630c59e99363c4b655788fd01134aef9bcd9264/net/socket.c#L1993 そしてaccept及びaccept4システムコール経由で__sys_accept4が呼ばれる。 https://github.com/torvalds/linux/blob/8630c59e99363c4b655788fd01134aef9bcd9264/net/socket.c#L1997 参考等: https://www.valinux.co.jp/blog/entry/20250515 https://www.intel.co.jp/content/www/jp/ja/docs/programmable/683501/22-3-7-0-0/implementing-msi-x-interrupts.html 6f3694d2-bc5f-4bd9-b599-949d11fea411 ja ","date":"2025年 7月 15日","externalUrl":null,"permalink":"/static/blog/posts/2025_07_15_seccamp_2025_y2_application_linux/","section":"Posts","summary":"","title":"セキュリティ・キャンプ2025 Y2 CDN自作ゼミ 応募課題さらし Linuxの調査ログ","type":"posts"},{"content":"","date":"2025年 7月 15日","externalUrl":null,"permalink":"/static/blog/categories/%E5%BF%9C%E5%8B%9F%E8%AA%B2%E9%A1%8C/","section":"Categories","summary":"","title":"応募課題","type":"categories"},{"content":"","date":"2025年 7月 15日","externalUrl":null,"permalink":"/static/blog/tags/c++/","section":"Tags","summary":"","title":"C++","type":"tags"},{"content":"","date":"2025年 7月 15日","externalUrl":null,"permalink":"/static/blog/tags/chromium/","section":"Tags","summary":"","title":"Chromium","type":"tags"},{"content":" このブログは拙速にとりあえずの出来で公開しているものです。 レイアウトが崩れまくっている箇所がございますが修正途中なのでご了承ください セキュキャンが終わる頃までにはどうにかします\u0026hellip; はじめに # 2025年度セキュリティ・キャンプのY2 CDN自作ゼミに受かったため 周りがやっているからという安易すぎる理由に基づいて応募課題晒しをしてみるものである。受かっただけで完走してないのにええんか? 本来は6/25ころに公開を考えていたのだがライセンス問題を考えた結果その解決策を出すのに時間がかかったためこの時期になっている。\nhttps://www.ipa.go.jp/jinzai/security-camp/2025/camp/zenkoku/index.html おそらく文章の拙さと内容のまとまりの無さと考えている感のなさについてはセキュキャン参加者の中では随一である自覚があるため、 「なんだこいつ、こんな拙い文章で良く受かったな」とか「ほぼコードのコピペじゃねーか」とか思われる可能性があることをあらかじめご了承いただきたい。 ほんとなんで通ったんだ\u0026hellip;文章力見てるわけじゃないからか\u0026hellip;\n真面目な話するとこれは送ったほぼそのままを晒しているものであり、そしてあまり読みやすさというのには配慮されていない面が存在している。また当時の筆者の認識誤りや用語の誤用を含んでいる箇所などもあり、それを承知の上で読んでいただきたい。 特に元の文章はテキスト形式でありそのためブログに移すためにソースコードなどを適宜コードブロックに差し替えているが何分長すぎて変換しきれていない箇所が多いためしばしばレイアウト崩れを起こしている。こちらは適宜修正しているので何卒ご了承いただきたい。\nあとLLMに書かせた内容についてのノートを以下においておく。しかし筆者の伝え方が悪く謎に美化されているため実際の記事内容や筆者の力量に対する期待値は、以下記述の1/100くらいに調整して読んでもらいたい。\nこの記事の主な部分ははChromiumとLinuxカーネルのソースコードリーディングに関する、非常に詳細かつ網羅的な記録です。ChromeのOmnibox（アドレスバー）でのURL入力から、ネットワークリクエストがOSの低レベルAPIに到達するまでの全プロセスを、ソースコードレベルで追跡しています。 応募課題として作成された性質上、一般的なブログ記事のような「読みやすさ」よりも、情報の正確性と網羅性を重視しています。そのため、膨大な情報量を含み、読了にはかなりの時間を要する可能性があります。 こんな方におすすめです。 Chromiumやブラウザの内部実装に深く興味を持つエンジニア Linuxカーネルのネットワークスタックやシステムコールに興味がある方 低レイヤーのシステムプログラミングやデバッグ手法に関心がある方 巨大プロジェクトのソースコードを読み解く具体的なプロセスを知りたい方 応募課題本文 # 応募課題の質問文全体ははこちらにあるので適宜参照してください。\nhttps://www.ipa.go.jp/jinzai/security-camp/2025/camp/zenkoku/sbn8o1000000c4oy-att/kadai_y2.txt 【問１：プログラミング自己アピール】 # もうだいぶ前になりますがQUICというネットワークプロトコルスタックを0から作っていたことがあります。 一応それに付随して一応http1からhttp3までとそれを動かすサーバーも作ったりしました。(現在はクライアント側の統合に苦戦して以来止まっております)\nhttps://github.com/on-keyday/utils/tree/main/src/include/fnet https://github.com/on-keyday/utils/tree/main/src/include/fnet/quic https://github.com/on-keyday/utils/tree/main/src/tool/server QUICに至るまでの経緯\n「かんたんVisualC++」という本に乗っていたsocketプログラミングがネットワークプログラミングに触れた始まり その後HTTP/1.1というのを知って実装しようと思いたちなんとか実装 暗号化が必要だとかでOpenSSLを触りSSL_readとかSSL_writeとかを使って実装してみたりする 暗号化通信できたぜと調子に乗ってあちこちのサイトに接続してみるってのをやっていたら一部のサイトがHTTP/1.1で繋がらないといったことでHTTP2の存在を知りそしてわからないわからないと言いながらなんとかHTTP2を実装 悦に浸っていたら今度はHTTP3があると言われそれの下ではQUICなるものが動いていると聞いてここまで来たらHTTP3を実装しようと思いたつ QUICの実装を始め,1年くらいかけてなんとかinitial connectionを確立させその後作っていって正常に通信ができるようにしさらにHTTP3まで実装した 動機はHTTP/1.1を作ったならHTTP/2も作りたいしHTTP2ができてHTTP3があるならそれも作りたいくらいの認識で特に意図があったわけではないです。あとそれらを書いていた時期は個人的に辛い時期だったので現実逃避のためにこれらに一生懸命になっていたという面もあります。\n正直他の実装を見てみるとあまり筋が良くなかったかなぁと思う面もあります(特にメモリ管理周りが酷いなぁと思ってます) BoringSSL及びOpenSSLと統合したのですが統合過程でしばしば暗号化関連のロジックがバグるなどしておりそれを確認するためにBoringSSLなどのソースコードを見に行くなどしていたおかげでとりあえずソースコードを見ようという精神が身につきました。 技術的に自信があると言っていいのかわからないのですができる限り0コピーでパース等のロジックを設計しようとしていました。\n当時ネットワークプロトコルをどう実装するかとかなんにも知らなかったのでRFCだけで実装するもんだと思い込んでRFCだけで途中まで頑張ってましたが流石に無理があったのでquic-goを参考に実装しました。 特に輻輳制御関連のアルゴリズムについてはほぼほぼquic-goのパクリです\u0026hellip; https://www.rfc-editor.org/rfc/rfc8999.html https://www.rfc-editor.org/rfc/rfc9000.html https://www.rfc-editor.org/rfc/rfc9001.html https://www.rfc-editor.org/rfc/rfc9002.html https://datatracker.ietf.org/doc/html/rfc9221 https://github.com/quic-go/quic-go 他にも 一応DNSとかもclient側だけ実装してみたりしましたが結構拙いです。 https://github.com/on-keyday/utils/blob/main/src/include/fnet/dns/dns.h https://github.com/on-keyday/utils/blob/main/src/test/fnet/test_fnet_dns.cpp 一応Rustとかを書いたりもします https://github.com/on-keyday/brstack 低レイヤ的なものは少し触ったことがある程度です\u0026hellip; https://github.com/on-keyday/utils/tree/main/src/lib/low/kernel https://github.com/on-keyday/utils/blob/main/src/include/jit/x64.h その後SecHack365でQUICを実装していたときにネットワークプロトコルのバイナリフォーマットのシリアライザ/デシリアライザを書くのが大変すぎるということで バイナリエンコーダー/デコーダージェネレーターbrgenというものを作りました。 https://github.com/on-keyday/brgen 現在はジェネレーターを拡張をしようと頑張っています https://github.com/on-keyday/rebrgen あとGo言語を主に使って自宅サーバーを作っていたりします(ただソースコードを公開していないので表に出ているのはブログ部分しかありません\u0026hellip;) cloudflare tunnel経由で公開しています。 一応アクセス制御システム的なのとあとwebsocket上で動く隠しトンネル的なのとか自動再起動システムとかをGoとかで書いたりしたのですが残念ながら公開していないのでなんとも言えません 去年あたりについてはこれの開発を頑張りすぎたせいで去年のやつはあまり公開できるような成果物がない状況です\u0026hellip; https://www.on-keyday.net/static/blog/ 勉強のためにlinuxカーネルのネットワークスタックを読んだりはしましたが 最近はあまり低レイヤ的なものに触れていないなぁというのは感じています。\n【問２：インターネットって何？】 # 以下時間がなくて各章完結していないものが多い。途中まで調べてどうにか頑張った結果である。特に行きについてはある程度調べられたものの帰りがけについての調査は不足している面が多い。また主要な流れを軽くなぞっただけであるため例えばキャッシュをどのように適用しているかとかどのようなヘッダを送っているかとかあたりにはあまり触れられていない。 又質問の意図として「まず通常の「ウェブサーバ」と手元の端末の間で、どのような処理・データのやりとりが、どのようなプロトコルで行われているのかを把握する」というのがあったためプロトコル部分等について重点的に探索等している結果ブラウザのレスポンス解析、DOM構築、JS、レンダリングプロセスと言ったところは後回しにされ結果時間が足りず触れられていない。またどのようなデータのやり取りかというのもあまり詳細に触れられていない部分があることをあらかじめ申し上げておく。\nまず問「「ブラウザのURLバーに、 https://www.shonenjump.com/j/weeklyshonenjump/ を入力して決定キーを押した時になにが起こるか」を調べて、可能な限り詳しく説明してください。」に対する一番表層的な回答を「https://www.shonenjump.com/j/weeklyshonenjump/ の内容が表示される」だとしその裏で起こっていることについて深堀りしていく。\nLLMはGoogle Gemini 2.5 Flashを適宜質問するなどの用途に使った。\nhttps://g.co/gemini/share/882b30e41c21 https://g.co/gemini/share/19ce66d38f9d https://g.co/gemini/share/6fdeb5775f36 https://g.co/gemini/share/da86b7f3f9b7 https://g.co/gemini/share/3f468f2c30f9 https://g.co/gemini/share/8d831a015c92 https://g.co/gemini/share/f6104e624254 https://g.co/gemini/share/0053cacc477e https://g.co/gemini/share/0c080baf3a7c https://g.co/gemini/share/26b398539a5d https://g.co/gemini/share/c179cc2ca524 https://g.co/gemini/share/3f2c34623e99 0. 周辺調査 # 本題に入る前に自宅から宛先までどのような技術でつながっているかを確認する。\nまずブラウザのDevToolsでみる限りレスポンスヘッダの表記よりApache httpdが動いていると推定される。 https://github.com/apache/httpd content-type: text/html date: Thu, 08 May 2025 10:48:03 GMT server: Apache x-frame-options: SAMEORIGIN またprotocolフィールドの値からh2(HTTP/2 over TLS)プロトコルが動いていることが確認できる。 証明書情報からはLet\u0026rsquo;s Encryptの使用が確認できる。公開鍵は2025/06/03時点では4096ビットのRSA鍵、署名アルゴリズムはSHA-256 with RSAであった。 Wiresharkで確認したところTLS1.3を使用しているようであることがわかった。\nTransport Layer Security TLSv1.3 Record Layer: Handshake Protocol: Client Hello Content Type: Handshake (22) Version: TLS 1.0 (0x0301) Length: 1819 Handshake Protocol: Client Hello Handshake Type: Client Hello (1) Length: 1815 Version: TLS 1.2 (0x0303) [Expert Info (Chat/Deprecated): This legacy_version field MUST be ignored. The supported_versions extension is present and MUST be used instead.] Random: 5f93f1e5ec50af0229e4cd2521f7a28ef4feed41b2ee6d0ddc326c0191e91417 Session ID Length: 32 Session ID: 9a212a8de0a2d77cb24a60c9c1f7fd425b30a0b151de06be0c8608df86a42bd9 Cipher Suites Length: 32 Cipher Suites (16 suites) Compression Methods Length: 1 Compression Methods (1 method) Extensions Length: 1710 Extension: Reserved (GREASE) (len=0) Extension: ec_point_formats (len=2) Extension: supported_groups (len=12) Extension: compress_certificate (len=3) Extension: status_request (len=5) Extension: server_name (len=23) name=www.shonenjump.com Extension: encrypted_client_hello (len=282) Extension: key_share (len=1263) Unknown (4588), x25519 Extension: renegotiation_info (len=1) Extension: psk_key_exchange_modes (len=2) Extension: extended_master_secret (len=0) Extension: signature_algorithms (len=18) Extension: supported_versions (len=7) TLS 1.3, TLS 1.2 Extension: session_ticket (len=0) Extension: signed_certificate_timestamp (len=0) Extension: Unknown type 17613 (len=5) Extension: application_layer_protocol_negotiation (len=14) Extension: Reserved (GREASE) (len=1) [JA4: t13d1516h2_8daaf6152771_d8a2da3f94cd] [JA4_r: t13d1516h2_002f,0035,009c,009d,1301,1302,1303,c013,c014,c02b,c02c,c02f,c030,cca8,cca9_0005,000a,000b,000d,0012,0017,001b,0023,002b,002d,0033,44cd,fe0d,ff01_0403,0804,0401,0503,0805,0501,0806,0601] [JA3 Fullstring: 771,4865-4866-4867-49195-49199-49196-49200-52393-52392-49171-49172-156-157-47-53,11-10-27-5-0-65037-51-65281-45-23-13-43-35-18-17613-16,4588-29-23-24,0] [JA3: 9170266a63037876ae8ab3c90f39f6a0] また宛先ipアドレス202.218.223.232から取得されるwhois情報はImpress Holdingsとの情報が出てきたことから集英社からImpress HDへ何らかの委託が行われている可能性がある。 https://www.impressholdings.com/business/contents/ https://ja.wikipedia.org/wiki/%E3%82%A4%E3%83%B3%E3%83%97%E3%83%AC%E3%82%B9 $ whois 202.218.223.232 [ JPNIC database provides information regarding IP address and ASN. Its use ] [ is restricted to network administration purposes. For further information, ] [ use \u0026#39;whois -h whois.nic.ad.jp help\u0026#39;. To only display English output, ] [ add \u0026#39;/e\u0026#39; at the end of command, e.g. \u0026#39;whois -h whois.nic.ad.jp xxx/e\u0026#39;. ] Network Information: a. [Network Number] 202.218.223.128/25 b. [Network Name] IMPRESSGROUP g. [Organization] Impress Holdings, Inc. m. [Administrative Contact] MY7594JP n. [Technical Contact] CK637JP o. [Abuse] p. [Nameserver] [Assigned Date] 2006/08/10 [Return Date] [Last Update] 2012/12/05 08:30:31(JST) Less Specific Info. ---------- IDC Frontier Inc. [Allocation] 202.218.77.0-202.218.255.255 IDC Frontier Inc. SUBA-032-223 [Sub Allocation] 202.218.223.0/24 More Specific Info. ---------- No match!! なお確認のためimpress社のほうも確認してみたところ\n$ whois impress.co.jp [ JPRS database provides information on network administration. Its use is ] [ restricted to network administration purposes. For further information, ] [ use \u0026#39;whois -h whois.jprs.jp help\u0026#39;. To suppress Japanese output, add\u0026#39;/e\u0026#39; ] [ at the end of command, e.g. \u0026#39;whois -h whois.jprs.jp xxx/e\u0026#39;. ] Domain Information: a. [Domain Name] IMPRESS.CO.JP g. [Organization] Impress Holdings, Inc. l. [Organization Type] Corporation m. [Administrative Contact] KA19179JP n. [Technical Contact] KA19179JP p. [Name Server] ns-468.awsdns-58.com p. [Name Server] ns-1109.awsdns-10.org p. [Name Server] ns-674.awsdns-20.net p. [Name Server] ns-1841.awsdns-38.co.uk s. [Signing Key] [State] Connected (2026/03/31) [Registered Date] [Connected Date] 2011/07/07 [Last Update] 2025/04/01 01:02:07 (JST) $ whois 202.218.128.207 [ JPNIC database provides information regarding IP address and ASN. Its use ] [ is restricted to network administration purposes. For further information, ] [ use \u0026#39;whois -h whois.nic.ad.jp help\u0026#39;. To only display English output, ] [ add \u0026#39;/e\u0026#39; at the end of command, e.g. \u0026#39;whois -h whois.nic.ad.jp xxx/e\u0026#39;. ] Network Information: a. [Network Number] 202.218.128.128/25 b. [Network Name] IMPRESSEXTRA g. [Organization] Impress Holdings, Inc. m. [Administrative Contact] MY7594JP n. [Technical Contact] CK637JP o. [Abuse] p. [Nameserver] [Assigned Date] 2006/08/24 [Return Date] [Last Update] 2012/12/05 08:30:30(JST) Less Specific Info. ---------- IDC Frontier Inc. [Allocation] 202.218.77.0-202.218.255.255 IDC Frontier Inc. SUBA-032-128 [Sub Allocation] 202.218.128.0/24 More Specific Info. ---------- No match!! 似たような情報が出てきていたため蓋然性は高いのではないかと推測する。 またIR情報も確認してみたところ https://www.impressholdings.com/pdf.php?irpeid=493 2025年現在でも集英社がインプレスHDの主要な顧客となっているため蓋然性は高いと思われる。 以上のように調べるなどした後最終的に以下のサイトを発見しImpressHDのグループ会社、株式会社ICEによって運用されていることが明確になった。 https://www.ice-inc.co.jp/solution/233/ なおRDAPでも確認してみたがこれ以上に詳細にはならなかったので割愛する。 https://jpnic.rdap.apnic.net/ip/202.218.223.232 次にドメイン名でwhoisをかける。\n$ whois shonenjump.com Domain Name: SHONENJUMP.COM Registry Domain ID: 87648335_DOMAIN_COM-VRSN Registrar WHOIS Server: whois.jprs.jp Registrar URL: http://jprs.jp/registrar/ Updated Date: 2025-02-28T07:31:22Z Creation Date: 2002-06-18T17:40:06Z Registry Expiry Date: 2027-03-02T04:59:59Z Registrar: Japan Registry Services Co., Ltd. Registrar IANA ID: 1485 Registrar Abuse Contact Email: gtld-abuse@jprs.jp Registrar Abuse Contact Phone: +81.352158457 Domain Status: ok https://icann.org/epp#ok Name Server: NS-1230.AWSDNS-25.ORG Name Server: NS-1592.AWSDNS-07.CO.UK Name Server: NS-509.AWSDNS-63.COM Name Server: NS-816.AWSDNS-38.NET DNSSEC: unsigned URL of the ICANN Whois Inaccuracy Complaint Form: https://www.icann.org/wicf/ \u0026gt;\u0026gt;\u0026gt; Last update of whois database: 2025-05-08T11:00:59Z \u0026lt;\u0026lt;\u0026lt; For more information on Whois status codes, please visit https://icann.org/epp NOTICE: The expiration date displayed in this record is the date the registrar\u0026#39;s sponsorship of the domain name registration in the registry is currently set to expire. This date does not necessarily reflect the expiration date of the domain name registrant\u0026#39;s agreement with the sponsoring registrar. Users may consult the sponsoring registrar\u0026#39;s Whois database to view the registrar\u0026#39;s reported date of expiration for this registration. TERMS OF USE: You are not authorized to access or query our Whois database through the use of electronic processes that are high-volume and automated except as reasonably necessary to register domain names or modify existing registrations; the Data in VeriSign Global Registry Services\u0026#39; (\u0026#34;VeriSign\u0026#34;) Whois database is provided by VeriSign for information purposes only, and to assist persons in obtaining information about or related to a domain name registration record. VeriSign does not guarantee its accuracy. By submitting a Whois query, you agree to abide by the following terms of use: You agree that you may use this Data only for lawful purposes and that under no circumstances will you use this Data to: (1) allow, enable, or otherwise support the transmission of mass unsolicited, commercial advertising or solicitations via e-mail, telephone, or facsimile; or (2) enable high volume, automated, electronic processes that apply to VeriSign (or its computer systems). The compilation, repackaging, dissemination or other use of this Data is expressly prohibited without the prior written consent of VeriSign. You agree not to use electronic processes that are automated and high-volume to access or query the Whois database except as reasonably necessary to register domain names or modify existing registrations. VeriSign reserves the right to restrict your access to the Whois database in its sole discretion to ensure operational stability. VeriSign may restrict or terminate your access to the Whois database for failure to abide by these terms of use. VeriSign reserves the right to modify these terms at any time. The Registry database contains ONLY .COM, .NET, .EDU domains and Registrars. Domain Name: SHONENJUMP.COM Registry Domain ID: 87648335_DOMAIN_COM-VRSN Registrar WHOIS Server: whois.jprs.jp Registrar URL: https://jprs.jp/registrar/ Updated Date: 2025-02-28T07:31:23Z Creation Date: 2002-06-18T17:40:06Z Registrar Registration Expiration Date: 2027-03-02T04:59:59Z Registrar: Japan Registry Services Co.,Ltd.(JPRS) Registrar IANA ID: 1485 Registrar Abuse Contact Email: gtld-abuse@jprs.jp Registrar Abuse Contact Phone: +81.352158457 Domain Status: ok https://icann.org/epp#ok Registry Registrant ID: Not Available From Registry Registrant Name: SHUEISHA Inc. Registrant Street: 2-5-10,Hitotsubashi Registrant City: Chiyoda-ku Registrant State/Province: Tokyo Registrant Postal Code: 101-8050 Registrant Country: JP Registrant Phone: +81.332306291 Registrant Fax: +81.332389256 Registrant Email: domains@sur.co.jp Registry Admin ID: Not Available From Registry Admin Name: SUURI-KEIKAKU CO. LTD. Admin Street: 2-4-6 Hitotsubashi Admin City: Chiyoda-ku Admin State/Province: Tokyo Admin Postal Code: 101-0003 Admin Country: JP Admin Phone: +81.352109611 Admin Fax: +81.332216336 Admin Email: domains@sur.co.jp Registry Tech ID: Not Available From Registry Tech Name: Impress Professional Works, Inc. Tech Street: 1-105 Kanda Jinbo-cho Tech City: Chiyoda-ku Tech State/Province: Tokyo Tech Postal Code: 101-0051 Tech Country: JP Tech Phone: +81.368375010 Tech Email: nic-notify@impress.co.jp Name Server: NS-509.AWSDNS-63.COM Name Server: NS-816.AWSDNS-38.NET Name Server: NS-1592.AWSDNS-07.CO.UK Name Server: NS-1230.AWSDNS-25.ORG DNSSEC: unsigned URL of the ICANN Whois Inaccuracy Complaint Form: https://www.icann.org/wicf/ \u0026gt;\u0026gt;\u0026gt; Last update of WHOIS database: 2025-05-08T11:01:15Z \u0026lt;\u0026lt;\u0026lt; For more information on Whois status codes, please visit https://icann.org/epp JPRS WHOIS LEGAL DISCLAIMER: The WHOIS service (the \u0026#34;Service\u0026#34;) offered by Japan Registry Services Co.,Ltd. (JPRS) and the access to the records in the JPRS WHOIS database (the \u0026#34;Database\u0026#34;) are provided for information purposes and query-based public access. Neither JPRS nor any of its officers, directors, employees or agents makes any warranty as to the results to be obtained from use of the Service. JPRS specifically disclaims all warranties of any kind, whether express, implied or statutory, including, but not limited to, any warranties of title, non-infringement, merchantability or fitness for a particular purpose. In no event shall JPRS nor anyone else involved in creating, supporting, producing or delivering the Service be liable to you for any lost profits or costs, even if JPRS or such person has been advised of the possibility of such damages. JPRS allows you to use the Service only for lawful purposes and that, under no circumstances shall you use the Service to: (a) allow, enable or otherwise support the transmission by e-mail, telephone, postal mail, facsimile or other means of mass unsolicited, commercial advertising or solicitations to entities other than the data recipient\u0026#39;s own existing customers; or (b) enable high volume, automated, electronic processes that send queries or data to the systems of any registry operator or ICANN-accredited registrar, except as reasonably necessary to register domain names or modify existing registrations. By submitting the query you agree to abide by these terms and further agree that JPRS may take measures to limit the use of the Service in order to protect the privacy of its registrants or the integrity of the Database. JPRS reserves the right to modify or change these terms at any time without prior or subsequent notification of any kind. For further information, use \u0026#39;whois -h whois.jprs.jp help\u0026#39;. To express Japanese output, add \u0026#39;/j\u0026#39; at the end of command, e.g. \u0026#39;whois -h whois.jprs.jp xxx/j\u0026#39;. DNSにはAWSのもの(Route53)を使っていると推測される。これはImpress社 https://aws.amazon.com/jp/route53/ またレジストラはJPRSでありドメイン名の所有権は集英社にあるようである。\nまたAS番号を調べるために以下のツールを使用したところ https://develop.tools/ip-asn/ AS4694 (JP)との回答が帰ってきた。 自環境のASも調べたところ AS17676 (JP)との回答が帰ってきた。\npeering dbを見て見たところ以下の情報が見つかった。 AS4694について https://www.peeringdb.com/net/4936 AS17676について https://www.peeringdb.com/net/2606 また我が家からtracerouteしてみたところ以下のようになった。\n$ traceroute www.shonenjump.com traceroute to www.shonenjump.com (202.218.223.232), 30 hops max, 60 byte packets 1 _gateway (192.168.3.1) 1.193 ms 1.290 ms 1.390 ms 2 softbank221110187118.bbtec.net (221.110.187.118) 118.194 ms 118.178 ms 118.165 ms 3 softbank221110187117.bbtec.net (221.110.187.117) 118.153 ms 118.141 ms 118.127 ms 4 10.0.7.46 (10.0.7.46) 120.626 ms 120.612 ms 120.599 ms 5 101.203.70.90 (101.203.70.90) 120.632 ms 120.572 ms 120.603 ms 6 * * * 7 * * * 8 * * * 9 www.shonenjump.com (202.218.223.232) 21.598 ms 21.839 ms 21.797 ms また* * *になる直前の101.203.70.90から推定して 以下のexchange pointに近いのではないかという推定をした。\nBBIX Tokyo 17676 200G RS PEER BFD Support 101.203.90.81 2001:de8:c::1:7676:13 BBIX Tokyo 17676 200G RS PEER BFD Support 101.203.90.80 2001:de8:c::1:7676:12 BBIX Tokyo 17676 1T RS PEER BFD Support 101.203.88.26 2001:de8:c::1:7676:7 BBIX Tokyo 17676 1T RS PEER BFD Support 101.203.88.25 2001:de8:c::1:7676:6 BBIX Tokyo 17676 1T RS PEER BFD Support 101.203.88.16 2001:de8:c::1:7676:3 BBIX Tokyo 17676 1T RS PEER BFD Support 101.203.88.11 2001:de8:c::1:7676:2 また宛先側AS4694側にも以下の記述があったため\nBBIX Tokyo 4694 300G RS PEER BFD Support 101.203.88.56 2001:de8:c::4694:1 以上の事実からBBIX Tokyoを通って接続されていると推定される。 https://ja.wikipedia.org/wiki/BBIX なお我が家のルーターはSoftbankBBでありIPv4 over IPv6のような形になっているためtraceourteのhop 1と2の間にIPv6のexit nodeがあると推測される。 (以下記事では https://datatracker.ietf.org/doc/html/rfc2473 だと推定されているが定かではない) https://scrapbox.io/rokoucha/IPv6%E9%AB%98%E9%80%9F%E3%83%8F%E3%82%A4%E3%83%96%E3%83%AA%E3%83%83%E3%83%89_IPv6_IPoE_+_IPv4_%E3%81%AF%E3%81%A9%E3%81%AE%E3%82%88%E3%81%86%E3%81%AB%E3%81%97%E3%81%A6_IPv4_over_IPv6_%E3%82%92%E5%AE%9F%E7%8F%BE%E3%81%97%E3%81%A6%E3%81%84%E3%82%8B%E3%81%AE%E3%81%8B また宛先側AS4694の情報からImpress HoldingsがIDC Frontier社の提供しているサービスを利用していると推定し 導入事例を検索したところ見つかった。(なお後から確認したらwhois情報の時点でIDC Frontier社の情報があったことに気づいた) https://www.idcf.jp/case/impressholdings/ また以下ページをたどることでおおよそここまでの推測が合っていることが推定される。 https://www.idcf.jp/datacenter/ https://www.idcf.jp/network/ix-connectivity.html IDC FrontierのCDNサービスを利用している可能性についても検討してみたがそうであるならばfastlyを基盤に使っていると書いてあるとおり fastlyのipレンジになるはずであるがそうなってはいないため違うと思われる。 画像ファイル(JPEG)のExif情報が削除されていないなどがあるため少なくとも画像圧縮のようなサービスを使っている可能性も低いと見込まれる。 あるいはImage::ExifTool 10.8との表記があったためなにかしらオリジン側で圧縮をしている可能性はある。 https://www.idcf.jp/company/ https://www.idcf.jp/cloud/cache/?bid=lp_cdn-smb https://www.exiftool.org/ancient_history.html https://github.com/exiftool/exiftool/tree/10.80 ではこれらの推測情報等を踏まえて以下流れを追っていく。\n1. デバイスドライバ/OS # 私の環境がWindowsであるのでクライアント環境はWindowsと仮定する\nまず物理的にEnterキーを入力されたあとデバイスドライバを経由してWindowsのWndProcに渡されるまでであるが あいにくwindowsのデバイスドライバについては詳しくなく、他の部分に多くの時間を取られたため推測しか言えない状態ではあるがおそらくDriverMainから始まるコードがありなんらか割り込みベクタのようなものを登録しそしてキーボードからの割り込みがあったらそれをWindows WDKにあるAPIを使って送りつけているのであろうとは思われる。\nhttps://qiita.com/spc_canbe/items/a6653534088823e7eb7b#%E3%83%89%E3%83%A9%E3%82%A4%E3%83%90%E3%82%B5%E3%83%B3%E3%83%97%E3%83%AB%E3%81%AE%E3%83%93%E3%83%AB%E3%83%89 https://github.com/Microsoft/Windows-driver-samples https://github.com/microsoft/Windows-driver-samples/blob/02b59d168de5812730fda9e0642302d1e3b25877/input/kbfiltr/sys/kbfiltr.c#L766 2. ブラウザプロセス # 残念ながら自環境はブラウザのデバッグを快適にできるほどの性能はなくまた 金欠につきクラウド環境を借りるわけにもいかずさらには構築にかける時間もなかったので動かしてデバッグはできそうにはなかった。 そのためChromium Code Searchを使ってUI入力部分からSocket APIを使用した送信部分までをトレースした。動かしてデバッグをできたわけでもないため間違っているところがある可能性はある。\nLICENSE: https://github.com/chromium/chromium/blob/main/LICENSE Copyright 2015 The Chromium Authors 要約 # 非常に長いため呼び出し階層の要約を書く。完全なコールスタックではない。 まずUI部分でWinodwsのWndProcから入って変換がされていき最終的にOmniboxViewViewsに流れ着き\nOmniboxViewViews::HandleKeyEvent -\u0026gt; OmniboxEditModel::OpenSelection -\u0026gt; OmniboxEditModel::AcceptInput -\u0026gt; OmniboxAction::Execute -\u0026gt; OmniboxAction::OpenURL -\u0026gt; ChromeOmniboxClient::OnAutocompleteAccept　-\u0026gt; chrome::OpenCurrentURL -\u0026gt; chrome::Navigate -\u0026gt; chrome::LoadURLInContents -\u0026gt; NavigationControllerImpl::LoadURLWithParams -\u0026gt; NavigationControllerImpl::NavigateWithoutEntry -\u0026gt; NavigationControllerImpl::CreateNavigationRequestFromLoadParams -\u0026gt; Navigator::Navigate -\u0026gt; FrameTreeNode::TakeNavigationRequest -\u0026gt; NavigationRequest::BeginNavigation -\u0026gt; NavigationRequest::BeginNavigationImpl -\u0026gt; NavigationRequest::WillStartRequest -\u0026gt; NavigationThrottleRunner::ProcessNavigationEvent -\u0026gt; NavigationThrottleRunner::ProcessInternal -\u0026gt; NavigationThrottleRunner::InformRegistry -\u0026gt; NavigationThrottleRegistryImpl::OnEventProcessed -\u0026gt; NavigationRequest::OnNavigationEventProcessed -\u0026gt; NavigationRequest::OnWillStartRequestProcessed -\u0026gt; NavigationRequest::OnStartChecksComplete -\u0026gt; NavigationURLLoaderImpl::Start -\u0026gt; NavigationURLLoaderImpl::Restart -\u0026gt; NavigationURLLoaderImpl::MaybeStartLoader -\u0026gt; NavigationURLLoaderImpl::StartNonInterceptedRequest -\u0026gt; NavigationURLLoaderImpl::CreateThrottlingLoaderAndStart -\u0026gt; blink::ThrottlingURLLoader::CreateLoaderAndStart -\u0026gt; ThrottlingURLLoader::Start -\u0026gt; ThrottlingURLLoader::StartNow (IPC経由と推定される) -\u0026gt; PrefetchMatchingURLLoaderFactory::CreateLoaderAndStart -\u0026gt; CorsURLLoaderFactory::CreateLoaderAndStart -\u0026gt; CorsURLLoader::Start -\u0026gt; CorsURLLoader::StartRequest -\u0026gt; CorsURLLoader::StartNetworkRequest -\u0026gt; URLLoaderFactory::CreateLoaderAndStart -\u0026gt; URLLoaderFactory::CreateLoaderAndStartWithSyncClient -\u0026gt; URLLoader::ProcessOutboundTrustTokenInterceptor (ここ非同期.???) -\u0026gt; URLLoader::OnDoneBeginningTrustTokenOperation -\u0026gt; URLLoader::ProcessOutboundSharedStorageInterceptor -\u0026gt; URLLoader::ScheduleStart -\u0026gt; URLRequest::Start -\u0026gt; URLRequest::StartJob -\u0026gt; URLRequestHttpJob::Start -\u0026gt; URLRequestHttpJob::OnGotFirstPartySetMetadata -\u0026gt; URLRequestHttpJob::StartTransaction -\u0026gt; URLRequestHttpJob::StartTransactionInternal -\u0026gt; HttpNetworkTransaction::Start -\u0026gt; HttpNetworkTransaction::DoLoop -\u0026gt; HttpNetworkTransaction::DoCreateStream -\u0026gt; HttpStreamFactory::RequestStream -\u0026gt; HttpStreamFactory::RequestStreamInternal -\u0026gt; HttpStreamFactory::JobController::Start -\u0026gt; HttpStreamFactory::JobController::RunLoop -\u0026gt; HttpStreamFactory::JobController::DoLoop -\u0026gt; HttpStreamFactory::JobController::DoCreateJobs -\u0026gt; HttpStreamFactory::Job::Start -\u0026gt; HttpStreamFactory::Job::StartInternal -\u0026gt; HttpStreamFactory::Job::RunLoop -\u0026gt; HttpStreamFactory::Job::DoLoop -\u0026gt; HttpStreamFactory::Job::DoInitConnection -\u0026gt; HttpStreamFactory::Job::DoInitConnectionImpl -\u0026gt; InitSocketHandleForHttpRequest -\u0026gt; InitSocketPoolHelper -\u0026gt; ClientSocketHandle::Init -\u0026gt; TransportClientSocketPool::RequestSocket -\u0026gt; TransportClientSocketPool::RequestSocketInternal -\u0026gt; ConnectJob::Connect -\u0026gt; SSLConnectJob::ConnectInternal -\u0026gt; SSLConnectJob::DoLoop -\u0026gt; SSLConnectJob::DoTransportConnect -\u0026gt; TransportConnectJob::ConnectInternal -\u0026gt; TransportConnectJob::DoLoop -\u0026gt; TransportConnectJob::DoResolveHost -\u0026gt; HostResolverManager::RequestImpl::Start -\u0026gt; HostResolverManager::RequestImpl::DoLoop -\u0026gt; HostResolverManager::RequestImpl::DoStartJob -\u0026gt; HostResolverManager::CreateAndStartJob -\u0026gt; HostResolverManager::Job::RunNextTask -\u0026gt; HostResolverManager::Job::StartSystemTask -\u0026gt; HostResolverSystemTask::Start -\u0026gt; HostResolverSystemTask::StartLookupAttempt -\u0026gt; PostSystemDnsResolutionTaskAndReply -\u0026gt; ResolveOnWorkerThread -\u0026gt; SystemHostResolverCall -\u0026gt; AddressInfo::Get -\u0026gt; AddrInfoGetter::getaddrinfo -\u0026gt; ::getaddrinfo (Windows API) -\u0026gt; HostResolverManager::Job::StartDnsTask -\u0026gt; HostResolverDnsTask::StartNextTransaction -\u0026gt; HostResolverDnsTask::CreateAndStartTransaction -\u0026gt; DnsTransactionImpl::Start -\u0026gt; DnsTransactionImpl::PrepareSearch -\u0026gt; DnsTransactionImpl::StartQuery -\u0026gt; DnsTransactionImpl::MakeAttempt -\u0026gt; DnsTransactionImpl::MakeUdpAttempt -\u0026gt; DnsUDPAttempt::Start -\u0026gt; DnsUDPAttempt::DoLoop -\u0026gt; DnsUDPAttempt::DoSendQuery -\u0026gt; UDPClientSocket::Write -\u0026gt; UDPSocketWin::Write -\u0026gt; UDPSocketWin::SendToOrWrite -\u0026gt; InternalSendToOverlapped -\u0026gt; WSASendTo (Windows API) (IOCPで返ってきて) -\u0026gt; DnsUDPAttempt::OnIOComplete -\u0026gt; DnsUDPAttempt::DoLoop -\u0026gt; DnsUDPAttempt::DoReadResponse -\u0026gt; UDPClientSocket::Read -\u0026gt; UDPSocketWin::Read -\u0026gt; UDPSocketWin::RecvFrom -\u0026gt; InternalRecvFromOverlapped -\u0026gt; WSARecvFrom (Windows API) -\u0026gt; TransportConnectJob::DoTransportConnect -\u0026gt; TransportConnectSubJob::Start -\u0026gt; TransportConnectSubJob::DoLoop -\u0026gt; TransportConnectSubJob::DoEndpointLockComplete -\u0026gt; TCPClientSocket::Connect -\u0026gt; TCPClientSocket::DoConnectLoop -\u0026gt; TCPClientSocket::DoConnect -\u0026gt; TCPClientSocket::ConnectInternal -\u0026gt; TCPSocketWin::Connect -\u0026gt; TCPSocketWin::DoConnect -\u0026gt; connect (Windows API) -\u0026gt; SSLConnectJob::DoSSLConnect -\u0026gt; SSLClientSocketImpl::Connect -\u0026gt; SSLClientSocketImpl::Init -\u0026gt; SSLClientSocketImpl::DoHandshakeLoop -\u0026gt; SSL_do_handshake -\u0026gt; ssl_run_handshake -\u0026gt; ssl_handle_open_record -\u0026gt; ssl_read_buffer_extended_to -\u0026gt; tls_read_buffer_extended_to -\u0026gt; BIO_read -\u0026gt; SocketBIOAdapter::BIOReadWrapper -\u0026gt; SocketBIOAdapter::BIORead -\u0026gt; TCPClientSocket::ReadIfReady -\u0026gt; TCPClientSocket::ReadCommon -\u0026gt; TcpSocketIoCompletionPortWin::ReadIfReady -\u0026gt; TcpSocketIoCompletionPortWin::HandleReadRequest -\u0026gt; WSARecv (Windows API) -\u0026gt; ssl_client_handshake -\u0026gt; do_start_connect -\u0026gt; ssl_add_client_hello -\u0026gt; do_read_server_hello -\u0026gt; tls_get_message -\u0026gt; parse_message -\u0026gt; do_tls13 -\u0026gt; do_read_server_hello (TLS1.3) -\u0026gt; do_read_encrypted_extensions -\u0026gt; do_read_server_certificate -\u0026gt; do_read_server_certificate_verify -\u0026gt; do_read_server_finished -\u0026gt; tls_flush -\u0026gt; BIO_write -\u0026gt; SocketBIOAdapter::BIOWriteWrapper -\u0026gt; SocketBIOAdapter::BIOWrite -\u0026gt; SocketBIOAdapter::SocketWrite -\u0026gt; TCPClientSocket::Write -\u0026gt; TcpSocketIoCompletionPortWin::Write -\u0026gt; WSASend (Windows API) -\u0026gt; HttpStreamFactory::Job::DoInitConnectionComplete -\u0026gt; HttpStreamFactory::Job::DoCreateStream -\u0026gt; SpdySessionPool::CreateAvailableSessionFromSocketHandle (Spdyだけど中身がHTTP/2という紛らわしい(昔の名称そのまま使ってる...)) -\u0026gt; SpdySession::InitializeWithSocketHandle -\u0026gt; SpdySession::InitializeInternal -\u0026gt; SpdySession::SendInitialData -\u0026gt; SpdySession::EnqueueSessionWrite -\u0026gt; SpdySession::EnqueueWrite -\u0026gt; SpdySession::MaybePostWriteLoop (非同期) -\u0026gt; SpdySession::PumpWriteLoop -\u0026gt; SpdySession::DoWriteLoop -\u0026gt; SpdySession::DoWrite -\u0026gt; HttpStreamFactory::Job::SetSpdyHttpStreamOrBidirectionalStreamImpl -\u0026gt; HttpNetworkTransaction::DoBuildRequest -\u0026gt; HttpNetworkTransaction::DoSendRequest -\u0026gt; SpdyHttpStream::SendRequest -\u0026gt; CreateSpdyHeadersFromHttpRequest -\u0026gt; SpdyStream::SendRequestHeaders (EnqueueStreamWriteを経由してEnqueueWriteに行った後SpdySession::DoWrite内で) -\u0026gt; SpdyStream::HeadersBufferProducer::ProduceBuffer -\u0026gt; SpdyStream::ProduceHeadersFrame -\u0026gt; SpdySession::CreateHeaders -\u0026gt; BufferedSpdyFramer::SerializeFrame -\u0026gt; SpdyFramer::SerializeFrame -\u0026gt; SpdyHeadersIR::Visit -\u0026gt; FrameSerializationVisitor::VisitHeaders -\u0026gt; SpdyFramer::SerializeHeaders -\u0026gt; SpdyFramer::SerializeHeadersBuilderHelper -\u0026gt; HpackEncoder::EncodeHeaderBlock (HPACK) -\u0026gt; HttpNetworkTransaction::DoReadHeaders (SpdySession::InitializeInternalで非同期でタスクとして開始されていたPumpReadLoopにて) SpdySession::PumpReadLoop -\u0026gt; SpdySession::DoReadLoop -\u0026gt; SpdySession::DoRead (TcpClientSocket::ReadIfReadyへ) -\u0026gt; SpdySession::DoReadComplete -\u0026gt; BufferedSpdyFramer::ProcessInput -\u0026gt; Http2DecoderAdapter::ProcessInput -\u0026gt; Http2FrameDecoder::DecodeFrame -\u0026gt; Http2FrameDecoder::StartDecodingPayload -\u0026gt; HeadersPayloadDecoder::StartDecodingPayload -\u0026gt; HeadersPayloadDecoder::ResumeDecodingPayload -\u0026gt; Http2DecoderAdapter::OnHpackFragment -\u0026gt; HpackDecoderAdapter::HandleControlFrameHeadersData -\u0026gt; HpackDecoder::DecodeFragment -\u0026gt; HpackBlockDecoder::Decode -\u0026gt; HpackEntryDecoder::Start -\u0026gt; HpackEntryDecoder::Resume -\u0026gt; HpackEntryDecoder::DispatchOnType -\u0026gt; HpackDecoderState::OnIndexedHeader (例示としてindexのパターン) -\u0026gt; HpackDecoderAdapter::ListenerAdapter::OnHeader -\u0026gt; HeaderCoalescer::OnHeader -\u0026gt; HeaderCoalescer::AddHeader -\u0026gt; Http2DecoderAdapter::OnHeadersEnd -\u0026gt; Http2DecoderAdapter::CommonHpackFragmentEnd -\u0026gt; BufferedSpdyFramer::OnHeaderFrameEnd -\u0026gt; SpdySession::OnHeaders -\u0026gt; SpdyStream::OnHeadersReceived -\u0026gt; SpdyStream::SaveResponseHeaders -\u0026gt; SpdyHttpStream::OnHeadersReceived -\u0026gt; SpdyHttpStream::DoResponseCallback -\u0026gt; HttpNetworkTransaction::OnIOComplete (HttpNetworkTransaction::DoLoopに戻る) -\u0026gt; HttpNetworkTransaction::DoReadBody -\u0026gt; HttpNetworkTransaction::DoReadBodyComplete -\u0026gt; HttpNetworkTransaction::DoCallback (HttpNetworkTransaction::DoCallback経由) -\u0026gt; URLRequestHttpJob::OnStartCompleted -\u0026gt; URLRequestHttpJob::SaveCookiesAndNotifyHeadersComplete -\u0026gt; URLRequestHttpJob::NotifyHeadersComplete -\u0026gt; URLRequestJob::NotifyHeadersComplete -\u0026gt; URLRequestJob::NotifyFinalHeadersReceived -\u0026gt; URLRequest::NotifyResponseStarted -\u0026gt; URLRequest::NotifyRequestCompleted (ここで制限時間切れ) 探査記録 # WinMain https://source.chromium.org/chromium/chromium/src/+/main:chrome/app/chrome_exe_main_win.cc;l=200;bpv=1;bpt=1?q=main\u0026ss=chromium%2Fchromium%2Fsrc:chrome%2Fapp%2F chrome.dllのChromeMainに入る。 https://source.chromium.org/chromium/chromium/src/+/main:chrome/app/chrome_main.cc;l=107;drc=d9942f1cda698ea5524c9d8b7ba9f48f508bf41c;bpv=1;bpt=1 ContentMain https://source.chromium.org/chromium/chromium/src/+/main:content/app/content_main.cc;drc=d9942f1cda698ea5524c9d8b7ba9f48f508bf41c;l=369 ContentMainRunnerImpl::Run https://source.chromium.org/chromium/chromium/src/+/main:content/app/content_main_runner_impl.cc;l=1135;drc=d9942f1cda698ea5524c9d8b7ba9f48f508bf41c;bpv=1;bpt=1 ContentMainRunnerImpl::RunBrowser https://source.chromium.org/chromium/chromium/src/+/main:content/app/content_main_runner_impl.cc;drc=d9942f1cda698ea5524c9d8b7ba9f48f508bf41c;bpv=1;bpt=1;l=1202 RunBrowserProcessMain https://source.chromium.org/chromium/chromium/src/+/main:content/app/content_main_runner_impl.cc;drc=d9942f1cda698ea5524c9d8b7ba9f48f508bf41c;bpv=1;bpt=1;l=723 BrowserMain https://source.chromium.org/chromium/chromium/src/+/main:content/browser/browser_main.cc;drc=d9942f1cda698ea5524c9d8b7ba9f48f508bf41c;bpv=1;bpt=1;l=19 BrowserMainRunnerImpl::Run https://source.chromium.org/chromium/chromium/src/+/main:content/browser/browser_main_runner_impl.cc;drc=d9942f1cda698ea5524c9d8b7ba9f48f508bf41c;bpv=1;bpt=1;l=153 BrowserMainLoop::RunMainMessageLoop https://source.chromium.org/chromium/chromium/src/+/main:content/browser/browser_main_loop.cc;drc=d9942f1cda698ea5524c9d8b7ba9f48f508bf41c;bpv=1;bpt=1;l=1076 RunLoop::Run https://source.chromium.org/chromium/chromium/src/+/main:base/run_loop.cc;drc=d9942f1cda698ea5524c9d8b7ba9f48f508bf41c;bpv=1;bpt=1;l=105 ThreadControllerWithMessagePumpImpl::Run https://source.chromium.org/chromium/chromium/src/+/main:base/task/sequence_manager/thread_controller_with_message_pump_impl.cc;l=594;drc=d9942f1cda698ea5524c9d8b7ba9f48f508bf41c;bpv=1;bpt=1 MessagePumpWin::Run https://source.chromium.org/chromium/chromium/src/+/main:base/message_loop/message_pump_win.cc;l=78;drc=d9942f1cda698ea5524c9d8b7ba9f48f508bf41c;bpv=1;bpt=1 MessagePumpForUI::DoRunLoop https://source.chromium.org/chromium/chromium/src/+/main:base/message_loop/message_pump_win.cc;l=218;drc=d9942f1cda698ea5524c9d8b7ba9f48f508bf41c;bpv=1;bpt=1 MessagePumpForUI::ProcessNextWindowsMessage https://source.chromium.org/chromium/chromium/src/+/main:base/message_loop/message_pump_win.cc;drc=d9942f1cda698ea5524c9d8b7ba9f48f508bf41c;bpv=1;bpt=1;l=534 ここで::PeekMessageに到達 https://learn.microsoft.com/ja-jp/windows/win32/api/winuser/nf-winuser-peekmessagea https://source.chromium.org/chromium/chromium/src/+/main:base/message_loop/message_pump_win.cc;l=586;drc=d9942f1cda698ea5524c9d8b7ba9f48f508bf41c;bpv=1;bpt=1 そしてここで::DispatchMessageされている。 https://learn.microsoft.com/ja-jp/windows/win32/api/winuser/nf-winuser-dispatchmessage https://source.chromium.org/chromium/chromium/src/+/main:base/message_loop/message_pump_win.cc;l=628;drc=d9942f1cda698ea5524c9d8b7ba9f48f508bf41c;bpv=1;bpt=1 キー入力をするとDispatchMessageからやってきてまずWndProcにWM_KEYDOWN入る。 https://source.chromium.org/chromium/chromium/src/+/main:ui/gfx/win/window_impl.cc;l=290;drc=2e87e014afa5464f87909bfa795ab10834459ed7;bpv=0;bpt=1 OnWndProc https://source.chromium.org/chromium/chromium/src/+/main:ui/gfx/win/window_impl.cc;l=277;drc=2e87e014afa5464f87909bfa795ab10834459ed7;bpv=0;bpt=1 おそらくここで適宜されたWM_KEYDOWNに対応するOnKeyEventに入る https://source.chromium.org/chromium/chromium/src/+/main:ui/platform_window/win/win_window.h;l=80;drc=2e87e014afa5464f87909bfa795ab10834459ed7 KeyEventに変換されDispatchEventされる。 https://source.chromium.org/chromium/chromium/src/+/main:ui/platform_window/win/win_window.cc;l=270;drc=2e87e014afa5464f87909bfa795ab10834459ed7 ここからauraのコードに入る WindowTreeHostPlatformの実装(ui::PlatformWindowDelegateの実装,WindowTreeHostの実装,後で重要になるui::EventSourceの実装) https://source.chromium.org/chromium/chromium/src/+/main:ui/aura/window_tree_host_platform.h;l=28;drc=2e87e014afa5464f87909bfa795ab10834459ed7 WindowTreeHostPlatform::DispatchEventでSendEventToSinkが呼ばれる https://source.chromium.org/chromium/chromium/src/+/main:ui/aura/window_tree_host_platform.cc;l=295;drc=2e87e014afa5464f87909bfa795ab10834459ed7 EventSourece::SendEventToSink https://source.chromium.org/chromium/chromium/src/+/main:ui/events/event_source.cc;drc=2e87e014afa5464f87909bfa795ab10834459ed7;l=112 最終的にEventSkin::OnEventFromSource\nEventDispatchDetails EventSource::DeliverEventToSink(Event* event) { EventSink* sink = GetEventSink(); CHECK(sink); return sink-\u0026gt;OnEventFromSource(event); } EventSinkはここからくると思われる https://source.chromium.org/chromium/chromium/src/+/main:ui/views/widget/widget.cc;l=2328;drc=2e87e014afa5464f87909bfa795ab10834459ed7 ui::EventSink* Widget::GetEventSink() { return root_view_.get(); } root_view_の実装はinternal::RootViewでそれから派生したBrowserRootView https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/ui/views/frame/browser_root_view.h;l=30;drc=2e87e014afa5464f87909bfa795ab10834459ed7;bpv=1;bpt=1 RootViewはViewから派生。Viewはui::EventProcessorから派生。EventProcessorがEventSinkから派生している。 EventProcessorで実際の処理のようだ。 https://source.chromium.org/chromium/chromium/src/+/main:ui/events/event_processor.cc;l=16;drc=2e87e014afa5464f87909bfa795ab10834459ed7 EventDispatcherDelegate::DispatchEvent https://source.chromium.org/chromium/chromium/src/+/main:ui/events/event_dispatcher.cc;drc=2e87e014afa5464f87909bfa795ab10834459ed7;l=78 EventDispatcherDelegate::DispatchEventToTarget https://source.chromium.org/chromium/chromium/src/+/main:ui/events/event_dispatcher.cc;l=78;drc=2e87e014afa5464f87909bfa795ab10834459ed7 void EventDispatcher::ProcessEvent(EventTarget* target, Event* event) https://source.chromium.org/chromium/chromium/src/+/main:ui/events/event_dispatcher.cc;drc=2e87e014afa5464f87909bfa795ab10834459ed7;l=113 void EventDispatcher::DispatchEvent(EventHandler* handler, Event* event) https://source.chromium.org/chromium/chromium/src/+/main:ui/events/event_dispatcher.cc;drc=2e87e014afa5464f87909bfa795ab10834459ed7;l=180 void EventHandler::OnEvent(Event* event) https://source.chromium.org/chromium/chromium/src/+/main:ui/events/event_handler.cc;drc=2e87e014afa5464f87909bfa795ab10834459ed7;l=26 OmniboxViewViewsはview::TextFieldを継承しており https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/ui/views/omnibox/omnibox_view_views.h;l=60?q=OmniboxViewViews\u0026ss=chromium%2Fchromium%2Fsrc view::TextFieldはview::Viewを継承しており https://source.chromium.org/chromium/chromium/src/+/main:ui/views/controls/textfield/textfield.h;l=72;drc=2e87e014afa5464f87909bfa795ab10834459ed7 view::Viewはui::EventHandlerを継承している。 https://source.chromium.org/chromium/chromium/src/+/main:ui/views/view.h;drc=2e87e014afa5464f87909bfa795ab10834459ed7;l=312 void View::OnKeyEvent(ui::KeyEvent* event)でOnKeyPressedまたはOnKeyReleasedに変換されている https://source.chromium.org/chromium/chromium/src/+/main:ui/views/view.cc;l=1640;drc=2e87e014afa5464f87909bfa795ab10834459ed7 まずbool Textfield::OnKeyPressed(const ui::KeyEvent\u0026amp; event)に入る https://source.chromium.org/chromium/chromium/src/+/main:ui/views/controls/textfield/textfield.cc;l=788;drc=467a8e68f685f9cfa47ee3fbfca20c22f7f6e893;bpv=1;bpt=1 通常のキー入力の場合はExecuteTextEditCommandに入る。 void OmniboxViewViews::ExecuteTextEditCommand(ui::TextEditCommand command) https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/ui/views/omnibox/omnibox_view_views.cc;l=1585;drc=2e87e014afa5464f87909bfa795ab10834459ed7 void Textfield::ExecuteTextEditCommand(ui::TextEditCommand command) に処理が移譲 https://source.chromium.org/chromium/chromium/src/+/main:ui/views/controls/textfield/textfield.cc;l=2191;drc=467a8e68f685f9cfa47ee3fbfca20c22f7f6e893 EnterKeyの場合はoverrideされた bool OmniboxViewViews::HandleKeyEvent(views::Textfield* textfield,const ui::KeyEvent\u0026amp; event) でハンドリングされる。 https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/ui/views/omnibox/omnibox_view_views.cc;l=1658;drc=2e87e014afa5464f87909bfa795ab10834459ed7 case ui::VKEY_RETURN: { WindowOpenDisposition disposition = WindowOpenDisposition::CURRENT_TAB; if ((alt \u0026amp;\u0026amp; !shift) || (shift \u0026amp;\u0026amp; command)) { disposition = WindowOpenDisposition::NEW_FOREGROUND_TAB; } else if (alt || command) { disposition = WindowOpenDisposition::NEW_BACKGROUND_TAB; } else if (shift) { disposition = WindowOpenDisposition::NEW_WINDOW; } // According to unit tests and comments, holding control when pressing // enter has special behavior handled by `AcceptInput` so in this case // the user is selecting their input (possibly with modification like // appending \u0026#34;.com\u0026#34;) and not the row match. This is indicated with an // explicit `kNoMatch` line selection. if (model()-\u0026gt;PopupIsOpen() \u0026amp;\u0026amp; !control) { model()-\u0026gt;OpenSelection(model()-\u0026gt;GetPopupSelection(), event.time_stamp(), disposition); } else { model()-\u0026gt;OpenSelection( OmniboxPopupSelection(OmniboxPopupSelection::kNoMatch), event.time_stamp(), disposition); } return true; } ここでOpenSelectionが呼ばれる。 https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/ui/views/omnibox/omnibox_view_views.cc;l=1687;drc=2e87e014afa5464f87909bfa795ab10834459ed7 void OmniboxEditModel::OpenSelection(OmniboxPopupSelection selection, base::TimeTicks timestamp, WindowOpenDisposition disposition)内でAcceptInput呼び出し https://source.chromium.org/chromium/chromium/src/+/main:components/omnibox/browser/omnibox_edit_model.cc;l=937;drc=2e87e014afa5464f87909bfa795ab10834459ed7 void OmniboxEditModel::AcceptInput(WindowOpenDisposition disposition, base::TimeTicks match_selection_timestamp) https://source.chromium.org/chromium/chromium/src/+/main:components/omnibox/browser/omnibox_edit_model.cc;l=2506;drc=2e87e014afa5464f87909bfa795ab10834459ed7;bpv=1;bpt=1?q=AcceptInput\u0026sq=\u0026ss=chromium%2Fchromium%2Fsrc omniboxのisURLの判定 https://source.chromium.org/chromium/chromium/src/+/main:components/omnibox/browser/omnibox_edit_model.cc;drc=2e87e014afa5464f87909bfa795ab10834459ed7;bpv=0;l=571 2. https://source.chromium.org/chromium/chromium/src/+/main:components/omnibox/browser/autocomplete_match_type.h;drc=2e87e014afa5464f87909bfa795ab10834459ed7;bpv=0;l=12 void OmniboxEditModel::OpenMatch(OmniboxPopupSelection selection, AutocompleteMatch match, WindowOpenDisposition disposition, const GURL\u0026amp; alternate_nav_url, const std::u16string\u0026amp; pasted_text, base::TimeTicks match_selection_timestamp) { // If the user is executing an action, this will be non-null and some match // opening and metrics behavior will be adjusted accordingly. OmniboxAction* action = nullptr; if (selection.state == OmniboxPopupSelection::NORMAL \u0026amp;\u0026amp; match.takeover_action) { DCHECK(match_selection_timestamp != base::TimeTicks()); action = match.takeover_action.get(); } else if (selection.IsAction()) { DCHECK_LT(selection.action_index, match.actions.size()); action = match.actions[selection.action_index].get(); } // Invalid URLs such as chrome://history can end up here, but that\u0026#39;s okay // if the user is executing an action instead of navigating to the URL. if (!match.destination_url.is_valid() \u0026amp;\u0026amp; !action) { return; } // NULL_RESULT_MESSAGE matches are informational only and cannot be acted // upon. Immediately return when attempting to open one. if (match.type == AutocompleteMatchType::NULL_RESULT_MESSAGE) { return; } // Switch the window disposition to SWITCH_TO_TAB for open tab matches that // originated while in keyword mode. This is to support the keyword mode // starter pack\u0026#39;s tab search (@tabs) feature, which should open all // suggestions in the existing open tab. bool is_open_tab_match = match.from_keyword \u0026amp;\u0026amp; match.provider-\u0026gt;type() == AutocompleteProvider::TYPE_OPEN_TAB; // Also switch the window disposition for tab switch actions. The action // itself will already open with SWITCH_TO_TAB disposition, but the change // is needed earlier for metrics. bool is_tab_switch_action = action \u0026amp;\u0026amp; action-\u0026gt;ActionId() == OmniboxActionId::TAB_SWITCH; if (is_open_tab_match || is_tab_switch_action) { disposition = WindowOpenDisposition::SWITCH_TO_TAB; } TRACE_EVENT(\u0026#34;omnibox\u0026#34;, \u0026#34;OmniboxEditModel::OpenMatch\u0026#34;, \u0026#34;match\u0026#34;, match, \u0026#34;disposition\u0026#34;, disposition, \u0026#34;altenate_nav_url\u0026#34;, alternate_nav_url, \u0026#34;pasted_text\u0026#34;, pasted_text); const base::TimeTicks\u0026amp; now(base::TimeTicks::Now()); base::TimeDelta elapsed_time_since_user_first_modified_omnibox( now - time_user_first_modified_omnibox_); autocomplete_controller() -\u0026gt;UpdateMatchDestinationURLWithAdditionalSearchboxStats( elapsed_time_since_user_first_modified_omnibox, \u0026amp;match); GURL destination_url = action ? action-\u0026gt;getUrl() : match.destination_url; // Save the result of the interaction, but do not record the histogram yet. focus_resulted_in_navigation_ = true; RecordActionShownForAllActions(autocomplete_controller()-\u0026gt;result(), selection); HistoryFuzzyProvider::RecordOpenMatchMetrics( autocomplete_controller()-\u0026gt;result(), match); std::u16string input_text(pasted_text); if (input_text.empty()) { input_text = user_input_in_progress_ ? user_text_ : url_for_editing_; } // Create a dummy AutocompleteInput for use in calling VerbatimMatchForInput() // to create an alternate navigational match. AutocompleteInput alternate_input( input_text, GetPageClassification(), controller_-\u0026gt;client()-\u0026gt;GetSchemeClassifier(), controller_-\u0026gt;client()-\u0026gt;ShouldDefaultTypedNavigationsToHttps(), 0, false); // Somehow we can occasionally get here with no active tab. It\u0026#39;s not // clear why this happens. alternate_input.set_current_url(controller_-\u0026gt;client()-\u0026gt;GetURL()); alternate_input.set_current_title(controller_-\u0026gt;client()-\u0026gt;GetTitle()); base::TimeDelta elapsed_time_since_last_change_to_default_match( now - autocomplete_controller()-\u0026gt;last_time_default_match_changed()); DCHECK(match.provider); // These elapsed times don\u0026#39;t really make sense for matches that come from // omnibox focus (because the user did not modify the omnibox), so for those // we set the elapsed times to something that will be ignored by // metrics_log.cc. They also don\u0026#39;t necessarily make sense if the omnibox // dropdown is closed or the user used paste-and-go. (In most // cases when this happens, the user never modified the omnibox.) const bool popup_open = PopupIsOpen(); const base::TimeDelta default_time_delta = base::Milliseconds(-1); if (input_.IsZeroSuggest() || !pasted_text.empty()) { elapsed_time_since_user_first_modified_omnibox = default_time_delta; elapsed_time_since_last_change_to_default_match = default_time_delta; } base::TimeDelta elapsed_time_since_user_focused_omnibox = default_time_delta; if (!last_omnibox_focus_.is_null()) { elapsed_time_since_user_focused_omnibox = now - last_omnibox_focus_; // Only record focus to open time when a focus actually happened (as // opposed to, say, dragging a link onto the omnibox). LogFocusToOpenTime(elapsed_time_since_user_focused_omnibox, input_.IsZeroSuggest(), GetPageClassification(), match, selection.IsAction() ? selection.action_index : -1); } // In some unusual cases, we ignore autocomplete_controller()-\u0026gt;result() and // instead log a fake result set with a single element (|match|) and // selected_index of 0. For these cases: // 1. If the popup is closed (there is no result set). This doesn\u0026#39;t apply // for WebUI searchboxes since they don\u0026#39;t have an associated popup. // 2. If the index is out of bounds. This should only happen if // |selection.line| is // kNoMatch, which can happen if the default search provider is disabled. // 3. If this is paste-and-go (meaning the contents of the dropdown // are ignored regardless). const bool dropdown_ignored = (!popup_open \u0026amp;\u0026amp; !omnibox::IsWebUISearchbox(GetPageClassification())) || selection.line \u0026gt;= autocomplete_controller()-\u0026gt;result().size() || !pasted_text.empty(); ACMatches fake_single_entry_matches; fake_single_entry_matches.push_back(match); AutocompleteResult fake_single_entry_result; fake_single_entry_result.AppendMatches(fake_single_entry_matches); std::u16string user_text = input_.IsZeroSuggest() ? std::u16string() : input_text; size_t completed_length = match.allowed_to_be_default_match ? match.inline_autocompletion.length() : std::u16string::npos; bool is_incognito = autocomplete_controller() -\u0026gt;autocomplete_provider_client() -\u0026gt;IsOffTheRecord(); OmniboxLog log( user_text, just_deleted_text_, input_.type(), is_keyword_selected(), keyword_mode_entry_method_, popup_open, dropdown_ignored ? OmniboxPopupSelection(0) : selection, disposition, !pasted_text.empty(), SessionID::InvalidValue(), // don\u0026#39;t know tab ID; set later if appropriate GetPageClassification(), elapsed_time_since_user_first_modified_omnibox, completed_length, elapsed_time_since_last_change_to_default_match, dropdown_ignored ? fake_single_entry_result : autocomplete_controller()-\u0026gt;result(), destination_url, is_incognito, match.zero_prefix_suggestions_shown_in_session, match.zero_prefix_search_suggestions_shown_in_session, match.zero_prefix_url_suggestions_shown_in_session, match.typed_search_suggestions_shown_in_session, match.typed_url_suggestions_shown_in_session); // Check disabled on iOS as the platform shows a default suggestion on focus // (crbug.com/40061502). #if !BUILDFLAG(IS_IOS) DCHECK(dropdown_ignored || (log.elapsed_time_since_user_first_modified_omnibox \u0026gt;= log.elapsed_time_since_last_change_to_default_match)) \u0026lt;\u0026lt; \u0026#34;We should\u0026#39;ve got the notification that the user modified the \u0026#34; \u0026lt;\u0026lt; \u0026#34;omnibox text at same time or before the most recent time the \u0026#34; \u0026lt;\u0026lt; \u0026#34;default match changed.\u0026#34;; #endif log.elapsed_time_since_user_focused_omnibox = elapsed_time_since_user_focused_omnibox; log.ukm_source_id = controller_-\u0026gt;client()-\u0026gt;GetUKMSourceId(); if ((disposition == WindowOpenDisposition::CURRENT_TAB) \u0026amp;\u0026amp; controller_-\u0026gt;client()-\u0026gt;CurrentPageExists()) { // If we know the destination is being opened in the current tab, // we can easily get the tab ID. (If it\u0026#39;s being opened in a new // tab, we don\u0026#39;t know the tab ID yet.) log.tab_id = controller_-\u0026gt;client()-\u0026gt;GetSessionID(); } autocomplete_controller()-\u0026gt;AddProviderAndTriggeringLogs(\u0026amp;log); base::UmaHistogramEnumeration(\u0026#34;Omnibox.SuggestionUsed.RichAutocompletion\u0026#34;, match.rich_autocompletion_triggered); size_t ipv4_parts_count = CountNumberOfIPv4Parts(user_text, destination_url, completed_length); // The histogram is collected to decide if shortened IPv4 addresses // like 127.1 should be deprecated. // Only valid IP addresses manually inputted by the user will be counted. if (ipv4_parts_count \u0026gt; 0) { base::UmaHistogramCounts100(\u0026#34;Omnibox.IPv4AddressPartsCount\u0026#34;, ipv4_parts_count); } controller_-\u0026gt;client()-\u0026gt;OnURLOpenedFromOmnibox(\u0026amp;log); OmniboxEventGlobalTracker::GetInstance()-\u0026gt;OnURLOpened(\u0026amp;log); LOCAL_HISTOGRAM_BOOLEAN(\u0026#34;Omnibox.EventCount\u0026#34;, true); omnibox::answer_data_parser::LogAnswerUsed(match.answer_type); TemplateURLService* service = controller_-\u0026gt;client()-\u0026gt;GetTemplateURLService(); TemplateURL* template_url = match.GetTemplateURL(service, false); if (template_url) { // |match| is a Search navigation or a URL navigation in keyword mode; log // search engine usage metrics. AutocompleteMatch::LogSearchEngineUsed(match, service); if (ui::PageTransitionTypeIncludingQualifiersIs( match.transition, ui::PAGE_TRANSITION_KEYWORD) || match.provider-\u0026gt;type() == AutocompleteProvider::TYPE_UNSCOPED_EXTENSION) { // User is in keyword mode or accepted an unscoped extension suggestion, // increment usage count for the keyword. base::RecordAction(base::UserMetricsAction(\u0026#34;AcceptedKeyword\u0026#34;)); EmitAcceptedKeywordSuggestionHistogram(keyword_mode_entry_method_, template_url); controller_-\u0026gt;client()-\u0026gt;GetTemplateURLService()-\u0026gt;IncrementUsageCount( template_url); // Notify the extension of the selected input, but ignore if the selection // corresponds to an action created by an extension in unscoped mode. if (template_url-\u0026gt;type() == TemplateURL::OMNIBOX_API_EXTENSION \u0026amp;\u0026amp; !action) { controller_-\u0026gt;client()-\u0026gt;ProcessExtensionMatch(input_text, template_url, match, disposition); if (disposition != WindowOpenDisposition::NEW_BACKGROUND_TAB \u0026amp;\u0026amp; view_) { base::AutoReset\u0026lt;bool\u0026gt; tmp(\u0026amp;in_revert_, true); view_-\u0026gt;RevertAll(); } // Avoid calling `OmniboxClient::OnAutocompleteAccept()`. The extension // was notfied of the accepted input and will handle the navigation. return; } } else { DCHECK(ui::PageTransitionTypeIncludingQualifiersIs( match.transition, ui::PAGE_TRANSITION_GENERATED) || ui::PageTransitionTypeIncludingQualifiersIs( match.transition, ui::PAGE_TRANSITION_RELOAD)); // NOTE: We purposefully don\u0026#39;t increment the usage count of the default // search engine here like we do for explicit keywords above; see comments // in template_url.h. } } else { // |match| is a URL navigation, not a search. // For logging the below histogram, only record uses that depend on the // omnibox suggestion system, i.e., TYPED navigations. That is, exclude // omnibox URL interactions that are treated as reloads or link-following // (i.e., cut-and-paste of URLs) or paste-and-go. if (ui::PageTransitionTypeIncludingQualifiersIs( match.transition, ui::PAGE_TRANSITION_TYPED) \u0026amp;\u0026amp; pasted_text.empty()) { navigation_metrics::RecordOmniboxURLNavigation(destination_url); } // The following histograms should be recorded for both TYPED and pasted // URLs, but should still exclude reloads. if (ui::PageTransitionTypeIncludingQualifiersIs( match.transition, ui::PAGE_TRANSITION_TYPED) || ui::PageTransitionTypeIncludingQualifiersIs(match.transition, ui::PAGE_TRANSITION_LINK)) { net::cookie_util::RecordCookiePortOmniboxHistograms(destination_url); } } if (action) { OmniboxAction::ExecutionContext context( *(autocomplete_controller()-\u0026gt;autocomplete_provider_client()), base::BindOnce(\u0026amp;OmniboxClient::OnAutocompleteAccept, controller_-\u0026gt;client()-\u0026gt;AsWeakPtr()), match_selection_timestamp, disposition); action-\u0026gt;Execute(context); // Actions aren\u0026#39;t generally able to change omnibox state, but it may be // worth considering an extension to OmniboxAction::ExecutionContext // if more action types want to enter keyword modes, close the popup, etc. // Note: The CONTEXTUAL_SEARCH_OPEN_LENS action does not enter the omnibox // into \u0026#39;@page\u0026#39; keyword mode and the omnibox is committed and closed as // normal in that case, with the lens UI managing its own state, so there\u0026#39;s // no need for the omnibox to close lens. if (action-\u0026gt;ActionId() == OmniboxActionId::CONTEXTUAL_SEARCH_ASK_ABOUT_PAGE || action-\u0026gt;ActionId() == OmniboxActionId::CONTEXTUAL_SEARCH_SELECT_REGION) { if (const TemplateURL* page_turl = controller_-\u0026gt;client() -\u0026gt;GetTemplateURLService() -\u0026gt;FindStarterPackTemplateURL( TemplateURLStarterPackData::kPage)) { EnterKeywordMode( OmniboxEventProto::SELECT_SUGGESTION, page_turl, l10n_util::GetStringUTF16(IDS_OMNIBOX_PAGE_SCOPE_PLACEHOLDER_TEXT)); if (action-\u0026gt;ActionId() == OmniboxActionId::CONTEXTUAL_SEARCH_SELECT_REGION \u0026amp;\u0026amp; view_) { view_-\u0026gt;CloseOmniboxPopup(); } close_lens_ = true; return; } } else if (action-\u0026gt;ActionId() == OmniboxActionId::CONTEXTUAL_SEARCH_FULFILLMENT) { // Lens fulfills matches in the side panel, and should not be closed // by the omnibox after opening this match. close_lens_ = false; } } if (disposition != WindowOpenDisposition::NEW_BACKGROUND_TAB \u0026amp;\u0026amp; view_) { base::AutoReset\u0026lt;bool\u0026gt; tmp(\u0026amp;in_revert_, true); view_-\u0026gt;RevertAll(); // Revert the box to its unedited state. } if (!action) { // Track whether the destination URL sends us to a search results page // using the default search provider. TemplateURLService* template_url_service = controller_-\u0026gt;client()-\u0026gt;GetTemplateURLService(); if (template_url_service \u0026amp;\u0026amp; template_url_service-\u0026gt;IsSearchResultsPageFromDefaultSearchProvider( match.destination_url)) { base::RecordAction( base::UserMetricsAction(\u0026#34;OmniboxDestinationURLIsSearchOnDSP\u0026#34;)); base::UmaHistogramBoolean(\u0026#34;Omnibox.Search.OffTheRecord\u0026#34;, is_incognito); } BookmarkModel* bookmark_model = controller_-\u0026gt;client()-\u0026gt;GetBookmarkModel(); if (bookmark_model \u0026amp;\u0026amp; bookmark_model-\u0026gt;IsBookmarked(destination_url)) { controller_-\u0026gt;client()-\u0026gt;OnBookmarkLaunched(); } // This block should be the last call in OpenMatch, because controller_ is // not guaranteed to exist after client()-\u0026gt;OnAutocompleteAccept is called. if (destination_url.is_valid()) { // This calls RevertAll again. base::AutoReset\u0026lt;bool\u0026gt; tmp(\u0026amp;in_revert_, true); controller_-\u0026gt;client()-\u0026gt;OnAutocompleteAccept( destination_url, match.post_content.get(), disposition, ui::PageTransitionFromInt(match.transition | ui::PAGE_TRANSITION_FROM_ADDRESS_BAR), match.type, match_selection_timestamp, input_.added_default_scheme_to_typed_url(), input_.typed_url_had_http_scheme() \u0026amp;\u0026amp; match.type == AutocompleteMatchType::URL_WHAT_YOU_TYPED, input_text, match, VerbatimMatchForInput( autocomplete_controller()-\u0026gt;history_url_provider(), autocomplete_controller()-\u0026gt;autocomplete_provider_client(), alternate_input, alternate_nav_url, false)); } } } https://source.chromium.org/chromium/chromium/src/+/main:components/omnibox/browser/omnibox_edit_model.cc;drc=467a8e68f685f9cfa47ee3fbfca20c22f7f6e893;l=2588 void OmniboxAction::Execute(OmniboxAction::ExecutionContext\u0026amp; context) https://source.chromium.org/chromium/chromium/src/+/main:components/omnibox/browser/actions/omnibox_action.cc;drc=467a8e68f685f9cfa47ee3fbfca20c22f7f6e893;l=96 void OmniboxAction::OpenURL(OmniboxAction::ExecutionContext\u0026amp; context, const GURL\u0026amp; url) const { // Set `match_type` as if the user just typed |url| verbatim. // `destination_url_entered_without_scheme` is used to determine whether // navigations typed without a scheme and upgraded to HTTPS should fall back // to HTTP. The URL might have been entered without a scheme, but Action // destination URLs don\u0026#39;t need a fallback so it\u0026#39;s fine to pass false here. std::move(context.open_url_callback_) .Run(url, nullptr, context.disposition_, ui::PAGE_TRANSITION_GENERATED, /*match_type=*/AutocompleteMatchType::URL_WHAT_YOU_TYPED, context.match_selection_timestamp_, /*destination_url_entered_without_scheme=*/false, /*destination_url_entered_with_http_scheme=*/false, u\u0026#34;\u0026#34;, AutocompleteMatch(), AutocompleteMatch()); } https://source.chromium.org/chromium/chromium/src/+/main:components/omnibox/browser/actions/omnibox_action.cc;drc=467a8e68f685f9cfa47ee3fbfca20c22f7f6e893;l=136 context.open_url_callback_の実態は以下を継承したもの\nvirtual void OnAutocompleteAccept( const GURL\u0026amp; destination_url, TemplateURLRef::PostContent* post_content, WindowOpenDisposition disposition, ui::PageTransition transition, AutocompleteMatchType::Type match_type, base::TimeTicks match_selection_timestamp, bool destination_url_entered_without_scheme, bool destination_url_entered_with_http_scheme, const std::u16string\u0026amp; text, const AutocompleteMatch\u0026amp; match, const AutocompleteMatch\u0026amp; alternative_nav_match) = 0; https://source.chromium.org/chromium/chromium/src/+/main:components/omnibox/browser/omnibox_client.h;drc=467a8e68f685f9cfa47ee3fbfca20c22f7f6e893;l=257 実態はおそらくこれ\nvoid ChromeOmniboxClient::OnAutocompleteAccept( const GURL\u0026amp; destination_url, TemplateURLRef::PostContent* post_content, WindowOpenDisposition disposition, ui::PageTransition transition, AutocompleteMatchType::Type match_type, base::TimeTicks match_selection_timestamp, bool destination_url_entered_without_scheme, bool destination_url_entered_with_http_scheme, const std::u16string\u0026amp; text, const AutocompleteMatch\u0026amp; match, const AutocompleteMatch\u0026amp; alternative_nav_match) { TRACE_EVENT(\u0026#34;omnibox\u0026#34;, \u0026#34;ChromeOmniboxClient::OnAutocompleteAccept\u0026#34;, \u0026#34;text\u0026#34;, text, \u0026#34;match\u0026#34;, match, \u0026#34;alternative_nav_match\u0026#34;, alternative_nav_match); // Store the details necessary to open the omnibox match via browser commands. location_bar_-\u0026gt;set_navigation_params(LocationBar::NavigationParams( destination_url, disposition, transition, match_selection_timestamp, destination_url_entered_without_scheme, destination_url_entered_with_http_scheme, match.extra_headers)); if (browser_) { auto navigation = chrome::OpenCurrentURL(browser_); ChromeOmniboxNavigationObserver::Create(navigation.get(), profile_, text, match, alternative_nav_match); } #if BUILDFLAG(ENABLE_EXTENSIONS) extensions::MaybeShowExtensionControlledSearchNotification( location_bar_-\u0026gt;GetWebContents(), match_type); #endif } https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/ui/omnibox/chrome_omnibox_client.cc;l=725;bpv=1;bpt=1?q=OmniboxClient::OnAutocompleteAccept\u0026ss=chromium%2Fchromium%2Fsrc base::WeakPtr\u0026lt;content::NavigationHandle\u0026gt; OpenCurrentURL(Browser* browser) { base::RecordAction(UserMetricsAction(\u0026#34;LoadURL\u0026#34;)); // TODO(crbug.com/40820294): Eliminate extra checks once source of // bad pointer dereference is identified. See also TODO comment below. CHECK(browser); BrowserWindow* window = browser-\u0026gt;window(); CHECK(window); LocationBar* location_bar = window-\u0026gt;GetLocationBar(); if (!location_bar) { return nullptr; } GURL url(location_bar-\u0026gt;navigation_params().destination_url); TRACE_EVENT1(\u0026#34;navigation\u0026#34;, \u0026#34;chrome::OpenCurrentURL\u0026#34;, \u0026#34;url\u0026#34;, url); if (ShouldInterceptChromeURLNavigationInIncognito(browser, url)) { ProcessInterceptedChromeURLNavigationInIncognito(browser, url); return nullptr; } NavigateParams params(browser, url, location_bar-\u0026gt;navigation_params().transition); params.disposition = location_bar-\u0026gt;navigation_params().disposition; // Use ADD_INHERIT_OPENER so that all pages opened by the omnibox at least // inherit the opener. In some cases the tabstrip will determine the group // should be inherited, in which case the group is inherited instead of the // opener. params.tabstrip_add_types = AddTabTypes::ADD_FORCE_INDEX | AddTabTypes::ADD_INHERIT_OPENER; params.input_start = location_bar-\u0026gt;navigation_params().match_selection_timestamp; params.is_using_https_as_default_scheme = location_bar-\u0026gt;navigation_params().url_typed_without_scheme; params.url_typed_with_http_scheme = location_bar-\u0026gt;navigation_params().url_typed_with_http_scheme; params.extra_headers = location_bar-\u0026gt;navigation_params().extra_headers; auto result = Navigate(\u0026amp;params); #if BUILDFLAG(ENABLE_EXTENSIONS) DCHECK(extensions::ExtensionSystem::Get(browser-\u0026gt;profile()) -\u0026gt;extension_service()); // TODO(crbug.com/40820294): Eliminate extra checks once source of // bad pointer dereference is identified. See also TODO comment above. extensions::ExtensionRegistry* extension_registry = extensions::ExtensionRegistry::Get(browser-\u0026gt;profile()); CHECK(extension_registry); const extensions::Extension* extension = extension_registry-\u0026gt;enabled_extensions().GetAppByURL(url); if (extension) { extensions::RecordAppLaunchType(extension_misc::APP_LAUNCH_OMNIBOX_LOCATION, extension-\u0026gt;GetType()); } #endif return result; } https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/ui/browser_commands.cc;drc=467a8e68f685f9cfa47ee3fbfca20c22f7f6e893;bpv=1;bpt=1;l=842 そしてNavigate関数が呼ばれる。\nbase::WeakPtr\u0026lt;content::NavigationHandle\u0026gt; Navigate(NavigateParams* params) { TRACE_EVENT1(\u0026#34;navigation\u0026#34;, \u0026#34;chrome::Navigate\u0026#34;, \u0026#34;disposition\u0026#34;, params-\u0026gt;disposition); Browser* source_browser = params-\u0026gt;browser; if (source_browser) { params-\u0026gt;initiating_profile = source_browser-\u0026gt;profile(); } DCHECK(params-\u0026gt;initiating_profile); // If the created window is a partitioned popin, a valid source exists, and // the disposition is NEW_POPUP then the resulting popup should be tab-modal. // See: https://explainers-by-googlers.github.io/partitioned-popins/ params-\u0026gt;is_tab_modal_popup_deprecated |= params-\u0026gt;window_features.is_partitioned_popin \u0026amp;\u0026amp; params-\u0026gt;source_contents \u0026amp;\u0026amp; params-\u0026gt;disposition == WindowOpenDisposition::NEW_POPUP; #if BUILDFLAG(IS_CHROMEOS) if (params-\u0026gt;initiating_profile-\u0026gt;IsOffTheRecord() \u0026amp;\u0026amp; params-\u0026gt;initiating_profile-\u0026gt;GetOTRProfileID().IsCaptivePortal() \u0026amp;\u0026amp; params-\u0026gt;disposition != WindowOpenDisposition::NEW_POPUP \u0026amp;\u0026amp; params-\u0026gt;disposition != WindowOpenDisposition::CURRENT_TAB \u0026amp;\u0026amp; !IncognitoModeForced(params-\u0026gt;initiating_profile)) { // Navigation outside of the current tab or the initial popup window from a // captive portal signin window should be prevented. params-\u0026gt;disposition = WindowOpenDisposition::CURRENT_TAB; } #endif if (params-\u0026gt;initiating_profile-\u0026gt;ShutdownStarted()) { // Don\u0026#39;t navigate when the profile is shutting down. return nullptr; } // Block navigation requests when in locked fullscreen mode. We allow // navigation requests in the webapp when locked for OnTask (only relevant for // non-web browser scenarios). // TODO(b/365146870): Remove once we consolidate locked fullscreen with // OnTask. if (source_browser) { bool should_block_navigation = platform_util::IsBrowserLockedFullscreen(source_browser); #if BUILDFLAG(IS_CHROMEOS) if (source_browser-\u0026gt;IsLockedForOnTask()) { should_block_navigation = false; } #endif // BUILDFLAG(IS_CHROMEOS) if (should_block_navigation) { return nullptr; } } // Open System Apps in their standalone window if necessary. // TODO(crbug.com/40136163): Remove this code after we integrate with intent // handling. #if BUILDFLAG(IS_CHROMEOS) const std::optional\u0026lt;ash::SystemWebAppType\u0026gt; capturing_system_app_type = ash::GetCapturingSystemAppForURL(params-\u0026gt;initiating_profile, params-\u0026gt;url); if (capturing_system_app_type \u0026amp;\u0026amp; (!params-\u0026gt;browser || !ash::IsBrowserForSystemWebApp(params-\u0026gt;browser, capturing_system_app_type.value()))) { ash::SystemAppLaunchParams swa_params; swa_params.url = params-\u0026gt;url; ash::LaunchSystemWebAppAsync(params-\u0026gt;initiating_profile, capturing_system_app_type.value(), swa_params); // It\u0026#39;s okay to early return here, because LaunchSystemWebAppAsync uses a // different logic to choose (and create if necessary) a browser window for // system apps. // // It\u0026#39;s okay to skip the checks and cleanups below. The link captured system // app will either open in its own browser window, or navigate an existing // browser window exclusively used by this app. For the initiating browser, // the navigation should appear to be cancelled. return nullptr; } #endif // BUILDFLAG(IS_CHROMEOS) #if !BUILDFLAG(IS_ANDROID) // Force isolated PWAs to open in an app window. params-\u0026gt;force_open_pwa_window = content::SiteIsolationPolicy::ShouldUrlUseApplicationIsolationLevel( params-\u0026gt;initiating_profile, params-\u0026gt;url); params-\u0026gt;open_pwa_window_if_possible |= params-\u0026gt;force_open_pwa_window; #endif if (!AdjustNavigateParamsForURL(params)) { return nullptr; } // Picture-in-picture browser windows must have a source contents in order for // the window to function correctly. If we have no source contents to work // with (e.g. if an extension popup attempts to open a PiP window), we should // cancel the navigation. The source URL must also be of a type that\u0026#39;s // allowed to open document PiP. See `PictureInPictureWindowManager` for // details on what\u0026#39;s allowed. if (params-\u0026gt;disposition == WindowOpenDisposition::NEW_PICTURE_IN_PICTURE) { const GURL\u0026amp; url = params-\u0026gt;source_contents ? params-\u0026gt;source_contents-\u0026gt;GetLastCommittedURL() : GURL(); if (!PictureInPictureWindowManager::IsSupportedForDocumentPictureInPicture( url)) { return nullptr; } } // If no source WebContents was specified, we use the selected one from the // target browser. This must happen before GetBrowserAndTabForDisposition() // has a chance to replace |params-\u0026gt;browser| with another one, but after the // above check that relies on the original source_contents value. if (!params-\u0026gt;source_contents \u0026amp;\u0026amp; params-\u0026gt;browser) { params-\u0026gt;source_contents = params-\u0026gt;browser-\u0026gt;tab_strip_model()-\u0026gt;GetActiveWebContents(); } WebContents* contents_to_navigate_or_insert = params-\u0026gt;contents_to_insert.get(); if (params-\u0026gt;switch_to_singleton_tab) { DCHECK_EQ(params-\u0026gt;disposition, WindowOpenDisposition::SINGLETON_TAB); contents_to_navigate_or_insert = params-\u0026gt;switch_to_singleton_tab; } #if !BUILDFLAG(IS_ANDROID) // If this is a Picture in Picture window, then notify the pip manager about // it. This enables the opener and pip window to stay connected, so that (for // example), the pip window does not outlive the opener. // // We do this before creating the browser window, so that the browser can talk // to the PictureInPictureWindowManager. Otherwise, the manager has no idea // that there\u0026#39;s a pip window. if (params-\u0026gt;disposition == WindowOpenDisposition::NEW_PICTURE_IN_PICTURE) { // Picture in picture windows may not be opened by other picture in // picture windows, or without an opener. if (!params-\u0026gt;browser || params-\u0026gt;browser-\u0026gt;is_type_picture_in_picture()) { params-\u0026gt;browser = nullptr; return nullptr; } PictureInPictureWindowManager::GetInstance()-\u0026gt;EnterDocumentPictureInPicture( params-\u0026gt;source_contents, contents_to_navigate_or_insert); } #endif // !BUILDFLAG(IS_ANDROID) // TODO(crbug.com/364657540): Revisit integration with web_application system // later if needed. int singleton_index; #if !BUILDFLAG(IS_ANDROID) std::unique_ptr\u0026lt;web_app::NavigationCapturingProcess\u0026gt; app_navigation = web_app::NavigationCapturingProcess::MaybeHandleAppNavigation(*params); std::optional\u0026lt;std::tuple\u0026lt;Browser*, int\u0026gt;\u0026gt; app_browser_tab_override; if (app_navigation) { app_browser_tab_override = app_navigation-\u0026gt;GetInitialBrowserAndTabOverrideForNavigation(*params); } std::tie(params-\u0026gt;browser, singleton_index) = app_browser_tab_override.has_value() ? *app_browser_tab_override : GetBrowserAndTabForDisposition(*params); #else // !BUILDFLAG(IS_ANDROID) std::tie(params-\u0026gt;browser, singleton_index) = GetBrowserAndTabForDisposition(*params); #endif if (!params-\u0026gt;browser) { return nullptr; } // Trying to open a background tab when in a non-tabbed app browser results in // focusing a regular browser window and opening a tab in the background // of that window. Change the disposition to NEW_FOREGROUND_TAB so that // the new tab is focused. if (source_browser \u0026amp;\u0026amp; source_browser-\u0026gt;is_type_app() \u0026amp;\u0026amp; params-\u0026gt;disposition == WindowOpenDisposition::NEW_BACKGROUND_TAB \u0026amp;\u0026amp; !(source_browser-\u0026gt;app_controller() \u0026amp;\u0026amp; source_browser-\u0026gt;app_controller()-\u0026gt;has_tab_strip())) { params-\u0026gt;disposition = WindowOpenDisposition::NEW_FOREGROUND_TAB; } if (singleton_index != -1) { contents_to_navigate_or_insert = params-\u0026gt;browser-\u0026gt;tab_strip_model()-\u0026gt;GetWebContentsAt(singleton_index); } else if (params-\u0026gt;disposition == WindowOpenDisposition::SWITCH_TO_TAB) { // The user is trying to open a tab that no longer exists. If we open a new // tab, it could leave orphaned NTPs around, but always overwriting the // current tab could could clobber state that the user was trying to // preserve. Fallback to the behavior used for singletons: overwrite the // current tab if it\u0026#39;s the NTP, otherwise open a new tab. params-\u0026gt;disposition = WindowOpenDisposition::SINGLETON_TAB; ShowSingletonTabOverwritingNTP(params); return nullptr; } if (params-\u0026gt;force_open_pwa_window) { CHECK(web_app::AppBrowserController::IsWebApp(params-\u0026gt;browser)); } #if BUILDFLAG(IS_CHROMEOS) if (source_browser \u0026amp;\u0026amp; source_browser != params-\u0026gt;browser) { // When the newly created browser was spawned by a browser which visits // another user\u0026#39;s desktop, it should be shown on the same desktop as the // originating one. (This is part of the desktop separation per profile). auto* window_manager = MultiUserWindowManagerHelper::GetWindowManager(); // Some unit tests have no client instantiated. if (window_manager) { aura::Window* src_window = source_browser-\u0026gt;window()-\u0026gt;GetNativeWindow(); aura::Window* new_window = params-\u0026gt;browser-\u0026gt;window()-\u0026gt;GetNativeWindow(); const AccountId\u0026amp; src_account_id = window_manager-\u0026gt;GetUserPresentingWindow(src_window); if (src_account_id != window_manager-\u0026gt;GetUserPresentingWindow(new_window)) { // Once the window gets presented, it should be shown on the same // desktop as the desktop of the creating browser. Note that this // command will not show the window if it wasn\u0026#39;t shown yet by the // browser creation. window_manager-\u0026gt;ShowWindowForUser(new_window, src_account_id); } } } #endif // Navigate() must not return early after this point. if (GetSourceProfile(params) != params-\u0026gt;browser-\u0026gt;profile()) { // A tab is being opened from a link from a different profile, we must reset // source information that may cause state to be shared. params-\u0026gt;opener = nullptr; params-\u0026gt;source_contents = nullptr; params-\u0026gt;source_site_instance = nullptr; params-\u0026gt;referrer = content::Referrer(); } // Make sure the Browser is shown if params call for it. ScopedBrowserShower shower(params, \u0026amp;contents_to_navigate_or_insert); if (params-\u0026gt;is_tab_modal_popup_deprecated) { shower.set_source_contents(params-\u0026gt;source_contents); } // Some dispositions need coercion to base types. NormalizeDisposition(params); // If a new window has been created, it needs to be shown. if (params-\u0026gt;window_action == NavigateParams::NO_ACTION \u0026amp;\u0026amp; source_browser != params-\u0026gt;browser \u0026amp;\u0026amp; params-\u0026gt;browser-\u0026gt;tab_strip_model()-\u0026gt;empty()) { params-\u0026gt;window_action = NavigateParams::SHOW_WINDOW; } // If we create a popup window from a non user-gesture, don\u0026#39;t activate it. if (params-\u0026gt;window_action == NavigateParams::SHOW_WINDOW \u0026amp;\u0026amp; params-\u0026gt;disposition == WindowOpenDisposition::NEW_POPUP \u0026amp;\u0026amp; params-\u0026gt;user_gesture == false) { params-\u0026gt;window_action = NavigateParams::SHOW_WINDOW_INACTIVE; } // Determine if the navigation was user initiated. If it was, we need to // inform the target WebContents, and we may need to update the UI. bool user_initiated = params-\u0026gt;transition \u0026amp; ui::PAGE_TRANSITION_FROM_ADDRESS_BAR || !ui::PageTransitionIsWebTriggerable(params-\u0026gt;transition); base::WeakPtr\u0026lt;content::NavigationHandle\u0026gt; navigation_handle; std::unique_ptr\u0026lt;tabs::TabModel\u0026gt; tab_to_insert; if (params-\u0026gt;contents_to_insert) { tab_to_insert = std::make_unique\u0026lt;tabs::TabModel\u0026gt;(std::move(params-\u0026gt;contents_to_insert), params-\u0026gt;browser-\u0026gt;tab_strip_model()); } // If no target WebContents was specified (and we didn\u0026#39;t seek and find a // singleton), we need to construct one if we are supposed to target a new // tab. if (!contents_to_navigate_or_insert) { DCHECK(!params-\u0026gt;url.is_empty()); if (params-\u0026gt;disposition != WindowOpenDisposition::CURRENT_TAB) { tab_to_insert = std::make_unique\u0026lt;tabs::TabModel\u0026gt;( CreateTargetContents(*params, params-\u0026gt;url), params-\u0026gt;browser-\u0026gt;tab_strip_model()); contents_to_navigate_or_insert = tab_to_insert-\u0026gt;GetContents(); apps::SetAppIdForWebContents(params-\u0026gt;browser-\u0026gt;profile(), contents_to_navigate_or_insert, params-\u0026gt;app_id); #if BUILDFLAG(ENABLE_CAPTIVE_PORTAL_DETECTION) captive_portal::CaptivePortalTabHelper::FromWebContents( contents_to_navigate_or_insert) -\u0026gt;set_window_type(params-\u0026gt;captive_portal_window_type); #endif } else { // ... otherwise if we\u0026#39;re loading in the current tab, thnave target is the // same as the source. DCHECK(params-\u0026gt;source_contents); contents_to_navigate_or_insert = params-\u0026gt;source_contents; } // Try to handle non-navigational URLs that popup dialogs and such, these // should not actually navigate. if (!HandleNonNavigationAboutURL(params-\u0026gt;url)) { // Perform the actual navigation, tracking whether it came from the // renderer. navigation_handle = LoadURLInContents(contents_to_navigate_or_insert, params-\u0026gt;url, params); } } else { // |contents_to_navigate_or_insert| was specified non-NULL, and so we assume // it has already been navigated appropriately. We need to do nothing more // other than add it to the appropriate tabstrip. } // If the user navigated from the omnibox, and the selected tab is going to // lose focus, then make sure the focus for the source tab goes away from the // omnibox. if (params-\u0026gt;source_contents \u0026amp;\u0026amp; (params-\u0026gt;disposition == WindowOpenDisposition::NEW_FOREGROUND_TAB || params-\u0026gt;disposition == WindowOpenDisposition::NEW_WINDOW) \u0026amp;\u0026amp; (params-\u0026gt;tabstrip_add_types \u0026amp; AddTabTypes::ADD_INHERIT_OPENER)) { params-\u0026gt;source_contents-\u0026gt;Focus(); } if (tab_to_insert) { // Save data needed for link capturing into apps that cannot otherwise be // inferred later in the navigation. These are only needed when the // navigation happens in a different tab to the link click. apps::SetLinkCapturingSourceDisposition(tab_to_insert-\u0026gt;GetContents(), params-\u0026gt;disposition); } if (params-\u0026gt;source_contents == contents_to_navigate_or_insert) { // The navigation occurred in the source tab. params-\u0026gt;browser-\u0026gt;UpdateUIForNavigationInTab( contents_to_navigate_or_insert, params-\u0026gt;transition, params-\u0026gt;window_action, user_initiated); } else if (singleton_index == -1) { if (source_browser != params-\u0026gt;browser) { params-\u0026gt;tabstrip_index = params-\u0026gt;browser-\u0026gt;tab_strip_model()-\u0026gt;count(); } // If some non-default value is set for the index, we should tell the // TabStripModel to respect it. if (params-\u0026gt;tabstrip_index != -1) { params-\u0026gt;tabstrip_add_types |= AddTabTypes::ADD_FORCE_INDEX; } // Maybe notify that an open operation has been done from a gesture. // TODO(crbug.com/40719979): preferably pipe this information through the // TabStripModel instead. See bug for deeper discussion. if (params-\u0026gt;user_gesture \u0026amp;\u0026amp; source_browser == params-\u0026gt;browser) { params-\u0026gt;browser-\u0026gt;window()-\u0026gt;LinkOpeningFromGesture(params-\u0026gt;disposition); } DCHECK(tab_to_insert); // The navigation should insert a new tab into the target Browser. params-\u0026gt;browser-\u0026gt;tab_strip_model()-\u0026gt;AddTab( std::move(tab_to_insert), params-\u0026gt;tabstrip_index, params-\u0026gt;transition, params-\u0026gt;tabstrip_add_types, params-\u0026gt;group); } if (singleton_index \u0026gt;= 0) { // If switching browsers, make sure it is shown. if (params-\u0026gt;disposition == WindowOpenDisposition::SWITCH_TO_TAB \u0026amp;\u0026amp; params-\u0026gt;browser != source_browser) { params-\u0026gt;window_action = NavigateParams::SHOW_WINDOW; } if (contents_to_navigate_or_insert-\u0026gt;IsCrashed()) { contents_to_navigate_or_insert-\u0026gt;GetController().Reload( content::ReloadType::NORMAL, true); } else if (params-\u0026gt;path_behavior == NavigateParams::IGNORE_AND_NAVIGATE \u0026amp;\u0026amp; contents_to_navigate_or_insert-\u0026gt;GetURL() != params-\u0026gt;url) { navigation_handle = LoadURLInContents(contents_to_navigate_or_insert, params-\u0026gt;url, params); } // If the singleton tab isn\u0026#39;t already selected, select it. if (params-\u0026gt;source_contents != contents_to_navigate_or_insert) { // Use the index before the potential close below, because it could // make the index refer to a different tab. auto gesture_type = user_initiated ? TabStripUserGestureDetails::GestureType::kOther : TabStripUserGestureDetails::GestureType::kNone; bool should_close_this_tab = false; if (params-\u0026gt;disposition == WindowOpenDisposition::SWITCH_TO_TAB) { // Close orphaned NTP (and the like) with no history when the user // switches away from them. if (params-\u0026gt;source_contents) { if (params-\u0026gt;source_contents-\u0026gt;GetController().CanGoBack() || (params-\u0026gt;source_contents-\u0026gt;GetLastCommittedURL().spec() != chrome::kChromeUINewTabURL \u0026amp;\u0026amp; params-\u0026gt;source_contents-\u0026gt;GetLastCommittedURL().spec() != url::kAboutBlankURL)) { // Blur location bar before state save in ActivateTabAt() below. params-\u0026gt;source_contents-\u0026gt;Focus(); } else { should_close_this_tab = true; } } } params-\u0026gt;browser-\u0026gt;tab_strip_model()-\u0026gt;ActivateTabAt( singleton_index, TabStripUserGestureDetails(gesture_type)); // Close tab after switch so index remains correct. if (should_close_this_tab) { params-\u0026gt;source_contents-\u0026gt;Close(); } } } params-\u0026gt;navigated_or_inserted_contents = contents_to_navigate_or_insert; // At this point, the `params-\u0026gt;navigated_or_inserted_contents` is guaranteed to // be non null, so perform tasks if the navigation has been captured by a web // app, like enqueueing launch params. #if !BUILDFLAG(IS_ANDROID) if (app_navigation) { web_app::NavigationCapturingProcess::AfterWebContentsCreation( std::move(app_navigation), *params-\u0026gt;navigated_or_inserted_contents, navigation_handle.get()); } #endif // !BUILDFLAG(IS_ANDROID) return navigation_handle; } https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/ui/browser_navigator.cc;drc=467a8e68f685f9cfa47ee3fbfca20c22f7f6e893;bpv=1;bpt=1;l=586 おそらくLoadURLInContentsが呼ばれる\nbase::WeakPtr\u0026lt;content::NavigationHandle\u0026gt; LoadURLInContents( WebContents* target_contents, const GURL\u0026amp; url, NavigateParams* params) { NavigationController::LoadURLParams load_url_params(url); load_url_params.initiator_frame_token = params-\u0026gt;initiator_frame_token; load_url_params.initiator_process_id = params-\u0026gt;initiator_process_id; load_url_params.initiator_origin = params-\u0026gt;initiator_origin; load_url_params.initiator_base_url = params-\u0026gt;initiator_base_url; load_url_params.source_site_instance = params-\u0026gt;source_site_instance; load_url_params.referrer = params-\u0026gt;referrer; load_url_params.frame_name = params-\u0026gt;frame_name; load_url_params.frame_tree_node_id = params-\u0026gt;frame_tree_node_id; load_url_params.redirect_chain = params-\u0026gt;redirect_chain; load_url_params.transition_type = params-\u0026gt;transition; load_url_params.extra_headers = params-\u0026gt;extra_headers; load_url_params.should_replace_current_entry = params-\u0026gt;should_replace_current_entry; load_url_params.is_renderer_initiated = params-\u0026gt;is_renderer_initiated; load_url_params.started_from_context_menu = params-\u0026gt;started_from_context_menu; load_url_params.has_user_gesture = params-\u0026gt;user_gesture; load_url_params.blob_url_loader_factory = params-\u0026gt;blob_url_loader_factory; load_url_params.input_start = params-\u0026gt;input_start; load_url_params.was_activated = params-\u0026gt;was_activated; load_url_params.href_translate = params-\u0026gt;href_translate; load_url_params.reload_type = params-\u0026gt;reload_type; load_url_params.impression = params-\u0026gt;impression; load_url_params.suggested_system_entropy = params-\u0026gt;suggested_system_entropy; // |frame_tree_node_id| is invalid for main frame navigations. if (params-\u0026gt;frame_tree_node_id.is_null()) { bool force_no_https_upgrade = params-\u0026gt;url_typed_with_http_scheme || params-\u0026gt;captive_portal_window_type != captive_portal::CaptivePortalWindowType::kNone; std::unique_ptr\u0026lt;ChromeNavigationUIData\u0026gt; navigation_ui_data = ChromeNavigationUIData::CreateForMainFrameNavigation( target_contents, params-\u0026gt;is_using_https_as_default_scheme, force_no_https_upgrade); navigation_ui_data-\u0026gt;set_navigation_initiated_from_sync( params-\u0026gt;navigation_initiated_from_sync); load_url_params.navigation_ui_data = std::move(navigation_ui_data); } if (params-\u0026gt;post_data) { load_url_params.load_type = NavigationController::LOAD_TYPE_HTTP_POST; load_url_params.post_data = params-\u0026gt;post_data; } return target_contents-\u0026gt;GetController().LoadURLWithParams(load_url_params); } https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/ui/browser_navigator.cc;l=454;drc=467a8e68f685f9cfa47ee3fbfca20c22f7f6e893;bpv=1;bpt=1 LoadURLWithParamsで\nbase::WeakPtr\u0026lt;NavigationHandle\u0026gt; NavigationControllerImpl::LoadURLWithParams( const LoadURLParams\u0026amp; params) { if (params.is_renderer_initiated) DCHECK(params.initiator_origin.has_value()); TRACE_EVENT1(\u0026#34;browser,navigation\u0026#34;, \u0026#34;NavigationControllerImpl::LoadURLWithParams\u0026#34;, \u0026#34;url\u0026#34;, params.url.possibly_invalid_spec()); bool is_explicit_navigation = GetContentClient()-\u0026gt;browser()-\u0026gt;IsExplicitNavigation( params.transition_type); if (HandleDebugURL(params.url, params.transition_type, is_explicit_navigation)) { // If Telemetry is running, allow the URL load to proceed as if it\u0026#39;s // unhandled, otherwise Telemetry can\u0026#39;t tell if Navigation completed. if (!base::CommandLine::ForCurrentProcess()-\u0026gt;HasSwitch( switches::kEnableGpuBenchmarking)) { return nullptr; } } // Checks based on params.load_type. switch (params.load_type) { case LOAD_TYPE_DEFAULT: case LOAD_TYPE_HTTP_POST: #if BUILDFLAG(IS_ANDROID) case LOAD_TYPE_PDF_ANDROID: #endif break; case LOAD_TYPE_DATA: if (!params.url.SchemeIs(url::kDataScheme)) { NOTREACHED() \u0026lt;\u0026lt; \u0026#34;Data load must use data scheme.\u0026#34;; } break; } // The user initiated a load, we don\u0026#39;t need to reload anymore. needs_reload_ = false; return NavigateWithoutEntry(params); } https://source.chromium.org/chromium/chromium/src/+/main:content/browser/renderer_host/navigation_controller_impl.cc;l=1347;drc=467a8e68f685f9cfa47ee3fbfca20c22f7f6e893;bpv=1;bpt=1 base::WeakPtr\u0026lt;NavigationHandle\u0026gt; NavigationControllerImpl::NavigateWithoutEntry( const LoadURLParams\u0026amp; params) https://source.chromium.org/chromium/chromium/src/+/main:content/browser/renderer_host/navigation_controller_impl.cc;drc=467a8e68f685f9cfa47ee3fbfca20c22f7f6e893;bpv=1;bpt=1;l=3662 NavigationRequestに変換\nstd::unique_ptr\u0026lt;NavigationRequest\u0026gt; request = CreateNavigationRequestFromLoadParams( node, params, override_user_agent, should_replace_current_entry, params.has_user_gesture, network::mojom::SourceLocation::New(), reload_type, pending_entry_, pending_entry_-\u0026gt;GetFrameEntry(node), actual_navigation_start_time, navigation_start_time); https://source.chromium.org/chromium/chromium/src/+/main:content/browser/renderer_host/navigation_controller_impl.cc;l=3800;drc=467a8e68f685f9cfa47ee3fbfca20c22f7f6e893;bpv=1;bpt=1 void Navigator::Navigate(std::unique_ptr request, ReloadType reload_type)を実行 https://source.chromium.org/chromium/chromium/src/+/main:content/browser/renderer_host/navigator.cc;drc=467a8e68f685f9cfa47ee3fbfca20c22f7f6e893;bpv=1;bpt=1;l=815 frame_tree_node-\u0026gt;TakeNavigationRequest(std::move(request));の呼び出し https://source.chromium.org/chromium/chromium/src/+/main:content/browser/renderer_host/navigator.cc;l=937;drc=467a8e68f685f9cfa47ee3fbfca20c22f7f6e893;bpv=1;bpt=1 void FrameTreeNode::TakeNavigationRequest( std::unique_ptr navigation_request) https://source.chromium.org/chromium/chromium/src/+/main:content/browser/renderer_host/frame_tree_node.cc;drc=467a8e68f685f9cfa47ee3fbfca20c22f7f6e893;bpv=1;bpt=1;l=578 最終的にここにNavigationRequestが設定されてpendingされる。(おそらく非同期?) https://source.chromium.org/chromium/chromium/src/+/main:content/browser/renderer_host/frame_tree_node.cc;l=621;drc=467a8e68f685f9cfa47ee3fbfca20c22f7f6e893;bpv=1;bpt=1 NavigationURLLoaderがこの設定されたやつを制御するっぽい? https://source.chromium.org/chromium/chromium/src/+/main:content/browser/loader/navigation_url_loader.h;drc=467a8e68f685f9cfa47ee3fbfca20c22f7f6e893;bpv=1;bpt=1;l=40 ここでFrameTreeNode::TakeNavigationRequestで取得したNavigationRequestを使ってリクエスト実行開始 https://source.chromium.org/chromium/chromium/src/+/main:content/browser/renderer_host/navigator.cc;l=950;drc=d9942f1cda698ea5524c9d8b7ba9f48f508bf41c;bpv=1;bpt=1 NavigationRequest::BeginNavigation() https://source.chromium.org/chromium/chromium/src/+/main:content/browser/renderer_host/navigation_request.cc;l=2383;drc=d9942f1cda698ea5524c9d8b7ba9f48f508bf41c;bpv=1;bpt=1 NavigationRequest::BeginNavigationImpl() https://source.chromium.org/chromium/chromium/src/+/main:content/browser/renderer_host/navigation_request.cc;l=2738;drc=d9942f1cda698ea5524c9d8b7ba9f48f508bf41c;bpv=1;bpt=1 NavigationRequest::WillStartRequest() https://source.chromium.org/chromium/chromium/src/+/main:content/browser/renderer_host/navigation_request.cc;l=7976;drc=d9942f1cda698ea5524c9d8b7ba9f48f508bf41c;bpv=1;bpt=1 NavigationThrottleRunner::ProcessNavigationEvent https://source.chromium.org/chromium/chromium/src/+/main:content/browser/renderer_host/navigation_throttle_runner.cc;l=128;drc=d9942f1cda698ea5524c9d8b7ba9f48f508bf41c;bpv=1;bpt=1 NavigationThrottleRunner::ProcessInternal() https://source.chromium.org/chromium/chromium/src/+/main:content/browser/renderer_host/navigation_throttle_runner.cc;l=220;drc=d9942f1cda698ea5524c9d8b7ba9f48f508bf41c;bpv=1;bpt=1 https://source.chromium.org/chromium/chromium/src/+/main:content/browser/renderer_host/navigation_throttle_runner.cc;l=249;drc=8fd12b9c0e73b29ea33990af0ef603e1db43e4c3;bpv=1;bpt=1 void NavigationThrottleRunner::InformRegistry( const NavigationThrottle::ThrottleCheckResult\u0026amp; result) { // Now that the event has executed, reset the current event to kNoEvent since // we\u0026#39;re no longer processing any event. Do it before the call to the // delegate, as it might lead to the deletion of this // NavigationThrottleRunner. NavigationThrottleEvent event = current_event_; current_event_ = NavigationThrottleEvent::kNoEvent; registry_-\u0026gt;OnEventProcessed(event, result); // DO NOT ADD CODE AFTER THIS. The NavigationThrottleRunner might have been // deleted by the previous call. } https://source.chromium.org/chromium/chromium/src/+/main:content/browser/renderer_host/navigation_throttle_registry_impl.cc;l=179;drc=8fd12b9c0e73b29ea33990af0ef603e1db43e4c3;bpv=1;bpt=1 void NavigationThrottleRegistryImpl::OnEventProcessed( NavigationThrottleEvent event, NavigationThrottle::ThrottleCheckResult result) { navigation_request_-\u0026gt;OnNavigationEventProcessed(event, result); } https://source.chromium.org/chromium/chromium/src/+/main:content/browser/renderer_host/navigation_request.cc;l=7704;drc=8fd12b9c0e73b29ea33990af0ef603e1db43e4c3;bpv=1;bpt=1 void NavigationRequest::OnNavigationEventProcessed( NavigationThrottleEvent event, NavigationThrottle::ThrottleCheckResult result) { NavigationRequest::OnWillStartRequestProcessed https://source.chromium.org/chromium/chromium/src/+/main:content/browser/renderer_host/navigation_request.cc;l=7706;drc=736622ed7d9cf605750afa417b3f4e681eef686c?q=OnWillStartRequestProcessed\u0026ss=chromium%2Fchromium%2Fsrc void NavigationRequest::OnWillStartRequestProcessed( NavigationThrottle::ThrottleCheckResult result) { DCHECK_EQ(WILL_START_REQUEST, state_); DCHECK_NE(NavigationThrottle::BLOCK_RESPONSE, result.action()); DCHECK(processing_navigation_throttle_); processing_navigation_throttle_ = false; if (result.action() != NavigationThrottle::PROCEED) SetState(CANCELING); if (complete_callback_for_testing_ \u0026amp;\u0026amp; std::move(complete_callback_for_testing_).Run(result)) { return; } if (MaybeEvictFromBackForwardCacheBySubframeNavigation( frame_tree_node_-\u0026gt;current_frame_host())) { // DO NOT ADD CODE AFTER THIS, as the NavigationRequest might have been // deleted by the previous calls. return; } OnStartChecksComplete(result); // DO NOT ADD CODE AFTER THIS, as the NavigationRequest might have been // deleted by the previous calls. } void NavigationRequest::OnStartChecksComplete( NavigationThrottle::ThrottleCheckResult result) https://source.chromium.org/chromium/chromium/src/+/main:content/browser/renderer_host/navigation_request.cc;l=5444;drc=736622ed7d9cf605750afa417b3f4e681eef686c?q=OnWillStartRequestProcessed\u0026ss=chromium%2Fchromium%2Fsrc commit_params_-\u0026gt;navigation_timing-\u0026gt;fetch_start = base::TimeTicks::Now(); ここでスタートしているようだ\u0026hellip; https://source.chromium.org/chromium/chromium/src/+/main:content/browser/renderer_host/navigation_request.cc;l=5592;drc=736622ed7d9cf605750afa417b3f4e681eef686c?q=OnWillStartRequestProcessed\u0026ss=chromium%2Fchromium%2Fsrc loader_-\u0026gt;Start(); 実装はおそらくここと思われる。\nvoid NavigationURLLoaderImpl::Start() https://source.chromium.org/chromium/chromium/src/+/main:content/browser/loader/navigation_url_loader_impl.cc;l=590;drc=736622ed7d9cf605750afa417b3f4e681eef686c;bpv=1;bpt=1 void NavigationURLLoaderImpl::Restart() https://source.chromium.org/chromium/chromium/src/+/main:content/browser/loader/navigation_url_loader_impl.cc;drc=736622ed7d9cf605750afa417b3f4e681eef686c;bpv=1;bpt=1;l=736 https://source.chromium.org/chromium/chromium/src/+/main:content/browser/loader/navigation_url_loader_impl.cc;drc=4f289372eacd2b226e9d2830cc4a87b4ad621133;bpv=1;bpt=1;l=778?q=SingleRequestURLLoaderFactory\u0026ss=chromium%2Fchromium%2Fsrc\u0026start=1 void NavigationURLLoaderImpl::MaybeStartLoader( size_t next_interceptor_index, std::optional\u0026lt;NavigationLoaderInterceptor::Result\u0026gt; interceptor_result) { DCHECK_CURRENTLY_ON(BrowserThread::UI); DCHECK(started_); if (interceptor_result) { subresource_loader_params_ = std::move(interceptor_result-\u0026gt;subresource_loader_params); if (!interceptor_result-\u0026gt;single_request_factory) { // Skip the subsequent interceptors and start with the default behavior. // // Here `subresource_loader_params_` can still have non-default values // e.g. when there\u0026#39;s a controlling service worker that doesn\u0026#39;t have a // fetch event handler so it doesn\u0026#39;t intercept requests. StartNonInterceptedRequest( std::move(interceptor_result-\u0026gt;response_head_update_params)); return; } // Intercept the request with `interceptor_result-\u0026gt;single_request_factory`. StartInterceptedRequest( std::move(interceptor_result-\u0026gt;single_request_factory)); return; } subresource_loader_params_ = {}; if (next_interceptor_index \u0026gt;= interceptors_.size()) { // All interceptors have been checked and none has elected to handle the // request. Start with the default behavior. StartNonInterceptedRequest(ResponseHeadUpdateParams()); return; } // Fallback to the next interceptor. auto* next_interceptor = interceptors_[next_interceptor_index].get(); next_interceptor-\u0026gt;MaybeCreateLoader( *resource_request_, browser_context_, base::BindOnce(\u0026amp;NavigationURLLoaderImpl::MaybeStartLoader, weak_factory_.GetWeakPtr(), next_interceptor_index + 1), base::BindOnce(\u0026amp;NavigationURLLoaderImpl::FallbackToNonInterceptedRequest, weak_factory_.GetWeakPtr())); } https://source.chromium.org/chromium/chromium/src/+/main:content/browser/loader/navigation_url_loader_impl.cc;l=809;drc=736622ed7d9cf605750afa417b3f4e681eef686c;bpv=1;bpt=1 // All interceptors have been checked and none has elected to handle the // request. Start with the default behavior. StartNonInterceptedRequest(ResponseHeadUpdateParams()); return; https://source.chromium.org/chromium/chromium/src/+/main:content/browser/loader/navigation_url_loader_impl.cc;drc=736622ed7d9cf605750afa417b3f4e681eef686c;bpv=1;bpt=1;l=850 void NavigationURLLoaderImpl::StartNonInterceptedRequest( ResponseHeadUpdateParams head_update_params) { // If we already have the default `url_loader_` we must come here after a // redirect. No interceptors wanted to intercept the redirected request, so // let the loader just follow the redirect. if (url_loader_) { DCHECK(!redirect_info_.new_url.is_empty()); url_loader_-\u0026gt;FollowRedirect( std::move(url_loader_removed_headers_), std::move(url_loader_modified_headers_), std::move(url_loader_modified_cors_exempt_headers_)); return; } head_update_params_ = std::move(head_update_params); scoped_refptr\u0026lt;network::SharedURLLoaderFactory\u0026gt; factory; if (network::IsURLHandledByNetworkService(resource_request_-\u0026gt;url)) { factory = network_loader_factory_; default_loader_used_ = true; } else { factory = GetOrCreateNonNetworkLoaderFactory(); } response_loader_receiver_.reset(); CreateThrottlingLoaderAndStart(std::move(factory), /*additional_throttles=*/{}); } https://source.chromium.org/chromium/chromium/src/+/main:content/browser/loader/navigation_url_loader_impl.cc;drc=736622ed7d9cf605750afa417b3f4e681eef686c;bpv=1;bpt=1;l=1018 void NavigationURLLoaderImpl::CreateThrottlingLoaderAndStart( scoped_refptr\u0026lt;network::SharedURLLoaderFactory\u0026gt; factory, std::vector\u0026lt;std::unique_ptr\u0026lt;blink::URLLoaderThrottle\u0026gt;\u0026gt; additional_throttles) { TRACE_EVENT_WITH_FLOW0( \u0026#34;navigation\u0026#34;, \u0026#34;NavigationURLLoaderImpl::CreateThrottlingLoaderAndStart\u0026#34;, TRACE_ID_LOCAL(this), TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT); CHECK(!url_loader_); std::vector\u0026lt;std::unique_ptr\u0026lt;blink::URLLoaderThrottle\u0026gt;\u0026gt; throttles = CreateURLLoaderThrottles(); for (auto\u0026amp;\u0026amp; throttle : additional_throttles) { throttles.push_back(std::move(throttle)); } uint32_t options = GetURLLoaderOptions(resource_request_-\u0026gt;is_outermost_main_frame); url_loader_ = blink::ThrottlingURLLoader::CreateLoaderAndStart( std::move(factory), std::move(throttles), global_request_id_.request_id, options, resource_request_.get(), /*client=*/this, kNavigationUrlLoaderTrafficAnnotation, GetUIThreadTaskRunner({BrowserTaskType::kNavigationNetworkResponse}), /*cors_exempt_header_list=*/std::nullopt, /*client_receiver_delegate=*/nullptr, \u0026amp;request_info_-\u0026gt;common_params-\u0026gt;initiator_origin_trial_features); } https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/common/loader/throttling_url_loader.cc;drc=736622ed7d9cf605750afa417b3f4e681eef686c;bpv=1;bpt=1;l=261 // static std::unique_ptr\u0026lt;ThrottlingURLLoader\u0026gt; ThrottlingURLLoader::CreateLoaderAndStart( scoped_refptr\u0026lt;network::SharedURLLoaderFactory\u0026gt; factory, std::vector\u0026lt;std::unique_ptr\u0026lt;URLLoaderThrottle\u0026gt;\u0026gt; throttles, int32_t request_id, uint32_t options, network::ResourceRequest* url_request, network::mojom::URLLoaderClient* client, const net::NetworkTrafficAnnotationTag\u0026amp; traffic_annotation, scoped_refptr\u0026lt;base::SequencedTaskRunner\u0026gt; task_runner, std::optional\u0026lt;std::vector\u0026lt;std::string\u0026gt;\u0026gt; cors_exempt_header_list, ClientReceiverDelegate* client_receiver_delegate, const std::vector\u0026lt;int\u0026gt;* initiator_origin_trial_features) { DCHECK(url_request); std::unique_ptr\u0026lt;ThrottlingURLLoader\u0026gt; loader( new ThrottlingURLLoader(std::move(throttles), client, traffic_annotation, client_receiver_delegate)); loader-\u0026gt;Start(std::move(factory), request_id, options, url_request, std::move(task_runner), std::move(cors_exempt_header_list), initiator_origin_trial_features); return loader; } https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/common/loader/throttling_url_loader.cc;drc=736622ed7d9cf605750afa417b3f4e681eef686c;bpv=1;bpt=1;l=404 void ThrottlingURLLoader::Start( scoped_refptr\u0026lt;network::SharedURLLoaderFactory\u0026gt; factory, int32_t request_id, uint32_t options, network::ResourceRequest* url_request, scoped_refptr\u0026lt;base::SequencedTaskRunner\u0026gt; task_runner, std::optional\u0026lt;std::vector\u0026lt;std::string\u0026gt;\u0026gt; cors_exempt_header_list, const std::vector\u0026lt;int\u0026gt;* initiator_origin_trial_features) { TRACE_EVENT_WITH_FLOW1(\u0026#34;loading\u0026#34;, \u0026#34;ThrottlingURLLoader::Start\u0026#34;, TRACE_ID_LOCAL(this), TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT, \u0026#34;request_id\u0026#34;, request_id); DCHECK_EQ(DEFERRED_NONE, deferred_stage_); DCHECK(!loader_completed_); bool deferred = false; DCHECK(deferring_throttles_.empty()); if (!throttles_.empty()) { original_url_ = url_request-\u0026gt;url; for (auto\u0026amp; entry : throttles_) { auto* throttle = entry.throttle.get(); bool throttle_deferred = false; #if DCHECK_IS_ON() std::set\u0026lt;std::string\u0026gt; initial_headers, initial_cors_exempt_headers; if (cors_exempt_header_list) { for (auto\u0026amp; header : url_request-\u0026gt;headers.GetHeaderVector()) initial_headers.insert(header.key); for (auto\u0026amp; header : url_request-\u0026gt;cors_exempt_headers.GetHeaderVector()) initial_cors_exempt_headers.insert(header.key); } #endif base::Time start = base::Time::Now(); throttle-\u0026gt;WillStartRequest(url_request, \u0026amp;throttle_deferred); RecordExecutionTimeHistogram(GetStageNameForHistogram(DEFERRED_START), start); #if DCHECK_IS_ON() if (cors_exempt_header_list) { CheckThrottleWillNotCauseCorsPreflight( initial_headers, initial_cors_exempt_headers, url_request-\u0026gt;headers, url_request-\u0026gt;cors_exempt_headers, *cors_exempt_header_list); } #endif if (original_url_ != url_request-\u0026gt;url) { DCHECK(throttle_will_start_redirect_url_.is_empty()) \u0026lt;\u0026lt; \u0026#34;ThrottlingURLLoader doesn\u0026#39;t support multiple throttles \u0026#34; \u0026#34;changing the URL.\u0026#34;; if (original_url_.SchemeIsHTTPOrHTTPS() \u0026amp;\u0026amp; !url_request-\u0026gt;url.SchemeIsHTTPOrHTTPS()) { NOTREACHED() \u0026lt;\u0026lt; \u0026#34;A URLLoaderThrottle can\u0026#39;t redirect from http(s) to \u0026#34; \u0026lt;\u0026lt; \u0026#34;a non http(s) scheme.\u0026#34;; } else { throttle_will_start_redirect_url_ = url_request-\u0026gt;url; } // Restore the original URL so that all throttles see the same original // URL. url_request-\u0026gt;url = original_url_; } if (!HandleThrottleResult(throttle, throttle_deferred, \u0026amp;deferred)) return; } } if (initiator_origin_trial_features \u0026amp;\u0026amp; base::Contains( *initiator_origin_trial_features, static_cast\u0026lt;int\u0026gt;( mojom::OriginTrialFeature::kDeviceBoundSessionCredentials))) { url_request-\u0026gt;allows_device_bound_session_registration = true; } start_info_ = std::make_unique\u0026lt;StartInfo\u0026gt;(factory, request_id, options, url_request, std::move(task_runner), std::move(cors_exempt_header_list)); if (deferred) deferred_stage_ = DEFERRED_START; else StartNow(); } https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/common/loader/throttling_url_loader.cc;l=404;drc=736622ed7d9cf605750afa417b3f4e681eef686c;bpv=1;bpt=1 void ThrottlingURLLoader::StartNow() { TRACE_EVENT_WITH_FLOW0(\u0026#34;loading\u0026#34;, \u0026#34;ThrottlingURLLoader::StartNow\u0026#34;, TRACE_ID_LOCAL(this), TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT); DCHECK(start_info_); if (!throttle_will_start_redirect_url_.is_empty()) { auto first_party_url_policy = start_info_-\u0026gt;url_request.update_first_party_url_on_redirect ? net::RedirectInfo::FirstPartyURLPolicy::UPDATE_URL_ON_REDIRECT : net::RedirectInfo::FirstPartyURLPolicy::NEVER_CHANGE_URL; net::RedirectInfo redirect_info = net::RedirectInfo::ComputeRedirectInfo( start_info_-\u0026gt;url_request.method, start_info_-\u0026gt;url_request.url, start_info_-\u0026gt;url_request.site_for_cookies, first_party_url_policy, start_info_-\u0026gt;url_request.referrer_policy, start_info_-\u0026gt;url_request.referrer.spec(), // Use status code 307 to preserve the method, so POST requests work. net::HTTP_TEMPORARY_REDIRECT, throttle_will_start_redirect_url_, std::nullopt, false, false, false); // Set Critical-CH restart info and clear for next redirect. redirect_info.critical_ch_restart_time = critical_ch_restart_time_; critical_ch_restart_time_ = base::TimeTicks(); bool should_clear_upload = false; net::RedirectUtil::UpdateHttpRequest( start_info_-\u0026gt;url_request.url, start_info_-\u0026gt;url_request.method, redirect_info, std::nullopt, std::nullopt, \u0026amp;start_info_-\u0026gt;url_request.headers, \u0026amp;should_clear_upload); if (should_clear_upload) { start_info_-\u0026gt;url_request.request_body = nullptr; } // Set the new URL in the ResourceRequest struct so that it is the URL // that\u0026#39;s requested. start_info_-\u0026gt;url_request.url = throttle_will_start_redirect_url_; auto response_head = network::mojom::URLResponseHead::New(); std::string header_string = base::StringPrintf( \u0026#34;HTTP/1.1 %i Internal Redirect\\n\u0026#34; \u0026#34;Location: %s\u0026#34;, net::HTTP_TEMPORARY_REDIRECT, throttle_will_start_redirect_url_.spec().c_str()); response_head-\u0026gt;headers = base::MakeRefCounted\u0026lt;net::HttpResponseHeaders\u0026gt;( net::HttpUtil::AssembleRawHeaders(header_string)); response_head-\u0026gt;encoded_data_length = header_string.size(); start_info_-\u0026gt;task_runner-\u0026gt;PostTask( FROM_HERE, base::BindOnce(\u0026amp;ThrottlingURLLoader::OnReceiveRedirect, weak_factory_.GetWeakPtr(), std::move(redirect_info), std::move(response_head))); return; } if (start_info_-\u0026gt;url_request.keepalive) { base::UmaHistogramBoolean(\u0026#34;FetchKeepAlive.Renderer.Total.Started\u0026#34;, true); } DCHECK(start_info_-\u0026gt;url_loader_factory); start_info_-\u0026gt;url_loader_factory-\u0026gt;CreateLoaderAndStart( url_loader_.BindNewPipeAndPassReceiver(start_info_-\u0026gt;task_runner), start_info_-\u0026gt;request_id, start_info_-\u0026gt;options, start_info_-\u0026gt;url_request, client_receiver_.BindNewPipeAndPassRemote(start_info_-\u0026gt;task_runner), net::MutableNetworkTrafficAnnotationTag(traffic_annotation_)); // TODO(https://crbug.com/919736): Remove this call. client_receiver_.internal_state()-\u0026gt;EnableBatchDispatch(); client_receiver_.set_disconnect_handler(base::BindOnce( \u0026amp;ThrottlingURLLoader::OnClientConnectionError, base::Unretained(this))); if (priority_info_) { auto priority_info = std::move(priority_info_); url_loader_-\u0026gt;SetPriority(priority_info-\u0026gt;priority, priority_info-\u0026gt;intra_priority_value); } // Initialize with the request URL, may be updated when on redirects response_url_ = start_info_-\u0026gt;url_request.url; } 以下のところでリクエスト開始のようである。 https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/common/loader/throttling_url_loader.cc;l=549;drc=736622ed7d9cf605750afa417b3f4e681eef686c;bpv=1;bpt=1 start_info_-\u0026gt;url_loader_factory-\u0026gt;CreateLoaderAndStart( url_loader_.BindNewPipeAndPassReceiver(start_info_-\u0026gt;task_runner), start_info_-\u0026gt;request_id, start_info_-\u0026gt;options, start_info_-\u0026gt;url_request, client_receiver_.BindNewPipeAndPassRemote(start_info_-\u0026gt;task_runner), net::MutableNetworkTrafficAnnotationTag(traffic_annotation_)); このurl_loader_factoryの出どころはここで https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/common/loader/throttling_url_loader.cc;l=479-481;drc=4f289372eacd2b226e9d2830cc4a87b4ad621133 start_info_ = std::make_unique\u0026lt;StartInfo\u0026gt;(factory, request_id, options, url_request, std::move(task_runner), std::move(cors_exempt_header_list)); さらにこのfactory引数自体の出どころは以下に遡れる。 https://source.chromium.org/chromium/chromium/src/+/main:content/browser/loader/navigation_url_loader_impl.cc;l=867;drc=736622ed7d9cf605750afa417b3f4e681eef686c factory = network_loader_factory_; さらにnetwork_loader_factory_の出どころは以下 https://source.chromium.org/chromium/chromium/src/+/main:content/browser/loader/navigation_url_loader_impl.cc;l=1721;drc=4f289372eacd2b226e9d2830cc4a87b4ad621133;bpv=1;bpt=1 NavigationURLLoaderImpl::NavigationURLLoaderImpl( BrowserContext* browser_context, StoragePartition* storage_partition, std::unique_ptr\u0026lt;NavigationRequestInfo\u0026gt; request_info, std::unique_ptr\u0026lt;NavigationUIData\u0026gt; navigation_ui_data, ServiceWorkerMainResourceHandle* service_worker_handle, scoped_refptr\u0026lt;PrefetchedSignedExchangeCache\u0026gt; prefetched_signed_exchange_cache, NavigationURLLoaderDelegate* delegate, mojo::PendingRemote\u0026lt;network::mojom::CookieAccessObserver\u0026gt; cookie_observer, mojo::PendingRemote\u0026lt;network::mojom::TrustTokenAccessObserver\u0026gt; trust_token_observer, mojo::PendingRemote\u0026lt;network::mojom::SharedDictionaryAccessObserver\u0026gt; shared_dictionary_observer, mojo::PendingRemote\u0026lt;network::mojom::URLLoaderNetworkServiceObserver\u0026gt; url_loader_network_observer, mojo::PendingRemote\u0026lt;network::mojom::DevToolsObserver\u0026gt; devtools_observer, mojo::PendingRemote\u0026lt;network::mojom::DeviceBoundSessionAccessObserver\u0026gt; device_bound_session_observer, std::vector\u0026lt;std::unique_ptr\u0026lt;NavigationLoaderInterceptor\u0026gt;\u0026gt; initial_interceptors) : delegate_(delegate), browser_context_(browser_context), storage_partition_(static_cast\u0026lt;StoragePartitionImpl*\u0026gt;(storage_partition)), service_worker_handle_(service_worker_handle), request_info_(std::move(request_info)), url_(request_info_-\u0026gt;common_params-\u0026gt;url), frame_tree_node_id_(request_info_-\u0026gt;frame_tree_node_id), global_request_id_(GlobalRequestID::MakeBrowserInitiated()), web_contents_getter_( base::BindRepeating(\u0026amp;WebContents::FromFrameTreeNodeId, frame_tree_node_id_)), navigation_ui_data_(std::move(navigation_ui_data)), interceptors_(std::move(initial_interceptors)), prefetched_signed_exchange_cache_( std::move(prefetched_signed_exchange_cache)), loader_creation_time_(base::TimeTicks::Now()), ukm_source_id_(FrameTreeNode::GloballyFindByID(frame_tree_node_id_) -\u0026gt;navigation_request() -\u0026gt;GetNextPageUkmSourceId()) { TRACE_EVENT_WITH_FLOW0(\u0026#34;navigation\u0026#34;, \u0026#34;NavigationURLLoaderImpl::NavigationURLLoaderImpl\u0026#34;, TRACE_ID_LOCAL(this), TRACE_EVENT_FLAG_FLOW_OUT); DCHECK_CURRENTLY_ON(BrowserThread::UI); TRACE_EVENT_NESTABLE_ASYNC_BEGIN_WITH_TIMESTAMP1( \u0026#34;navigation\u0026#34;, \u0026#34;Navigation timeToResponseStarted\u0026#34;, TRACE_ID_LOCAL(this), request_info_-\u0026gt;common_params-\u0026gt;navigation_start, \u0026#34;FrameTreeNode id\u0026#34;, frame_tree_node_id_); mojo::PendingRemote\u0026lt;network::mojom::AcceptCHFrameObserver\u0026gt; accept_ch_frame_observer; // Use |kNavigationNetworkResponse| thread runner. Messages received related // to AcceptCHFrame are not order dependent and can restart the navigation, // blocking navigation when they do. accept_ch_frame_observers_.Add( this, accept_ch_frame_observer.InitWithNewPipeAndPassReceiver(), GetUIThreadTaskRunner({BrowserTaskType::kNavigationNetworkResponse})); FrameTreeNode* frame_tree_node = FrameTreeNode::GloballyFindByID(frame_tree_node_id_); DCHECK(frame_tree_node); DCHECK(frame_tree_node-\u0026gt;navigation_request()); resource_request_ = CreateResourceRequest( *request_info_, frame_tree_node, std::move(cookie_observer), std::move(trust_token_observer), std::move(shared_dictionary_observer), std::move(url_loader_network_observer), std::move(devtools_observer), std::move(device_bound_session_observer), std::move(accept_ch_frame_observer)); network_loader_factory_ = CreateNetworkLoaderFactory( browser_context_, storage_partition_, frame_tree_node, ukm::SourceIdObj::FromInt64(ukm_source_id_), \u0026amp;bypass_redirect_checks_); } CreateNetworkLoaderFactory関数 https://source.chromium.org/chromium/chromium/src/+/main:content/browser/loader/navigation_url_loader_impl.cc;drc=4f289372eacd2b226e9d2830cc4a87b4ad621133;bpv=1;bpt=1;l=1804 scoped_refptr\u0026lt;network::SharedURLLoaderFactory\u0026gt; NavigationURLLoaderImpl::CreateNetworkLoaderFactory( BrowserContext* browser_context, StoragePartitionImpl* storage_partition, FrameTreeNode* frame_tree_node, const ukm::SourceIdObj\u0026amp; ukm_id, bool* bypass_redirect_checks) { mojo::PendingRemote\u0026lt;network::mojom::TrustedURLLoaderHeaderClient\u0026gt; header_client; // The embedder may want to proxy all network-bound URLLoaderFactory // receivers that it can. If it elects to do so, those proxies will be // connected when loader is created if the request type supports proxying. network::URLLoaderFactoryBuilder factory_builder; // Here we give nullptr for `factory_override`, because CORS is no-op for // navigations. GetContentClient()-\u0026gt;browser()-\u0026gt;WillCreateURLLoaderFactory( browser_context, frame_tree_node-\u0026gt;current_frame_host(), frame_tree_node-\u0026gt;current_frame_host()-\u0026gt;GetProcess()-\u0026gt;GetDeprecatedID(), ContentBrowserClient::URLLoaderFactoryType::kNavigation, url::Origin(), net::IsolationInfo(), frame_tree_node-\u0026gt;navigation_request()-\u0026gt;GetNavigationId(), ukm_id, factory_builder, \u0026amp;header_client, bypass_redirect_checks, /*disable_secure_dns=*/nullptr, /*factory_override=*/nullptr, GetUIThreadTaskRunner({BrowserTaskType::kNavigationNetworkResponse})); auto devtools_params = devtools_instrumentation::WillCreateURLLoaderFactoryParams::ForFrame( frame_tree_node-\u0026gt;current_frame_host()); devtools_params.Run(/*is_navigation=*/true, /*is_download=*/false, factory_builder, /*factory_override=*/nullptr); net::CookieSettingOverrides devtools_cookie_overrides; devtools_instrumentation::ApplyNetworkCookieControlsOverrides( devtools_params.agent_host(), devtools_cookie_overrides); net::CookieSettingOverrides cookie_overrides; if (ShouldAllowSameSiteNoneCookiesInSandbox(*frame_tree_node)) { // Include a CookieSettingOverride in the UrlLoaderFactoryParams for the // frame\u0026#39;s SharedURLLoaderFactory if the frame contains the // `allow-same-site-none-cookies` value in its sandbox policy. cookie_overrides.Put( net::CookieSettingOverride::kAllowSameSiteNoneCookiesInSandbox); } if (header_client) { return base::MakeRefCounted\u0026lt;network::WrapperSharedURLLoaderFactory\u0026gt;( CreateURLLoaderFactoryWithHeaderClient( std::move(header_client), std::move(factory_builder), storage_partition, std::move(devtools_cookie_overrides), std::move(cookie_overrides))); } else { if (!devtools_cookie_overrides.empty() || !cookie_overrides.empty()) { network::mojom::URLLoaderFactoryParamsPtr params = storage_partition-\u0026gt;CreateURLLoaderFactoryParams(); params-\u0026gt;devtools_cookie_setting_overrides = std::move(devtools_cookie_overrides); params-\u0026gt;cookie_setting_overrides = std::move(cookie_overrides); return std::move(factory_builder) .Finish(storage_partition-\u0026gt;GetNetworkContext(), std::move(params)); } return std::move(factory_builder) .Finish(storage_partition-\u0026gt;GetURLLoaderFactoryForBrowserProcess()); } } GetURLLoaderFactoryForBrowserProcess https://source.chromium.org/chromium/chromium/src/+/main:content/browser/storage_partition_impl.cc;l=1602;drc=4f289372eacd2b226e9d2830cc4a87b4ad621133 scoped_refptr\u0026lt;network::SharedURLLoaderFactory\u0026gt; StoragePartitionImpl::GetURLLoaderFactoryForBrowserProcess() { CHECK(shared_url_loader_factory_for_browser_process_); return shared_url_loader_factory_for_browser_process_-\u0026gt;factory(); } https://source.chromium.org/chromium/chromium/src/+/main:content/browser/storage_partition_impl.cc;l=1432;drc=4f289372eacd2b226e9d2830cc4a87b4ad621133;bpv=1;bpt=1 shared_url_loader_factory_for_browser_process_ = std::make_unique\u0026lt; ReconnectableURLLoaderFactoryForIOThreadWrapper\u0026gt;(base::BindRepeating( \u0026amp;StoragePartitionImpl::CreateURLLoaderFactoryForBrowserProcessInternal, GetWeakPtr())); shared_url_loader_factory_for_browser_process_-\u0026gt;factory_for_io_thread() -\u0026gt;Initialize(); https://source.chromium.org/chromium/chromium/src/+/main:content/browser/storage_partition_impl.cc;l=3679;drc=4f289372eacd2b226e9d2830cc4a87b4ad621133;bpv=1;bpt=1?q=StoragePartitionImpl::CreateURLLoaderFactoryForBrowserProcessInternal\u0026ss=chromium%2Fchromium%2Fsrc void StoragePartitionImpl::CreateURLLoaderFactoryForBrowserProcessInternal( mojo::PendingRemote\u0026lt;network::mojom::URLLoaderFactory\u0026gt;* out_factory) { CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); network::URLLoaderFactoryBuilder factory_builder; if (url_loader_factory::GetTestingInterceptor()) { url_loader_factory::GetTestingInterceptor().Run( network::mojom::kBrowserProcessId, factory_builder); } *out_factory = std::move(factory_builder) .Finish\u0026lt;mojo::PendingRemote\u0026lt;network::mojom::URLLoaderFactory\u0026gt;\u0026gt;( GetNetworkContext(), CreateURLLoaderFactoryParams()); } https://source.chromium.org/chromium/chromium/src/+/main:services/network/public/cpp/url_loader_factory_builder.h;drc=4f289372eacd2b226e9d2830cc4a87b4ad621133;bpv=1;bpt=1;l=81 // This `Finish()` variant returns the resulting factory as a // mojom::URLLoaderFactory-ish `OutType`. // See `WrapAs` methods for supported `OutType`. template \u0026lt;typename OutType = scoped_refptr\u0026lt;SharedURLLoaderFactory\u0026gt;, typename... Args\u0026gt; [[nodiscard]] OutType Finish(Args... terminal_args) \u0026amp;\u0026amp; { if (IsEmpty()) { // The builder has no interceptors, so just forward the terminal. return WrapAs\u0026lt;OutType\u0026gt;(std::forward\u0026lt;Args\u0026gt;(terminal_args)...); } // Connect `head_` -(interceptors)-\u0026gt; `tail_` -\u0026gt; the terminal, and return the // head. ConnectTerminal(std::move(tail_), std::forward\u0026lt;Args\u0026gt;(terminal_args)...); return WrapAs\u0026lt;OutType\u0026gt;(std::move(head_)); } https://source.chromium.org/chromium/chromium/src/+/main:services/network/public/cpp/url_loader_factory_builder.h;l=123;drc=4f289372eacd2b226e9d2830cc4a87b4ad621133;bpv=1;bpt=1 template \u0026lt;typename OutType\u0026gt; static OutType WrapAs(mojom::NetworkContext* context, mojom::URLLoaderFactoryParamsPtr factory_param) { mojo::PendingRemote\u0026lt;mojom::URLLoaderFactory\u0026gt; remote; context-\u0026gt;CreateURLLoaderFactory(remote.InitWithNewPipeAndPassReceiver(), std::move(factory_param)); return WrapAs\u0026lt;OutType\u0026gt;(std::move(remote)); } https://source.chromium.org/chromium/chromium/src/+/main:services/network/network_context.cc;l=1009;drc=4f289372eacd2b226e9d2830cc4a87b4ad621133;bpv=1;bpt=1 void NetworkContext::CreateURLLoaderFactory( mojo::PendingReceiver\u0026lt;mojom::URLLoaderFactory\u0026gt; receiver, mojom::URLLoaderFactoryParamsPtr params) { scoped_refptr\u0026lt;ResourceSchedulerClient\u0026gt; resource_scheduler_client = base::MakeRefCounted\u0026lt;ResourceSchedulerClient\u0026gt;( ResourceScheduler::ClientId::Create(params-\u0026gt;top_frame_id), IsBrowserInitiated(params-\u0026gt;process_id == mojom::kBrowserProcessId), resource_scheduler_.get(), url_request_context_-\u0026gt;network_quality_estimator()); CreateURLLoaderFactory(std::move(receiver), std::move(params), std::move(resource_scheduler_client)); } https://source.chromium.org/chromium/chromium/src/+/main:services/network/network_context.cc;drc=4f289372eacd2b226e9d2830cc4a87b4ad621133;bpv=1;bpt=1;l=963 void NetworkContext::CreateURLLoaderFactory( mojo::PendingReceiver\u0026lt;mojom::URLLoaderFactory\u0026gt; receiver, mojom::URLLoaderFactoryParamsPtr params, scoped_refptr\u0026lt;ResourceSchedulerClient\u0026gt; resource_scheduler_client) { url_loader_factories_.emplace( std::make_unique\u0026lt;PrefetchMatchingURLLoaderFactory\u0026gt;( this, std::move(params), std::move(resource_scheduler_client), std::move(receiver), \u0026amp;cors_origin_access_list_, prefetch_cache_.get())); } https://source.chromium.org/chromium/chromium/src/+/main:services/network/prefetch_matching_url_loader_factory.cc;l=62?q=PrefetchMatchingURLLoaderFactory\u0026ss=chromium%2Fchromium%2Fsrc void PrefetchMatchingURLLoaderFactory::CreateLoaderAndStart( mojo::PendingReceiver\u0026lt;mojom::URLLoader\u0026gt; loader, int32_t request_id, uint32_t options, ResourceRequest\u0026amp; request, mojo::PendingRemote\u0026lt;mojom::URLLoaderClient\u0026gt; client, const net::MutableNetworkTrafficAnnotationTag\u0026amp; traffic_annotation) { // If we don\u0026#39;t think the request should be permitted from a render process, we // don\u0026#39;t try to match it and instead let CorsURLLoaderFactory deal with the // issue. if (cache_ \u0026amp;\u0026amp; IsRequestSafeForMatching(request)) { PrefetchURLLoaderClient* prefetch_client = cache_-\u0026gt;Lookup( next_-\u0026gt;isolation_info().network_isolation_key(), request.url); if (prefetch_client) { // A prefetch exists with the same NIK and URL. if (prefetch_client-\u0026gt;Matches(request)) { if (use_matches_) { // The match has succeeded, and we are going to hand-off the // in-progress prefetch to the renderer. // TODO(crbug.com/342445996): Check that `options` is compatible with // the prefetch and whether anything needs to be done to the ongoing // request to match it. prefetch_client-\u0026gt;Consume(std::move(loader), std::move(client)); return; } // The match has succeeded, but the kNetworkContextPrefetchUseMatches // feature is not enabled. Try to make it possible for the render // process to reuse the cache entry. // Give the real URLLoader time to reach the cache, then cancel the // prefetch. cache_-\u0026gt;DelayedErase(prefetch_client); } else { // There was an entry with the same NIK and URL, but it failed to match // on some other field. It is unlikely the renderer will subsequently // issue a matching request for the same URL, so erase the cache entry // to save resources. cache_-\u0026gt;Erase(prefetch_client); } } } next_-\u0026gt;CreateLoaderAndStart(std::move(loader), request_id, options, request, std::move(client), traffic_annotation); } https://source.chromium.org/chromium/chromium/src/+/main:services/network/prefetch_matching_url_loader_factory.cc;l=62?q=PrefetchMatchingURLLoaderFactory\u0026ss=chromium%2Fchromium%2Fsrc PrefetchMatchingURLLoaderFactory::PrefetchMatchingURLLoaderFactory( NetworkContext* context, mojom::URLLoaderFactoryParamsPtr params, scoped_refptr\u0026lt;ResourceSchedulerClient\u0026gt; resource_scheduler_client, mojo::PendingReceiver\u0026lt;mojom::URLLoaderFactory\u0026gt; receiver, const cors::OriginAccessList* origin_access_list, PrefetchCache* cache) : ignore_factory_reset_(params-\u0026gt;ignore_factory_reset), next_(std::make_unique\u0026lt;cors::CorsURLLoaderFactory\u0026gt;( context, std::move(params), std::move(resource_scheduler_client), origin_access_list, this)), context_(context), cache_(cache), use_matches_(base::FeatureList::IsEnabled( features::kNetworkContextPrefetchUseMatches)) { receivers_.Add(this, std::move(receiver)); // This use of base::Unretained() is safe because `receivers_` won\u0026#39;t call the // disconnect handler after it has been destroyed. receivers_.set_disconnect_handler(base::BindRepeating( \u0026amp;PrefetchMatchingURLLoaderFactory::OnDisconnect, base::Unretained(this))); } https://source.chromium.org/chromium/chromium/src/+/main:services/network/cors/cors_url_loader_factory.cc;l=212?q=CorsURLLoaderFactory::CorsURLLoaderFactory\u0026ss=chromium%2Fchromium%2Fsrc CorsURLLoaderFactory::CorsURLLoaderFactory( NetworkContext* context, mojom::URLLoaderFactoryParamsPtr params, scoped_refptr\u0026lt;ResourceSchedulerClient\u0026gt; resource_scheduler_client, const OriginAccessList* origin_access_list, PrefetchMatchingURLLoaderFactory* owner) : context_(context), is_trusted_(params-\u0026gt;is_trusted), disable_web_security_(params-\u0026gt;disable_web_security), process_id_(params-\u0026gt;process_id), request_initiator_origin_lock_(params-\u0026gt;request_initiator_origin_lock), ignore_isolated_world_origin_(params-\u0026gt;ignore_isolated_world_origin), trust_token_issuance_policy_(params-\u0026gt;trust_token_issuance_policy), trust_token_redemption_policy_(params-\u0026gt;trust_token_redemption_policy), isolation_info_(params-\u0026gt;isolation_info), automatically_assign_isolation_info_( params-\u0026gt;automatically_assign_isolation_info), debug_tag_(params-\u0026gt;debug_tag), cross_origin_embedder_policy_( params-\u0026gt;client_security_state ? params-\u0026gt;client_security_state-\u0026gt;cross_origin_embedder_policy : CrossOriginEmbedderPolicy()), coep_reporter_(std::move(params-\u0026gt;coep_reporter)), dip_reporter_(std::move(params-\u0026gt;dip_reporter)), client_security_state_(params-\u0026gt;client_security_state.Clone()), url_loader_network_service_observer_( std::move(params-\u0026gt;url_loader_network_observer)), shared_dictionary_observer_( std::move(params-\u0026gt;shared_dictionary_observer)), require_cross_site_request_for_cookies_( params-\u0026gt;require_cross_site_request_for_cookies), factory_cookie_setting_overrides_(params-\u0026gt;cookie_setting_overrides), devtools_cookie_setting_overrides_( params-\u0026gt;devtools_cookie_setting_overrides), origin_access_list_(origin_access_list), owner_(owner) { TRACE_EVENT(\u0026#34;loading\u0026#34;, \u0026#34;CorsURLLoaderFactory::CorsURLLoaderFactory\u0026#34;, perfetto::Flow::FromPointer(this)); DCHECK(context_); DCHECK(origin_access_list_); DCHECK_NE(mojom::kInvalidProcessId, process_id_); DCHECK_EQ(net::IsolationInfo::RequestType::kOther, params-\u0026gt;isolation_info.request_type()); if (context_-\u0026gt;url_request_context()-\u0026gt;bound_network() != net::handles::kInvalidNetworkHandle) { // CorsURLLoaderFactories bound to a network allow CORS preflight load // options (see CorsURLLoaderFactory::IsCorsPreflighLoadOptionAllowed for // the rationale). To prevent security issues, such a factory must not be // issuing CORS preflight requests itself. This is, for example, to prevent // compromised renderers from using a CorsURLLoaderFactory that doesn\u0026#39;t // trigger CORS preflight requests, but also does not fail requests which // represent CORS preflights. In other words, these two things are mutually // exclusive and a CorsURLLoaderFactory must either: // 1. Perform CORS, but fail loads that represent CORS preflight requests // 2. Allow loads that represent CORS preflight requests, but do not perform // CORS CHECK(disable_web_security_); } if (params-\u0026gt;automatically_assign_isolation_info) { DCHECK(params-\u0026gt;isolation_info.IsEmpty()); // Only the browser process is currently permitted to use automatically // assigned IsolationInfo, to prevent cross-site information leaks. DCHECK_EQ(mojom::kBrowserProcessId, process_id_); } if (context_-\u0026gt;GetSharedDictionaryManager() \u0026amp;\u0026amp; client_security_state_ \u0026amp;\u0026amp; client_security_state_-\u0026gt;is_web_secure_context) { std::optional\u0026lt;net::SharedDictionaryIsolationKey\u0026gt; isolation_key = net::SharedDictionaryIsolationKey::MaybeCreate(params-\u0026gt;isolation_info); if (isolation_key) { shared_dictionary_storage_ = context_-\u0026gt;GetSharedDictionaryManager()-\u0026gt;GetStorage(*isolation_key); } } auto factory_override = std::move(params-\u0026gt;factory_override); auto network_loader_factory = std::make_unique\u0026lt;network::URLLoaderFactory\u0026gt;( context, std::move(params), std::move(resource_scheduler_client), this); if (factory_override) { DCHECK(factory_override-\u0026gt;overriding_factory); factory_override_ = std::make_unique\u0026lt;FactoryOverride\u0026gt;( std::move(factory_override), std::move(network_loader_factory)); } else { network_loader_factory_ = std::move(network_loader_factory); } } https://source.chromium.org/chromium/chromium/src/+/main:services/network/url_loader_factory.cc;l=85?q=URLLoaderFactory::URLLoaderFactory\u0026ss=chromium%2Fchromium%2Fsrc URLLoaderFactory::URLLoaderFactory( NetworkContext* context, mojom::URLLoaderFactoryParamsPtr params, scoped_refptr\u0026lt;ResourceSchedulerClient\u0026gt; resource_scheduler_client, cors::CorsURLLoaderFactory* cors_url_loader_factory) : context_(context), params_(std::move(params)), resource_scheduler_client_(std::move(resource_scheduler_client)), header_client_(std::move(params_-\u0026gt;header_client)), cors_url_loader_factory_(cors_url_loader_factory), cookie_observer_(std::move(params_-\u0026gt;cookie_observer)), trust_token_observer_(std::move(params_-\u0026gt;trust_token_observer)), devtools_observer_(std::move(params_-\u0026gt;devtools_observer)), device_bound_session_observer_( params_-\u0026gt;device_bound_session_observer ? base::MakeRefCounted\u0026lt; RefCountedDeviceBoundSessionAccessObserverRemote\u0026gt;( mojo::Remote\u0026lt;mojom::DeviceBoundSessionAccessObserver\u0026gt;( std::move(params_-\u0026gt;device_bound_session_observer))) : nullptr) { DCHECK(context); DCHECK_NE(mojom::kInvalidProcessId, params_-\u0026gt;process_id); DCHECK(!params_-\u0026gt;factory_override); // Only non-navigation IsolationInfos should be bound to URLLoaderFactories. DCHECK_EQ(net::IsolationInfo::RequestType::kOther, params_-\u0026gt;isolation_info.request_type()); DCHECK(!params_-\u0026gt;automatically_assign_isolation_info || params_-\u0026gt;isolation_info.IsEmpty()); DCHECK(cors_url_loader_factory_); if (!params_-\u0026gt;top_frame_id) { params_-\u0026gt;top_frame_id = base::UnguessableToken::Create(); } if (context_-\u0026gt;network_service()) { context_-\u0026gt;network_service()-\u0026gt;keepalive_statistics_recorder()-\u0026gt;Register( *params_-\u0026gt;top_frame_id); } } https://source.chromium.org/chromium/chromium/src/+/main:services/network/cors/cors_url_loader_factory.cc;l=357?q=CorsURLLoaderFactory::CorsURLLoaderFactory\u0026ss=chromium%2Fchromium%2Fsrc void CorsURLLoaderFactory::CreateLoaderAndStart( mojo::PendingReceiver\u0026lt;mojom::URLLoader\u0026gt; receiver, int32_t request_id, uint32_t options, ResourceRequest\u0026amp; resource_request, mojo::PendingRemote\u0026lt;mojom::URLLoaderClient\u0026gt; client, const net::MutableNetworkTrafficAnnotationTag\u0026amp; traffic_annotation) { TRACE_EVENT(\u0026#34;loading\u0026#34;, \u0026#34;CorsURLLoaderFactory::CreateLoaderAndStart\u0026#34;, perfetto::Flow::FromPointer(this)); std::optional\u0026lt;base::ElapsedTimer\u0026gt; timer; std::optional\u0026lt;base::ElapsedThreadTimer\u0026gt; thread_timer; if (metrics_subsampler_.ShouldSample(0.001)) { timer.emplace(); if (base::ThreadTicks::IsSupported()) { thread_timer.emplace(); } } debug::ScopedResourceRequestCrashKeys request_crash_keys(resource_request); SCOPED_CRASH_KEY_NUMBER(\u0026#34;net\u0026#34;, \u0026#34;traffic_annotation_hash\u0026#34;, traffic_annotation.unique_id_hash_code); SCOPED_CRASH_KEY_STRING64(\u0026#34;network\u0026#34;, \u0026#34;factory_debug_tag\u0026#34;, debug_tag_); if (!IsValidRequest(resource_request, options)) { mojo::Remote\u0026lt;mojom::URLLoaderClient\u0026gt;(std::move(client)) -\u0026gt;OnComplete(URLLoaderCompletionStatus(net::ERR_INVALID_ARGUMENT)); return; } if (resource_request.destination == network::mojom::RequestDestination::kWebBundle) { DCHECK(resource_request.web_bundle_token_params.has_value()); mojo::PendingRemote\u0026lt;mojom::DevToolsObserver\u0026gt; devtools_observer; if (resource_request.devtools_request_id.has_value()) { devtools_observer = GetDevToolsObserver(resource_request); } base::WeakPtr\u0026lt;WebBundleURLLoaderFactory\u0026gt; web_bundle_url_loader_factory = context_-\u0026gt;GetWebBundleManager().CreateWebBundleURLLoaderFactory( resource_request.url, *resource_request.web_bundle_token_params, process_id_, std::move(devtools_observer), resource_request.devtools_request_id, cross_origin_embedder_policy_, coep_reporter()); client = web_bundle_url_loader_factory-\u0026gt;MaybeWrapURLLoaderClient( std::move(client)); if (!client) { return; } } mojom::URLLoaderFactory* const inner_url_loader_factory = factory_override_ ? factory_override_-\u0026gt;get() : network_loader_factory_.get(); DCHECK(inner_url_loader_factory); const net::IsolationInfo* isolation_info_ptr = \u0026amp;isolation_info_; auto isolation_info = url_loader_util::GetIsolationInfo( isolation_info_, automatically_assign_isolation_info_, resource_request); if (isolation_info.has_value()) { isolation_info_ptr = \u0026amp;isolation_info.value(); } // Check if the initiator\u0026#39;s network access has been revoked. // This check is only relevant if there is a partition nonce in the // isolation info. (All requests originating from a fenced frame have a // nonce specified.) if (isolation_info.has_value() \u0026amp;\u0026amp; isolation_info-\u0026gt;nonce().has_value() \u0026amp;\u0026amp; !context_-\u0026gt;IsNetworkForNonceAndUrlAllowed(*isolation_info-\u0026gt;nonce(), resource_request.url)) { mojo::Remote\u0026lt;mojom::URLLoaderClient\u0026gt;(std::move(client)) -\u0026gt;OnComplete( URLLoaderCompletionStatus(net::ERR_NETWORK_ACCESS_REVOKED)); return; } if (!disable_web_security_) { mojo::PendingRemote\u0026lt;mojom::DevToolsObserver\u0026gt; devtools_observer; const bool always_clone = !base::FeatureList::IsEnabled( network::features::kCloneDevToolsConnectionOnlyIfRequested); if (always_clone || resource_request.devtools_request_id.has_value()) { devtools_observer = GetDevToolsObserver(resource_request); } scoped_refptr\u0026lt;SharedDictionaryStorage\u0026gt; shared_dictionary_storage = shared_dictionary_storage_; if (context_-\u0026gt;GetSharedDictionaryManager() \u0026amp;\u0026amp; IsTrustedNavigationRequestFromSecureContext(resource_request)) { // For trusted navigation requests, we need to get a storage using // `isolation_info_ptr`. std::optional\u0026lt;net::SharedDictionaryIsolationKey\u0026gt; isolation_key = net::SharedDictionaryIsolationKey::MaybeCreate(*isolation_info_ptr); if (isolation_key) { shared_dictionary_storage = context_-\u0026gt;GetSharedDictionaryManager()-\u0026gt;GetStorage(*isolation_key); } } std::unique_ptr\u0026lt;CorsURLLoader\u0026gt; loader; if (base::FeatureList::IsEnabled( network::features::kAvoidResourceRequestCopies)) { loader = std::make_unique\u0026lt;CorsURLLoader\u0026gt;( std::move(receiver), process_id_, request_id, options, base::BindOnce(\u0026amp;CorsURLLoaderFactory::DestroyCorsURLLoader, base::Unretained(this)), std::move(resource_request), ignore_isolated_world_origin_, factory_override_ \u0026amp;\u0026amp; factory_override_-\u0026gt;ShouldSkipCorsEnabledSchemeCheck(), std::move(client), traffic_annotation, inner_url_loader_factory, factory_override_ ? nullptr : network_loader_factory_.get(), origin_access_list_, GetAllowAnyCorsExemptHeaderForBrowser(), *isolation_info_ptr, std::move(devtools_observer), client_security_state_.get(), \u0026amp;url_loader_network_service_observer_, cross_origin_embedder_policy_, shared_dictionary_storage, shared_dictionary_observer_ ? shared_dictionary_observer_.get() : nullptr, context_, factory_cookie_setting_overrides_, devtools_cookie_setting_overrides_); } else { loader = std::make_unique\u0026lt;CorsURLLoader\u0026gt;( std::move(receiver), process_id_, request_id, options, base::BindOnce(\u0026amp;CorsURLLoaderFactory::DestroyCorsURLLoader, base::Unretained(this)), resource_request, ignore_isolated_world_origin_, factory_override_ \u0026amp;\u0026amp; factory_override_-\u0026gt;ShouldSkipCorsEnabledSchemeCheck(), std::move(client), traffic_annotation, inner_url_loader_factory, factory_override_ ? nullptr : network_loader_factory_.get(), origin_access_list_, GetAllowAnyCorsExemptHeaderForBrowser(), *isolation_info_ptr, std::move(devtools_observer), client_security_state_.get(), \u0026amp;url_loader_network_service_observer_, cross_origin_embedder_policy_, shared_dictionary_storage, shared_dictionary_observer_ ? shared_dictionary_observer_.get() : nullptr, context_, factory_cookie_setting_overrides_, devtools_cookie_setting_overrides_); } auto* raw_loader = loader.get(); OnCorsURLLoaderCreated(std::move(loader)); raw_loader-\u0026gt;Start(); } else { inner_url_loader_factory-\u0026gt;CreateLoaderAndStart( std::move(receiver), request_id, options, resource_request, std::move(client), traffic_annotation); } if (timer.has_value()) { UMA_HISTOGRAM_CUSTOM_MICROSECONDS_TIMES( \u0026#34;NetworkService.CorsURLLoaderFactory.CreateLoaderAndStart2.Duration\u0026#34;, timer-\u0026gt;Elapsed(), base::Microseconds(1), base::Milliseconds(16), 100); } if (thread_timer.has_value()) { UMA_HISTOGRAM_CUSTOM_MICROSECONDS_TIMES( \u0026#34;NetworkService.CorsURLLoaderFactory.CreateLoaderAndStart2.\u0026#34; \u0026#34;ThreadDuration\u0026#34;, thread_timer-\u0026gt;Elapsed(), base::Microseconds(1), base::Milliseconds(16), 100); } } このメソッドでraw_loader-\u0026gt;Start()を呼んでいる箇所がある\nhttps://source.chromium.org/chromium/chromium/src/+/main:services/network/cors/cors_url_loader.cc;l=293;bpv=1;bpt=1?q=CorsURLLoader::CorsURLLoader\u0026ss=chromium%2Fchromium%2Fsrc CorsURLLoader::CorsURLLoader( mojo::PendingReceiver\u0026lt;mojom::URLLoader\u0026gt; loader_receiver, int32_t process_id, int32_t request_id, uint32_t options, DeleteCallback delete_callback, ResourceRequest resource_request, bool ignore_isolated_world_origin, bool skip_cors_enabled_scheme_check, mojo::PendingRemote\u0026lt;mojom::URLLoaderClient\u0026gt; client, const net::MutableNetworkTrafficAnnotationTag\u0026amp; traffic_annotation, mojom::URLLoaderFactory* network_loader_factory, URLLoaderFactory* sync_network_loader_factory, const OriginAccessList* origin_access_list, bool allow_any_cors_exempt_header, const net::IsolationInfo\u0026amp; isolation_info, mojo::PendingRemote\u0026lt;mojom::DevToolsObserver\u0026gt; devtools_observer, const mojom::ClientSecurityState* factory_client_security_state, mojo::Remote\u0026lt;mojom::URLLoaderNetworkServiceObserver\u0026gt;* url_loader_network_service_observer, const CrossOriginEmbedderPolicy\u0026amp; cross_origin_embedder_policy, scoped_refptr\u0026lt;SharedDictionaryStorage\u0026gt; shared_dictionary_storage, raw_ptr\u0026lt;mojom::SharedDictionaryAccessObserver\u0026gt; shared_dictionary_observer, NetworkContext* context, net::CookieSettingOverrides factory_cookie_setting_overrides, net::CookieSettingOverrides devtools_cookie_setting_overrides) : receiver_(this, std::move(loader_receiver)), process_id_(process_id), request_id_(request_id), options_(options), delete_callback_(std::move(delete_callback)), network_loader_factory_(network_loader_factory), sync_network_loader_factory_(sync_network_loader_factory), request_(std::move(resource_request)), forwarding_client_(std::move(client)), traffic_annotation_(traffic_annotation), origin_access_list_(origin_access_list), skip_cors_enabled_scheme_check_(skip_cors_enabled_scheme_check), allow_any_cors_exempt_header_(allow_any_cors_exempt_header), isolation_info_(isolation_info), factory_client_security_state_(factory_client_security_state), url_loader_network_service_observer_(url_loader_network_service_observer), cross_origin_embedder_policy_(cross_origin_embedder_policy), devtools_observer_(std::move(devtools_observer)), weak_devtools_observer_factory_(\u0026amp;devtools_observer_), // CORS preflight related events are logged in a series of URL_REQUEST // logs. net_log_(net::NetLogWithSource::Make(net::NetLog::Get(), net::NetLogSourceType::URL_REQUEST)), context_(context), shared_dictionary_storage_(std::move(shared_dictionary_storage)), shared_dictionary_observer_(shared_dictionary_observer), factory_cookie_setting_overrides_(factory_cookie_setting_overrides), devtools_cookie_setting_overrides_(devtools_cookie_setting_overrides) { TRACE_EVENT(\u0026#34;loading\u0026#34;, \u0026#34;CorsURLLoader::CorsURLLoader\u0026#34;, net::NetLogWithSourceToFlow(net_log_), \u0026#34;url\u0026#34;, request_.url.spec(), \u0026#34;process_id\u0026#34;, process_id_, \u0026#34;request_id\u0026#34;, request_id_, \u0026#34;traffic_annotation_id\u0026#34;, traffic_annotation_.unique_id_hash_code); CHECK(url_loader_network_service_observer_ != nullptr); if (ignore_isolated_world_origin) request_.isolated_world_origin = std::nullopt; receiver_.set_disconnect_handler( base::BindOnce(\u0026amp;CorsURLLoader::OnMojoDisconnect, base::Unretained(this))); request_.net_log_create_info = net_log_.source(); DCHECK(network_loader_factory_); DCHECK(origin_access_list_); SetCorsFlagIfNeeded(); if (shared_dictionary_storage_) { if (request_.mode != mojom::RequestMode::kNoCors) { request_.load_flags |= net::LOAD_CAN_USE_SHARED_DICTIONARY; } else if (request_.request_initiator \u0026amp;\u0026amp; request_.request_initiator-\u0026gt;IsSameOriginWith(request_.url)) { // For no-cors mode requests, we can use shared dictionaries only for same // origin requests. When redirected to another origin, // net::URLRequest::Redirect() disables the LOAD_CAN_USE_SHARED_DICTIONARY // flag. request_.load_flags |= net::LOAD_CAN_USE_SHARED_DICTIONARY; request_.load_flags |= net::LOAD_DISABLE_SHARED_DICTIONARY_AFTER_CROSS_ORIGIN_REDIRECT; } // This is intended to load the dictionary as soon as possible. Without // this, the dictionary will be loaded from the disk when // `HttpNetworkTransaction` builds the request header just before sending it // to the server. shared_dictionary_storage_-\u0026gt;GetDictionary( request_.url, request_.destination, base::BindOnce( [](base::WeakPtr\u0026lt;CorsURLLoader\u0026gt; loader, scoped_refptr\u0026lt;net::SharedDictionary\u0026gt; shared_dictionary) { if (loader) { loader-\u0026gt;shared_dictionary_ = std::move(shared_dictionary); } }, weak_factory_.GetWeakPtr())); } } https://source.chromium.org/chromium/chromium/src/+/main:services/network/cors/cors_url_loader.cc;l=293?q=CorsURLLoader::CorsURLLoader\u0026ss=chromium%2Fchromium%2Fsrc void CorsURLLoader::Start() { TRACE_EVENT(\u0026#34;loading\u0026#34;, \u0026#34;CorsURLLoader::Start\u0026#34;, net::NetLogWithSourceToFlow(net_log_)); if (fetch_cors_flag_ \u0026amp;\u0026amp; IsCorsEnabledRequestMode(request_.mode)) { // Username and password should be stripped in a CORS-enabled request. if (request_.url.has_username() || request_.url.has_password()) { GURL::Replacements replacements; replacements.SetUsernameStr(\u0026#34;\u0026#34;); replacements.SetPasswordStr(\u0026#34;\u0026#34;); request_.url = request_.url.ReplaceComponents(replacements); } } last_response_url_ = request_.url; net_log_.BeginEvent(net::NetLogEventType::CORS_REQUEST, [\u0026amp;] { return NetLogCorsURLLoaderStartParams(request_); }); StartRequest(); } StartRequest https://source.chromium.org/chromium/chromium/src/+/main:services/network/cors/cors_url_loader.cc;drc=4f289372eacd2b226e9d2830cc4a87b4ad621133;l=885?q=CorsURLLoader::CorsURLLoader\u0026ss=chromium%2Fchromium%2Fsrc void CorsURLLoader::StartRequest() { TRACE_EVENT(\u0026#34;loading\u0026#34;, \u0026#34;CorsURLLoader::StartRequest\u0026#34;, net::NetLogWithSourceToFlow(net_log_)); // All results should be reported to `forwarding_client_` as part of a // `URLResponseHead`, then `pna_preflight_result_` reset to `kNone`. CHECK_EQ(pna_preflight_result_, mojom::PrivateNetworkAccessPreflightResult::kNone); if (fetch_cors_flag_ \u0026amp;\u0026amp; !skip_cors_enabled_scheme_check_ \u0026amp;\u0026amp; !base::Contains(url::GetCorsEnabledSchemes(), request_.url.scheme())) { HandleComplete(URLLoaderCompletionStatus( CorsErrorStatus(mojom::CorsError::kCorsDisabledScheme))); return; } auto should_include_origin_header = [\u0026amp;]() -\u0026gt; bool { if (!request_.request_initiator) { return false; } if (request_.credentials_mode == mojom::CredentialsMode::kInclude \u0026amp;\u0026amp; GetStorageAccessStatus() == net::cookie_util::StorageAccessStatus::kInactive) { // Lower layers will add the Sec-Fetch-Storage-Access header, and the // server may respond with a \u0026#34;retry\u0026#34; header. The server needs to know the // origin in that event. return true; } // If the `CORS flag` is set, `httpRequest`’s method is neither `GET` nor // `HEAD`, or `httpRequest`’s mode is \u0026#34;websocket\u0026#34;, then append // `Origin`/the result of serializing a request origin with `httpRequest`, // to `httpRequest`’s header list. // // We exclude navigation requests to keep the existing behavior. // TODO(yhirano): Reconsider this. if (request_.mode == network::mojom::RequestMode::kNavigate) { return false; } if (fetch_cors_flag_) { return true; } return request_.method != net::HttpRequestHeaders::kGetMethod \u0026amp;\u0026amp; request_.method != net::HttpRequestHeaders::kHeadMethod; }; if (should_include_origin_header()) { if (tainted_) { request_.headers.SetHeader(net::HttpRequestHeaders::kOrigin, url::Origin().Serialize()); } else { request_.headers.SetHeader(net::HttpRequestHeaders::kOrigin, request_.request_initiator-\u0026gt;Serialize()); } } if (fetch_cors_flag_ \u0026amp;\u0026amp; request_.mode == mojom::RequestMode::kSameOrigin) { DCHECK(request_.request_initiator); HandleComplete(URLLoaderCompletionStatus( CorsErrorStatus(mojom::CorsError::kDisallowedByMode))); return; } response_tainting_ = CalculateResponseTainting( request_.url, request_.mode, request_.request_initiator, request_.isolated_world_origin, fetch_cors_flag_, tainted_, *origin_access_list_); // Note that even when `needs_preflight` holds we might not make a preflight // request. This happens when `fetch_cors_flag_` is false, e.g. when the // origin of the url is equal to the origin of the request, and the preflight // reason is not `kPrivateNetworkAccess`. In the case of a private network // access we always send a preflight, even for CORS-disabled requests. // // See the first step of the HTTP-no-service-worker fetch algorithm defined in // the Private Network Access spec: // https://wicg.github.io/private-network-access/#http-no-service-worker-fetch std::optional\u0026lt;PreflightRequiredReason\u0026gt; needs_preflight = NeedsPreflight(request_); bool preflight_required = needs_preflight.has_value() \u0026amp;\u0026amp; (fetch_cors_flag_ || *needs_preflight == PreflightRequiredReason::kPrivateNetworkAccess); net_log_.AddEvent(net::NetLogEventType::CHECK_CORS_PREFLIGHT_REQUIRED, [\u0026amp;] { return NetLogPreflightRequiredParams(needs_preflight); }); has_authorization_covered_by_wildcard_ = false; if (!preflight_required) { StartNetworkRequest(); return; } preflight_mode_.Clear(); if (fetch_cors_flag_ \u0026amp;\u0026amp; NeedsCorsPreflight(request_).has_value()) { preflight_mode_.Put(PreflightController::PreflightType::kCors); } if (NeedsPrivateNetworkAccessPreflight(request_).has_value()) { preflight_mode_.Put( PreflightController::PreflightType::kPrivateNetworkAccess); } CHECK(!preflight_mode_.empty()); // Since we\u0026#39;re doing a preflight, we won\u0026#39;t reuse the original request. Cancel // it now to free up the socket. network_loader_.reset(); mojo::PendingRemote\u0026lt;mojom::URLLoaderNetworkServiceObserver\u0026gt; remote_observer; if (needs_preflight.has_value() \u0026amp;\u0026amp; *needs_preflight == PreflightRequiredReason::kPrivateNetworkAccess) { // TODO(crbug.com/40229602): Create a base function and clean up all // need_pna_permission check in the code base. const mojom::ClientSecurityState* state = GetClientSecurityState(); const bool needs_pna_permission = state \u0026amp;\u0026amp; PrivateNetworkAccessChecker::NeedPermission( request_.url, state-\u0026gt;is_web_secure_context, request_.required_ip_address_space); if (needs_pna_permission \u0026amp;\u0026amp; url_loader_network_service_observer_-\u0026gt;is_bound()) { // Fail the request if `targetAddressSpace` on fetch option is not the // same as the real target address space. if (request_.required_ip_address_space != request_.target_ip_address_space) { HandleComplete(URLLoaderCompletionStatus( CorsErrorStatus(mojom::CorsError::kInvalidPrivateNetworkAccess))); return; } (*url_loader_network_service_observer_) -\u0026gt;Clone(remote_observer.InitWithNewPipeAndPassReceiver()); } } context_-\u0026gt;cors_preflight_controller()-\u0026gt;PerformPreflightCheck( base::BindOnce(\u0026amp;CorsURLLoader::OnPreflightRequestComplete, weak_factory_.GetWeakPtr()), request_, PreflightController::WithTrustedHeaderClient( options_ \u0026amp; mojom::kURLLoadOptionUseHeaderClient), context_-\u0026gt;cors_non_wildcard_request_headers_support(), GetPrivateNetworkAccessPreflightBehavior( request_.required_ip_address_space), tainted_, net::NetworkTrafficAnnotationTag(traffic_annotation_), network_loader_factory_, isolation_info_, CloneClientSecurityState(), weak_devtools_observer_factory_.GetWeakPtr(), net_log_, context_-\u0026gt;acam_preflight_spec_conformant(), std::move(remote_observer), preflight_mode_); } StartNetworkRequest関数(!prefilight_required) https://source.chromium.org/chromium/chromium/src/+/main:services/network/cors/cors_url_loader.cc;drc=4f289372eacd2b226e9d2830cc4a87b4ad621133;l=1163?q=CorsURLLoader::CorsURLLoader\u0026ss=chromium%2Fchromium%2Fsrc void CorsURLLoader::StartNetworkRequest() { TRACE_EVENT(\u0026#34;loading\u0026#34;, \u0026#34;CorsURLLoader::StartNetworkRequest\u0026#34;, net::NetLogWithSourceToFlow(net_log_)); // Here we overwrite the credentials mode sent to URLLoader because // network::URLLoader doesn\u0026#39;t understand |kSameOrigin|. // TODO(crbug.com/40619226): Fix this. auto original_credentials_mode = request_.credentials_mode; if (original_credentials_mode == mojom::CredentialsMode::kSameOrigin) { request_.credentials_mode = CalculateCredentialsFlag(original_credentials_mode, response_tainting_) ? mojom::CredentialsMode::kInclude : mojom::CredentialsMode::kOmit; } // Binding |this| as an unretained pointer is safe because // |network_client_receiver_| shares this object\u0026#39;s lifetime. network_loader_.reset(); network_loader_start_time_ = base::TimeTicks::Now(); if (sync_network_loader_factory_) { sync_network_loader_factory_-\u0026gt;CreateLoaderAndStartWithSyncClient( network_loader_.BindNewPipeAndPassReceiver(), request_id_, options_, request_, network_client_receiver_.BindNewPipeAndPassRemote(), sync_client_receiver_factory_.GetWeakPtr(), traffic_annotation_); } else { network_loader_factory_-\u0026gt;CreateLoaderAndStart( network_loader_.BindNewPipeAndPassReceiver(), request_id_, options_, request_, network_client_receiver_.BindNewPipeAndPassRemote(), traffic_annotation_); } network_client_receiver_.set_disconnect_handler(base::BindOnce( \u0026amp;CorsURLLoader::OnNetworkClientMojoDisconnect, base::Unretained(this))); request_.credentials_mode = original_credentials_mode; } https://source.chromium.org/chromium/chromium/src/+/main:services/network/url_loader_factory.cc;l=132;drc=4f289372eacd2b226e9d2830cc4a87b4ad621133;bpv=1;bpt=1 void URLLoaderFactory::CreateLoaderAndStart( mojo::PendingReceiver\u0026lt;mojom::URLLoader\u0026gt; receiver, int32_t request_id, uint32_t options, const ResourceRequest\u0026amp; resource_request, mojo::PendingRemote\u0026lt;mojom::URLLoaderClient\u0026gt; client, const net::MutableNetworkTrafficAnnotationTag\u0026amp; traffic_annotation) { CreateLoaderAndStartWithSyncClient(std::move(receiver), request_id, options, resource_request, std::move(client), /* sync_client= */ nullptr, traffic_annotation); } https://source.chromium.org/chromium/chromium/src/+/main:services/network/url_loader_factory.cc;drc=4f289372eacd2b226e9d2830cc4a87b4ad621133;bpv=1;bpt=1;l=200 void URLLoaderFactory::CreateLoaderAndStartWithSyncClient( mojo::PendingReceiver\u0026lt;mojom::URLLoader\u0026gt; receiver, int32_t request_id, uint32_t options, const ResourceRequest\u0026amp; resource_request, mojo::PendingRemote\u0026lt;mojom::URLLoaderClient\u0026gt; client, base::WeakPtr\u0026lt;mojom::URLLoaderClient\u0026gt; sync_client, const net::MutableNetworkTrafficAnnotationTag\u0026amp; traffic_annotation) { // Requests with |trusted_params| when params_-\u0026gt;is_trusted is not set should // have been rejected at the CorsURLLoader layer. DCHECK(!resource_request.trusted_params || params_-\u0026gt;is_trusted); if (resource_request.web_bundle_token_params.has_value() \u0026amp;\u0026amp; resource_request.destination != network::mojom::RequestDestination::kWebBundle) { mojo::Remote\u0026lt;mojom::TrustedHeaderClient\u0026gt; trusted_header_client; if (header_client_ \u0026amp;\u0026amp; (options \u0026amp; mojom::kURLLoadOptionUseHeaderClient)) { // CORS preflight request must not come here. DCHECK(!(options \u0026amp; mojom::kURLLoadOptionAsCorsPreflight)); header_client_-\u0026gt;OnLoaderCreated( request_id, trusted_header_client.BindNewPipeAndPassReceiver()); } // Load a subresource from a WebBundle. context_-\u0026gt;GetWebBundleManager().StartSubresourceRequest( std::move(receiver), resource_request, std::move(client), params_-\u0026gt;process_id, std::move(trusted_header_client)); return; } base::WeakPtr\u0026lt;KeepaliveStatisticsRecorder\u0026gt; keepalive_statistics_recorder; if (context_-\u0026gt;network_service()) { keepalive_statistics_recorder = context_-\u0026gt;network_service() -\u0026gt;keepalive_statistics_recorder() -\u0026gt;AsWeakPtr(); } bool exhausted = false; if (!context_-\u0026gt;CanCreateLoader(params_-\u0026gt;process_id)) { exhausted = true; } int keepalive_request_size = 0; if (resource_request.keepalive) { base::UmaHistogramEnumeration( \u0026#34;FetchKeepAlive.Requests2.Network\u0026#34;, internal::FetchKeepAliveRequestNetworkMetricType::kOnCreate); } if (resource_request.keepalive \u0026amp;\u0026amp; keepalive_statistics_recorder) { const size_t url_size = resource_request.url.spec().size(); size_t headers_size = 0; net::HttpRequestHeaders merged_headers = resource_request.headers; merged_headers.MergeFrom(resource_request.cors_exempt_headers); for (const auto\u0026amp; pair : merged_headers.GetHeaderVector()) { headers_size += (pair.key.size() + pair.value.size()); } keepalive_request_size = url_size + headers_size; const auto\u0026amp; top_frame_id = *params_-\u0026gt;top_frame_id; const auto\u0026amp; recorder = *keepalive_statistics_recorder; if (!exhausted) { if (recorder.num_inflight_requests() \u0026gt;= kMaxKeepaliveConnections || recorder.NumInflightRequestsPerTopLevelFrame(top_frame_id) \u0026gt;= kMaxKeepaliveConnectionsPerTopLevelFrame || recorder.GetTotalRequestSizePerTopLevelFrame(top_frame_id) + keepalive_request_size \u0026gt; kMaxTotalKeepaliveRequestSize) { exhausted = true; } } } if (exhausted) { URLLoaderCompletionStatus status; status.error_code = net::ERR_INSUFFICIENT_RESOURCES; status.exists_in_cache = false; status.completion_time = base::TimeTicks::Now(); mojo::Remote\u0026lt;mojom::URLLoaderClient\u0026gt;(std::move(client))-\u0026gt;OnComplete(status); return; } std::unique_ptr\u0026lt;TrustTokenRequestHelperFactory\u0026gt; trust_token_factory; if (resource_request.trust_token_params) { trust_token_factory = std::make_unique\u0026lt;TrustTokenRequestHelperFactory\u0026gt;( context_-\u0026gt;trust_token_store(), context_-\u0026gt;network_service()-\u0026gt;trust_token_key_commitments(), // It\u0026#39;s safe to use Unretained because |context_| is guaranteed to // outlive the URLLoader that will own this // TrustTokenRequestHelperFactory. base::BindRepeating(\u0026amp;NetworkContext::client, base::Unretained(context_)), // It\u0026#39;s safe to access cookie manager for |context_| here because // NetworkContext::CookieManager outlives the URLLoaders associated with // the NetworkContext. base::BindRepeating( [](NetworkContext* context, const GURL\u0026amp; resource_request_url, const GURL\u0026amp; top_frame_origin) { // Private state tokens will be blocked if the user has either // disabled the anti-abuse content setting or blocked the top // level site or issuer from storing data through the cookie // content settings. return ( // PST is not disabled through settings. !context-\u0026gt;are_trust_tokens_blocked() \u0026amp;\u0026amp; // and top frame is not blocked. context-\u0026gt;cookie_manager() -\u0026gt;cookie_settings() .ArePrivateStateTokensAllowed(top_frame_origin) \u0026amp;\u0026amp; // and issuer is not blocked. context-\u0026gt;cookie_manager() -\u0026gt;cookie_settings() .ArePrivateStateTokensAllowed(resource_request_url)); }, base::Unretained(context_), resource_request.url, params_-\u0026gt;isolation_info.top_frame_origin() .value_or(url::Origin()) .GetURL())); } std::unique_ptr\u0026lt;SharedDictionaryAccessChecker\u0026gt; shared_dictionary_checker; if (context_-\u0026gt;GetSharedDictionaryManager()) { if (resource_request.trusted_params \u0026amp;\u0026amp; resource_request.trusted_params-\u0026gt;shared_dictionary_observer) { shared_dictionary_checker = std::make_unique\u0026lt;SharedDictionaryAccessChecker\u0026gt;( *context_, std::move(const_cast\u0026lt;mojo::PendingRemote\u0026lt; mojom::SharedDictionaryAccessObserver\u0026gt;\u0026amp;\u0026gt;( resource_request.trusted_params -\u0026gt;shared_dictionary_observer))); } else { shared_dictionary_checker = std::make_unique\u0026lt;SharedDictionaryAccessChecker\u0026gt;( *context_, cors_url_loader_factory_-\u0026gt;GetSharedDictionaryAccessObserver()); } } auto cookie_observer = CreateObserverWrapper\u0026lt;mojom::CookieAccessObserver\u0026gt;( resource_request.trusted_params, \u0026amp;ResourceRequest::TrustedParams::cookie_observer, cookie_observer_); auto trust_token_observer = CreateObserverWrapper\u0026lt;mojom::TrustTokenAccessObserver\u0026gt;( resource_request.trusted_params, \u0026amp;ResourceRequest::TrustedParams::trust_token_observer, trust_token_observer_); auto url_loader_network_observer = CreateObserverWrapper\u0026lt;mojom::URLLoaderNetworkServiceObserver\u0026gt;( resource_request.trusted_params, \u0026amp;ResourceRequest::TrustedParams::url_loader_network_observer, GetURLLoaderNetworkServiceObserver()); auto devtools_observer = CreateObserverWrapper\u0026lt;mojom::DevToolsObserver\u0026gt;( resource_request.trusted_params, \u0026amp;ResourceRequest::TrustedParams::devtools_observer, devtools_observer_); auto device_bound_session_observer = CreateObserverWrapper\u0026lt;mojom::DeviceBoundSessionAccessObserver\u0026gt;( resource_request.trusted_params, \u0026amp;ResourceRequest::TrustedParams::device_bound_session_observer, device_bound_session_observer_ ? device_bound_session_observer_-\u0026gt;data.get() : nullptr); mojo::PendingRemote\u0026lt;mojom::AcceptCHFrameObserver\u0026gt; accept_ch_frame_observer; if (resource_request.trusted_params \u0026amp;\u0026amp; resource_request.trusted_params-\u0026gt;accept_ch_frame_observer) { accept_ch_frame_observer = std::move( const_cast\u0026lt;mojo::PendingRemote\u0026lt;mojom::AcceptCHFrameObserver\u0026gt;\u0026amp;\u0026gt;( resource_request.trusted_params-\u0026gt;accept_ch_frame_observer)); } auto loader = std::make_unique\u0026lt;URLLoader\u0026gt;( *this, base::BindOnce(\u0026amp;cors::CorsURLLoaderFactory::DestroyURLLoader, base::Unretained(cors_url_loader_factory_)), std::move(receiver), options, resource_request, std::move(client), std::move(sync_client), static_cast\u0026lt;net::NetworkTrafficAnnotationTag\u0026gt;(traffic_annotation), request_id, keepalive_request_size, std::move(keepalive_statistics_recorder), std::move(trust_token_factory), context_-\u0026gt;GetSharedDictionaryManager(), std::move(shared_dictionary_checker), std::move(cookie_observer), std::move(trust_token_observer), std::move(url_loader_network_observer), std::move(devtools_observer), std::move(device_bound_session_observer), std::move(accept_ch_frame_observer), resource_request.shared_storage_writable_eligible, *context_-\u0026gt;GetSharedResourceChecker()); cors_url_loader_factory_-\u0026gt;OnURLLoaderCreated(std::move(loader)); } https://source.chromium.org/chromium/chromium/src/+/main:services/network/cors/cors_url_loader_factory.h;drc=4f289372eacd2b226e9d2830cc4a87b4ad621133;l=164 template \u0026lt;class T\u0026gt; void OnLoaderCreated( std::unique_ptr\u0026lt;T\u0026gt; loader, std::set\u0026lt;std::unique_ptr\u0026lt;T\u0026gt;, base::UniquePtrComparator\u0026gt;\u0026amp; loaders) { context_-\u0026gt;LoaderCreated(process_id_); loaders.insert(std::move(loader)); } URLLoaderFactoryはURLLoaderContextを継承している。 https://source.chromium.org/chromium/chromium/src/+/main:services/network/url_loader_factory.h;l=50;drc=4f289372eacd2b226e9d2830cc4a87b4ad621133;bpv=1;bpt=1 https://source.chromium.org/chromium/chromium/src/+/main:services/network/url_loader.cc;l=340?q=URLLoader::URLLoader\u0026ss=chromium%2Fchromium%2Fsrc URLLoader::URLLoader( URLLoaderContext\u0026amp; context, DeleteCallback delete_callback, mojo::PendingReceiver\u0026lt;mojom::URLLoader\u0026gt; url_loader_receiver, int32_t options, const ResourceRequest\u0026amp; request, mojo::PendingRemote\u0026lt;mojom::URLLoaderClient\u0026gt; url_loader_client, base::WeakPtr\u0026lt;mojom::URLLoaderClient\u0026gt; sync_url_loader_client, const net::NetworkTrafficAnnotationTag\u0026amp; traffic_annotation, base::StrictNumeric\u0026lt;int32_t\u0026gt; request_id, int keepalive_request_size, base::WeakPtr\u0026lt;KeepaliveStatisticsRecorder\u0026gt; keepalive_statistics_recorder, std::unique_ptr\u0026lt;TrustTokenRequestHelperFactory\u0026gt; trust_token_helper_factory, SharedDictionaryManager* shared_dictionary_manager, std::unique_ptr\u0026lt;SharedDictionaryAccessChecker\u0026gt; shared_dictionary_checker, ObserverWrapper\u0026lt;mojom::CookieAccessObserver\u0026gt; cookie_observer, ObserverWrapper\u0026lt;mojom::TrustTokenAccessObserver\u0026gt; trust_token_observer, ObserverWrapper\u0026lt;mojom::URLLoaderNetworkServiceObserver\u0026gt; url_loader_network_observer, ObserverWrapper\u0026lt;mojom::DevToolsObserver\u0026gt; devtools_observer, ObserverWrapper\u0026lt;mojom::DeviceBoundSessionAccessObserver\u0026gt; device_bound_session_observer, mojo::PendingRemote\u0026lt;mojom::AcceptCHFrameObserver\u0026gt; accept_ch_frame_observer, bool shared_storage_writable_eligible, SharedResourceChecker\u0026amp; shared_resource_checker) : url_request_context_(context.GetUrlRequestContext()), network_context_client_(context.GetNetworkContextClient()), delete_callback_(std::move(delete_callback)), resource_type_(request.resource_type), is_load_timing_enabled_(request.enable_load_timing), factory_params_(context.GetFactoryParams()), coep_reporter_(context.GetCoepReporter()), dip_reporter_(context.GetDipReporter()), request_id_(request_id), keepalive_request_size_(keepalive_request_size), keepalive_(request.keepalive), client_security_state_( request.trusted_params ? request.trusted_params-\u0026gt;client_security_state.Clone() : nullptr), do_not_prompt_for_login_(request.do_not_prompt_for_login), receiver_(this, std::move(url_loader_receiver)), url_loader_client_(std::move(url_loader_client), std::move(sync_url_loader_client)), writable_handle_watcher_(FROM_HERE, mojo::SimpleWatcher::ArmingPolicy::MANUAL, base::SequencedTaskRunner::GetCurrentDefault()), peer_closed_handle_watcher_( FROM_HERE, mojo::SimpleWatcher::ArmingPolicy::MANUAL, base::SequencedTaskRunner::GetCurrentDefault()), per_factory_orb_state_(context.GetMutableOrbState()), devtools_request_id_(request.devtools_request_id), options_(PopulateOptions(options, factory_params_-\u0026gt;is_orb_enabled, !!devtools_request_id())), request_mode_(request.mode), request_credentials_mode_(request.credentials_mode), has_user_activation_(request.trusted_params \u0026amp;\u0026amp; request.trusted_params-\u0026gt;has_user_activation), request_destination_(request.destination), expected_public_keys_(request.expected_public_keys), resource_scheduler_client_(context.GetResourceSchedulerClient()), keepalive_statistics_recorder_(std::move(keepalive_statistics_recorder)), fetch_window_id_(request.fetch_window_id), private_network_access_interceptor_(request, GetClientSecurityState(), options_), trust_token_interceptor_(TrustTokenUrlLoaderInterceptor::MaybeCreate( std::move(trust_token_helper_factory))), shared_dictionary_checker_(std::move(shared_dictionary_checker)), origin_access_list_(context.GetOriginAccessList()), cookie_observer_(std::move(cookie_observer)), trust_token_observer_(std::move(trust_token_observer)), url_loader_network_observer_(std::move(url_loader_network_observer)), devtools_observer_(std::move(devtools_observer)), device_bound_session_observer_(std::move(device_bound_session_observer)), device_bound_session_observer_shared_remote_( MaybeInitializeDeviceBoundSessionAccessObserverSharedRemote( device_bound_session_observer_, context)), shared_storage_request_helper_( std::make_unique\u0026lt;SharedStorageRequestHelper\u0026gt;( shared_storage_writable_eligible, url_loader_network_observer_.get())), ad_auction_event_record_request_helper_( request.attribution_reporting_eligibility, url_loader_network_observer_.get()), has_fetch_streaming_upload_body_( url_loader_util::HasFetchStreamingUploadBody(request)), accept_ch_frame_interceptor_(AcceptCHFrameInterceptor::MaybeCreate( std::move(accept_ch_frame_observer))), allow_cookies_from_browser_( request.trusted_params \u0026amp;\u0026amp; request.trusted_params-\u0026gt;allow_cookies_from_browser), cookies_from_browser_(allow_cookies_from_browser_ ? url_loader_util::GetCookiesFromHeaders( request.headers, request.cors_exempt_headers) : std::string()), include_request_cookies_with_response_( request.trusted_params \u0026amp;\u0026amp; request.trusted_params-\u0026gt;include_request_cookies_with_response), include_load_timing_internal_info_with_response_( request.trusted_params.has_value()), provide_data_use_updates_(context.DataUseUpdatesEnabled()), partial_decoder_decoding_buffer_size_(net::kMaxBytesToSniff), permissions_policy_(request.permissions_policy) { DCHECK(delete_callback_); if (options_ \u0026amp; mojom::kURLLoadOptionReadAndDiscardBody) { if (!factory_params_-\u0026gt;is_orb_enabled) { discard_buffer_ = base::MakeRefCounted\u0026lt;net::IOBufferWithSize\u0026gt;(kDiscardBufferSize); } } mojom::TrustedURLLoaderHeaderClient* url_loader_header_client = context.GetUrlLoaderHeaderClient(); if (url_loader_header_client \u0026amp;\u0026amp; (options_ \u0026amp; mojom::kURLLoadOptionUseHeaderClient)) { if (options_ \u0026amp; mojom::kURLLoadOptionAsCorsPreflight) { url_loader_header_client-\u0026gt;OnLoaderForCorsPreflightCreated( request, header_client_.BindNewPipeAndPassReceiver()); } else { url_loader_header_client-\u0026gt;OnLoaderCreated( request_id_, header_client_.BindNewPipeAndPassReceiver()); } // Make sure the loader dies if |header_client_| has an error, otherwise // requests can hang. header_client_.set_disconnect_handler( base::BindOnce(\u0026amp;URLLoader::OnMojoDisconnect, base::Unretained(this))); } receiver_.set_disconnect_handler( base::BindOnce(\u0026amp;URLLoader::OnMojoDisconnect, base::Unretained(this))); url_request_ = url_request_context_-\u0026gt;CreateRequest( request.url, request.priority, this, traffic_annotation, /*is_for_websockets=*/false, request.net_log_create_info); TRACE_EVENT(\u0026#34;loading\u0026#34;, \u0026#34;URLLoader::URLLoader\u0026#34;, net::NetLogWithSourceToFlow(url_request_-\u0026gt;net_log())); // Set up UserData (pointing to `this`) first. url_request_-\u0026gt;SetUserData(kUserDataKey, std::make_unique\u0026lt;UnownedPointer\u0026gt;(this)); // Configure the main request parameters. This must happen after setting // UserData, as `ConfigureUrlRequest` might internally retrieve data (e.g., // PermissionsPolicy) via the `url_request_`\u0026#39;s UserData pointer. url_loader_util::ConfigureUrlRequest(request, *factory_params_, *origin_access_list_, *url_request_, shared_resource_checker); if (context.ShouldRequireIsolationInfo()) { DCHECK(!url_request_-\u0026gt;isolation_info().IsEmpty()); } SetUpUrlRequestCallbacks(shared_dictionary_manager); throttling_token_ = network::ScopedThrottlingToken::MaybeCreate( url_request_-\u0026gt;net_log().source().id, request.throttling_profile_id); if (keepalive_ \u0026amp;\u0026amp; keepalive_statistics_recorder_) { keepalive_statistics_recorder_-\u0026gt;OnLoadStarted( *factory_params_-\u0026gt;top_frame_id, keepalive_request_size_); } if (request.net_log_reference_info) { // Log source object that created the request, if available. url_request_-\u0026gt;net_log().AddEventReferencingSource( net::NetLogEventType::CREATED_BY, request.net_log_reference_info.value()); } // Resolve elements from request_body and prepare upload data. if (request.request_body.get()) { OpenFilesForUpload(request); return; } ProcessOutboundTrustTokenInterceptor(request); } https://source.chromium.org/chromium/chromium/src/+/main:services/network/url_loader.cc;drc=4f289372eacd2b226e9d2830cc4a87b4ad621133;l=701?q=URLLoader::URLLoader\u0026ss=chromium%2Fchromium%2Fsrc void URLLoader::ProcessOutboundTrustTokenInterceptor( const ResourceRequest\u0026amp; request) { // If no Trust Token parameters are specified, proceed to the next // interceptor. if (!request.trust_token_params) { ProcessOutboundSharedStorageInterceptor(); return; } // If trust_token_params exist, the interceptor MUST have been created in the // URLLoader constructor. CHECK(trust_token_interceptor_); // Ask the interceptor if any special load flags are needed. url_request_-\u0026gt;SetLoadFlags(url_request_-\u0026gt;load_flags() | trust_token_interceptor_-\u0026gt;GetAdditionalLoadFlags( request.trust_token_params.value())); // Delegate the Begin phase of the Trust Token operation to the interceptor. // The interceptor will asynchronously handle helper creation, calling Begin, // and determining the outcome. trust_token_interceptor_-\u0026gt;BeginOperation( request.trust_token_params-\u0026gt;operation, url_request_-\u0026gt;url(), url_request_-\u0026gt;isolation_info().top_frame_origin().value_or(url::Origin()), url_request_-\u0026gt;extra_request_headers(), request.trust_token_params.value(), url_request_-\u0026gt;net_log(), // Provide a getter for the TrustTokenAccessObserver. base::BindOnce( [](base::WeakPtr\u0026lt;URLLoader\u0026gt; weak_ptr) -\u0026gt; mojom::TrustTokenAccessObserver* { return weak_ptr ? weak_ptr-\u0026gt;trust_token_observer_.get() : nullptr; }, weak_ptr_factory_.GetWeakPtr()), // Provide a getter for the DevTools reporting callback. base::BindOnce( [](base::WeakPtr\u0026lt;URLLoader\u0026gt; weak_ptr) -\u0026gt; base::OnceCallback\u0026lt;void(mojom::TrustTokenOperationResultPtr)\u0026gt; { if (weak_ptr \u0026amp;\u0026amp; weak_ptr-\u0026gt;devtools_observer_.get() \u0026amp;\u0026amp; weak_ptr-\u0026gt;devtools_request_id_) { return base::BindOnce( \u0026amp;mojom::DevToolsObserver::OnTrustTokenOperationDone, base::Unretained(weak_ptr-\u0026gt;devtools_observer_.get()), *weak_ptr-\u0026gt;devtools_request_id_); } return base::OnceCallback\u0026lt;void( mojom::TrustTokenOperationResultPtr)\u0026gt;(); }, weak_ptr_factory_.GetWeakPtr()), base::BindOnce(\u0026amp;URLLoader::OnDoneBeginningTrustTokenOperation, weak_ptr_factory_.GetWeakPtr())); } https://source.chromium.org/chromium/chromium/src/+/main:services/network/url_loader.cc;l=678;drc=4f289372eacd2b226e9d2830cc4a87b4ad621133?q=URLLoader::URLLoader\u0026ss=chromium%2Fchromium%2Fsrc void URLLoader::OnDoneBeginningTrustTokenOperation( base::expected\u0026lt;net::HttpRequestHeaders, net::Error\u0026gt; result) { // If `result` does not have a value, the operation failed or completed // locally. if (!result.has_value()) { // Defer calling NotifyCompleted to make sure the URLLoader // finishes initializing before getting deleted. base::SequencedTaskRunner::GetCurrentDefault()-\u0026gt;PostTask( FROM_HERE, base::BindOnce(\u0026amp;URLLoader::NotifyCompleted, weak_ptr_factory_.GetWeakPtr(), result.error())); return; } // Operation succeeded and returned headers to add/overwrite. // Apply the headers provided by the interceptor. for (const auto\u0026amp; header_pair : result-\u0026gt;GetHeaderVector()) { url_request_-\u0026gt;SetExtraRequestHeaderByName( header_pair.key, header_pair.value, /*overwrite=*/true); } // Trust Token outbound processing is done, proceed to the next interceptor. ProcessOutboundSharedStorageInterceptor(); } void URLLoader::ProcessOutboundSharedStorageInterceptor() { DCHECK(shared_storage_request_helper_); shared_storage_request_helper_-\u0026gt;ProcessOutgoingRequest(*url_request_); ScheduleStart(); } void URLLoader::ScheduleStart() { TRACE_EVENT(\u0026#34;loading\u0026#34;, \u0026#34;URLLoader::ScheduleStart\u0026#34;, net::NetLogWithSourceToFlow(url_request_-\u0026gt;net_log())); bool defer = false; if (resource_scheduler_client_) { resource_scheduler_request_handle_ = resource_scheduler_client_-\u0026gt;ScheduleRequest( !(options_ \u0026amp; mojom::kURLLoadOptionSynchronous), url_request_.get()); resource_scheduler_request_handle_-\u0026gt;set_resume_callback( base::BindOnce(\u0026amp;URLLoader::ResumeStart, base::Unretained(this))); resource_scheduler_request_handle_-\u0026gt;WillStartRequest(\u0026amp;defer); } if (defer) url_request_-\u0026gt;LogBlockedBy(\u0026#34;ResourceScheduler\u0026#34;); else url_request_-\u0026gt;Start(); } https://source.chromium.org/chromium/chromium/src/+/main:net/url_request/url_request.cc;drc=4f289372eacd2b226e9d2830cc4a87b4ad621133;l=586 void URLRequest::Start() { DCHECK(delegate_); // We do not support credentials with a non-general // NetworkIsolationPartition. CHECK(isolation_info_.GetNetworkIsolationPartition() == NetworkIsolationPartition::kGeneral || !allow_credentials()); if (status_ != OK) return; if (context_-\u0026gt;require_network_anonymization_key()) { DCHECK(!isolation_info_.IsEmpty()); } // Some values can be NULL, but the job factory must not be. DCHECK(context_-\u0026gt;job_factory()); // Anything that sets |blocked_by_| before start should have cleaned up after // itself. DCHECK(blocked_by_.empty()); g_url_requests_started = true; response_info_.request_time = base::Time::Now(); load_timing_info_ = LoadTimingInfo(); load_timing_info_.request_start_time = response_info_.request_time; load_timing_info_.request_start = base::TimeTicks::Now(); if (network_delegate()) { OnCallToDelegate(NetLogEventType::NETWORK_DELEGATE_BEFORE_URL_REQUEST); int error = network_delegate()-\u0026gt;NotifyBeforeURLRequest( this, base::BindOnce(\u0026amp;URLRequest::BeforeRequestComplete, base::Unretained(this)), \u0026amp;delegate_redirect_url_); // If ERR_IO_PENDING is returned, the delegate will invoke // |BeforeRequestComplete| later. if (error != ERR_IO_PENDING) BeforeRequestComplete(error); return; } StartJob(context_-\u0026gt;job_factory()-\u0026gt;CreateJob(this)); } https://source.chromium.org/chromium/chromium/src/+/main:net/url_request/url_request.cc;drc=4f289372eacd2b226e9d2830cc4a87b4ad621133;l=690 void URLRequest::StartJob(std::unique_ptr\u0026lt;URLRequestJob\u0026gt; job) { DCHECK(!is_pending_); DCHECK(!job_); if (is_created_from_network_anonymization_key_) { DCHECK(load_flags() \u0026amp; LOAD_DISABLE_CACHE); DCHECK(!allow_credentials_); } net_log_.BeginEvent(NetLogEventType::URL_REQUEST_START_JOB, [\u0026amp;] { return NetLogURLRequestStartParams( url(), method_, load_flags(), isolation_info_, site_for_cookies_, initiator_, upload_data_stream_ ? upload_data_stream_-\u0026gt;identifier() : -1); }); job_ = std::move(job); job_-\u0026gt;SetExtraRequestHeaders(extra_request_headers_); job_-\u0026gt;SetPriority(priority_); job_-\u0026gt;SetRequestHeadersCallback(request_headers_callback_); job_-\u0026gt;SetEarlyResponseHeadersCallback(early_response_headers_callback_); if (is_shared_dictionary_read_allowed_callback_) { job_-\u0026gt;SetIsSharedDictionaryReadAllowedCallback( is_shared_dictionary_read_allowed_callback_); } job_-\u0026gt;SetResponseHeadersCallback(response_headers_callback_); if (shared_dictionary_getter_) { job_-\u0026gt;SetSharedDictionaryGetter(shared_dictionary_getter_); } if (upload_data_stream_.get()) job_-\u0026gt;SetUpload(upload_data_stream_.get()); is_pending_ = true; is_redirecting_ = false; deferred_redirect_info_.reset(); response_info_.was_cached = false; maybe_sent_cookies_.clear(); maybe_stored_cookies_.clear(); GURL referrer_url(referrer_); bool same_origin_for_metrics; if (referrer_url != URLRequestJob::ComputeReferrerForPolicy( referrer_policy_, referrer_url, url(), \u0026amp;same_origin_for_metrics)) { if (!network_delegate() || !network_delegate()-\u0026gt;CancelURLRequestWithPolicyViolatingReferrerHeader( *this, url(), referrer_url)) { referrer_.clear(); } else { // We need to clear the referrer anyway to avoid an infinite recursion // when starting the error job. referrer_.clear(); net_log_.AddEventWithStringParams(NetLogEventType::CANCELLED, \u0026#34;source\u0026#34;, \u0026#34;delegate\u0026#34;); RestartWithJob( std::make_unique\u0026lt;URLRequestErrorJob\u0026gt;(this, ERR_BLOCKED_BY_CLIENT)); return; } } RecordReferrerGranularityMetrics(same_origin_for_metrics); // Start() always completes asynchronously. // // Status is generally set by URLRequestJob itself, but Start() calls // directly into the URLRequestJob subclass, so URLRequestJob can\u0026#39;t set it // here. // TODO(mmenke): Make the URLRequest manage its own status. status_ = ERR_IO_PENDING; job_-\u0026gt;Start(); } https://source.chromium.org/chromium/chromium/src/+/main:net/url_request/url_request_http_job.cc;l=429;drc=4f289372eacd2b226e9d2830cc4a87b4ad621133;bpv=1;bpt=1 void URLRequestHttpJob::Start() { DCHECK(!transaction_.get()); request_info_.url = request_-\u0026gt;url(); request_info_.method = request_-\u0026gt;method(); request_info_.network_isolation_key = request_-\u0026gt;isolation_info().network_isolation_key(); request_info_.network_anonymization_key = request_-\u0026gt;isolation_info().network_anonymization_key(); request_info_.possibly_top_frame_origin = request_-\u0026gt;isolation_info().top_frame_origin(); request_info_.frame_origin = request_-\u0026gt;isolation_info().frame_origin(); request_info_.is_subframe_document_resource = request_-\u0026gt;isolation_info().request_type() == net::IsolationInfo::RequestType::kSubFrame; request_info_.is_main_frame_navigation = request_-\u0026gt;isolation_info().IsMainFrameRequest(); request_info_.initiator = request_-\u0026gt;initiator(); request_info_.load_flags = request_-\u0026gt;load_flags(); request_info_.priority_incremental = request_-\u0026gt;priority_incremental(); request_info_.secure_dns_policy = request_-\u0026gt;secure_dns_policy(); request_info_.traffic_annotation = net::MutableNetworkTrafficAnnotationTag(request_-\u0026gt;traffic_annotation()); request_info_.socket_tag = request_-\u0026gt;socket_tag(); request_info_.idempotency = request_-\u0026gt;GetIdempotency(); #if BUILDFLAG(ENABLE_REPORTING) request_info_.reporting_upload_depth = request_-\u0026gt;reporting_upload_depth(); #endif request_info_.is_shared_resource = request_-\u0026gt;is_shared_resource(); CookieStore* cookie_store = request()-\u0026gt;context()-\u0026gt;cookie_store(); const CookieAccessDelegate* delegate = cookie_store ? cookie_store-\u0026gt;cookie_access_delegate() : nullptr; request_-\u0026gt;net_log().BeginEvent(NetLogEventType::FIRST_PARTY_SETS_METADATA); std::optional\u0026lt; std::pair\u0026lt;FirstPartySetMetadata, FirstPartySetsCacheFilter::MatchInfo\u0026gt;\u0026gt; maybe_metadata = cookie_util::ComputeFirstPartySetMetadataMaybeAsync( SchemefulSite(request()-\u0026gt;url()), request()-\u0026gt;isolation_info(), delegate, base::BindOnce(\u0026amp;URLRequestHttpJob::OnGotFirstPartySetMetadata, weak_factory_.GetWeakPtr())); if (maybe_metadata.has_value()) { auto [metadata, match_info] = std::move(maybe_metadata).value(); OnGotFirstPartySetMetadata(std::move(metadata), std::move(match_info)); } } https://source.chromium.org/chromium/chromium/src/+/main:net/url_request/url_request_http_job.cc;l=489;drc=4f289372eacd2b226e9d2830cc4a87b4ad621133;bpv=1;bpt=1 void URLRequestHttpJob::OnGotFirstPartySetMetadata( FirstPartySetMetadata first_party_set_metadata, FirstPartySetsCacheFilter::MatchInfo match_info) { first_party_set_metadata_ = std::move(first_party_set_metadata); request_info_.fps_cache_filter = match_info.clear_at_run_id; request_info_.browser_run_id = match_info.browser_run_id; request_-\u0026gt;net_log().EndEvent( NetLogEventType::FIRST_PARTY_SETS_METADATA, [\u0026amp;]() { return FirstPartySetMetadataNetLogParams( first_party_set_metadata_, base::OptionalToPtr(request_info_.fps_cache_filter)); }); // Privacy mode could still be disabled in SetCookieHeaderAndStart if we are // going to send previously saved cookies. request_info_.privacy_mode = DeterminePrivacyMode(); request()-\u0026gt;net_log().AddEventWithStringParams( NetLogEventType::COMPUTED_PRIVACY_MODE, \u0026#34;privacy_mode\u0026#34;, PrivacyModeToDebugString(request_info_.privacy_mode)); // Strip Referer from request_info_.extra_headers to prevent, e.g., plugins // from overriding headers that are controlled using other means. Otherwise a // plugin could set a referrer although sending the referrer is inhibited. request_info_.extra_headers.RemoveHeader(HttpRequestHeaders::kReferer); // URLRequest::SetReferrer ensures that we do not send username and password // fields in the referrer. GURL referrer(request_-\u0026gt;referrer()); // Our consumer should have made sure that this is a safe referrer (e.g. via // URLRequestJob::ComputeReferrerForPolicy). if (referrer.is_valid()) { std::string referer_value = referrer.spec(); request_info_.extra_headers.SetHeader(HttpRequestHeaders::kReferer, referer_value); } request_info_.extra_headers.SetHeaderIfMissing( HttpRequestHeaders::kUserAgent, http_user_agent_settings_ ? http_user_agent_settings_-\u0026gt;GetUserAgent() : std::string()); AddExtraHeaders(); if (ShouldAddCookieHeader()) { AddCookieHeaderAndStart(); } else { StartTransaction(); } } https://source.chromium.org/chromium/chromium/src/+/main:net/url_request/url_request_http_job.cc;l=669;drc=4f289372eacd2b226e9d2830cc4a87b4ad621133;bpv=1;bpt=1 void URLRequestHttpJob::StartTransaction() { DCHECK(!override_response_info_); NetworkDelegate* network_delegate = request()-\u0026gt;network_delegate(); if (network_delegate) { OnCallToDelegate( NetLogEventType::NETWORK_DELEGATE_BEFORE_START_TRANSACTION); int rv = network_delegate-\u0026gt;NotifyBeforeStartTransaction( request_, request_info_.extra_headers, base::BindOnce(\u0026amp;URLRequestHttpJob::NotifyBeforeStartTransactionCallback, weak_factory_.GetWeakPtr())); // If an extension blocks the request, we rely on the callback to // MaybeStartTransactionInternal(). if (rv == ERR_IO_PENDING) return; MaybeStartTransactionInternal(rv); return; } StartTransactionInternal(); } https://source.chromium.org/chromium/chromium/src/+/main:net/url_request/url_request_http_job.cc;drc=4f289372eacd2b226e9d2830cc4a87b4ad621133;bpv=1;bpt=1;l=697 void URLRequestHttpJob::StartTransactionInternal() { DCHECK(!override_response_headers_); // NOTE: This method assumes that request_info_ is already setup properly. // If we already have a transaction, then we should restart the transaction // with auth provided by auth_credentials_. int rv = OK; // Notify NetworkQualityEstimator. NetworkQualityEstimator* network_quality_estimator = request()-\u0026gt;context()-\u0026gt;network_quality_estimator(); if (network_quality_estimator) network_quality_estimator-\u0026gt;NotifyStartTransaction(*request_); if (transaction_.get()) { rv = transaction_-\u0026gt;RestartWithAuth( auth_credentials_, base::BindOnce(\u0026amp;URLRequestHttpJob::OnStartCompleted, base::Unretained(this))); auth_credentials_ = AuthCredentials(); } else { DCHECK(request_-\u0026gt;context()-\u0026gt;http_transaction_factory()); transaction_ = request_-\u0026gt;context()-\u0026gt;http_transaction_factory()-\u0026gt;CreateTransaction( priority_); CHECK(transaction_); if (request_info_.url.SchemeIsWSOrWSS()) { base::SupportsUserData::Data* data = request_-\u0026gt;GetUserData(kWebSocketHandshakeUserDataKey); if (data) { transaction_-\u0026gt;SetWebSocketHandshakeStreamCreateHelper( static_cast\u0026lt;WebSocketHandshakeStreamBase::CreateHelper*\u0026gt;(data)); } else { rv = ERR_DISALLOWED_URL_SCHEME; } } if (rv == OK \u0026amp;\u0026amp; request_info_.method == \u0026#34;CONNECT\u0026#34;) { // CONNECT has different kinds of targets than other methods (RFC 9110, // section 9.3.6), which are incompatible with URLRequest. rv = ERR_METHOD_NOT_SUPPORTED; } if (rv == OK) { transaction_-\u0026gt;SetConnectedCallback(base::BindRepeating( \u0026amp;URLRequestHttpJob::NotifyConnectedCallback, base::Unretained(this))); transaction_-\u0026gt;SetRequestHeadersCallback(request_headers_callback_); transaction_-\u0026gt;SetEarlyResponseHeadersCallback( early_response_headers_callback_); transaction_-\u0026gt;SetResponseHeadersCallback(response_headers_callback_); if (is_shared_dictionary_read_allowed_callback_) { transaction_-\u0026gt;SetIsSharedDictionaryReadAllowedCallback( is_shared_dictionary_read_allowed_callback_); } rv = transaction_-\u0026gt;Start( \u0026amp;request_info_, base::BindOnce(\u0026amp;URLRequestHttpJob::OnStartCompleted, base::Unretained(this)), request_-\u0026gt;net_log()); start_time_ = base::TimeTicks::Now(); } } if (rv == ERR_IO_PENDING) return; // The transaction started synchronously, but we need to notify the // URLRequest delegate via the message loop. base::SingleThreadTaskRunner::GetCurrentDefault()-\u0026gt;PostTask( FROM_HERE, base::BindOnce(\u0026amp;URLRequestHttpJob::OnStartCompleted, weak_factory_.GetWeakPtr(), rv)); } https://source.chromium.org/chromium/chromium/src/+/main:net/http/http_network_transaction.cc;l=359;drc=4f289372eacd2b226e9d2830cc4a87b4ad621133;bpv=1;bpt=1 int HttpNetworkTransaction::Start(const HttpRequestInfo* request_info, CompletionOnceCallback callback, const NetLogWithSource\u0026amp; net_log) { TRACE_EVENT(\u0026#34;net\u0026#34;, \u0026#34;HttpNetworkTransaction::Start\u0026#34;, NetLogWithSourceToFlow(net_log), \u0026#34;url\u0026#34;, request_info-\u0026gt;url); if (session_-\u0026gt;power_suspended()) { return ERR_NETWORK_IO_SUSPENDED; } if (request_info-\u0026gt;load_flags \u0026amp; LOAD_ONLY_FROM_CACHE) { return ERR_CACHE_MISS; } DCHECK(request_info-\u0026gt;traffic_annotation.is_valid()); DCHECK(request_info-\u0026gt;IsConsistent()); net_log_ = net_log; request_ = request_info; url_ = request_-\u0026gt;url; network_anonymization_key_ = request_-\u0026gt;network_anonymization_key; start_timeticks_ = base::TimeTicks::Now(); #if BUILDFLAG(ENABLE_REPORTING) // Store values for later use in NEL report generation. request_method_ = request_-\u0026gt;method; if (std::optional\u0026lt;std::string\u0026gt; header = request_-\u0026gt;extra_headers.GetHeader(HttpRequestHeaders::kReferer); header) { request_referrer_.swap(header.value()); } if (std::optional\u0026lt;std::string\u0026gt; header = request_-\u0026gt;extra_headers.GetHeader(HttpRequestHeaders::kUserAgent); header) { request_user_agent_.swap(header.value()); } request_reporting_upload_depth_ = request_-\u0026gt;reporting_upload_depth; #endif // BUILDFLAG(ENABLE_REPORTING) if (request_-\u0026gt;idempotency == IDEMPOTENT || (request_-\u0026gt;idempotency == DEFAULT_IDEMPOTENCY \u0026amp;\u0026amp; HttpUtil::IsMethodSafe(request_info-\u0026gt;method))) { can_send_early_data_ = true; } if (request_-\u0026gt;load_flags \u0026amp; LOAD_PREFETCH) { response_.unused_since_prefetch = true; } if (request_-\u0026gt;load_flags \u0026amp; LOAD_RESTRICTED_PREFETCH_FOR_MAIN_FRAME) { DCHECK(response_.unused_since_prefetch); response_.restricted_prefetch = true; } next_state_ = STATE_CREATE_STREAM; int rv = DoLoop(OK); if (rv == ERR_IO_PENDING) callback_ = std::move(callback); // This always returns ERR_IO_PENDING because DoCreateStream() does, but // GenerateNetworkErrorLoggingReportIfError() should be called here if any // other Error can be returned. DCHECK_EQ(rv, ERR_IO_PENDING); return rv; } https://source.chromium.org/chromium/chromium/src/+/main:net/http/http_network_transaction.cc;drc=4f289372eacd2b226e9d2830cc4a87b4ad621133;bpv=1;bpt=1;l=985 int HttpNetworkTransaction::DoLoop(int result) { DCHECK(next_state_ != STATE_NONE); int rv = result; do { State state = next_state_; next_state_ = STATE_NONE; switch (state) { case STATE_CREATE_STREAM: DCHECK_EQ(OK, rv); rv = DoCreateStream(); break; case STATE_CREATE_STREAM_COMPLETE: rv = DoCreateStreamComplete(rv); break; case STATE_CONNECTED_CALLBACK: rv = DoConnectedCallback(); break; case STATE_CONNECTED_CALLBACK_COMPLETE: rv = DoConnectedCallbackComplete(rv); break; case STATE_INIT_STREAM: DCHECK_EQ(OK, rv); rv = DoInitStream(); break; case STATE_INIT_STREAM_COMPLETE: rv = DoInitStreamComplete(rv); break; case STATE_GENERATE_PROXY_AUTH_TOKEN: DCHECK_EQ(OK, rv); rv = DoGenerateProxyAuthToken(); break; case STATE_GENERATE_PROXY_AUTH_TOKEN_COMPLETE: rv = DoGenerateProxyAuthTokenComplete(rv); break; case STATE_GENERATE_SERVER_AUTH_TOKEN: DCHECK_EQ(OK, rv); rv = DoGenerateServerAuthToken(); break; case STATE_GENERATE_SERVER_AUTH_TOKEN_COMPLETE: rv = DoGenerateServerAuthTokenComplete(rv); break; case STATE_INIT_REQUEST_BODY: DCHECK_EQ(OK, rv); rv = DoInitRequestBody(); break; case STATE_INIT_REQUEST_BODY_COMPLETE: rv = DoInitRequestBodyComplete(rv); break; case STATE_BUILD_REQUEST: DCHECK_EQ(OK, rv); net_log_.BeginEvent(NetLogEventType::HTTP_TRANSACTION_SEND_REQUEST); rv = DoBuildRequest(); break; case STATE_BUILD_REQUEST_COMPLETE: rv = DoBuildRequestComplete(rv); break; case STATE_SEND_REQUEST: DCHECK_EQ(OK, rv); rv = DoSendRequest(); break; case STATE_SEND_REQUEST_COMPLETE: rv = DoSendRequestComplete(rv); net_log_.EndEventWithNetErrorCode( NetLogEventType::HTTP_TRANSACTION_SEND_REQUEST, rv); break; case STATE_READ_HEADERS: DCHECK_EQ(OK, rv); net_log_.BeginEvent(NetLogEventType::HTTP_TRANSACTION_READ_HEADERS); rv = DoReadHeaders(); break; case STATE_READ_HEADERS_COMPLETE: rv = DoReadHeadersComplete(rv); net_log_.EndEventWithNetErrorCode( NetLogEventType::HTTP_TRANSACTION_READ_HEADERS, rv); break; case STATE_READ_BODY: DCHECK_EQ(OK, rv); net_log_.BeginEvent(NetLogEventType::HTTP_TRANSACTION_READ_BODY); rv = DoReadBody(); break; case STATE_READ_BODY_COMPLETE: rv = DoReadBodyComplete(rv); net_log_.EndEventWithNetErrorCode( NetLogEventType::HTTP_TRANSACTION_READ_BODY, rv); break; case STATE_DRAIN_BODY_FOR_AUTH_RESTART: DCHECK_EQ(OK, rv); net_log_.BeginEvent( NetLogEventType::HTTP_TRANSACTION_DRAIN_BODY_FOR_AUTH_RESTART); rv = DoDrainBodyForAuthRestart(); break; case STATE_DRAIN_BODY_FOR_AUTH_RESTART_COMPLETE: rv = DoDrainBodyForAuthRestartComplete(rv); net_log_.EndEventWithNetErrorCode( NetLogEventType::HTTP_TRANSACTION_DRAIN_BODY_FOR_AUTH_RESTART, rv); break; default: NOTREACHED() \u0026lt;\u0026lt; \u0026#34;bad state\u0026#34;; } } while (rv != ERR_IO_PENDING \u0026amp;\u0026amp; next_state_ != STATE_NONE); return rv; } https://source.chromium.org/chromium/chromium/src/+/main:net/http/http_network_transaction.cc;drc=4f289372eacd2b226e9d2830cc4a87b4ad621133;bpv=1;bpt=1;l=1090 int HttpNetworkTransaction::DoCreateStream() { TRACE_EVENT(\u0026#34;net\u0026#34;, \u0026#34;HttpNetworkTransaction::CreateStream\u0026#34;, NetLogWithSourceToFlow(net_log_), \u0026#34;retry_attempts\u0026#34;, retry_attempts_, \u0026#34;num_restarts\u0026#34;, num_restarts_); response_.network_accessed = true; next_state_ = STATE_CREATE_STREAM_COMPLETE; // IP based pooling is only disabled on a retry after 421 Misdirected Request // is received. Alternative Services are also disabled in this case (though // they can also be disabled when retrying after a QUIC error). if (!enable_ip_based_pooling_) { DCHECK(!enable_alternative_services_); } create_stream_start_time_ = base::TimeTicks::Now(); // Reset `create_stream_end_time__` to prevent an inconsistent state in // case that `DoCreateStream` is called multiple times. create_stream_end_time_ = base::TimeTicks(); if (ForWebSocketHandshake()) { stream_request_ = session_-\u0026gt;http_stream_factory()-\u0026gt;RequestWebSocketHandshakeStream( *request_, priority_, /*allowed_bad_certs=*/observed_bad_certs_, this, websocket_handshake_stream_base_create_helper_, enable_ip_based_pooling_, enable_alternative_services_, net_log_); } else { stream_request_ = session_-\u0026gt;http_stream_factory()-\u0026gt;RequestStream( *request_, priority_, /*allowed_bad_certs=*/observed_bad_certs_, this, enable_ip_based_pooling_, enable_alternative_services_, net_log_); } DCHECK(stream_request_.get()); return ERR_IO_PENDING; } https://source.chromium.org/chromium/chromium/src/+/main:net/http/http_stream_factory.cc;drc=4f289372eacd2b226e9d2830cc4a87b4ad621133;bpv=1;bpt=1;l=167 std::unique_ptr\u0026lt;HttpStreamRequest\u0026gt; HttpStreamFactory::RequestStream( const HttpRequestInfo\u0026amp; request_info, RequestPriority priority, const std::vector\u0026lt;SSLConfig::CertAndStatus\u0026gt;\u0026amp; allowed_bad_certs, HttpStreamRequest::Delegate* delegate, bool enable_ip_based_pooling, bool enable_alternative_services, const NetLogWithSource\u0026amp; net_log) { return RequestStreamInternal(request_info, priority, allowed_bad_certs, delegate, nullptr, HttpStreamRequest::HTTP_STREAM, /*is_websocket=*/false, enable_ip_based_pooling, enable_alternative_services, net_log); } https://source.chromium.org/chromium/chromium/src/+/main:net/http/http_stream_factory.cc;drc=4f289372eacd2b226e9d2830cc4a87b4ad621133;bpv=1;bpt=1;l=218 std::unique_ptr\u0026lt;HttpStreamRequest\u0026gt; HttpStreamFactory::RequestStreamInternal( const HttpRequestInfo\u0026amp; request_info, RequestPriority priority, const std::vector\u0026lt;SSLConfig::CertAndStatus\u0026gt;\u0026amp; allowed_bad_certs, HttpStreamRequest::Delegate* delegate, WebSocketHandshakeStreamBase::CreateHelper* websocket_handshake_stream_create_helper, HttpStreamRequest::StreamType stream_type, bool is_websocket, bool enable_ip_based_pooling, bool enable_alternative_services, const NetLogWithSource\u0026amp; net_log) { // This is only needed in the non-preconnect path, as preconnects do not // require a NetworkIsolationKey. DCHECK(request_info.IsConsistent()); auto job_controller = std::make_unique\u0026lt;JobController\u0026gt;( this, delegate, session_, job_factory_.get(), request_info, /* is_preconnect = */ false, is_websocket, enable_ip_based_pooling, enable_alternative_services, session_-\u0026gt;context() .quic_context-\u0026gt;params() -\u0026gt;delay_main_job_with_available_spdy_session, allowed_bad_certs); JobController* job_controller_raw_ptr = job_controller.get(); job_controller_set_.insert(std::move(job_controller)); return job_controller_raw_ptr-\u0026gt;Start(delegate, websocket_handshake_stream_create_helper, net_log, stream_type, priority); } https://source.chromium.org/chromium/chromium/src/+/main:net/http/http_stream_factory_job_controller.cc;drc=4f289372eacd2b226e9d2830cc4a87b4ad621133;bpv=1;bpt=1;l=206 std::unique_ptr\u0026lt;HttpStreamRequest\u0026gt; HttpStreamFactory::JobController::Start( HttpStreamRequest::Delegate* delegate, WebSocketHandshakeStreamBase::CreateHelper* websocket_handshake_stream_create_helper, const NetLogWithSource\u0026amp; source_net_log, HttpStreamRequest::StreamType stream_type, RequestPriority priority) { DCHECK(!request_); stream_type_ = stream_type; priority_ = priority; auto request = std::make_unique\u0026lt;HttpStreamRequest\u0026gt;( this, websocket_handshake_stream_create_helper, source_net_log, stream_type); // Keep a raw pointer but release ownership of HttpStreamRequest instance. request_ = request.get(); // Associates |net_log_| with |source_net_log|. source_net_log.AddEventReferencingSource( NetLogEventType::HTTP_STREAM_JOB_CONTROLLER_BOUND, net_log_.source()); net_log_.AddEventReferencingSource( NetLogEventType::HTTP_STREAM_JOB_CONTROLLER_BOUND, source_net_log.source()); RunLoop(OK); return request; } https://source.chromium.org/chromium/chromium/src/+/main:net/http/http_stream_factory_job_controller.cc;drc=4f289372eacd2b226e9d2830cc4a87b4ad621133;bpv=1;bpt=1;l=747 void HttpStreamFactory::JobController::RunLoop(int result) { int rv = DoLoop(result); if (rv == ERR_IO_PENDING) { return; } if (rv != OK) { // DoLoop can only fail during proxy resolution step which happens before // any jobs are created. Notify |request_| of the failure one message loop // iteration later to avoid re-entrancy. DCHECK(!main_job_); DCHECK(!alternative_job_); DCHECK(!dns_alpn_h3_job_); base::SingleThreadTaskRunner::GetCurrentDefault()-\u0026gt;PostTask( FROM_HERE, base::BindOnce(\u0026amp;HttpStreamFactory::JobController::NotifyRequestFailed, ptr_factory_.GetWeakPtr(), rv)); } } int HttpStreamFactory::JobController::DoLoop(int rv) { DCHECK_NE(next_state_, STATE_NONE); do { State state = next_state_; next_state_ = STATE_NONE; switch (state) { case STATE_RESOLVE_PROXY: DCHECK_EQ(OK, rv); rv = DoResolveProxy(); break; case STATE_RESOLVE_PROXY_COMPLETE: rv = DoResolveProxyComplete(rv); break; case STATE_CREATE_JOBS: DCHECK_EQ(OK, rv); rv = DoCreateJobs(); break; default: NOTREACHED() \u0026lt;\u0026lt; \u0026#34;bad state\u0026#34;; } } while (next_state_ != STATE_NONE \u0026amp;\u0026amp; rv != ERR_IO_PENDING); return rv; } https://source.chromium.org/chromium/chromium/src/+/main:net/http/http_stream_factory_job_controller.cc;drc=4f289372eacd2b226e9d2830cc4a87b4ad621133;bpv=1;bpt=1;l=840 int HttpStreamFactory::JobController::DoCreateJobs() { DCHECK(!main_job_); DCHECK(!alternative_job_); DCHECK(origin_url_.is_valid()); DCHECK(origin_url_.IsStandard()); url::SchemeHostPort destination(origin_url_); DCHECK(destination.IsValid()); ConvertWsToHttp(destination); // Create an alternative job if alternative service is set up for this domain. // This is applicable even if the connection will be made via a proxy. alternative_service_info_ = GetAlternativeServiceInfoFor( http_request_info_url_, request_info_, delegate_, stream_type_); if (session_-\u0026gt;host_resolver()-\u0026gt;IsHappyEyeballsV3Enabled() \u0026amp;\u0026amp; proxy_info_.is_direct() \u0026amp;\u0026amp; !is_websocket_) { SwitchToHttpStreamPool(); return OK; } quic::ParsedQuicVersion quic_version = quic::ParsedQuicVersion::Unsupported(); if (alternative_service_info_.protocol() == NextProto::kProtoQUIC) { quic_version = SelectQuicVersion(alternative_service_info_.advertised_versions()); DCHECK_NE(quic_version, quic::ParsedQuicVersion::Unsupported()); } // Getting ALPN for H3 from DNS has a lot of preconditions. Among them: // - proxied connections perform DNS on the proxy, so they can\u0026#39;t get supported // ALPNs from DNS const bool dns_alpn_h3_job_enabled = !session_-\u0026gt;ShouldForceQuic(destination, proxy_info_, is_websocket_) \u0026amp;\u0026amp; enable_alternative_services_ \u0026amp;\u0026amp; session_-\u0026gt;params().use_dns_https_svcb_alpn \u0026amp;\u0026amp; base::EqualsCaseInsensitiveASCII(origin_url_.scheme(), url::kHttpsScheme) \u0026amp;\u0026amp; session_-\u0026gt;IsQuicEnabled() \u0026amp;\u0026amp; proxy_info_.is_direct() \u0026amp;\u0026amp; !session_-\u0026gt;http_server_properties()-\u0026gt;IsAlternativeServiceBroken( GetAlternativeServiceForDnsJob(origin_url_), request_info_.network_anonymization_key); if (is_preconnect_) { // Due to how the socket pools handle priorities and idle sockets, only IDLE // priority currently makes sense for preconnects. The priority for // preconnects is currently ignored (see RequestSocketsForPool()), but could // be used at some point for proxy resolution or something. // Note: When `dns_alpn_h3_job_enabled` is true, we create a // PRECONNECT_DNS_ALPN_H3 job. If no matching HTTPS DNS ALPN records are // received, the PRECONNECT_DNS_ALPN_H3 job will fail with // ERR_DNS_NO_MATCHING_SUPPORTED_ALPN, and `preconnect_backup_job_` will // be started in OnPreconnectsComplete(). std::unique_ptr\u0026lt;Job\u0026gt; preconnect_job = job_factory_-\u0026gt;CreateJob( this, dns_alpn_h3_job_enabled ? PRECONNECT_DNS_ALPN_H3 : PRECONNECT, session_, request_info_, IDLE, proxy_info_, allowed_bad_certs_, destination, origin_url_, is_websocket_, enable_ip_based_pooling_, net_log_.net_log(), NextProto::kProtoUnknown, quic::ParsedQuicVersion::Unsupported(), management_config_); // When there is an valid alternative service info, and `preconnect_job` // has no existing QUIC session, create a job for the alternative service. if (alternative_service_info_.protocol() != NextProto::kProtoUnknown \u0026amp;\u0026amp; !preconnect_job-\u0026gt;HasAvailableQuicSession()) { GURL alternative_url = CreateAltSvcUrl( origin_url_, alternative_service_info_.GetHostPortPair()); RewriteUrlWithHostMappingRules(alternative_url); url::SchemeHostPort alternative_destination = url::SchemeHostPort(alternative_url); ConvertWsToHttp(alternative_destination); main_job_ = job_factory_-\u0026gt;CreateJob( this, PRECONNECT, session_, request_info_, IDLE, proxy_info_, allowed_bad_certs_, std::move(alternative_destination), origin_url_, is_websocket_, enable_ip_based_pooling_, session_-\u0026gt;net_log(), alternative_service_info_.protocol(), quic_version, management_config_); } else { main_job_ = std::move(preconnect_job); if (dns_alpn_h3_job_enabled) { preconnect_backup_job_ = job_factory_-\u0026gt;CreateJob( this, PRECONNECT, session_, request_info_, IDLE, proxy_info_, allowed_bad_certs_, std::move(destination), origin_url_, is_websocket_, enable_ip_based_pooling_, net_log_.net_log(), NextProto::kProtoUnknown, quic::ParsedQuicVersion::Unsupported(), management_config_); } } main_job_-\u0026gt;Preconnect(num_streams_); return OK; } main_job_ = job_factory_-\u0026gt;CreateJob( this, MAIN, session_, request_info_, priority_, proxy_info_, allowed_bad_certs_, std::move(destination), origin_url_, is_websocket_, enable_ip_based_pooling_, net_log_.net_log(), NextProto::kProtoUnknown, quic::ParsedQuicVersion::Unsupported(), management_config_); // Alternative Service can only be set for HTTPS requests while Alternative // Proxy is set for HTTP requests. // The main job may use HTTP/3 if the origin is specified in // `--origin-to-force-quic-on` switch. In that case, do not create // `alternative_job_` and `dns_alpn_h3_job_`. if ((alternative_service_info_.protocol() != NextProto::kProtoUnknown) \u0026amp;\u0026amp; !main_job_-\u0026gt;using_quic()) { DCHECK(origin_url_.SchemeIs(url::kHttpsScheme)); DCHECK(!is_websocket_); DVLOG(1) \u0026lt;\u0026lt; \u0026#34;Selected alternative service (host: \u0026#34; \u0026lt;\u0026lt; alternative_service_info_.GetHostPortPair().host() \u0026lt;\u0026lt; \u0026#34; port: \u0026#34; \u0026lt;\u0026lt; alternative_service_info_.GetHostPortPair().port() \u0026lt;\u0026lt; \u0026#34; version: \u0026#34; \u0026lt;\u0026lt; quic_version \u0026lt;\u0026lt; \u0026#34;)\u0026#34;; GURL alternative_url = CreateAltSvcUrl( origin_url_, alternative_service_info_.GetHostPortPair()); RewriteUrlWithHostMappingRules(alternative_url); url::SchemeHostPort alternative_destination = url::SchemeHostPort(alternative_url); ConvertWsToHttp(alternative_destination); alternative_job_ = job_factory_-\u0026gt;CreateJob( this, ALTERNATIVE, session_, request_info_, priority_, proxy_info_, allowed_bad_certs_, std::move(alternative_destination), origin_url_, is_websocket_, enable_ip_based_pooling_, net_log_.net_log(), alternative_service_info_.protocol(), quic_version, management_config_); } if (dns_alpn_h3_job_enabled \u0026amp;\u0026amp; !main_job_-\u0026gt;using_quic()) { DCHECK(!is_websocket_); url::SchemeHostPort dns_alpn_h3_destination = url::SchemeHostPort(origin_url_); dns_alpn_h3_job_ = job_factory_-\u0026gt;CreateJob( this, DNS_ALPN_H3, session_, request_info_, priority_, proxy_info_, allowed_bad_certs_, std::move(dns_alpn_h3_destination), origin_url_, is_websocket_, enable_ip_based_pooling_, net_log_.net_log(), NextProto::kProtoUnknown, quic::ParsedQuicVersion::Unsupported(), management_config_); } ClearInappropriateJobs(); if (main_job_ \u0026amp;\u0026amp; (alternative_job_ || (dns_alpn_h3_job_ \u0026amp;\u0026amp; (!main_job_-\u0026gt;TargettedSocketGroupHasActiveSocket() \u0026amp;\u0026amp; !main_job_-\u0026gt;HasAvailableSpdySession())))) { // We don\u0026#39;t block |main_job_| when |alternative_job_| doesn\u0026#39;t exists and // |dns_alpn_h3_job_| exists and an active socket is available for // |main_job_|. This is intended to make the fallback logic faster. main_job_is_blocked_ = true; } if (alternative_job_) { alternative_job_-\u0026gt;Start(request_-\u0026gt;stream_type()); } if (dns_alpn_h3_job_) { dns_alpn_h3_job_-\u0026gt;Start(request_-\u0026gt;stream_type()); } if (main_job_) { main_job_-\u0026gt;Start(request_-\u0026gt;stream_type()); } return OK; } https://source.chromium.org/chromium/chromium/src/+/main:net/http/http_stream_factory_job.cc;l=238;drc=4f289372eacd2b226e9d2830cc4a87b4ad621133;bpv=1;bpt=1 void HttpStreamFactory::Job::Start(HttpStreamRequest::StreamType stream_type) { started_ = true; stream_type_ = stream_type; const NetLogWithSource* delegate_net_log = delegate_-\u0026gt;GetNetLog(); if (delegate_net_log) { net_log_.BeginEvent(NetLogEventType::HTTP_STREAM_JOB, [\u0026amp;] { base::Value::Dict dict; const auto\u0026amp; source = delegate_net_log-\u0026gt;source(); if (source.IsValid()) { source.AddToEventParameters(dict); } dict.Set(\u0026#34;logical_destination\u0026#34;, url::SchemeHostPort(origin_url_).Serialize()); dict.Set(\u0026#34;destination\u0026#34;, destination_.Serialize()); dict.Set(\u0026#34;expect_spdy\u0026#34;, expect_spdy_); dict.Set(\u0026#34;using_quic\u0026#34;, using_quic_); dict.Set(\u0026#34;priority\u0026#34;, RequestPriorityToString(priority_)); dict.Set(\u0026#34;type\u0026#34;, NetLogHttpStreamJobType(job_type_)); return dict; }); delegate_net_log-\u0026gt;AddEventReferencingSource( NetLogEventType::HTTP_STREAM_REQUEST_STARTED_JOB, net_log_.source()); } StartInternal(); } https://source.chromium.org/chromium/chromium/src/+/main:net/http/http_stream_factory_job.cc;drc=4f289372eacd2b226e9d2830cc4a87b4ad621133;bpv=1;bpt=1;l=518 void HttpStreamFactory::Job::RunLoop(int result) { result = DoLoop(result); if (result == ERR_IO_PENDING) { return; } // Stop watching for new SpdySessions, to avoid receiving a new SPDY session // while doing anything other than waiting to establish a connection. spdy_session_request_.reset(); // Record histograms which are required for the end of session creation. RecordCompletionHistograms(result); if ((job_type_ == PRECONNECT) || (job_type_ == PRECONNECT_DNS_ALPN_H3)) { base::SingleThreadTaskRunner::GetCurrentDefault()-\u0026gt;PostTask( FROM_HERE, base::BindOnce(\u0026amp;HttpStreamFactory::Job::OnPreconnectsComplete, ptr_factory_.GetWeakPtr(), result)); return; } if (IsCertificateError(result)) { // Retrieve SSL information from the socket. SSLInfo ssl_info; GetSSLInfo(\u0026amp;ssl_info); next_state_ = STATE_WAITING_USER_ACTION; base::SingleThreadTaskRunner::GetCurrentDefault()-\u0026gt;PostTask( FROM_HERE, base::BindOnce(\u0026amp;HttpStreamFactory::Job::OnCertificateErrorCallback, ptr_factory_.GetWeakPtr(), result, ssl_info)); return; } switch (result) { case ERR_SSL_CLIENT_AUTH_CERT_NEEDED: base::SingleThreadTaskRunner::GetCurrentDefault()-\u0026gt;PostTask( FROM_HERE, base::BindOnce( \u0026amp;Job::OnNeedsClientAuthCallback, ptr_factory_.GetWeakPtr(), base::RetainedRef(connection_-\u0026gt;ssl_cert_request_info()))); return; case OK: next_state_ = STATE_DONE; if (is_websocket_) { DCHECK(websocket_stream_); base::SingleThreadTaskRunner::GetCurrentDefault()-\u0026gt;PostTask( FROM_HERE, base::BindOnce(\u0026amp;Job::OnWebSocketHandshakeStreamReadyCallback, ptr_factory_.GetWeakPtr())); } else if (stream_type_ == HttpStreamRequest::BIDIRECTIONAL_STREAM) { if (!bidirectional_stream_impl_) { base::SingleThreadTaskRunner::GetCurrentDefault()-\u0026gt;PostTask( FROM_HERE, base::BindOnce(\u0026amp;Job::OnStreamFailedCallback, ptr_factory_.GetWeakPtr(), ERR_FAILED)); } else { base::SingleThreadTaskRunner::GetCurrentDefault()-\u0026gt;PostTask( FROM_HERE, base::BindOnce(\u0026amp;Job::OnBidirectionalStreamImplReadyCallback, ptr_factory_.GetWeakPtr())); } } else { DCHECK(stream_.get()); base::SingleThreadTaskRunner::GetCurrentDefault()-\u0026gt;PostTask( FROM_HERE, base::BindOnce(\u0026amp;Job::OnStreamReadyCallback, ptr_factory_.GetWeakPtr())); } return; default: base::SingleThreadTaskRunner::GetCurrentDefault()-\u0026gt;PostTask( FROM_HERE, base::BindOnce(\u0026amp;Job::OnStreamFailedCallback, ptr_factory_.GetWeakPtr(), result)); return; } } int HttpStreamFactory::Job::DoLoop(int result) { DCHECK_NE(next_state_, STATE_NONE); int rv = result; do { State state = next_state_; next_state_ = STATE_NONE; switch (state) { case STATE_START: DCHECK_EQ(OK, rv); rv = DoStart(); break; case STATE_WAIT: DCHECK_EQ(OK, rv); rv = DoWait(); break; case STATE_WAIT_COMPLETE: rv = DoWaitComplete(rv); break; case STATE_INIT_CONNECTION: DCHECK_EQ(OK, rv); rv = DoInitConnection(); break; case STATE_INIT_CONNECTION_COMPLETE: rv = DoInitConnectionComplete(rv); break; case STATE_WAITING_USER_ACTION: rv = DoWaitingUserAction(rv); break; case STATE_CREATE_STREAM: DCHECK_EQ(OK, rv); rv = DoCreateStream(); break; case STATE_CREATE_STREAM_COMPLETE: rv = DoCreateStreamComplete(rv); break; default: NOTREACHED() \u0026lt;\u0026lt; \u0026#34;bad state\u0026#34;; } } while (rv != ERR_IO_PENDING \u0026amp;\u0026amp; next_state_ != STATE_NONE); return rv; } int HttpStreamFactory::Job::StartInternal() { CHECK_EQ(STATE_NONE, next_state_); next_state_ = STATE_START; RunLoop(OK); return ERR_IO_PENDING; } https://source.chromium.org/chromium/chromium/src/+/main:net/http/http_stream_factory_job.cc;l=687;drc=4f289372eacd2b226e9d2830cc4a87b4ad621133;bpv=1;bpt=1 int HttpStreamFactory::Job::DoInitConnection() { net_log_.BeginEvent(NetLogEventType::HTTP_STREAM_JOB_INIT_CONNECTION); int result = DoInitConnectionImpl(); if (!expect_on_quic_session_created_ \u0026amp;\u0026amp; !expect_on_quic_host_resolution_) { delegate_-\u0026gt;OnConnectionInitialized(this, result); } return result; } int HttpStreamFactory::Job::DoInitConnectionImpl() { DCHECK(!connection_-\u0026gt;is_initialized()); if (using_quic_ \u0026amp;\u0026amp; !proxy_info_.is_direct() \u0026amp;\u0026amp; !proxy_info_.proxy_chain().Last().is_quic()) { // QUIC can not be spoken to non-QUIC proxies. This error should not be // user visible, because the non-alternative Job should be resumed. return ERR_NO_SUPPORTED_PROXIES; } DCHECK(proxy_info_.proxy_chain().IsValid()); next_state_ = STATE_INIT_CONNECTION_COMPLETE; if (using_quic_) { // TODO(mmenke): Clean this up. `disable_cert_verification_network_fetches` // is enabled in ConnectJobFactory for H1/H2 connections. Also need to add // it to the SpdySessionKey for H2 connections. SSLConfig server_ssl_config; server_ssl_config.disable_cert_verification_network_fetches = disable_cert_verification_network_fetches(); return DoInitConnectionImplQuic(server_ssl_config.GetCertVerifyFlags()); } // Check first if there is a pushed stream matching the request, or an HTTP/2 // connection this request can pool to. If so, then go straight to using // that. if (CanUseExistingSpdySession()) { if (!existing_spdy_session_) { if (!spdy_session_request_) { // If not currently watching for an H2 session, use // SpdySessionPool::RequestSession() to check for a session, and start // watching for one. bool should_throttle_connect = ShouldThrottleConnectForSpdy(); base::RepeatingClosure resume_callback = should_throttle_connect ? base::BindRepeating( \u0026amp;HttpStreamFactory::Job::ResumeInitConnection, ptr_factory_.GetWeakPtr()) : base::RepeatingClosure(); bool is_blocking_request_for_session; existing_spdy_session_ = session_-\u0026gt;spdy_session_pool()-\u0026gt;RequestSession( spdy_session_key_, enable_ip_based_pooling_, is_websocket_, net_log_, resume_callback, this, \u0026amp;spdy_session_request_, \u0026amp;is_blocking_request_for_session); if (!existing_spdy_session_ \u0026amp;\u0026amp; should_throttle_connect \u0026amp;\u0026amp; !is_blocking_request_for_session) { net_log_.AddEvent(NetLogEventType::HTTP_STREAM_JOB_THROTTLED); next_state_ = STATE_INIT_CONNECTION; base::SingleThreadTaskRunner::GetCurrentDefault()-\u0026gt;PostDelayedTask( FROM_HERE, resume_callback, base::Milliseconds(kHTTP2ThrottleMs)); return ERR_IO_PENDING; } } else if (enable_ip_based_pooling_) { // If already watching for an H2 session, still need to check for an // existing connection that can be reused through IP pooling, as those // don\u0026#39;t post session available notifications. // // TODO(mmenke): Make sessions created through IP pooling invoke the // callback. existing_spdy_session_ = session_-\u0026gt;spdy_session_pool()-\u0026gt;FindAvailableSession( spdy_session_key_, enable_ip_based_pooling_, is_websocket_, net_log_); } } if (existing_spdy_session_) { // Stop watching for SpdySessions. spdy_session_request_.reset(); // If we\u0026#39;re preconnecting, but we already have a SpdySession, we don\u0026#39;t // actually need to preconnect any sockets, so we\u0026#39;re done. if (job_type_ == PRECONNECT) { return OK; } negotiated_protocol_ = NextProto::kProtoHTTP2; next_state_ = STATE_CREATE_STREAM; return OK; } } establishing_tunnel_ = !UsingHttpProxyWithoutTunnel(); if (job_type_ == PRECONNECT) { DCHECK(!is_websocket_); DCHECK(request_info_.socket_tag == SocketTag()); // The lifeime of the preconnect tasks is not controlled by |connection_|. // It may outlives |this|. So we can\u0026#39;t use |io_callback_| which holds // base::Unretained(this). auto callback = base::BindOnce(\u0026amp;Job::OnIOComplete, ptr_factory_.GetWeakPtr()); // TODO(crbug.com/391578657): Check proxy info for did try IPP proxy to // populate `fail_if_alias_requires_proxy_override` and pass into method for // Preconnect. return PreconnectSocketsForHttpRequest( destination_, request_info_.load_flags, priority_, session_, proxy_info_, allowed_bad_certs_, request_info_.privacy_mode, request_info_.network_anonymization_key, request_info_.secure_dns_policy, net_log_, num_streams_, /*fail_if_alias_requires_proxy_override_=*/false, std::move(callback)); } // TODO(crbug.com/383134117): Check proxy info for did try IPP proxy to // populate `fail_if_alias_requires_proxy_override` and pass into // `InitSocketHandleForWebSocketRequest` and `InitSocketHandleForHttpRequest` ClientSocketPool::ProxyAuthCallback proxy_auth_callback = base::BindRepeating(\u0026amp;HttpStreamFactory::Job::OnNeedsProxyAuthCallback, base::Unretained(this)); if (is_websocket_) { DCHECK(request_info_.socket_tag == SocketTag()); DCHECK_EQ(SecureDnsPolicy::kAllow, request_info_.secure_dns_policy); return InitSocketHandleForWebSocketRequest( destination_, request_info_.load_flags, priority_, session_, proxy_info_, allowed_bad_certs_, request_info_.privacy_mode, request_info_.network_anonymization_key, net_log_, connection_.get(), io_callback_, proxy_auth_callback, /*fail_if_alias_requires_proxy_override_=*/false); } return InitSocketHandleForHttpRequest( destination_, request_info_.load_flags, priority_, session_, proxy_info_, allowed_bad_certs_, request_info_.privacy_mode, request_info_.network_anonymization_key, request_info_.secure_dns_policy, request_info_.socket_tag, net_log_, connection_.get(), io_callback_, proxy_auth_callback, /*fail_if_alias_requires_proxy_override_=*/false); } https://source.chromium.org/chromium/chromium/src/+/main:net/socket/client_socket_pool_manager.cc;drc=4f289372eacd2b226e9d2830cc4a87b4ad621133;l=213 int InitSocketHandleForHttpRequest( url::SchemeHostPort endpoint, int request_load_flags, RequestPriority request_priority, HttpNetworkSession* session, const ProxyInfo\u0026amp; proxy_info, const std::vectorSSLConfig::CertAndStatus \u0026amp; allowed_bad_certs, PrivacyMode privacy_mode, NetworkAnonymizationKey network_anonymization_key, SecureDnsPolicy secure_dns_policy, const SocketTag\u0026amp; socket_tag, const NetLogWithSource\u0026amp; net_log, ClientSocketHandle* socket_handle, CompletionOnceCallback callback, const ClientSocketPool::ProxyAuthCallback\u0026amp; proxy_auth_callback, bool fail_if_alias_requires_proxy_override) { DCHECK(socket_handle); return InitSocketPoolHelper( std::move(endpoint), request_load_flags, request_priority, session, proxy_info, allowed_bad_certs, privacy_mode, std::move(network_anonymization_key), secure_dns_policy, socket_tag, net_log, 0, socket_handle, HttpNetworkSession::NORMAL_SOCKET_POOL, std::move(callback), proxy_auth_callback, fail_if_alias_requires_proxy_override); }\nhttps://source.chromium.org/chromium/chromium/src/+/main:net/socket/client_socket_pool_manager.cc;drc=4f289372eacd2b226e9d2830cc4a87b4ad621133;l=84 int InitSocketPoolHelper( url::SchemeHostPort endpoint, int request_load_flags, RequestPriority request_priority, HttpNetworkSession* session, const ProxyInfo\u0026amp; proxy_info, const std::vectorSSLConfig::CertAndStatus \u0026amp; allowed_bad_certs, PrivacyMode privacy_mode, NetworkAnonymizationKey network_anonymization_key, SecureDnsPolicy secure_dns_policy, const SocketTag\u0026amp; socket_tag, const NetLogWithSource\u0026amp; net_log, int num_preconnect_streams, ClientSocketHandle* socket_handle, HttpNetworkSession::SocketPoolType socket_pool_type, CompletionOnceCallback callback, const ClientSocketPool::ProxyAuthCallback\u0026amp; proxy_auth_callback, bool fail_if_alias_requires_proxy_override) { DCHECK(endpoint.IsValid());\nsession-\u0026gt;ApplyTestingFixedPort(endpoint);\nbool disable_cert_network_fetches = !!(request_load_flags \u0026amp; LOAD_DISABLE_CERT_NETWORK_FETCHES); ClientSocketPool::GroupId connection_group( std::move(endpoint), privacy_mode, std::move(network_anonymization_key), secure_dns_policy, disable_cert_network_fetches); scoped_refptrClientSocketPool::SocketParams socket_params = CreateSocketParams(connection_group, allowed_bad_certs);\nClientSocketPool* pool = session-\u0026gt;GetSocketPool(socket_pool_type, proxy_info.proxy_chain()); ClientSocketPool::RespectLimits respect_limits = ClientSocketPool::RespectLimits::ENABLED; if ((request_load_flags \u0026amp; LOAD_IGNORE_LIMITS) != 0) respect_limits = ClientSocketPool::RespectLimits::DISABLED;\nstd::optional proxy_annotation = proxy_info.is_direct() ? std::nullopt : std::optional( proxy_info.traffic_annotation()); if (num_preconnect_streams) { return pool-\u0026gt;RequestSockets(connection_group, std::move(socket_params), proxy_annotation, num_preconnect_streams, fail_if_alias_requires_proxy_override, std::move(callback), net_log); }\nreturn socket_handle-\u0026gt;Init( connection_group, std::move(socket_params), proxy_annotation, request_priority, socket_tag, respect_limits, std::move(callback), proxy_auth_callback, fail_if_alias_requires_proxy_override, pool, net_log); }\nhttps://source.chromium.org/chromium/chromium/src/+/main:net/socket/client_socket_handle.cc;drc=4f289372eacd2b226e9d2830cc4a87b4ad621133;l=31 int ClientSocketHandle::Init( const ClientSocketPool::GroupId\u0026amp; group_id, scoped_refptrClientSocketPool::SocketParams socket_params, const std::optional\u0026amp; proxy_annotation_tag, RequestPriority priority, const SocketTag\u0026amp; socket_tag, ClientSocketPool::RespectLimits respect_limits, CompletionOnceCallback callback, const ClientSocketPool::ProxyAuthCallback\u0026amp; proxy_auth_callback, bool fail_if_alias_requires_proxy_override, ClientSocketPool* pool, const NetLogWithSource\u0026amp; net_log) { requesting_source_ = net_log.source();\nCHECK(group_id.destination().IsValid()); ResetInternal(true /* cancel /, false / cancel_connect_job */); ResetErrorState(); pool_ = pool; group_id_ = group_id; CompletionOnceCallback io_complete_callback = base::BindOnce(\u0026amp;ClientSocketHandle::OnIOComplete, base::Unretained(this)); int rv = pool_-\u0026gt;RequestSocket( group_id, std::move(socket_params), proxy_annotation_tag, priority, socket_tag, respect_limits, this, std::move(io_complete_callback), proxy_auth_callback, fail_if_alias_requires_proxy_override, net_log); if (rv == ERR_IO_PENDING) { callback_ = std::move(callback); } else { HandleInitCompletion(rv); } return rv; }\nhttps://source.chromium.org/chromium/chromium/src/+/main:net/socket/transport_client_socket_pool.cc;l=249;drc=4f289372eacd2b226e9d2830cc4a87b4ad621133;bpv=1;bpt=1 int TransportClientSocketPool::RequestSocket( const GroupId\u0026amp; group_id, scoped_refptr params, const std::optional\u0026amp; proxy_annotation_tag, RequestPriority priority, const SocketTag\u0026amp; socket_tag, RespectLimits respect_limits, ClientSocketHandle* handle, CompletionOnceCallback callback, const ProxyAuthCallback\u0026amp; proxy_auth_callback, bool fail_if_alias_requires_proxy_override, const NetLogWithSource\u0026amp; net_log) { CHECK(callback); CHECK(handle);\nNetLogTcpClientSocketPoolRequestedSocket(net_log, group_id);\nstd::unique_ptr request = std::make_unique( handle, std::move(callback), proxy_auth_callback, fail_if_alias_requires_proxy_override, priority, socket_tag, respect_limits, NORMAL, std::move(params), proxy_annotation_tag, net_log);\n// Cleanup any timed-out idle sockets. CleanupIdleSockets(false, nullptr /* net_log_reason_utf8 */);\nrequest-\u0026gt;net_log().BeginEvent(NetLogEventType::SOCKET_POOL);\nint rv = RequestSocketInternal(group_id, request, /preconnect_done_closure=/base::OnceClosure()); if (rv != ERR_IO_PENDING) { if (rv == OK) { request-\u0026gt;handle()-\u0026gt;socket()-\u0026gt;ApplySocketTag(request-\u0026gt;socket_tag()); } request-\u0026gt;net_log().EndEventWithNetErrorCode(NetLogEventType::SOCKET_POOL, rv); CHECK(!request-\u0026gt;handle()-\u0026gt;is_initialized()); request.reset(); } else { Group group = GetOrCreateGroup( group_id, !request-\u0026gt;fail_if_alias_requires_proxy_override()); group-\u0026gt;InsertUnboundRequest(std::move(request)); // Have to do this asynchronously, as closing sockets in higher level pools // call back in to |this|, which will cause all sorts of fun and exciting // re-entrancy issues if the socket pool is doing something else at the // time. if (group-\u0026gt;CanUseAdditionalSocketSlot(max_sockets_per_group_)) { base::SingleThreadTaskRunner::GetCurrentDefault()-\u0026gt;PostTask( FROM_HERE, base::BindOnce( \u0026amp;TransportClientSocketPool::TryToCloseSocketsInLayeredPools, weak_factory_.GetWeakPtr())); } } return rv; }\nhttps://source.chromium.org/chromium/chromium/src/+/main:net/socket/transport_client_socket_pool.cc;drc=4f289372eacd2b226e9d2830cc4a87b4ad621133;bpv=1;bpt=1;l=391 int TransportClientSocketPool::RequestSocketInternal( const GroupId\u0026amp; group_id, const Request\u0026amp; request, base::OnceClosure preconnect_done_closure) { #if DCHECK_IS_ON() DCHECK(!request_in_process_); base::AutoReset auto_reset(\u0026amp;request_in_process_, true); #endif // DCHECK_IS_ON()\nClientSocketHandle* const handle = request.handle(); const bool preconnecting = !handle; DCHECK_EQ(preconnecting, !!preconnect_done_closure);\nGroup* group = nullptr; auto group_it = group_map_.find(group_id); if (group_it != group_map_.end()) { group = group_it-\u0026gt;second;\nif (!(request.flags() \u0026amp; NO_IDLE_SOCKETS)) { // Try to reuse a socket. if (AssignIdleSocketToRequest(request, group)) return OK; } // If there are more ConnectJobs than pending requests, don't need to do // anything. Can just wait for the extra job to connect, and then assign it // to the request. if (!preconnecting \u0026amp;\u0026amp; group-\u0026gt;TryToUseNeverAssignedConnectJob()) return ERR_IO_PENDING; // Can we make another active socket now? if (!group-\u0026gt;HasAvailableSocketSlot(max_sockets_per_group_) \u0026amp;\u0026amp; request.respect_limits() == RespectLimits::ENABLED) { // TODO(willchan): Consider whether or not we need to close a socket in a // higher layered group. I don't think this makes sense since we would // just reuse that socket then if we needed one and wouldn't make it down // to this layer. request.net_log().AddEvent( NetLogEventType::SOCKET_POOL_STALLED_MAX_SOCKETS_PER_GROUP); return preconnecting ? ERR_PRECONNECT_MAX_SOCKET_LIMIT : ERR_IO_PENDING; } }\nif (ReachedMaxSocketsLimit() \u0026amp;\u0026amp; request.respect_limits() == RespectLimits::ENABLED) { // NOTE(mmenke): Wonder if we really need different code for each case // here. Only reason for them now seems to be preconnects. if (idle_socket_count_ \u0026gt; 0) { // There\u0026rsquo;s an idle socket in this pool. Either that\u0026rsquo;s because there\u0026rsquo;s // still one in this group, but we got here due to preconnecting // bypassing idle sockets, or because there\u0026rsquo;s an idle socket in another // group. bool closed = CloseOneIdleSocketExceptInGroup(group); if (preconnecting \u0026amp;\u0026amp; !closed) return ERR_PRECONNECT_MAX_SOCKET_LIMIT; } else { // We could check if we really have a stalled group here, but it // requires a scan of all groups, so just flip a flag here, and do the // check later. request.net_log().AddEvent( NetLogEventType::SOCKET_POOL_STALLED_MAX_SOCKETS); return preconnecting ? ERR_PRECONNECT_MAX_SOCKET_LIMIT : ERR_IO_PENDING; } }\n// We couldn\u0026rsquo;t find a socket to reuse, and there\u0026rsquo;s space to allocate one, // so allocate and connect a new one. group = GetOrCreateGroup(group_id, !request.fail_if_alias_requires_proxy_override()); std::unique_ptr connect_job( CreateConnectJob(group_id, request.socket_params(), proxy_chain_, request.proxy_annotation_tag(), request.priority(), request.socket_tag(), group)); connect_job-\u0026gt;net_log().AddEvent( NetLogEventType::SOCKET_POOL_CONNECT_JOB_CREATED, [\u0026amp;] { return NetLogCreateConnectJobParams(false /* backup_job */, \u0026amp;group_id); });\nint rv = connect_job-\u0026gt;Connect(); if (rv == ERR_IO_PENDING) { if (preconnect_done_closure) { DCHECK(preconnecting); connect_job-\u0026gt;set_done_closure(std::move(preconnect_done_closure)); } // If we didn\u0026rsquo;t have any sockets in this group, set a timer for potentially // creating a new one. If the SYN is lost, this backup socket may complete // before the slow socket, improving end user latency. if (connect_backup_jobs_enabled_ \u0026amp;\u0026amp; group-\u0026gt;IsEmpty()) group-\u0026gt;StartBackupJobTimer(group_id); group-\u0026gt;AddJob(std::move(connect_job), preconnecting); connecting_socket_count_++; return rv; }\nLogBoundConnectJobToRequest(connect_job-\u0026gt;net_log().source(), request); if (preconnecting) { if (rv == OK) AddIdleSocket(connect_job-\u0026gt;PassSocket(), group); } else { DCHECK(handle); if (rv != OK) handle-\u0026gt;SetAdditionalErrorState(connect_job.get()); std::unique_ptr socket = connect_job-\u0026gt;PassSocket(); if (socket) { HandOutSocket(std::move(socket), StreamSocketHandle::SocketReuseType::kUnused, connect_job-\u0026gt;connect_timing(), handle, /time_idle=/base::TimeDelta(), group, request.net_log()); } } if (group-\u0026gt;IsEmpty()) RemoveGroup(group_id);\nreturn rv; }\nhttps://source.chromium.org/chromium/chromium/src/+/main:net/socket/connect_job.cc;l=130;drc=4f289372eacd2b226e9d2830cc4a87b4ad621133;bpv=1;bpt=1 int ConnectJob::Connect() { if (!timeout_duration_.is_zero()) { timer_.Start(FROM_HERE, timeout_duration_, this, \u0026amp;ConnectJob::OnTimeout); }\nLogConnectStart();\nint rv = ConnectInternal();\nif (rv != ERR_IO_PENDING) { LogConnectCompletion(rv); delegate_ = nullptr; }\nreturn rv; }\nhttps://source.chromium.org/chromium/chromium/src/+/main:net/socket/ssl_connect_job.cc;l=488;drc=4f289372eacd2b226e9d2830cc4a87b4ad621133;bpv=1;bpt=1 int SSLConnectJob::ConnectInternal() { next_state_ = GetInitialState(params_-\u0026gt;GetConnectionType()); return DoLoop(OK); }\nhttps://source.chromium.org/chromium/chromium/src/+/main:net/socket/ssl_connect_job.cc;drc=4f289372eacd2b226e9d2830cc4a87b4ad621133;bpv=1;bpt=1;l=201 int SSLConnectJob::DoLoop(int result) { TRACE_EVENT0(NetTracingCategory(), \u0026ldquo;SSLConnectJob::DoLoop\u0026rdquo;); DCHECK_NE(next_state_, STATE_NONE);\nint rv = result; do { State state = next_state_; next_state_ = STATE_NONE; switch (state) { case STATE_TRANSPORT_CONNECT: DCHECK_EQ(OK, rv); rv = DoTransportConnect(); break; case STATE_TRANSPORT_CONNECT_COMPLETE: rv = DoTransportConnectComplete(rv); break; case STATE_SOCKS_CONNECT: DCHECK_EQ(OK, rv); rv = DoSOCKSConnect(); break; case STATE_SOCKS_CONNECT_COMPLETE: rv = DoSOCKSConnectComplete(rv); break; case STATE_TUNNEL_CONNECT: DCHECK_EQ(OK, rv); rv = DoTunnelConnect(); break; case STATE_TUNNEL_CONNECT_COMPLETE: rv = DoTunnelConnectComplete(rv); break; case STATE_SSL_CONNECT: DCHECK_EQ(OK, rv); rv = DoSSLConnect(); break; case STATE_SSL_CONNECT_COMPLETE: rv = DoSSLConnectComplete(rv); break; default: NOTREACHED() \u0026laquo; \u0026ldquo;bad state\u0026rdquo;; } } while (rv != ERR_IO_PENDING \u0026amp;\u0026amp; next_state_ != STATE_NONE);\nreturn rv; }\nhttps://source.chromium.org/chromium/chromium/src/+/main:net/socket/ssl_connect_job.cc;drc=4f289372eacd2b226e9d2830cc4a87b4ad621133;bpv=1;bpt=1;l=246 int SSLConnectJob::DoTransportConnect() { DCHECK(!nested_connect_job_); DCHECK(params_-\u0026gt;GetDirectConnectionParams()); DCHECK(!TimerIsRunning());\nnext_state_ = STATE_TRANSPORT_CONNECT_COMPLETE; // If this is an ECH retry, connect to the same server as before. std::optionalTransportConnectJob::EndpointResultOverride endpoint_result_override; if (ech_retry_configs_) { DCHECK(ssl_client_context()-\u0026gt;config().ech_enabled); DCHECK(endpoint_result_); endpoint_result_override.emplace(*endpoint_result_, dns_aliases_); } nested_connect_job_ = std::make_unique( priority(), socket_tag(), common_connect_job_params(), params_-\u0026gt;GetDirectConnectionParams(), this, \u0026amp;net_log(), std::move(endpoint_result_override)); return nested_connect_job_-\u0026gt;Connect(); }\nhttps://source.chromium.org/chromium/chromium/src/+/main:net/socket/transport_connect_job.cc;l=513;drc=4f289372eacd2b226e9d2830cc4a87b4ad621133;bpv=1;bpt=1 int TransportConnectJob::ConnectInternal() { next_state_ = STATE_RESOLVE_HOST; return DoLoop(OK); }\nhttps://source.chromium.org/chromium/chromium/src/+/main:net/socket/transport_connect_job.cc;drc=4f289372eacd2b226e9d2830cc4a87b4ad621133;bpv=1;bpt=1;l=209 int TransportConnectJob::DoLoop(int result) { DCHECK_NE(next_state_, STATE_NONE);\nint rv = result; do { State state = next_state_; next_state_ = STATE_NONE; switch (state) { case STATE_RESOLVE_HOST: DCHECK_EQ(OK, rv); rv = DoResolveHost(); break; case STATE_RESOLVE_HOST_COMPLETE: rv = DoResolveHostComplete(rv); break; case STATE_RESOLVE_HOST_CALLBACK_COMPLETE: DCHECK_EQ(OK, rv); rv = DoResolveHostCallbackComplete(); break; case STATE_TRANSPORT_CONNECT: DCHECK_EQ(OK, rv); rv = DoTransportConnect(); break; case STATE_TRANSPORT_CONNECT_COMPLETE: rv = DoTransportConnectComplete(rv); break; default: NOTREACHED(); } } while (rv != ERR_IO_PENDING \u0026amp;\u0026amp; next_state_ != STATE_NONE);\nreturn rv; }\nhttps://source.chromium.org/chromium/chromium/src/+/main:net/socket/transport_connect_job.cc;drc=4f289372eacd2b226e9d2830cc4a87b4ad621133;bpv=1;bpt=1;l=243 int TransportConnectJob::DoResolveHost() { connect_timing_.domain_lookup_start = base::TimeTicks::Now();\nif (has_dns_override_) { DCHECK_EQ(1u, endpoint_results_.size()); connect_timing_.domain_lookup_end = connect_timing_.domain_lookup_start; next_state_ = STATE_TRANSPORT_CONNECT; return OK; }\nnext_state_ = STATE_RESOLVE_HOST_COMPLETE;\nHostResolver::ResolveHostParameters parameters; parameters.initial_priority = priority(); parameters.secure_dns_policy = params_-\u0026gt;secure_dns_policy(); if (std::holds_alternativeurl::SchemeHostPort (params_-\u0026gt;destination())) { request_ = host_resolver()-\u0026gt;CreateRequest( std::geturl::SchemeHostPort (params_-\u0026gt;destination()), params_-\u0026gt;network_anonymization_key(), net_log(), parameters); } else { request_ = host_resolver()-\u0026gt;CreateRequest( std::get(params_-\u0026gt;destination()), params_-\u0026gt;network_anonymization_key(), net_log(), parameters); }\nreturn request_-\u0026gt;Start(base::BindOnce(\u0026amp;TransportConnectJob::OnIOComplete, base::Unretained(this))); }\nhttps://source.chromium.org/chromium/chromium/src/+/main:net/dns/host_resolver_manager_request_impl.cc;l=73;drc=4f289372eacd2b226e9d2830cc4a87b4ad621133;bpv=1;bpt=1 int HostResolverManager::RequestImpl::Start(CompletionOnceCallback callback) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); DCHECK(callback); // Start() may only be called once per request. CHECK(!job_.has_value()); DCHECK(!complete_); DCHECK(!callback_); // Parent HostResolver must still be alive to call Start(). DCHECK(resolver_);\nif (!resolve_context_) { complete_ = true; resolver_.reset(); set_error_info(ERR_CONTEXT_SHUT_DOWN, false); return ERR_NAME_NOT_RESOLVED; }\nLogStartRequest();\nnext_state_ = STATE_IPV6_REACHABILITY; callback_ = std::move(callback);\nint rv = OK; rv = DoLoop(rv); return rv; }\nhttps://source.chromium.org/chromium/chromium/src/+/main:net/dns/host_resolver_manager_request_impl.cc;drc=4f289372eacd2b226e9d2830cc4a87b4ad621133;bpv=1;bpt=1;l=244 int HostResolverManager::RequestImpl::DoLoop(int rv) { do { ResolveState state = next_state_; next_state_ = STATE_NONE; switch (state) { case STATE_IPV6_REACHABILITY: rv = DoIPv6Reachability(); break; case STATE_GET_PARAMETERS: DCHECK_EQ(OK, rv); rv = DoGetParameters(); break; case STATE_GET_PARAMETERS_COMPLETE: rv = DoGetParametersComplete(rv); break; case STATE_RESOLVE_LOCALLY: rv = DoResolveLocally(); break; case STATE_START_JOB: rv = DoStartJob(); break; case STATE_FINISH_REQUEST: rv = DoFinishRequest(rv); break; default: NOTREACHED() \u0026laquo; \u0026ldquo;next_state_: \u0026quot; \u0026laquo; next_state_; } } while (next_state_ != STATE_NONE \u0026amp;\u0026amp; rv != ERR_IO_PENDING);\nreturn rv; }\nhttps://source.chromium.org/chromium/chromium/src/+/main:net/dns/host_resolver_manager_request_impl.cc;l=355;drc=4f289372eacd2b226e9d2830cc4a87b4ad621133;bpv=1;bpt=1 int HostResolverManager::RequestImpl::DoStartJob() { resolver_-\u0026gt;CreateAndStartJob(std::move(job_key_), std::move(tasks_), this); DCHECK(!complete_); resolver_.reset(); return ERR_IO_PENDING; }\nhttps://source.chromium.org/chromium/chromium/src/+/main:net/dns/host_resolver_manager.cc;l=951;drc=4f289372eacd2b226e9d2830cc4a87b4ad621133;bpv=1;bpt=1 void HostResolverManager::CreateAndStartJob(JobKey key, std::deque tasks, RequestImpl* request) { DCHECK(!tasks.empty());\nauto jobit = jobs_.find(key); Job* job; if (jobit == jobs_.end()) { job = AddJobWithoutRequest(key, request-\u0026gt;parameters().cache_usage, request-\u0026gt;host_cache(), std::move(tasks), request-\u0026gt;priority(), request-\u0026gt;source_net_log()); job-\u0026gt;AddRequest(request); job-\u0026gt;RunNextTask(); } else { job = jobit-\u0026gt;second.get(); job-\u0026gt;AddRequest(request); } }\nhttps://source.chromium.org/chromium/chromium/src/+/main:net/dns/host_resolver_manager_job.cc;drc=4f289372eacd2b226e9d2830cc4a87b4ad621133;bpv=1;bpt=1;l=401 void HostResolverManager::Job::RunNextTask() { // If there are no tasks left to try, cache any stored results and complete // the request with the last stored result. All stored results should be // errors. if (tasks_.empty()) { // If there are no stored results, complete with an error. if (completion_results_.size() == 0) { CompleteRequestsWithError(ERR_NAME_NOT_RESOLVED, /task_type=/std::nullopt); return; }\n// Cache all but the last result here. The last result will be cached // as part of CompleteRequests. for (size_t i = 0; i \u0026lt; completion_results_.size() - 1; ++i) { const auto\u0026amp; result = completion_results_[i]; DCHECK_NE(OK, result.entry.error()); MaybeCacheResult(result.entry, result.ttl, result.secure); } const auto\u0026amp; last_result = completion_results_.back(); DCHECK_NE(OK, last_result.entry.error()); CompleteRequests(last_result.entry, last_result.ttl, true /* allow_cache */, last_result.secure, last_result.secure ? TaskType::SECURE_DNS : TaskType::DNS); return; }\nTaskType next_task = tasks_.front();\n// Schedule insecure DnsTasks and HostResolverSystemTasks with the // dispatcher. if (!dispatched_ \u0026amp;\u0026amp; (next_task == TaskType::DNS || next_task == TaskType::SYSTEM || next_task == TaskType::MDNS)) { dispatched_ = true; job_running_ = false; Schedule(false); DCHECK(is_running() || is_queued());\n// Check for queue overflow. PrioritizedDispatcher\u0026amp; dispatcher = *resolver_-\u0026gt;dispatcher_; if (dispatcher.num_queued_jobs() \u0026gt; resolver_-\u0026gt;max_queued_jobs_) { Job* evicted = static_cast\u0026lt;Job*\u0026gt;(dispatcher.EvictOldestLowest()); DCHECK(evicted); evicted-\u0026gt;OnEvicted(); } return; }\nif (start_time_ == base::TimeTicks()) { net_log_.AddEvent(NetLogEventType::HOST_RESOLVER_MANAGER_JOB_STARTED); start_time_ = tick_clock_-\u0026gt;NowTicks(); } tasks_.pop_front(); job_running_ = true;\nswitch (next_task) { case TaskType::SYSTEM: StartSystemTask(); break; case TaskType::DNS: StartDnsTask(false /* secure /); break; case TaskType::SECURE_DNS: StartDnsTask(true / secure */); break; case TaskType::MDNS: StartMdnsTask(); break; case TaskType::INSECURE_CACHE_LOOKUP: InsecureCacheLookup(); break; case TaskType::NAT64: StartNat64Task(); break; case TaskType::SECURE_CACHE_LOOKUP: case TaskType::CACHE_LOOKUP: case TaskType::CONFIG_PRESET: case TaskType::HOSTS: // These task types should have been handled synchronously in // ResolveLocally() prior to Job creation. NOTREACHED(); } }\nまずはとりあえずSystemTaskの方から向かってみる。 https://source.chromium.org/chromium/chromium/src/+/main:net/dns/host_resolver_manager_job.cc;l=621;drc=4f289372eacd2b226e9d2830cc4a87b4ad621133;bpv=1;bpt=1?q=StartSystemTask\u0026sq=\u0026ss=chromium%2Fchromium%2Fsrc void HostResolverManager::Job::StartSystemTask() { DCHECK(dispatched_); DCHECK_EQ(1, num_occupied_job_slots_); DCHECK(HasAddressType(key_.query_types));\nstd::optionalHostResolverSystemTask::CacheParams cache_params; if (key_.resolve_context-\u0026gt;host_resolver_cache()) { cache_params.emplace(*key_.resolve_context-\u0026gt;host_resolver_cache(), key_.network_anonymization_key); }\nsystem_task_ = HostResolverSystemTask::Create( std::string(key_.host.GetHostnameWithoutBrackets()), HostResolver::DnsQueryTypeSetToAddressFamily(key_.query_types), key_.flags, resolver_-\u0026gt;host_resolver_system_params_, net_log_, key_.GetTargetNetwork(), std::move(cache_params));\n// Start() could be called from within Resolve(), hence it must NOT directly // call OnSystemTaskComplete, for example, on synchronous failure. system_task_-\u0026gt;Start(base::BindOnce(\u0026amp;Job::OnSystemTaskComplete, base::Unretained(this), tick_clock_-\u0026gt;NowTicks())); }\nhttps://source.chromium.org/chromium/chromium/src/+/main:net/dns/host_resolver_system_task.cc;drc=4f289372eacd2b226e9d2830cc4a87b4ad621133;bpv=1;bpt=1;l=299 void HostResolverSystemTask::Start(SystemDnsResultsCallback results_cb) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); DCHECK(results_cb); DCHECK(!results_cb_); results_cb_ = std::move(results_cb); net_log_.BeginEvent(NetLogEventType::HOST_RESOLVER_SYSTEM_TASK); StartLookupAttempt(); }\nvoid HostResolverSystemTask::StartLookupAttempt() { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); DCHECK(!was_completed()); ++attempt_number_;\nnet_log_.AddEventWithIntParams( NetLogEventType::HOST_RESOLVER_MANAGER_ATTEMPT_STARTED, \u0026ldquo;attempt_number\u0026rdquo;, attempt_number_);\n// If the results aren\u0026rsquo;t received within a given time, RetryIfNotComplete // will start a new attempt if none of the outstanding attempts have // completed yet. // Use a WeakPtr to avoid keeping the HostResolverSystemTask alive after // completion or cancellation. if (attempt_number_ \u0026lt;= params_.max_retry_attempts) { base::SequencedTaskRunner::GetCurrentDefault()-\u0026gt;PostDelayedTask( FROM_HERE, base::BindOnce(\u0026amp;HostResolverSystemTask::StartLookupAttempt, weak_ptr_factory_.GetWeakPtr()), params_.unresponsive_delay * std::pow(params_.retry_factor, attempt_number_ - 1)); }\nauto lookup_complete_cb = base::BindOnce(\u0026amp;HostResolverSystemTask::OnLookupComplete, weak_ptr_factory_.GetWeakPtr(), attempt_number_);\n// If a hook has been installed, call it instead of posting a resolution task // to a worker thread. if (GetSystemDnsResolverOverride()) { GetSystemDnsResolverOverride().Run(hostname_, address_family_, flags_, std::move(lookup_complete_cb), network_); // Do not add code below. lookup_complete_cb may have already deleted // this. } else { base::OnceCallback\u0026lt;int(AddressList * addrlist, int* os_error)\u0026gt; resolve_cb = base::BindOnce(\u0026amp;ResolveOnWorkerThread, params_.resolver_proc, hostname_, address_family_, flags_, network_); PostSystemDnsResolutionTaskAndReply(std::move(resolve_cb), std::move(lookup_complete_cb)); } }\nhttps://source.chromium.org/chromium/chromium/src/+/main:net/dns/host_resolver_system_task.cc;drc=4f289372eacd2b226e9d2830cc4a87b4ad621133;bpv=1;bpt=1;l=74 // Posts a synchronous callback to a thread pool task runner created with // MayBlock, USER_BLOCKING, and CONTINUE_ON_SHUTDOWN. This task runner can be // overridden by assigning to GetSystemDnsResolutionTaskRunnerOverride(). // results_cb will be called later on the current sequence with the results of // the DNS resolution. void PostSystemDnsResolutionTaskAndReply( base::OnceCallback\u0026lt;int(AddressList* addrlist, int* os_error)\u0026gt; system_dns_resolution_callback, SystemDnsResultsCallback results_cb) { auto addr_list = std::make_uniquenet::AddressList (); net::AddressList* addr_list_ptr = addr_list.get(); auto os_error = std::make_unique(); int* os_error_ptr = os_error.get();\n// This callback owns |addr_list| and |os_error| and just calls |results_cb| // with the results. auto call_with_results_cb = base::BindOnce( [](SystemDnsResultsCallback results_cb, std::unique_ptrnet::AddressList addr_list, std::unique_ptr os_error, int net_error) { std::move(results_cb).Run(std::move(*addr_list), *os_error, net_error); }, std::move(results_cb), std::move(addr_list), std::move(os_error));\nscoped_refptrbase::TaskRunner system_dns_resolution_task_runner = GetSystemDnsResolutionTaskRunnerOverride(); if (!system_dns_resolution_task_runner) { // In production this will run on every call, otherwise some tests will // leave a stale task runner around after tearing down their task // environment. This should not be less performant than the regular // base::ThreadPool::PostTask(). system_dns_resolution_task_runner = base::ThreadPool::CreateTaskRunner( {base::MayBlock(), base::TaskPriority::USER_BLOCKING, base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN}); } system_dns_resolution_task_runner-\u0026gt;PostTaskAndReplyWithResult( FROM_HERE, base::BindOnce(std::move(system_dns_resolution_callback), addr_list_ptr, os_error_ptr), std::move(call_with_results_cb)); }\nhttps://source.chromium.org/chromium/chromium/src/+/main:net/dns/host_resolver_system_task.cc;l=111?q=PostSystemDnsResolutionTaskAndReply\u0026ss=chromium%2Fchromium%2Fsrc int ResolveOnWorkerThread(scoped_refptr resolver_proc, std::optionalstd::string hostname, AddressFamily address_family, HostResolverFlags flags, handles::NetworkHandle network, AddressList* addrlist, int* os_error) { std::string hostname_str = hostname ? *std::move(hostname) : GetHostName(); if (resolver_proc) { return resolver_proc-\u0026gt;Resolve(hostname_str, address_family, flags, addrlist, os_error, network); } else { return SystemHostResolverCall(hostname_str, address_family, flags, addrlist, os_error, network); } }\nhttps://source.chromium.org/chromium/chromium/src/+/main:net/dns/host_resolver_system_task.cc;drc=4f289372eacd2b226e9d2830cc4a87b4ad621133;bpv=1;bpt=1;l=490 int SystemHostResolverCall(const std::string\u0026amp; host, AddressFamily address_family, HostResolverFlags host_resolver_flags, AddressList* addrlist, int* os_error_opt, handles::NetworkHandle network) { struct addrinfo hints = {0}; hints.ai_family = AddressFamilyToAF(address_family);\n#if BUILDFLAG(IS_WIN) // DO NOT USE AI_ADDRCONFIG ON WINDOWS. // // The following comment in \u0026lt;winsock2.h\u0026gt; is the best documentation I found // on AI_ADDRCONFIG for Windows: // Flags used in \u0026ldquo;hints\u0026rdquo; argument to getaddrinfo() // - AI_ADDRCONFIG is supported starting with Vista // - default is AI_ADDRCONFIG ON whether the flag is set or not // because the performance penalty in not having ADDRCONFIG in // the multi-protocol stack environment is severe; // this defaulting may be disabled by specifying the AI_ALL flag, // in that case AI_ADDRCONFIG must be EXPLICITLY specified to // enable ADDRCONFIG behavior // // Not only is AI_ADDRCONFIG unnecessary, but it can be harmful. If the // computer is not connected to a network, AI_ADDRCONFIG causes getaddrinfo // to fail with WSANO_DATA (11004) for \u0026ldquo;localhost\u0026rdquo;, probably because of the // following note on AI_ADDRCONFIG in the MSDN getaddrinfo page: // The IPv4 or IPv6 loopback address is not considered a valid global // address. // See http://crbug.com/5234 . // // OpenBSD does not support it, either. hints.ai_flags = 0; #else // On other operating systems, AI_ADDRCONFIG may reduce the amount of // unnecessary DNS lookups, e.g. getaddrinfo() will not send a request for // AAAA records if the current machine has no IPv6 addresses configured and // therefore could not use the resulting AAAA record anyway. On some ancient // routers, AAAA DNS queries won\u0026rsquo;t be handled correctly and will cause // multiple retransmitions and large latency spikes. hints.ai_flags = AI_ADDRCONFIG; #endif\n// On Linux AI_ADDRCONFIG doesn\u0026rsquo;t consider loopback addresses, even if only // loopback addresses are configured. So don\u0026rsquo;t use it when there are only // loopback addresses. See loopback_only.h and // https://fedoraproject.org/wiki/QA/Networking/NameResolution/ADDRCONFIG for // a description of some of the issues AI_ADDRCONFIG can cause. if (host_resolver_flags \u0026amp; HOST_RESOLVER_LOOPBACK_ONLY) { hints.ai_flags \u0026amp;= ~AI_ADDRCONFIG; }\nif (host_resolver_flags \u0026amp; HOST_RESOLVER_CANONNAME) hints.ai_flags |= AI_CANONNAME;\n#if BUILDFLAG(IS_WIN) // See crbug.com/1176970. Flag not documented (other than the declaration // comment in ws2def.h) but confirmed by Microsoft to work for this purpose // and be safe. if (host_resolver_flags \u0026amp; HOST_RESOLVER_AVOID_MULTICAST) hints.ai_flags |= AI_DNS_ONLY; #endif // BUILDFLAG(IS_WIN)\n// Restrict result set to only this socket type to avoid duplicates. hints.ai_socktype = SOCK_STREAM;\n// This function can block for a long time. Use ScopedBlockingCall to increase // the current thread pool\u0026rsquo;s capacity and thus avoid reducing CPU usage by the // current process during that time. base::ScopedBlockingCall scoped_blocking_call(FROM_HERE, base::BlockingType::WILL_BLOCK); DnsReloaderMaybeReload();\nauto [ai, err, os_error] = AddressInfo::Get(host, hints, nullptr, network); bool should_retry = false; // If the lookup was restricted (either by address family, or address // detection), and the results where all localhost of a single family, // maybe we should retry. There were several bugs related to these // issues, for example http://crbug.com/42058 and http://crbug.com/49024 if ((hints.ai_family != AF_UNSPEC || hints.ai_flags \u0026amp; AI_ADDRCONFIG) \u0026amp;\u0026amp; ai \u0026amp;\u0026amp; ai-\u0026gt;IsAllLocalhostOfOneFamily()) { if (host_resolver_flags \u0026amp; HOST_RESOLVER_DEFAULT_FAMILY_SET_DUE_TO_NO_IPV6) { hints.ai_family = AF_UNSPEC; should_retry = true; } if (hints.ai_flags \u0026amp; AI_ADDRCONFIG) { hints.ai_flags \u0026amp;= ~AI_ADDRCONFIG; should_retry = true; } } if (should_retry) { std::tie(ai, err, os_error) = AddressInfo::Get(host, hints, nullptr, network); }\nif (os_error_opt) *os_error_opt = os_error;\nif (!ai) return err;\n*addrlist = ai-\u0026gt;CreateAddressList(); return OK; }\nhttps://source.chromium.org/chromium/chromium/src/+/main:net/dns/address_info.cc;drc=4f289372eacd2b226e9d2830cc4a87b4ad621133;bpv=1;bpt=1;l=56 AddressInfo::AddressInfoAndResult AddressInfo::Get( const std::string\u0026amp; host, const addrinfo\u0026amp; hints, std::unique_ptr getter, handles::NetworkHandle network) { if (getter == nullptr) getter = std::make_unique(); int err = OK; int os_error = 0; std::unique_ptr\u0026lt;addrinfo, FreeAddrInfoFunc\u0026gt; ai = getter-\u0026gt;getaddrinfo(host, \u0026amp;hints, \u0026amp;os_error, network);\nif (!ai) { err = ERR_NAME_NOT_RESOLVED;\n// If the call to getaddrinfo() failed because of a system error, report // it separately from ERR_NAME_NOT_RESOLVED. #if BUILDFLAG(IS_WIN) if (os_error != WSAHOST_NOT_FOUND \u0026amp;\u0026amp; os_error != WSANO_DATA) err = ERR_NAME_RESOLUTION_FAILED; #elif BUILDFLAG(IS_ANDROID) // Workaround for Android\u0026rsquo;s getaddrinfo leaving ai==nullptr without an // error. // http://crbug.com/134142 err = ERR_NAME_NOT_RESOLVED; #elif BUILDFLAG(IS_POSIX) \u0026amp;\u0026amp; !BUILDFLAG(IS_FREEBSD) if (os_error != EAI_NONAME \u0026amp;\u0026amp; os_error != EAI_NODATA) err = ERR_NAME_RESOLUTION_FAILED; #endif\nreturn AddressInfoAndResult(std::optional\u0026lt;AddressInfo\u0026gt;(), err, os_error); }\nreturn AddressInfoAndResult( std::optional(AddressInfo(std::move(ai), std::move(getter))), OK, 0); }\ngetaddrinfo関数の呼び出しに到達。 https://learn.microsoft.com/ja-jp/windows/win32/api/ws2tcpip/nf-ws2tcpip-getaddrinfo 本仮定ではクライアント環境がWindowsであるためこれ以上はリバースエンジニアリングでもしないと遡れないが下手に触ってライセンスの問題を踏みたくないためこれ以上はクライアントサイドではいかない。 https://source.chromium.org/chromium/chromium/src/+/main:net/dns/address_info.cc;drc=4f289372eacd2b226e9d2830cc4a87b4ad621133;bpv=1;bpt=1;l=178 std::unique_ptr\u0026lt;addrinfo, FreeAddrInfoFunc\u0026gt; AddrInfoGetter::getaddrinfo( const std::string\u0026amp; host, const addrinfo* hints, int* out_os_error, handles::NetworkHandle network) { addrinfo* ai; // We wrap freeaddrinfo() in a lambda just in case some operating systems use // a different signature for it. FreeAddrInfoFunc deleter = [](addrinfo* ai) { ::freeaddrinfo(ai); };\nstd::unique_ptr\u0026lt;addrinfo, FreeAddrInfoFunc\u0026gt; rv = {nullptr, deleter};\nif (network != handles::kInvalidNetworkHandle) { // Currently, only Android supports lookups for a specific network. #if BUILDFLAG(IS_ANDROID) *out_os_error = android::GetAddrInfoForNetwork(network, host.c_str(), nullptr, hints, \u0026amp;ai); #elif BUILDFLAG(IS_WIN) *out_os_error = WSAEOPNOTSUPP; return rv; #else errno = ENOSYS; *out_os_error = EAI_SYSTEM; return rv; #endif // BUILDFLAG(IS_ANDROID) } else { *out_os_error = ::getaddrinfo(host.c_str(), nullptr, hints, \u0026amp;ai); }\nif (*out_os_error) { #if BUILDFLAG(IS_WIN) *out_os_error = WSAGetLastError(); #endif return rv; }\nrv.reset(ai); return rv; }\ngetaddrinfo実装は少なくとも同一プロセス内では様々なキャッシュ戦略を用いているため常にDNS Queryを飛ばしているわけではなく従って複数回アクセスしようとしても観測できないことも多いため、代わりにcurlを使ってみたところ Wiresharkで観測できた限りでは以下のようなリクエストを\nDomain Name System (query) Transaction ID: 0xa855 Flags: 0x0100 Standard query Questions: 1 Answer RRs: 0 Authority RRs: 0 Additional RRs: 0 Queries www.shonenjump.com: type A, class IN Name: www.shonenjump.com [Name Length: 18] [Label Count: 3] Type: A (1) (Host Address) Class: IN (0x0001) [Response In: 15886] Domain Name System (response) Transaction ID: 0xa855 Flags: 0x8180 Standard query response, No error Questions: 1 Answer RRs: 1 Authority RRs: 4 Additional RRs: 8 Queries www.shonenjump.com: type A, class IN Name: www.shonenjump.com [Name Length: 18] [Label Count: 3] Type: A (1) (Host Address) Class: IN (0x0001) Answers www.shonenjump.com: type A, class IN, addr 202.218.223.232 Name: www.shonenjump.com Type: A (1) (Host Address) Class: IN (0x0001) Time to live: 105 (1 minute, 45 seconds) Data length: 4 Address: 202.218.223.232 Authoritative nameservers shonenjump.com: type NS, class IN, ns ns-816.awsdns-38.net Name: shonenjump.com Type: NS (2) (authoritative Name Server) Class: IN (0x0001) Time to live: 49170 (13 hours, 39 minutes, 30 seconds) Data length: 22 Name Server: ns-816.awsdns-38.net shonenjump.com: type NS, class IN, ns ns-509.awsdns-63.com Name: shonenjump.com Type: NS (2) (authoritative Name Server) Class: IN (0x0001) Time to live: 49170 (13 hours, 39 minutes, 30 seconds) Data length: 19 Name Server: ns-509.awsdns-63.com shonenjump.com: type NS, class IN, ns ns-1592.awsdns-07.co.uk Name: shonenjump.com Type: NS (2) (authoritative Name Server) Class: IN (0x0001) Time to live: 49170 (13 hours, 39 minutes, 30 seconds) Data length: 25 Name Server: ns-1592.awsdns-07.co.uk shonenjump.com: type NS, class IN, ns ns-1230.awsdns-25.org Name: shonenjump.com Type: NS (2) (authoritative Name Server) Class: IN (0x0001) Time to live: 49170 (13 hours, 39 minutes, 30 seconds) Data length: 23 Name Server: ns-1230.awsdns-25.org Additional records ns-509.awsdns-63.com: type AAAA, class IN, addr 2600:9000:5301:fd00::1 Name: ns-509.awsdns-63.com Type: AAAA (28) (IP6 Address) Class: IN (0x0001) Time to live: 36894 (10 hours, 14 minutes, 54 seconds) Data length: 16 AAAA Address: 2600:9000:5301:fd00::1 ns-816.awsdns-38.net: type AAAA, class IN, addr 2600:9000:5303:3000::1 Name: ns-816.awsdns-38.net Type: AAAA (28) (IP6 Address) Class: IN (0x0001) Time to live: 37691 (10 hours, 28 minutes, 11 seconds) Data length: 16 AAAA Address: 2600:9000:5303:3000::1 ns-1230.awsdns-25.org: type AAAA, class IN, addr 2600:9000:5304:ce00::1 Name: ns-1230.awsdns-25.org Type: AAAA (28) (IP6 Address) Class: IN (0x0001) Time to live: 43955 (12 hours, 12 minutes, 35 seconds) Data length: 16 AAAA Address: 2600:9000:5304:ce00::1 ns-1592.awsdns-07.co.uk: type AAAA, class IN, addr 2600:9000:5306:3800::1 Name: ns-1592.awsdns-07.co.uk Type: AAAA (28) (IP6 Address) Class: IN (0x0001) Time to live: 36677 (10 hours, 11 minutes, 17 seconds) Data length: 16 AAAA Address: 2600:9000:5306:3800::1 ns-509.awsdns-63.com: type A, class IN, addr 205.251.193.253 Name: ns-509.awsdns-63.com Type: A (1) (Host Address) Class: IN (0x0001) Time to live: 36894 (10 hours, 14 minutes, 54 seconds) Data length: 4 Address: 205.251.193.253 ns-816.awsdns-38.net: type A, class IN, addr 205.251.195.48 Name: ns-816.awsdns-38.net Type: A (1) (Host Address) Class: IN (0x0001) Time to live: 37691 (10 hours, 28 minutes, 11 seconds) Data length: 4 Address: 205.251.195.48 ns-1230.awsdns-25.org: type A, class IN, addr 205.251.196.206 Name: ns-1230.awsdns-25.org Type: A (1) (Host Address) Class: IN (0x0001) Time to live: 43955 (12 hours, 12 minutes, 35 seconds) Data length: 4 Address: 205.251.196.206 ns-1592.awsdns-07.co.uk: type A, class IN, addr 205.251.198.56 Name: ns-1592.awsdns-07.co.uk Type: A (1) (Host Address) Class: IN (0x0001) Time to live: 36677 (10 hours, 11 minutes, 17 seconds) Data length: 4 Address: 205.251.198.56 [Request In: 15885] [Time: 0.015522000 seconds] 推測通りAWSのRoute 56を使っているものと思われる。\nとはいえ近年のブラウザは独自のDNS実装を使っていることも多いためそちらも眺めていく。 https://source.chromium.org/chromium/chromium/src/+/main:net/dns/host_resolver_manager_job.cc;l=707;drc=4f289372eacd2b226e9d2830cc4a87b4ad621133?q=StartDnsTask\u0026sq=\u0026ss=chromium%2Fchromium%2Fsrc void HostResolverManager::Job::StartDnsTask(bool secure) { DCHECK_EQ(secure, !dispatched_); DCHECK_EQ(dispatched_ ? 1 : 0, num_occupied_job_slots_); DCHECK(!resolver_-\u0026gt;ShouldForceSystemResolverDueToTestOverride());\n// Need to create the task even if we\u0026rsquo;re going to post a failure instead of // running it, as a \u0026ldquo;started\u0026rdquo; job needs a task to be properly cleaned up. dns_task_ = std::make_unique( resolver_-\u0026gt;dns_client_.get(), key_.host, key_.network_anonymization_key, key_.query_types, \u0026amp;key_.resolve_context, secure, key_.secure_dns_mode, this, net_log_, tick_clock_, !tasks_.empty() / fallback_available */, https_svcb_options_); if (resolver_-\u0026gt;IsHappyEyeballsV3Enabled()) { dns_task_results_manager_ = std::make_unique( this, key_.host, key_.query_types, net_log_); } dns_task_-\u0026gt;StartNextTransaction(); // Schedule a second transaction, if needed. DoH queries can bypass the // dispatcher and start all of their transactions immediately. if (secure) { while (dns_task_-\u0026gt;num_additional_transactions_needed() \u0026gt;= 1) { dns_task_-\u0026gt;StartNextTransaction(); } DCHECK_EQ(dns_task_-\u0026gt;num_additional_transactions_needed(), 0); } else if (dns_task_-\u0026gt;num_additional_transactions_needed() \u0026gt;= 1) { Schedule(true); } }\nhttps://source.chromium.org/chromium/chromium/src/+/main:net/dns/host_resolver_dns_task.cc;drc=4f289372eacd2b226e9d2830cc4a87b4ad621133;l=310 void HostResolverDnsTask::StartNextTransaction() { DCHECK_GE(num_additional_transactions_needed(), 1);\nif (!any_transaction_started_) { net_log_.BeginEvent(NetLogEventType::HOST_RESOLVER_DNS_TASK, [\u0026amp;] { return NetLogDnsTaskCreationParams(); }); } any_transaction_started_ = true;\nTransactionInfo transaction_info = std::move(transactions_needed_.front()); transactions_needed_.pop_front();\nDCHECK(IsAddressType(transaction_info.type) || secure_ || client_-\u0026gt;CanQueryAdditionalTypesViaInsecureDns());\n// Record how long this transaction has been waiting to be created. base::TimeDelta time_queued = tick_clock_-\u0026gt;NowTicks() - task_start_time_; UMA_HISTOGRAM_LONG_TIMES_100(\u0026ldquo;Net.DNS.JobQueueTime.PerTransaction\u0026rdquo;, time_queued); delegate_-\u0026gt;AddTransactionTimeQueued(time_queued);\nCreateAndStartTransaction(std::move(transaction_info)); }\nhttps://source.chromium.org/chromium/chromium/src/+/main:net/dns/host_resolver_dns_task.cc;drc=4f289372eacd2b226e9d2830cc4a87b4ad621133;l=429 void HostResolverDnsTask::CreateAndStartTransaction( TransactionInfo transaction_info) { DCHECK(!transaction_info.transaction); DCHECK_NE(DnsQueryType::UNSPECIFIED, transaction_info.type);\nstd::string transaction_hostname(host_.GetHostnameWithoutBrackets());\n// For HTTPS, prepend \u0026ldquo;.https.\u0026rdquo; for any non-default port. uint16_t request_port = 0; if (transaction_info.type == DnsQueryType::HTTPS \u0026amp;\u0026amp; host.HasScheme()) { const auto\u0026amp; scheme_host_port = host.AsSchemeHostPort(); transaction_hostname = dns_util::GetNameForHttpsQuery(scheme_host_port, \u0026amp;request_port); }\ntransaction_info.transaction = client_-\u0026gt;GetTransactionFactory()-\u0026gt;CreateTransaction( std::move(transaction_hostname), DnsQueryTypeToQtype(transaction_info.type), net_log_, secure_, secure_dns_mode_, \u0026amp;resolve_context_, fallback_available_ / fast_timeout */); transaction_info.transaction-\u0026gt;SetRequestPriority(delegate_-\u0026gt;priority());\nauto transaction_info_it = transactions_in_progress_.insert(std::move(transaction_info)).first;\n// Safe to pass transaction_info_it because it is only modified/removed // after async completion of this call or by destruction (which cancels the // transaction and prevents callback because it owns the DnsTransaction // object). transaction_info_it-\u0026gt;transaction-\u0026gt;Start(base::BindOnce( \u0026amp;HostResolverDnsTask::OnDnsTransactionComplete, base::Unretained(this), transaction_info_it, request_port)); }\nhttps://source.chromium.org/chromium/chromium/src/+/main:net/dns/dns_transaction.cc;l=1226;drc=4f289372eacd2b226e9d2830cc4a87b4ad621133;bpv=1;bpt=1 void Start(ResponseCallback callback) override { DCHECK(!callback.is_null()); DCHECK(callback_.is_null()); DCHECK(attempts_.empty());\ncallback_ = std::move(callback); net_log_.BeginEvent(NetLogEventType::DNS_TRANSACTION, [\u0026amp;] { return NetLogStartParams(hostname_, qtype_); }); time_from_start_ = std::make_unique\u0026lt;base::ElapsedTimer\u0026gt;(); AttemptResult result(PrepareSearch(), nullptr); if (result.rv == OK) { qnames_initial_size_ = qnames_.size(); result = ProcessAttemptResult(StartQuery()); } // Must always return result asynchronously, to avoid reentrancy. if (result.rv != ERR_IO_PENDING) { // Clear all other non-completed attempts. They are no longer needed and // they may interfere with this posted result. ClearAttempts(result.attempt); base::SingleThreadTaskRunner::GetCurrentDefault()-\u0026gt;PostTask( FROM_HERE, base::BindOnce(\u0026amp;DnsTransactionImpl::DoCallback, weak_ptr_factory_.GetWeakPtr(), result)); } }\nクエリの構築 https://source.chromium.org/chromium/chromium/src/+/main:net/dns/dns_transaction.cc;drc=4f289372eacd2b226e9d2830cc4a87b4ad621133;bpv=1;bpt=1;l=1278 // Prepares |qnames_| according to the DnsConfig. int PrepareSearch() { const DnsConfig\u0026amp; config = session_-\u0026gt;config();\nstd::optional\u0026lt;std::vector\u0026lt;uint8_t\u0026gt;\u0026gt; labeled_qname = dns_names_util::DottedNameToNetwork( hostname_, /*require_valid_internet_hostname=*/true); if (!labeled_qname.has_value()) return ERR_INVALID_ARGUMENT; if (hostname_.back() == '.') { // It's a fully-qualified name, no suffix search. qnames_.push_back(std::move(labeled_qname).value()); return OK; } int ndots = CountLabels(labeled_qname.value()) - 1; if (ndots \u0026gt; 0 \u0026amp;\u0026amp; !config.append_to_multi_label_name) { qnames_.push_back(std::move(labeled_qname).value()); return OK; } // Set true when `labeled_qname` is put on the list. bool had_qname = false; if (ndots \u0026gt;= config.ndots) { qnames_.push_back(labeled_qname.value()); had_qname = true; } for (const auto\u0026amp; suffix : config.search) { std::optional\u0026lt;std::vector\u0026lt;uint8_t\u0026gt;\u0026gt; qname = dns_names_util::DottedNameToNetwork( hostname_ + \u0026quot;.\u0026quot; + suffix, /*require_valid_internet_hostname=*/true); // Ignore invalid (too long) combinations. if (!qname.has_value()) continue; if (qname.value().size() == labeled_qname.value().size()) { if (had_qname) continue; had_qname = true; } qnames_.push_back(std::move(qname).value()); } if (ndots \u0026gt; 0 \u0026amp;\u0026amp; !had_qname) qnames_.push_back(std::move(labeled_qname).value()); return qnames_.empty() ? ERR_DNS_SEARCH_EMPTY : OK; }\nDNSクエリの開始 https://source.chromium.org/chromium/chromium/src/+/main:net/dns/dns_transaction.cc;drc=4f289372eacd2b226e9d2830cc4a87b4ad621133;bpv=1;bpt=1;l=1533 AttemptResult StartQuery() { std::optionalstd::string dotted_qname = dns_names_util::NetworkToDottedName(qnames_.front()); net_log_.BeginEventWithStringParams( NetLogEventType::DNS_TRANSACTION_QUERY, \u0026ldquo;qname\u0026rdquo;, dotted_qname.value_or(\u0026rdquo;???MALFORMED_NAME???\u0026quot;));\nattempts_.clear(); had_tcp_retry_ = false; if (secure_) { dns_server_iterator_ = resolve_context_-\u0026gt;GetDohIterator( session_-\u0026gt;config(), secure_dns_mode_, session_.get()); } else { dns_server_iterator_ = resolve_context_-\u0026gt;GetClassicDnsIterator( session_-\u0026gt;config(), session_.get()); } DCHECK(dns_server_iterator_); // Check for available server before starting as DoH servers might be // unavailable. if (!dns_server_iterator_-\u0026gt;AttemptAvailable()) return AttemptResult(ERR_BLOCKED_BY_CLIENT, nullptr); return MakeAttempt(); }\nhttps://source.chromium.org/chromium/chromium/src/+/main:net/dns/dns_transaction.cc;l=1356;drc=4f289372eacd2b226e9d2830cc4a87b4ad621133;bpv=1;bpt=1 AttemptResult MakeAttempt() { DCHECK(MoreAttemptsAllowed());\nDnsConfig config = session_-\u0026gt;config(); if (secure_) { DCHECK(!config.doh_config.servers().empty()); RecordAttemptUma(DnsAttemptType::kHttp); return MakeHTTPAttempt(); } DCHECK_GT(config.nameservers.size(), 0u); return MakeClassicDnsAttempt(); }\nAttemptResult MakeClassicDnsAttempt() { uint16_t id = session_-\u0026gt;NextQueryId(); std::unique_ptr query; if (attempts_.empty()) { query = std::make_unique(id, qnames_.front(), qtype_, opt_rdata_); } else { query = attempts_[0]-\u0026gt;GetQuery()-\u0026gt;CloneWithNewId(id); } DCHECK(dns_server_iterator_-\u0026gt;AttemptAvailable()); size_t server_index = dns_server_iterator_-\u0026gt;GetNextAttemptIndex();\nsize_t attempt_number = attempts_.size(); AttemptResult result; if (session_-\u0026gt;udp_tracker()-\u0026gt;low_entropy()) { result = MakeTcpAttempt(server_index, std::move(query)); RecordAttemptUma(DnsAttemptType::kTcpLowEntropy); } else { result = MakeUdpAttempt(server_index, std::move(query)); RecordAttemptUma(DnsAttemptType::kUdp); } if (result.rv == ERR_IO_PENDING) { base::TimeDelta fallback_period = resolve_context_-\u0026gt;NextClassicFallbackPeriod( server_index, attempt_number, session_.get()); timer_.Start(FROM_HERE, fallback_period, this, \u0026amp;DnsTransactionImpl::OnFallbackPeriodExpired); } return result; }\n// Makes another attempt at the current name, |qnames_.front()|, using the // next nameserver. AttemptResult MakeUdpAttempt(size_t server_index, std::unique_ptr query) { DCHECK(!secure_); DCHECK(!session_-\u0026gt;udp_tracker()-\u0026gt;low_entropy());\nconst DnsConfig\u0026amp; config = session_-\u0026gt;config(); DCHECK_LT(server_index, config.nameservers.size()); size_t attempt_number = attempts_.size(); std::unique_ptr\u0026lt;DatagramClientSocket\u0026gt; socket = resolve_context_-\u0026gt;url_request_context() -\u0026gt;GetNetworkSessionContext() -\u0026gt;client_socket_factory-\u0026gt;CreateDatagramClientSocket( DatagramSocket::RANDOM_BIND, net_log_.net_log(), net_log_.source()); attempts_.push_back(std::make_unique\u0026lt;DnsUDPAttempt\u0026gt;( server_index, std::move(socket), config.nameservers[server_index], std::move(query), session_-\u0026gt;udp_tracker())); ++attempts_count_; DnsAttempt* attempt = attempts_.back().get(); net_log_.AddEventReferencingSource(NetLogEventType::DNS_TRANSACTION_ATTEMPT, attempt-\u0026gt;GetSocketNetLog().source()); int rv = attempt-\u0026gt;Start(base::BindOnce( \u0026amp;DnsTransactionImpl::OnAttemptComplete, base::Unretained(this), attempt_number, true /* record_rtt */, base::TimeTicks::Now())); return AttemptResult(rv, attempt); }\nここのコンストラクタでDNSパケットの構築が行われている。 https://source.chromium.org/chromium/chromium/src/+/main:net/dns/dns_query.cc;l=107;drc=4f289372eacd2b226e9d2830cc4a87b4ad621133;bpv=1;bpt=1 // DNS query consists of a 12-byte header followed by a question section. // For details, see RFC 1035 section 4.1.1. This header template sets RD // bit, which directs the name server to pursue query recursively, and sets // the QDCOUNT to 1, meaning the question section has a single entry. DnsQuery::DnsQuery(uint16_t id, base::span qname, uint16_t qtype, const OptRecordRdata* opt_rdata, PaddingStrategy padding_strategy) : qname_size_(qname.size()) { #if DCHECK_IS_ON() std::optionalstd::string dotted_name = dns_names_util::NetworkToDottedName(qname); DCHECK(dotted_name \u0026amp;\u0026amp; !dotted_name.value().empty()); #endif // DCHECK_IS_ON()\nsize_t buffer_size = kHeaderSize + QuestionSize(qname_size_); std::unique_ptr merged_opt_rdata = AddPaddingIfNecessary(opt_rdata, padding_strategy, buffer_size); if (merged_opt_rdata) buffer_size += OptRecordSize(merged_opt_rdata.get());\nio_buffer_ = base::MakeRefCounted(buffer_size);\ndns_protocol::Header* header = header_in_io_buffer(); *header = {}; header-\u0026gt;id = base::HostToNet16(id); header-\u0026gt;flags = base::HostToNet16(dns_protocol::kFlagRD); header-\u0026gt;qdcount = base::HostToNet16(1);\n// Write question section after the header. auto writer = base::SpanWriter(io_buffer_-\u0026gt;span().subspan(kHeaderSize)); writer.Write(qname); writer.WriteU16BigEndian(qtype); writer.WriteU16BigEndian(dns_protocol::kClassIN);\nif (merged_opt_rdata) { DCHECK_NE(merged_opt_rdata-\u0026gt;OptCount(), 0u);\nheader-\u0026gt;arcount = base::HostToNet16(1); // Write OPT pseudo-resource record. writer.WriteU8BigEndian(0); // empty domain name (root domain) writer.WriteU16BigEndian(OptRecordRdata::kType); // type writer.WriteU16BigEndian(kMaxUdpPayloadSize); // class // ttl (next 3 fields) writer.WriteU8BigEndian(0); // rcode does not apply to requests writer.WriteU8BigEndian(0); // version // TODO(robpercival): Set \u0026quot;DNSSEC OK\u0026quot; flag if/when DNSSEC is supported: // https://tools.ietf.org/html/rfc3225#section-3 writer.WriteU16BigEndian(0); // flags // rdata writer.WriteU16BigEndian(merged_opt_rdata-\u0026gt;buf().size()); // rdata length writer.Write(base::as_byte_span(merged_opt_rdata-\u0026gt;buf())); } }\nhttps://source.chromium.org/chromium/chromium/src/+/main:net/dns/dns_transaction.cc;l=226;drc=4f289372eacd2b226e9d2830cc4a87b4ad621133;bpv=1;bpt=1 int Start(CompletionOnceCallback callback) override { DCHECK_EQ(STATE_NONE, next_state_); callback_ = std::move(callback); start_time_ = base::TimeTicks::Now(); next_state_ = STATE_CONNECT_COMPLETE;\nint rv = socket_-\u0026gt;ConnectAsync( server_, base::BindOnce(\u0026amp;DnsUDPAttempt::OnIOComplete, base::Unretained(this))); if (rv == ERR_IO_PENDING) { return rv; } return DoLoop(rv); }\nhttps://source.chromium.org/chromium/chromium/src/+/main:net/dns/dns_transaction.cc;drc=4f289372eacd2b226e9d2830cc4a87b4ad621133;bpv=1;bpt=1;l=270 int DoLoop(int result) { CHECK_NE(STATE_NONE, next_state_); int rv = result; do { State state = next_state_; next_state_ = STATE_NONE; switch (state) { case STATE_CONNECT_COMPLETE: rv = DoConnectComplete(rv); break; case STATE_SEND_QUERY: rv = DoSendQuery(rv); break; case STATE_SEND_QUERY_COMPLETE: rv = DoSendQueryComplete(rv); break; case STATE_READ_RESPONSE: rv = DoReadResponse(); break; case STATE_READ_RESPONSE_COMPLETE: rv = DoReadResponseComplete(rv); break; default: NOTREACHED(); } } while (rv != ERR_IO_PENDING \u0026amp;\u0026amp; next_state_ != STATE_NONE);\nif (rv != ERR_IO_PENDING) DCHECK_EQ(STATE_NONE, next_state_); return rv; }\nここでDNSパケットがソケットに渡されている。 https://source.chromium.org/chromium/chromium/src/+/main:net/dns/dns_transaction.cc;drc=4f289372eacd2b226e9d2830cc4a87b4ad621133;bpv=1;bpt=1;l=316 int DoSendQuery(int rv) { DCHECK_NE(ERR_IO_PENDING, rv); if (rv \u0026lt; 0) return rv; next_state_ = STATE_SEND_QUERY_COMPLETE; return socket_-\u0026gt;Write( query_-\u0026gt;io_buffer(), query_-\u0026gt;io_buffer()-\u0026gt;size(), base::BindOnce(\u0026amp;DnsUDPAttempt::OnIOComplete, base::Unretained(this)), kTrafficAnnotation); }\nおそらくこれ https://source.chromium.org/chromium/chromium/src/+/main:net/socket/udp_client_socket.cc;l=193;drc=4f289372eacd2b226e9d2830cc4a87b4ad621133;bpv=1;bpt=1 int UDPClientSocket::Write( IOBuffer* buf, int buf_len, CompletionOnceCallback callback, const NetworkTrafficAnnotationTag\u0026amp; traffic_annotation) { return socket_.Write(buf, buf_len, std::move(callback), traffic_annotation); }\nhttps://source.chromium.org/chromium/chromium/src/+/main:net/socket/udp_socket_win.cc;l=438;drc=4f289372eacd2b226e9d2830cc4a87b4ad621133;bpv=1;bpt=1 int UDPSocketWin::Write( IOBuffer* buf, int buf_len, CompletionOnceCallback callback, const NetworkTrafficAnnotationTag\u0026amp; /* traffic_annotation */) { return SendToOrWrite(buf, buf_len, remote_address_.get(), std::move(callback)); }\nint UDPSocketWin::SendTo(IOBuffer* buf, int buf_len, const IPEndPoint\u0026amp; address, CompletionOnceCallback callback) { if (dscp_manager_) { // Alert DscpManager in case this is a new remote address. Failure to // apply Dscp code is never fatal. int rv = dscp_manager_-\u0026gt;PrepareForSend(address); if (rv != OK) net_log_.AddEventWithNetErrorCode(NetLogEventType::UDP_SEND_ERROR, rv); } return SendToOrWrite(buf, buf_len, \u0026amp;address, std::move(callback)); }\nint UDPSocketWin::SendToOrWrite(IOBuffer* buf, int buf_len, const IPEndPoint* address, CompletionOnceCallback callback) { DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); DCHECK_NE(INVALID_SOCKET, socket_); CHECK(write_callback_.is_null()); DCHECK(!callback.is_null()); // Synchronous operation not supported. DCHECK_GT(buf_len, 0); DCHECK(!send_to_address_.get());\nint nwrite = core_ ? InternalSendToOverlapped(buf, buf_len, address) : InternalSendToNonBlocking(buf, buf_len, address); if (nwrite != ERR_IO_PENDING) return nwrite;\nif (address) send_to_address_ = std::make_unique(*address); write_callback_ = std::move(callback); return ERR_IO_PENDING; }\nWSASendToの呼び出しにまで到着 https://learn.microsoft.com/ja-jp/windows/win32/api/winsock2/nf-winsock2-wsasendto https://source.chromium.org/chromium/chromium/src/+/main:net/socket/udp_socket_win.cc;drc=4f289372eacd2b226e9d2830cc4a87b4ad621133;bpv=1;bpt=1;l=1018 int UDPSocketWin::InternalSendToOverlapped(IOBuffer* buf, int buf_len, const IPEndPoint* address) { DCHECK(!core_-\u0026gt;write_iobuffer_.get()); SockaddrStorage storage; struct sockaddr* addr = storage.addr(); // Convert address. if (!address) { addr = nullptr; storage.addr_len = 0; } else { if (!address-\u0026gt;ToSockAddr(addr, \u0026amp;storage.addr_len)) { int result = ERR_ADDRESS_INVALID; LogWrite(result, nullptr, nullptr); return result; } }\nWSABUF write_buffer; write_buffer.buf = buf-\u0026gt;data(); write_buffer.len = buf_len;\nDWORD flags = 0; DWORD num; int rv; if (send_ecn_ != ECN_NOT_ECT) { WSABUF control_buffer; char raw_control_buffer[WSA_CMSG_SPACE(sizeof(int))]; control_buffer.buf = raw_control_buffer; control_buffer.len = sizeof(raw_control_buffer); WSAMSG message; bool temp_address = !remote_address_.get(); if (temp_address) { remote_address_ = std::make_unique(*address); } PopulateWSAMSG(message, storage, \u0026amp;write_buffer, control_buffer, true); if (temp_address) { remote_address_.reset(); } rv = wsa_send_msg_(socket_, \u0026amp;message, flags, \u0026amp;num, \u0026amp;core_-\u0026gt;write_overlapped_, nullptr); } else { rv = WSASendTo(socket_, \u0026amp;write_buffer, 1, \u0026amp;num, flags, addr, storage.addr_len, \u0026amp;core_-\u0026gt;write_overlapped_, nullptr); } if (rv == 0) { if (ResetEventIfSignaled(core_-\u0026gt;write_overlapped_.hEvent)) { int result = num; LogWrite(result, buf-\u0026gt;data(), address); return result; } } else { int os_error = WSAGetLastError(); if (os_error != WSA_IO_PENDING) { int result = MapSystemError(os_error); LogWrite(result, nullptr, nullptr); return result; } }\ncore_-\u0026gt;WatchForWrite(); core_-\u0026gt;write_iobuffer_ = buf; return ERR_IO_PENDING; }\nそしておそらくだが\nhttps://source.chromium.org/chromium/chromium/src/+/main:net/dns/dns_transaction.cc;drc=4f289372eacd2b226e9d2830cc4a87b4ad621133;l=370 int DoReadResponse() { next_state_ = STATE_READ_RESPONSE_COMPLETE; response_ = std::make_unique(); return socket_-\u0026gt;Read( response_-\u0026gt;io_buffer(), response_-\u0026gt;io_buffer_size(), base::BindOnce(\u0026amp;DnsUDPAttempt::OnIOComplete, base::Unretained(this))); }\nint DoReadResponseComplete(int rv) { DCHECK_NE(ERR_IO_PENDING, rv); if (rv \u0026lt; 0) return rv; read_size_ = rv;\nbool parse_result = response_-\u0026gt;InitParse(rv, *query_); if (response_-\u0026gt;id()) udp_tracker_-\u0026gt;RecordResponseId(query_-\u0026gt;id(), response_-\u0026gt;id().value()); if (!parse_result) return ERR_DNS_MALFORMED_RESPONSE; if (response_-\u0026gt;flags() \u0026amp; dns_protocol::kFlagTC) return ERR_DNS_SERVER_REQUIRES_TCP; if (response_-\u0026gt;rcode() == dns_protocol::kRcodeNXDOMAIN) return ERR_NAME_NOT_RESOLVED; if (response_-\u0026gt;rcode() != dns_protocol::kRcodeNOERROR) return ERR_DNS_SERVER_FAILED; return OK; }\nvoid OnIOComplete(int rv) { rv = DoLoop(rv); if (rv != ERR_IO_PENDING) std::move(callback_).Run(rv); }\nhttps://source.chromium.org/chromium/chromium/src/+/main:net/socket/udp_client_socket.cc;l=187;drc=4f289372eacd2b226e9d2830cc4a87b4ad621133;bpv=1;bpt=1 int UDPClientSocket::Read(IOBuffer* buf, int buf_len, CompletionOnceCallback callback) { return socket_.Read(buf, buf_len, std::move(callback)); }\nhttps://source.chromium.org/chromium/chromium/src/+/main:net/socket/udp_socket_win.cc;l=411;drc=4f289372eacd2b226e9d2830cc4a87b4ad621133;bpv=1;bpt=1 int UDPSocketWin::Read(IOBuffer* buf, int buf_len, CompletionOnceCallback callback) { return RecvFrom(buf, buf_len, nullptr, std::move(callback)); }\nint UDPSocketWin::RecvFrom(IOBuffer* buf, int buf_len, IPEndPoint* address, CompletionOnceCallback callback) { DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); DCHECK_NE(INVALID_SOCKET, socket_); CHECK(read_callback_.is_null()); DCHECK(!recv_from_address_); DCHECK(!callback.is_null()); // Synchronous operation not supported. DCHECK_GT(buf_len, 0);\nint nread = core_ ? InternalRecvFromOverlapped(buf, buf_len, address) : InternalRecvFromNonBlocking(buf, buf_len, address); if (nread != ERR_IO_PENDING) return nread;\nread_callback_ = std::move(callback); recv_from_address_ = address; return ERR_IO_PENDING; }\nWSARecvまで到着 https://learn.microsoft.com/ja-jp/windows/win32/api/winsock2/nf-winsock2-wsarecv https://source.chromium.org/chromium/chromium/src/+/main:net/socket/udp_socket_win.cc;drc=4f289372eacd2b226e9d2830cc4a87b4ad621133;bpv=1;bpt=1;l=952 int UDPSocketWin::InternalRecvFromOverlapped(IOBuffer* buf, int buf_len, IPEndPoint* address) { DCHECK(!core_-\u0026gt;read_iobuffer_.get()); DCHECK(!core_-\u0026gt;read_message_.get()); SockaddrStorage\u0026amp; storage = core_-\u0026gt;recv_addr_storage_; storage.addr_len = sizeof(storage.addr_storage);\nWSABUF read_buffer; read_buffer.buf = buf-\u0026gt;data(); read_buffer.len = buf_len;\nDWORD flags = 0; DWORD num; CHECK_NE(INVALID_SOCKET, socket_); int rv; std::unique_ptr message; if (report_ecn_) { WSABUF control_buffer; control_buffer.buf = core_-\u0026gt;read_control_buffer_; control_buffer.len = sizeof(core_-\u0026gt;read_control_buffer_); message = std::make_unique(); PopulateWSAMSG(*message, storage, \u0026amp;read_buffer, control_buffer, false); rv = wsa_recv_msg_(socket_, message.get(), \u0026amp;num, \u0026amp;core_-\u0026gt;read_overlapped_, nullptr); if (rv == 0) { SetLastTosFromWSAMSG(message); } } else { rv = WSARecvFrom(socket_, \u0026amp;read_buffer, 1, \u0026amp;num, \u0026amp;flags, storage.addr(), \u0026amp;storage.addr_len, \u0026amp;core_-\u0026gt;read_overlapped_, nullptr); } if (rv == 0) { if (ResetEventIfSignaled(core_-\u0026gt;read_overlapped_.hEvent)) { int result = num; // Convert address. IPEndPoint address_storage; IPEndPoint address_to_log = nullptr; if (result \u0026gt;= 0) { if (address_storage.FromSockAddr(core_-\u0026gt;recv_addr_storage_.addr(), core_-\u0026gt;recv_addr_storage_.addr_len)) { if (address) { *address = address_storage; } address_to_log = \u0026amp;address_storage; } else { result = ERR_ADDRESS_INVALID; } } LogRead(result, buf-\u0026gt;data(), address_to_log); return result; } } else { int os_error = WSAGetLastError(); if (os_error != WSA_IO_PENDING) { int result = MapSystemError(os_error); LogRead(result, nullptr, nullptr); return result; } } core_-\u0026gt;WatchForRead(); core_-\u0026gt;read_iobuffer_ = buf; core_-\u0026gt;read_message_ = std::move(message); return ERR_IO_PENDING; }\nhttps://source.chromium.org/chromium/chromium/src/+/main:net/dns/dns_response.h;drc=4f289372eacd2b226e9d2830cc4a87b4ad621133;l=125 bool DnsResponse::InitParse(size_t nbytes, const DnsQuery\u0026amp; query) { const std::string_view question = query.question();\n// Response includes question, it should be at least that size. if (nbytes \u0026lt; kHeaderSize + question.size() || nbytes \u0026gt; io_buffer_size_) { return false; }\n// At this point, it has been validated that the response is at least large // enough to read the ID field. id_available_ = true;\n// Match the query id. DCHECK(id()); if (id().value() != query.id()) return false;\n// Not a response? if ((base::NetToHost16(header()-\u0026gt;flags) \u0026amp; dns_protocol::kFlagResponse) == 0) return false;\n// Match question count. if (base::NetToHost16(header()-\u0026gt;qdcount) != 1) return false;\nbase::span subspan = io_buffer_-\u0026gt;span().subspan(kHeaderSize, question.size()); // Match the question section. if (question != base::as_string_view(subspan)) { return false; }\nstd::optionalstd::string dotted_qname = dns_names_util::NetworkToDottedName(query.qname()); if (!dotted_qname.has_value()) return false; dotted_qnames_.push_back(std::move(dotted_qname).value()); qtypes_.push_back(query.qtype());\nsize_t num_records = base::NetToHost16(header()-\u0026gt;ancount) + base::NetToHost16(header()-\u0026gt;nscount) + base::NetToHost16(header()-\u0026gt;arcount);\n// Construct the parser. Only allow parsing up to num_records records. If // more records are present in the buffer, it\u0026rsquo;s just garbage extra data after // the formal end of the response and should be ignored. parser_ = DnsRecordParser(io_buffer_-\u0026gt;first(nbytes), kHeaderSize + question.size(), num_records); return true; }\nTransportConnectJob::DoLoopまで戻り、名前解決が完了してTransportConnectJob::DoTransportConnectに入ったところから行う。\nhttps://source.chromium.org/chromium/chromium/src/+/main:net/socket/transport_connect_job.cc;l=417;drc=d9942f1cda698ea5524c9d8b7ba9f48f508bf41c int TransportConnectJob::DoTransportConnect() { next_state_ = STATE_TRANSPORT_CONNECT_COMPLETE;\nconst HostResolverEndpointResult\u0026amp; endpoint = GetEndpointResultForCurrentSubJobs(); std::vector ipv4_addresses, ipv6_addresses; for (const auto\u0026amp; ip_endpoint : endpoint.ip_endpoints) { switch (ip_endpoint.GetFamily()) { case ADDRESS_FAMILY_IPV4: ipv4_addresses.push_back(ip_endpoint); break;\ncase ADDRESS_FAMILY_IPV6: ipv6_addresses.push_back(ip_endpoint); break; default: DVLOG(1) \u0026lt;\u0026lt; \u0026quot;Unexpected ADDRESS_FAMILY: \u0026quot; \u0026lt;\u0026lt; ip_endpoint.GetFamily(); break; } }\nif (!ipv4_addresses.empty()) { ipv4_job_ = std::make_unique( std::move(ipv4_addresses), this, SUB_JOB_IPV4); }\nif (!ipv6_addresses.empty()) { ipv6_job_ = std::make_unique( std::move(ipv6_addresses), this, SUB_JOB_IPV6); int result = ipv6_job_-\u0026gt;Start(); if (result != ERR_IO_PENDING) return HandleSubJobComplete(result, ipv6_job_.get()); if (ipv4_job_) { // This use of base::Unretained is safe because |fallback_timer_| is // owned by this object. fallback_timer_.Start( FROM_HERE, kIPv6FallbackTime, base::BindOnce(\u0026amp;TransportConnectJob::StartIPv4JobAsync, base::Unretained(this))); } return ERR_IO_PENDING; }\nDCHECK(!ipv6_job_); DCHECK(ipv4_job_); int result = ipv4_job_-\u0026gt;Start(); if (result != ERR_IO_PENDING) return HandleSubJobComplete(result, ipv4_job_.get()); return ERR_IO_PENDING; }\nhttps://source.chromium.org/chromium/chromium/src/+/main:net/socket/transport_connect_sub_job.cc;drc=d9942f1cda698ea5524c9d8b7ba9f48f508bf41c;l=125 // Start connecting. int TransportConnectSubJob::Start() { DCHECK_EQ(STATE_NONE, next_state_); next_state_ = STATE_OBTAIN_LOCK; return DoLoop(OK); }\nhttps://source.chromium.org/chromium/chromium/src/+/main:net/socket/transport_connect_sub_job.cc;drc=d9942f1cda698ea5524c9d8b7ba9f48f508bf41c;l=163 int TransportConnectSubJob::DoLoop(int result) { DCHECK_NE(next_state_, STATE_NONE);\nint rv = result; do { State state = next_state_; next_state_ = STATE_NONE; switch (state) { case STATE_OBTAIN_LOCK: DCHECK_EQ(OK, rv); rv = DoEndpointLock(); break; case STATE_OBTAIN_LOCK_COMPLETE: DCHECK_EQ(OK, rv); rv = DoEndpointLockComplete(); break; case STATE_TRANSPORT_CONNECT_COMPLETE: rv = DoTransportConnectComplete(rv); break; default: NOTREACHED(); } } while (rv != ERR_IO_PENDING \u0026amp;\u0026amp; next_state_ != STATE_NONE \u0026amp;\u0026amp; next_state_ != STATE_DONE);\nreturn rv; }\nhttps://source.chromium.org/chromium/chromium/src/+/main:net/socket/transport_connect_sub_job.cc;drc=d9942f1cda698ea5524c9d8b7ba9f48f508bf41c;l=244 int TransportConnectSubJob::DoEndpointLockComplete() { next_state_ = STATE_TRANSPORT_CONNECT_COMPLETE; AddressList one_address(CurrentAddress());\n// Create a SocketPerformanceWatcher, and pass the ownership. std::unique_ptr socket_performance_watcher; if (auto* factory = parent_job_-\u0026gt;socket_performance_watcher_factory(); factory != nullptr) { socket_performance_watcher = factory-\u0026gt;CreateSocketPerformanceWatcher( SocketPerformanceWatcherFactory::PROTOCOL_TCP, CurrentAddress().address()); }\nconst NetLogWithSource\u0026amp; net_log = parent_job_-\u0026gt;net_log(); transport_socket_ = parent_job_-\u0026gt;client_socket_factory()-\u0026gt;CreateTransportClientSocket( one_address, std::move(socket_performance_watcher), parent_job_-\u0026gt;network_quality_estimator(), net_log.net_log(), net_log.source());\nnet_log.AddEvent(NetLogEventType::TRANSPORT_CONNECT_JOB_CONNECT_ATTEMPT, [\u0026amp;] { auto dict = base::Value::Dict().Set(\u0026ldquo;address\u0026rdquo;, CurrentAddress().ToString()); transport_socket_-\u0026gt;NetLog().source().AddToEventParameters(dict); return dict; });\n// If websocket_endpoint_lock_manager_ is non-null, this class now owns an // endpoint lock. Wrap socket in a WebSocketStreamSocket to take ownership // of the lock and release it when the socket goes out of scope. This must // happen before any early returns in this method. if (parent_job_-\u0026gt;websocket_endpoint_lock_manager()) { transport_socket_ = std::make_unique( std::move(transport_socket_), parent_job_-\u0026gt;websocket_endpoint_lock_manager(), CurrentAddress()); }\ntransport_socket_-\u0026gt;ApplySocketTag(parent_job_-\u0026gt;socket_tag());\n// This use of base::Unretained() is safe because transport_socket_ is // destroyed in the destructor. return transport_socket_-\u0026gt;Connect(base::BindOnce( \u0026amp;TransportConnectSubJob::OnIOComplete, base::Unretained(this))); }\nTCPClientSocket::Connect https://source.chromium.org/chromium/chromium/src/+/main:net/socket/tcp_client_socket.cc;l=126;drc=d9942f1cda698ea5524c9d8b7ba9f48f508bf41c;bpv=1;bpt=1 int TCPClientSocket::Connect(CompletionOnceCallback callback) { DCHECK(!callback.is_null());\n// If connecting or already connected, then just return OK. if (socket_-\u0026gt;IsValid() \u0026amp;\u0026amp; current_address_index_ \u0026gt;= 0) return OK;\nDCHECK(!read_callback_); DCHECK(!write_callback_);\nif (was_disconnected_on_suspend_) { Disconnect(); was_disconnected_on_suspend_ = false; }\nsocket_-\u0026gt;StartLoggingMultipleConnectAttempts(addresses_);\n// We will try to connect to each address in addresses_. Start with the // first one in the list. next_connect_state_ = CONNECT_STATE_CONNECT; current_address_index_ = 0;\nint rv = DoConnectLoop(OK); if (rv == ERR_IO_PENDING) { connect_callback_ = std::move(callback); } else { socket_-\u0026gt;EndLoggingMultipleConnectAttempts(rv); }\nreturn rv; }\nhttps://source.chromium.org/chromium/chromium/src/+/main:net/socket/tcp_client_socket.cc;l=321;drc=d9942f1cda698ea5524c9d8b7ba9f48f508bf41c;bpv=1;bpt=1 int TCPClientSocket::DoConnectLoop(int result) { DCHECK_NE(next_connect_state_, CONNECT_STATE_NONE);\nint rv = result; do { ConnectState state = next_connect_state_; next_connect_state_ = CONNECT_STATE_NONE; switch (state) { case CONNECT_STATE_CONNECT: DCHECK_EQ(OK, rv); rv = DoConnect(); break; case CONNECT_STATE_CONNECT_COMPLETE: rv = DoConnectComplete(rv); break; default: NOTREACHED() \u0026laquo; \u0026ldquo;bad state \u0026quot; \u0026laquo; state; } } while (rv != ERR_IO_PENDING \u0026amp;\u0026amp; next_connect_state_ != CONNECT_STATE_NONE);\nreturn rv; }\nTCPClientSocket::DoConnect https://source.chromium.org/chromium/chromium/src/+/main:net/socket/tcp_client_socket.cc;l=231;drc=d9942f1cda698ea5524c9d8b7ba9f48f508bf41c;bpv=1;bpt=1 TCPClientSocket::ConnectInternal https://source.chromium.org/chromium/chromium/src/+/main:net/socket/tcp_client_socket.cc;l=286;drc=d9942f1cda698ea5524c9d8b7ba9f48f508bf41c;bpv=1;bpt=1 TCPSocketWin::Connect https://source.chromium.org/chromium/chromium/src/+/main:net/socket/tcp_socket_win.cc;l=586;drc=d9942f1cda698ea5524c9d8b7ba9f48f508bf41c TCPSocketWin::DoConnect ここでconnect関数に到達。 https://learn.microsoft.com/ja-jp/windows/win32/api/winsock2/nf-winsock2-connect https://source.chromium.org/chromium/chromium/src/+/main:net/socket/tcp_socket_win.cc;l=1055;drc=d9942f1cda698ea5524c9d8b7ba9f48f508bf41c Chromeとかならconnectexとか使ってんのかなと思っていたが普通にconnectだった(TCP fastopenを狙いでもしない限りはただただめんどくさいだけかもしれないというのはある\u0026hellip;) https://learn.microsoft.com/ja-jp/windows/win32/api/mswsock/nc-mswsock-lpfn_connectex SSLConnectJob::DoLoopまで戻った後DoSSLConnectに入るところへ行く。 https://source.chromium.org/chromium/chromium/src/+/main:net/socket/ssl_connect_job.cc;drc=d9942f1cda698ea5524c9d8b7ba9f48f508bf41c;l=336 int SSLConnectJob::DoSSLConnect() { TRACE_EVENT0(NetTracingCategory(), \u0026ldquo;SSLConnectJob::DoSSLConnect\u0026rdquo;); DCHECK(!TimerIsRunning());\nnext_state_ = STATE_SSL_CONNECT_COMPLETE;\n// Set the timeout to just the time allowed for the SSL handshake. ResetTimer(kSSLHandshakeTimeout);\n// Get the transport\u0026rsquo;s connect start and DNS times. const LoadTimingInfo::ConnectTiming\u0026amp; socket_connect_timing = nested_connect_job_-\u0026gt;connect_timing();\n// Overwriting |connect_start| serves two purposes - it adjusts timing so // |connect_start| doesn\u0026rsquo;t include dns times, and it adjusts the time so // as not to include time spent waiting for an idle socket. connect_timing_.connect_start = socket_connect_timing.connect_start; connect_timing_.domain_lookup_start = socket_connect_timing.domain_lookup_start; connect_timing_.domain_lookup_end = socket_connect_timing.domain_lookup_end;\nssl_negotiation_started_ = true; connect_timing_.ssl_start = base::TimeTicks::Now();\n// Save the HostResolverEndpointResult. nested_connect_job_ is destroyed // at the end of this function. endpoint_result_ = nested_connect_job_-\u0026gt;GetHostResolverEndpointResult();\nSSLConfig ssl_config = params_-\u0026gt;ssl_config(); ssl_config.ignore_certificate_errors = *common_connect_job_params()-\u0026gt;ignore_certificate_errors; ssl_config.network_anonymization_key = params_-\u0026gt;network_anonymization_key();\nif (ssl_client_context()-\u0026gt;config().ech_enabled) { if (ech_retry_configs_) { ssl_config.ech_config_list = *ech_retry_configs_; } else if (endpoint_result_) { ssl_config.ech_config_list = endpoint_result_-\u0026gt;metadata.ech_config_list; } if (!ssl_config.ech_config_list.empty()) { // Overriding the DNS lookup only works for direct connections. We // currently do not support ECH with other connection types. DCHECK_EQ(params_-\u0026gt;GetConnectionType(), SSLSocketParams::DIRECT); } }\nif (base::FeatureList::IsEnabled(features::kTLSTrustAnchorIDs) \u0026amp;\u0026amp; endpoint_result_ \u0026amp;\u0026amp; !endpoint_result_-\u0026gt;metadata.trust_anchor_ids.empty() \u0026amp;\u0026amp; !ssl_client_context()-\u0026gt;config().trust_anchor_ids.empty()) { ssl_config.trust_anchor_ids = SSLConfig::SelectTrustAnchorIDs( endpoint_result_-\u0026gt;metadata.trust_anchor_ids, ssl_client_context()-\u0026gt;config().trust_anchor_ids); }\nnet_log().AddEvent(NetLogEventType::SSL_CONNECT_JOB_SSL_CONNECT, [\u0026amp;] { base::Value::Dict dict; dict.Set(\u0026ldquo;ech_enabled\u0026rdquo;, ssl_client_context()-\u0026gt;config().ech_enabled); dict.Set(\u0026ldquo;ech_config_list\u0026rdquo;, NetLogBinaryValue(ssl_config.ech_config_list)); return dict; }); ssl_socket_ = client_socket_factory()-\u0026gt;CreateSSLClientSocket( ssl_client_context(), std::move(nested_socket_), params_-\u0026gt;host_and_port(), ssl_config); nested_connect_job_.reset(); return ssl_socket_-\u0026gt;Connect(callback_); }\nhttps://source.chromium.org/chromium/chromium/src/+/main:net/socket/ssl_client_socket_impl.cc;l=336;drc=d9942f1cda698ea5524c9d8b7ba9f48f508bf41c;bpv=1;bpt=1 int SSLClientSocketImpl::Connect(CompletionOnceCallback callback) {\nint SSLClientSocketImpl::Init() https://source.chromium.org/chromium/chromium/src/+/main:net/socket/ssl_client_socket_impl.cc;l=638;drc=d9942f1cda698ea5524c9d8b7ba9f48f508bf41c;bpv=1;bpt=1 ここでSSL_newなどで初期化した後 stream_socketをBIO経由でSSL部分と接続させている。\nBIO_METHODの定義がここにあり https://source.chromium.org/chromium/chromium/src/+/main:net/socket/socket_bio_adapter.cc;drc=d9942f1cda698ea5524c9d8b7ba9f48f508bf41c;bpv=1;bpt=1;l=446 SocketBIOAdapter::BIOWriteWrapper https://source.chromium.org/chromium/chromium/src/+/main:net/socket/socket_bio_adapter.cc;l=401;drc=d9942f1cda698ea5524c9d8b7ba9f48f508bf41c;bpv=1;bpt=1 int SocketBIOAdapter::BIOWrite(base::span in) https://source.chromium.org/chromium/chromium/src/+/main:net/socket/socket_bio_adapter.cc;drc=d9942f1cda698ea5524c9d8b7ba9f48f508bf41c;bpv=1;bpt=1;l=215 void SocketBIOAdapter::SocketWrite() https://source.chromium.org/chromium/chromium/src/+/main:net/socket/socket_bio_adapter.cc;drc=d9942f1cda698ea5524c9d8b7ba9f48f508bf41c;bpv=1;bpt=1;l=294 そして TCPClientSocket::Write https://source.chromium.org/chromium/chromium/src/+/main:net/socket/tcp_client_socket.cc;l=422;drc=d9942f1cda698ea5524c9d8b7ba9f48f508bf41c;bpv=1;bpt=1 おそらくTcpSocketIoCompletionPortWin::Write WSASendにたどり着く https://learn.microsoft.com/ja-jp/windows/win32/api/winsock2/nf-winsock2-wsasend https://source.chromium.org/chromium/chromium/src/+/main:net/socket/tcp_socket_io_completion_port_win.cc;l=300;drc=d9942f1cda698ea5524c9d8b7ba9f48f508bf41c;bpv=1;bpt=1 BoringSSLの関数が出てきた\u0026hellip; SSL_set_connect_state https://source.chromium.org/chromium/chromium/src/+/main:net/socket/ssl_client_socket_impl.cc;l=355;drc=d9942f1cda698ea5524c9d8b7ba9f48f508bf41c;bpv=1;bpt=1 SSLClientSocketImpl::DoHandshakeLoop https://source.chromium.org/chromium/chromium/src/+/main:net/socket/ssl_client_socket_impl.cc;drc=d9942f1cda698ea5524c9d8b7ba9f48f508bf41c;bpv=1;bpt=1;l=1217 SSL_do_handshake https://source.chromium.org/chromium/chromium/src/+/main:third_party/boringssl/src/ssl/ssl_lib.cc;drc=d9942f1cda698ea5524c9d8b7ba9f48f508bf41c;bpv=1;bpt=1;l=716 ssl_run_handshake https://source.chromium.org/chromium/chromium/src/+/main:third_party/boringssl/src/ssl/handshake.cc;l=495;drc=d9942f1cda698ea5524c9d8b7ba9f48f508bf41c;bpv=1;bpt=1 ssl_client_handshake https://source.chromium.org/chromium/chromium/src/+/main:third_party/boringssl/src/ssl/handshake_client.cc;drc=d9942f1cda698ea5524c9d8b7ba9f48f508bf41c;bpv=1;bpt=1;l=1817 do_start_connect https://source.chromium.org/chromium/chromium/src/+/main:third_party/boringssl/src/ssl/handshake_client.cc;l=343;drc=d9942f1cda698ea5524c9d8b7ba9f48f508bf41c;bpv=1;bpt=1 https://source.chromium.org/chromium/chromium/src/+/main:third_party/boringssl/src/ssl/encrypted_client_hello.cc;l=779;drc=d9942f1cda698ea5524c9d8b7ba9f48f508bf41c;bpv=1;bpt=1 https://source.chromium.org/chromium/chromium/src/+/main:third_party/boringssl/src/ssl/encrypted_client_hello.cc;drc=d9942f1cda698ea5524c9d8b7ba9f48f508bf41c;bpv=1;bpt=1;l=722 なかなか興味深いことにencrypted client helloに対応するなどしている\u0026hellip;ただし現段階ではECHによる接続確立ではないようなので setup_ech_greaseの方にフォールバックしているようだ bool ssl_encrypt_client_hello(SSL_HANDSHAKE *hs, Span enc) {\n硬直化(Ossification)対策 https://datatracker.ietf.org/doc/id/draft-ietf-tls-esni-08.html#name-grease-extensions https://asnokaze.hatenablog.com/entry/2020/02/01/013058 bool ssl_add_client_hello(SSL_HANDSHAKE *hs) こちらが実際のclient_helloを追加する箇所。 https://source.chromium.org/chromium/chromium/src/+/main:third_party/boringssl/src/ssl/handshake_client.cc;drc=d9942f1cda698ea5524c9d8b7ba9f48f508bf41c;bpv=1;bpt=1;l=218 ssl_write_client_hello_without_extensions https://source.chromium.org/chromium/chromium/src/+/main:third_party/boringssl/src/ssl/handshake_client.cc;drc=d9942f1cda698ea5524c9d8b7ba9f48f508bf41c;l=180 bool ssl_add_clienthello_tlsext(SSL_HANDSHAKE *hs, CBB *out, CBB *out_encoded, bool *out_needs_psk_binder, ssl_client_hello_type_t type, size_t header_len) https://source.chromium.org/chromium/chromium/src/+/main:third_party/boringssl/src/ssl/extensions.cc;drc=d9942f1cda698ea5524c9d8b7ba9f48f508bf41c;l=3875 最終的にRecordに乗せている(もしくはpending) https://source.chromium.org/chromium/chromium/src/+/main:third_party/boringssl/src/ssl/s3_both.cc;l=105;drc=d9942f1cda698ea5524c9d8b7ba9f48f508bf41c https://source.chromium.org/chromium/chromium/src/+/main:third_party/boringssl/src/ssl/s3_both.cc;drc=d9942f1cda698ea5524c9d8b7ba9f48f508bf41c;l=40 https://source.chromium.org/chromium/chromium/src/+/main:third_party/boringssl/src/ssl/tls_record.cc;drc=d9942f1cda698ea5524c9d8b7ba9f48f508bf41c;l=406 一旦tls_flushでフラッシュされる。 https://source.chromium.org/chromium/chromium/src/+/main:third_party/boringssl/src/ssl/internal.h;l=4038;drc=d9942f1cda698ea5524c9d8b7ba9f48f508bf41c;bpv=1;bpt=1 BIO_writeでデータが書き込まれ https://source.chromium.org/chromium/chromium/src/+/main:third_party/boringssl/src/ssl/s3_both.cc;l=233;drc=d9942f1cda698ea5524c9d8b7ba9f48f508bf41c;bpv=1;bpt=1 BIO_flushは今回はスタブである https://source.chromium.org/chromium/chromium/src/+/main:third_party/boringssl/src/ssl/s3_both.cc;l=245;drc=d9942f1cda698ea5524c9d8b7ba9f48f508bf41c;bpv=1;bpt=1 https://source.chromium.org/chromium/chromium/src/+/main:net/socket/socket_bio_adapter.cc;drc=d9942f1cda698ea5524c9d8b7ba9f48f508bf41c;l=432 do_read_server_hello(SSL_HANDSHAKE *hs) https://source.chromium.org/chromium/chromium/src/+/main:third_party/boringssl/src/ssl/handshake_client.cc;l=573;drc=d9942f1cda698ea5524c9d8b7ba9f48f508bf41c このメソッド自体はconsumeはしない bool tls_get_message(const SSL *ssl, SSLMessage *out) https://source.chromium.org/chromium/chromium/src/+/main:third_party/boringssl/src/ssl/s3_both.cc;l=404;drc=d9942f1cda698ea5524c9d8b7ba9f48f508bf41c;bpv=1;bpt=1 parse_message https://source.chromium.org/chromium/chromium/src/+/main:third_party/boringssl/src/ssl/s3_both.cc;drc=d9942f1cda698ea5524c9d8b7ba9f48f508bf41c;bpv=1;bpt=1;l=376 tls_read_buffer_extend_to及びBIO_read呼び出しまでのフロー ssl_run_handshake https://source.chromium.org/chromium/chromium/src/+/main:third_party/boringssl/src/ssl/handshake.cc;l=565;drc=f0cb0ae3f9b142a11fdc5efc77e27a5d53b6b6cf;bpv=1;bpt=1 ssl_handle_open_record https://source.chromium.org/chromium/chromium/src/+/main:third_party/boringssl/src/ssl/ssl_buffer.cc;l=205;drc=f0cb0ae3f9b142a11fdc5efc77e27a5d53b6b6cf;bpv=1;bpt=1 ssl_read_buffer_extended_to https://source.chromium.org/chromium/chromium/src/+/main:third_party/boringssl/src/ssl/ssl_buffer.cc;l=164;drc=f0cb0ae3f9b142a11fdc5efc77e27a5d53b6b6cf;bpv=1;bpt=1 tls_read_buffer_extended_to https://source.chromium.org/chromium/chromium/src/+/main:third_party/boringssl/src/ssl/ssl_buffer.cc;l=152;drc=f0cb0ae3f9b142a11fdc5efc77e27a5d53b6b6cf;bpv=1;bpt=1 SocketBIOAdapter::BIOReadWrapper https://source.chromium.org/chromium/chromium/src/+/main:net/socket/socket_bio_adapter.cc;drc=f0cb0ae3f9b142a11fdc5efc77e27a5d53b6b6cf;l=417 SocketBIOAdapter::BIORead https://source.chromium.org/chromium/chromium/src/+/main:net/socket/socket_bio_adapter.cc;drc=f0cb0ae3f9b142a11fdc5efc77e27a5d53b6b6cf;l=105 TCPClientSocket::ReadIfReady https://source.chromium.org/chromium/chromium/src/+/main:net/socket/tcp_client_socket.cc;l=410;drc=f0cb0ae3f9b142a11fdc5efc77e27a5d53b6b6cf;bpv=1;bpt=1 TCPClientSocket::ReadCommon https://source.chromium.org/chromium/chromium/src/+/main:net/socket/tcp_client_socket.cc;l=179;drc=f0cb0ae3f9b142a11fdc5efc77e27a5d53b6b6cf;bpv=1;bpt=1 TcpSocketIoCompletionPortWin::ReadIfReady https://source.chromium.org/chromium/chromium/src/+/main:net/socket/tcp_socket_io_completion_port_win.cc;l=256;drc=f0cb0ae3f9b142a11fdc5efc77e27a5d53b6b6cf;bpv=1;bpt=1 TcpSocketIoCompletionPortWin::HandleReadRequest WSARecvに到達 https://learn.microsoft.com/ja-jp/windows/win32/api/winsock2/nf-winsock2-wsarecv https://source.chromium.org/chromium/chromium/src/+/main:net/socket/tcp_socket_io_completion_port_win.cc;l=509;drc=f0cb0ae3f9b142a11fdc5efc77e27a5d53b6b6cf;bpv=1;bpt=1 do_read_server_hello state_tls13に移行 TLS1.3はTLS1.2から大幅にプロトコル変更がされているため個別のメソッドになっているものと思われる。 https://source.chromium.org/chromium/chromium/src/+/main:third_party/boringssl/src/ssl/handshake_client.cc;l=655;drc=d9942f1cda698ea5524c9d8b7ba9f48f508bf41c do_tls13でtls13_client_handshake呼び出し https://source.chromium.org/chromium/chromium/src/+/main:third_party/boringssl/src/ssl/handshake_client.cc;l=822;drc=d9942f1cda698ea5524c9d8b7ba9f48f508bf41c;bpv=1;bpt=1 static enum ssl_hs_wait_t do_read_server_hello(SSL_HANDSHAKE *hs) https://datatracker.ietf.org/doc/rfc9383/ https://source.chromium.org/chromium/chromium/src/+/main:third_party/boringssl/src/ssl/tls13_client.cc;l=371;drc=d9942f1cda698ea5524c9d8b7ba9f48f508bf41c;bpv=1;bpt=1 ここらへんから鍵共有をしてhandshake secretの導出をしている。 https://source.chromium.org/chromium/chromium/src/+/main:third_party/boringssl/src/ssl/tls13_client.cc;l=544;drc=d9942f1cda698ea5524c9d8b7ba9f48f508bf41c;bpv=1;bpt=1 static enum ssl_hs_wait_t do_read_encrypted_extensions(SSL_HANDSHAKE *hs) https://source.chromium.org/chromium/chromium/src/+/main:third_party/boringssl/src/ssl/tls13_client.cc;l=593;drc=d9942f1cda698ea5524c9d8b7ba9f48f508bf41c;bpv=1;bpt=1 ALPNのネゴシエーションはこのあたり https://source.chromium.org/chromium/chromium/src/+/main:third_party/boringssl/src/ssl/extensions.cc;l=4246;drc=d9942f1cda698ea5524c9d8b7ba9f48f508bf41c;bpv=1;bpt=1 https://source.chromium.org/chromium/chromium/src/+/main:third_party/boringssl/src/ssl/extensions.cc;drc=d9942f1cda698ea5524c9d8b7ba9f48f508bf41c;bpv=1;bpt=1;l=4117 https://source.chromium.org/chromium/chromium/src/+/main:third_party/boringssl/src/ssl/extensions.cc;l=4175;drc=d9942f1cda698ea5524c9d8b7ba9f48f508bf41c;bpv=1;bpt=1 https://source.chromium.org/chromium/chromium/src/+/main:third_party/boringssl/src/ssl/extensions.cc;l=1372;drc=d9942f1cda698ea5524c9d8b7ba9f48f508bf41c;bpv=1;bpt=1 static bool ext_alpn_parse_serverhello(SSL_HANDSHAKE *hs, uint8_t *out_alert, CBS *contents) { SSL *const ssl = hs-\u0026gt;ssl; if (contents == NULL) { if (SSL_is_quic(ssl)) { // ALPN is required when QUIC is used. OPENSSL_PUT_ERROR(SSL, SSL_R_NO_APPLICATION_PROTOCOL); *out_alert = SSL_AD_NO_APPLICATION_PROTOCOL; return false; } return true; }\nassert(!ssl-\u0026gt;s3-\u0026gt;initial_handshake_complete); assert(!hs-\u0026gt;config-\u0026gt;alpn_client_proto_list.empty());\nif (hs-\u0026gt;next_proto_neg_seen) { // NPN and ALPN may not be negotiated in the same connection. *out_alert = SSL_AD_ILLEGAL_PARAMETER; OPENSSL_PUT_ERROR(SSL, SSL_R_NEGOTIATED_BOTH_NPN_AND_ALPN); return false; }\n// The extension data consists of a ProtocolNameList which must have // exactly one ProtocolName. Each of these is length-prefixed. CBS protocol_name_list, protocol_name; if (!CBS_get_u16_length_prefixed(contents, \u0026amp;protocol_name_list) || // CBS_len(contents) != 0 || // !CBS_get_u8_length_prefixed(\u0026amp;protocol_name_list, \u0026amp;protocol_name) || // // Empty protocol names are forbidden. CBS_len(\u0026amp;protocol_name) == 0 || // CBS_len(\u0026amp;protocol_name_list) != 0) { return false; }\nif (!ssl_is_alpn_protocol_allowed(hs, protocol_name)) { OPENSSL_PUT_ERROR(SSL, SSL_R_INVALID_ALPN_PROTOCOL); *out_alert = SSL_AD_ILLEGAL_PARAMETER; return false; }\nif (!ssl-\u0026gt;s3-\u0026gt;alpn_selected.CopyFrom(protocol_name)) { *out_alert = SSL_AD_INTERNAL_ERROR; return false; }\nreturn true; }\nサーバー証明書受信及び証明書チェーンの構築 static enum ssl_hs_wait_t do_read_server_certificate(SSL_HANDSHAKE *hs) https://source.chromium.org/chromium/chromium/src/+/main:third_party/boringssl/src/ssl/tls13_client.cc;l=760;drc=d9942f1cda698ea5524c9d8b7ba9f48f508bf41c;bpv=1;bpt=1 https://source.chromium.org/chromium/chromium/src/+/main:third_party/boringssl/src/ssl/tls13_both.cc;drc=d9942f1cda698ea5524c9d8b7ba9f48f508bf41c;l=102 証明書チェーンの検証 static enum ssl_hs_wait_t do_read_server_certificate_verify(SSL_HANDSHAKE *hs) enum ssl_verify_result_t ssl_verify_peer_cert(SSL_HANDSHAKE *hs) https://source.chromium.org/chromium/chromium/src/+/main:third_party/boringssl/src/ssl/handshake.cc;drc=d9942f1cda698ea5524c9d8b7ba9f48f508bf41c;l=229 https://source.chromium.org/chromium/chromium/src/+/main:third_party/boringssl/src/ssl/tls13_client.cc;l=770;drc=d9942f1cda698ea5524c9d8b7ba9f48f508bf41c;bpv=1;bpt=1 公開鍵署名の検証 bool tls13_process_certificate_verify(SSL_HANDSHAKE *hs, const SSLMessage \u0026amp;msg) https://source.chromium.org/chromium/chromium/src/+/main:third_party/boringssl/src/ssl/tls13_both.cc;drc=d9942f1cda698ea5524c9d8b7ba9f48f508bf41c;bpv=1;bpt=1;l=334 ServerのFinishedメッセージのトランスクリプトハッシュの検証とアプリケーション鍵の導出 static enum ssl_hs_wait_t do_read_server_finished(SSL_HANDSHAKE *hs) https://source.chromium.org/chromium/chromium/src/+/main:third_party/boringssl/src/ssl/tls13_client.cc;l=787;drc=d9942f1cda698ea5524c9d8b7ba9f48f508bf41c;bpv=1;bpt=1 クライアントのEncrypted Extension送信 static enum ssl_hs_wait_t do_send_client_encrypted_extensions( SSL_HANDSHAKE *hs) https://source.chromium.org/chromium/chromium/src/+/main:third_party/boringssl/src/ssl/tls13_client.cc;l=864;drc=d9942f1cda698ea5524c9d8b7ba9f48f508bf41c;bpv=1;bpt=1 クライアントのFinishedメッセージ生成とアプリケーション鍵の設定 static enum ssl_hs_wait_t do_complete_second_flight(SSL_HANDSHAKE *hs) https://source.chromium.org/chromium/chromium/src/+/main:third_party/boringssl/src/ssl/tls13_client.cc;l=1009;drc=d9942f1cda698ea5524c9d8b7ba9f48f508bf41c;bpv=1;bpt=1 ハンドシェイクが完了する\nHttpStreamFactory::Job::DoInitConnectionが終わったところまで戻りHttpStreamFactory::Job::DoInitConnectionCompleteへ入る。\nHttpStreamFactory::Job::DoInitConnectionComplete(int result) ここでプロトコルが決定される。今回はHTTP/2となるはずである。 https://source.chromium.org/chromium/chromium/src/+/main:net/http/http_stream_factory_job.cc;l=954;drc=f0cb0ae3f9b142a11fdc5efc77e27a5d53b6b6cf // Determine the protocol (HTTP/1.1, HTTP/2, or HTTP/3). This covers both the // origin and some proxy cases. First, if the URL is HTTPS (or WSS), we may // negotiate HTTP/2 or HTTP/3 with the origin. Second, non-tunneled requests // (i.e. HTTP URLs) through an HTTPS or QUIC proxy work by sending the request // to the proxy directly. In that case, this logic also handles the proxy\u0026rsquo;s // negotiated protocol. HTTPS requests are always tunneled, so at most one of // these applies. // // Tunneled requests may also negotiate ALPN at the proxy, but // HttpProxyConnectJob handles ALPN. The resulting StreamSocket will not // report an ALPN protocol. if (result == OK) { if (using_quic_) { // TODO(davidben): Record these values consistently between QUIC and TCP // below. In the QUIC case, we only record it for origin connections. In // the TCP case, we also record it for non-tunneled, proxied requests. if (using_ssl_) { negotiated_protocol_ = NextProto::kProtoQUIC; } } else if (connection_-\u0026gt;socket()-\u0026gt;GetNegotiatedProtocol() != NextProto::kProtoUnknown) { // Only connections that use TLS (either to the origin or via a GET to a // secure proxy) can negotiate ALPN. bool get_to_secure_proxy = IsGetToProxy(proxy_info_.proxy_chain(), origin_url_) \u0026amp;\u0026amp; proxy_info_.proxy_chain().Last().is_secure_http_like(); DCHECK(using_ssl_ || get_to_secure_proxy); negotiated_protocol_ = connection_-\u0026gt;socket()-\u0026gt;GetNegotiatedProtocol(); net_log_.AddEvent(NetLogEventType::HTTP_STREAM_REQUEST_PROTO, [\u0026amp;] { return NetLogHttpStreamProtoParams(negotiated_protocol_); }); if (using_spdy()) { if (is_websocket_) { // WebSocket is not supported over a fresh HTTP/2 connection. This // should not be reachable. For the origin, we do not request HTTP/2 // on fresh WebSockets connections, because not all HTTP/2 servers // implement RFC 8441. For proxies, WebSockets are always tunneled. // // TODO(davidben): This isn\u0026rsquo;t a CHECK() because, previously, it was // reachable in https://crbug.com/828865 . However, if reachable, it // means a bug in the socket pools. The socket pools have since been // cleaned up, so this may no longer be reachable. Restore the CHECK // and see if this is still needed. return ERR_NOT_IMPLEMENTED; } } } }\nSSLClientSocketImpl::GetNegotiatedProtocol https://source.chromium.org/chromium/chromium/src/+/main:net/socket/ssl_client_socket_impl.cc;l=462;drc=f0cb0ae3f9b142a11fdc5efc77e27a5d53b6b6cf;bpv=1;bpt=1 SSLClientSocketImpl::DoHandshakeComplete内でALPNからプロトコルが決定される。 https://source.chromium.org/chromium/chromium/src/+/main:net/socket/ssl_client_socket_impl.cc;l=927;drc=f0cb0ae3f9b142a11fdc5efc77e27a5d53b6b6cf;bpv=1;bpt=1 NextProto NextProtoFromString(std::string_view proto_string) https://source.chromium.org/chromium/chromium/src/+/main:net/socket/next_proto.cc;l=11;drc=f0cb0ae3f9b142a11fdc5efc77e27a5d53b6b6cf;bpv=1;bpt=1 HttpStreamFactory::Job::DoCreateStream() https://source.chromium.org/chromium/chromium/src/+/main:net/http/http_stream_factory_job.cc;l=1094;drc=f0cb0ae3f9b142a11fdc5efc77e27a5d53b6b6cf;bpv=1;bpt=1 紛らわしいことにChrome内部ではHTTP/2は相変わらずspdyという名前で扱われているようだ\u0026hellip; https://source.chromium.org/chromium/chromium/src/+/main:net/http/http_stream_factory_job.cc;l=354;drc=f0cb0ae3f9b142a11fdc5efc77e27a5d53b6b6cf;bpv=1;bpt=1 bool HttpStreamFactory::Job::using_spdy() const { return negotiated_protocol_ == NextProto::kProtoHTTP2; }\nSpdySessionPool::CreateAvailableSessionFromSocketHandle https://source.chromium.org/chromium/chromium/src/+/main:net/spdy/spdy_session_pool.cc;l=151;drc=f0cb0ae3f9b142a11fdc5efc77e27a5d53b6b6cf SpdySession::InitializeWithSocketHandle https://source.chromium.org/chromium/chromium/src/+/main:net/spdy/spdy_session.cc;drc=f0cb0ae3f9b142a11fdc5efc77e27a5d53b6b6cf;l=883 void SpdySession::InitializeInternal(SpdySessionPool* pool) https://source.chromium.org/chromium/chromium/src/+/main:net/spdy/spdy_session.cc;drc=f0cb0ae3f9b142a11fdc5efc77e27a5d53b6b6cf;l=1601 void SpdySession::SendInitialData() ここでConnection PrefaceとかSETTINGS Frameの送信 https://source.chromium.org/chromium/chromium/src/+/main:net/spdy/spdy_session.cc;drc=f0cb0ae3f9b142a11fdc5efc77e27a5d53b6b6cf;l=2188 Connection Preface (PRI * HTTP/2.0\\r\\n\\r\\nSM\\r\\n\\r\\n) https://source.chromium.org/chromium/chromium/src/+/main:net/third_party/quiche/src/quiche/http2/core/spdy_protocol.cc;drc=f0cb0ae3f9b142a11fdc5efc77e27a5d53b6b6cf;l=25 SpdySession::EnqueueSessionWrite https://source.chromium.org/chromium/chromium/src/+/main:net/spdy/spdy_session.cc;drc=f0cb0ae3f9b142a11fdc5efc77e27a5d53b6b6cf;l=2475 SpdySession::EnqueueWrite https://source.chromium.org/chromium/chromium/src/+/main:net/spdy/spdy_session.cc;drc=f0cb0ae3f9b142a11fdc5efc77e27a5d53b6b6cf;l=2517 SpdySession::MaybePostWriteLoop 非同期化 https://source.chromium.org/chromium/chromium/src/+/main:net/spdy/spdy_session.cc;drc=f0cb0ae3f9b142a11fdc5efc77e27a5d53b6b6cf;l=2024 SpdySession::PumpWriteLoop(WriteState expected_write_state, int result) https://source.chromium.org/chromium/chromium/src/+/main:net/spdy/spdy_session.cc;l=2072;drc=f0cb0ae3f9b142a11fdc5efc77e27a5d53b6b6cf;bpv=1;bpt=1 int SpdySession::DoWriteLoop(WriteState expected_write_state, int result) https://source.chromium.org/chromium/chromium/src/+/main:net/spdy/spdy_session.cc;drc=f0cb0ae3f9b142a11fdc5efc77e27a5d53b6b6cf;bpv=1;bpt=1;l=2035 SpdySession::DoWrite() そしてここからSocketの書き込み要求(SSL Socket) https://source.chromium.org/chromium/chromium/src/+/main:net/spdy/spdy_session.cc;drc=f0cb0ae3f9b142a11fdc5efc77e27a5d53b6b6cf;l=2072 SpdyWriteQueue::Dequeue 優先度付きqueueが使われている。 https://source.chromium.org/chromium/chromium/src/+/main:net/spdy/spdy_write_queue.cc;drc=f0cb0ae3f9b142a11fdc5efc77e27a5d53b6b6cf;bpv=1;bpt=1;l=83 HttpStreamFactory::Job::SetSpdyHttpStreamOrBidirectionalStreamImpl https://source.chromium.org/chromium/chromium/src/+/main:net/http/http_stream_factory_job.cc;l=1057;drc=f0cb0ae3f9b142a11fdc5efc77e27a5d53b6b6cf;bpv=1;bpt=1 SpdyHttpStreamを設定する。 https://source.chromium.org/chromium/chromium/src/+/main:net/http/http_stream_factory_job.cc;l=1089;drc=f0cb0ae3f9b142a11fdc5efc77e27a5d53b6b6cf stream_ = std::make_unique(session, net_log_.source(), std::move(dns_aliases));\nHttpNetworkTransaction::DoLoopまで戻ってProxy関係のやつを飛ばしてHttpNetworkTransaction::DoBuildRequestから入る。 https://source.chromium.org/chromium/chromium/src/+/main:net/http/http_network_transaction.cc;drc=f0cb0ae3f9b142a11fdc5efc77e27a5d53b6b6cf;bpv=1;bpt=1;l=1489 HttpNetworkTransaction::BuildRequestHeaders https://source.chromium.org/chromium/chromium/src/+/main:net/http/http_network_transaction.cc;drc=f0cb0ae3f9b142a11fdc5efc77e27a5d53b6b6cf;l=1387 HttpNetworkTransaction::DoSendRequest() https://source.chromium.org/chromium/chromium/src/+/main:net/http/http_network_transaction.cc;l=1526;drc=f0cb0ae3f9b142a11fdc5efc77e27a5d53b6b6cf;bpv=1;bpt=1 int SpdyHttpStream::SendRequest(const HttpRequestHeaders\u0026amp; request_headers, HttpResponseInfo* response, CompletionOnceCallback callback) { https://source.chromium.org/chromium/chromium/src/+/main:net/spdy/spdy_http_stream.cc;l=205;drc=f0cb0ae3f9b142a11fdc5efc77e27a5d53b6b6cf;bpv=1;bpt=1 ここでHostとかConnectionとかHTTP/1.1特有のヘッダをフィルタリングし 小文字か処理とか:methodとか:authorityとか:pathとか:schemeとかのついかをしたりしている void CreateSpdyHeadersFromHttpRequest(const HttpRequestInfo\u0026amp; info, std::optional priority, const HttpRequestHeaders\u0026amp; request_headers, quiche::HttpHeaderBlock* headers) https://source.chromium.org/chromium/chromium/src/+/main:net/spdy/spdy_http_utils.cc;l=198;drc=f0cb0ae3f9b142a11fdc5efc77e27a5d53b6b6cf;bpv=1;bpt=1 AddUniqueSpdyHeader経由でquiche::HttpHeaderBlock::InsertResult HttpHeaderBlock::insert\nhttps://source.chromium.org/chromium/chromium/src/+/main:net/third_party/quiche/src/quiche/common/http/http_header_block.cc;drc=f0cb0ae3f9b142a11fdc5efc77e27a5d53b6b6cf;bpv=1;bpt=1;l=248 https://source.chromium.org/chromium/chromium/src/+/main:net/spdy/spdy_stream.cc;drc=f0cb0ae3f9b142a11fdc5efc77e27a5d53b6b6cf;bpv=1;bpt=1;l=619 SpdyStream::SendRequestHeaders(quiche::HttpHeaderBlock request_headers, SpdySendStatus send_status)\nSpdyStream::HeadersBufferProducer::ProduceBuffer https://source.chromium.org/chromium/chromium/src/+/main:net/spdy/spdy_stream.cc;l=83;drc=f0cb0ae3f9b142a11fdc5efc77e27a5d53b6b6cf;bpv=1;bpt=1 SpdyStream::ProduceHeadersFrame https://source.chromium.org/chromium/chromium/src/+/main:net/spdy/spdy_stream.cc;l=137;drc=f0cb0ae3f9b142a11fdc5efc77e27a5d53b6b6cf;bpv=1;bpt=1 SpdySession::CreateHeaders https://source.chromium.org/chromium/chromium/src/+/main:net/spdy/spdy_session.cc;drc=f0cb0ae3f9b142a11fdc5efc77e27a5d53b6b6cf;bpv=1;bpt=1;l=1061 std::unique_ptrspdy::SpdySerializedFrame SpdySession::CreateHeaders( spdy::SpdyStreamId stream_id, RequestPriority priority, spdy::SpdyControlFlags flags, quiche::HttpHeaderBlock block, NetLogSource source_dependency)\nBufferedSpdyFramer::SerializeFrame https://source.chromium.org/chromium/chromium/src/+/main:net/spdy/buffered_spdy_framer.h;drc=f0cb0ae3f9b142a11fdc5efc77e27a5d53b6b6cf;l=245 SpdyFramer::SerializeFrame https://source.chromium.org/chromium/chromium/src/+/main:net/third_party/quiche/src/quiche/http2/core/spdy_framer.cc;drc=f0cb0ae3f9b142a11fdc5efc77e27a5d53b6b6cf;l=953 void SpdyHeadersIR::Visit(SpdyFrameVisitor* visitor) const https://source.chromium.org/chromium/chromium/src/+/main:net/third_party/quiche/src/quiche/http2/core/spdy_protocol.cc;l=444;drc=f0cb0ae3f9b142a11fdc5efc77e27a5d53b6b6cf;bpv=1;bpt=1 FrameSerializationVisitor::VisitHeaders void VisitHeaders(const SpdyHeadersIR\u0026amp; headers) override https://source.chromium.org/chromium/chromium/src/+/main:net/third_party/quiche/src/quiche/http2/core/spdy_framer.cc;l=826;drc=f0cb0ae3f9b142a11fdc5efc77e27a5d53b6b6cf;bpv=1;bpt=1 実際のシリアライズ処理 bool SpdyFramer::SerializeHeaders(const SpdyHeadersIR\u0026amp; headers, ZeroCopyOutputBuffer* output) https://source.chromium.org/chromium/chromium/src/+/main:net/third_party/quiche/src/quiche/http2/core/spdy_framer.cc;drc=f0cb0ae3f9b142a11fdc5efc77e27a5d53b6b6cf;bpv=1;bpt=1;l=1088 SpdyFramer::SerializeHeadersBuilderHelper https://source.chromium.org/chromium/chromium/src/+/main:net/third_party/quiche/src/quiche/http2/core/spdy_framer.cc;drc=f0cb0ae3f9b142a11fdc5efc77e27a5d53b6b6cf;l=543 https://source.chromium.org/chromium/chromium/src/+/main:net/third_party/quiche/src/quiche/http2/hpack/hpack_encoder.cc;drc=f0cb0ae3f9b142a11fdc5efc77e27a5d53b6b6cf;l=96 再びHttpNetworkTransaction::DoLoopに戻った後HttpNetworkTransaction::DoReadHeaders https://source.chromium.org/chromium/chromium/src/+/main:net/http/http_network_transaction.cc;l=1549;drc=f0cb0ae3f9b142a11fdc5efc77e27a5d53b6b6cf SpdyHttpStream::ReadResponseHeaders 基本的に完了通知のみである。 https://source.chromium.org/chromium/chromium/src/+/main:net/spdy/spdy_http_stream.cc;l=89;drc=f0cb0ae3f9b142a11fdc5efc77e27a5d53b6b6cf;bpv=1;bpt=1 int SpdyHttpStream::ReadResponseHeaders(CompletionOnceCallback callback) { CHECK(!callback.is_null()); if (stream_closed_) return closed_stream_status_;\nCHECK(stream_);\n// Check if we already have the response headers. If so, return synchronously. if (response_headers_complete_) { CHECK(!stream_-\u0026gt;IsIdle()); return OK; }\n// Still waiting for the response, return IO_PENDING. CHECK(response_callback_.is_null()); response_callback_ = std::move(callback); return ERR_IO_PENDING; }\nSpdySession::PumpReadLoop(SpdySession::InitInternal内で非同期タスクとして生成) https://source.chromium.org/chromium/chromium/src/+/main:net/spdy/spdy_session.cc;l=1883;drc=f0cb0ae3f9b142a11fdc5efc77e27a5d53b6b6cf;bpv=1;bpt=1 SpdySession::DoReadLoop https://source.chromium.org/chromium/chromium/src/+/main:net/spdy/spdy_session.cc;drc=f0cb0ae3f9b142a11fdc5efc77e27a5d53b6b6cf;l=1891 SpdySession::DoRead https://source.chromium.org/chromium/chromium/src/+/main:net/spdy/spdy_session.cc;drc=f0cb0ae3f9b142a11fdc5efc77e27a5d53b6b6cf;bpv=1;bpt=1;l=1942 SpdySession::DoReadComplete https://source.chromium.org/chromium/chromium/src/+/main:net/spdy/spdy_session.cc;drc=f0cb0ae3f9b142a11fdc5efc77e27a5d53b6b6cf;bpv=1;bpt=1;l=1942 BufferedSpdyFramer::ProcessInput https://source.chromium.org/chromium/chromium/src/+/main:net/spdy/buffered_spdy_framer.cc;drc=f0cb0ae3f9b142a11fdc5efc77e27a5d53b6b6cf;bpv=1;bpt=1;l=225 size_t Http2DecoderAdapter::ProcessInput(const char* data, size_t len) { https://source.chromium.org/chromium/chromium/src/+/main:net/third_party/quiche/src/quiche/http2/core/http2_frame_decoder_adapter.cc;l=267;drc=f0cb0ae3f9b142a11fdc5efc77e27a5d53b6b6cf;bpv=1;bpt=1 DecodeStatus Http2FrameDecoder::DecodeFrame(DecodeBuffer* db) { https://source.chromium.org/chromium/chromium/src/+/main:net/third_party/quiche/src/quiche/http2/decoder/http2_frame_decoder.cc;drc=f0cb0ae3f9b142a11fdc5efc77e27a5d53b6b6cf;bpv=1;bpt=1;l=52 Http2FrameDecoder::StartDecodingPayload(DecodeBuffer* db) https://source.chromium.org/chromium/chromium/src/+/main:net/third_party/quiche/src/quiche/http2/decoder/http2_frame_decoder.cc;drc=f0cb0ae3f9b142a11fdc5efc77e27a5d53b6b6cf;bpv=1;bpt=1;l=87 HeadersPayloadDecoder::StartDecodingPayload https://source.chromium.org/chromium/chromium/src/+/main:net/third_party/quiche/src/quiche/http2/decoder/payload_decoders/headers_payload_decoder.cc;drc=f0cb0ae3f9b142a11fdc5efc77e27a5d53b6b6cf;bpv=1;bpt=1;l=43 ステートマシンベースでどこでも中断できるようになっている\u0026hellip; DecodeStatus HeadersPayloadDecoder::ResumeDecodingPayload( FrameDecoderState* state, DecodeBuffer* db) https://source.chromium.org/chromium/chromium/src/+/main:net/third_party/quiche/src/quiche/http2/decoder/payload_decoders/headers_payload_decoder.cc;drc=f0cb0ae3f9b142a11fdc5efc77e27a5d53b6b6cf;bpv=1;bpt=1;l=100 HPACKのデータを受信したらこれが呼ばれる。 void Http2DecoderAdapter::OnHpackFragment(const char* data, size_t len) https://source.chromium.org/chromium/chromium/src/+/main:net/third_party/quiche/src/quiche/http2/core/http2_frame_decoder_adapter.cc;l=474;drc=f0cb0ae3f9b142a11fdc5efc77e27a5d53b6b6cf;bpv=1;bpt=1 HpackDecoderAdapter::HandleControlFrameHeadersData https://source.chromium.org/chromium/chromium/src/+/main:net/third_party/quiche/src/quiche/http2/hpack/hpack_decoder_adapter.cc;drc=f0cb0ae3f9b142a11fdc5efc77e27a5d53b6b6cf;bpv=1;bpt=1;l=47 bool HpackDecoder::DecodeFragment(DecodeBuffer* db) { https://source.chromium.org/chromium/chromium/src/+/main:net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_decoder.cc;l=55;drc=f0cb0ae3f9b142a11fdc5efc77e27a5d53b6b6cf;bpv=1;bpt=1 DecodeStatus HpackBlockDecoder::Decode(DecodeBuffer* db) https://source.chromium.org/chromium/chromium/src/+/main:net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_block_decoder.cc;drc=f0cb0ae3f9b142a11fdc5efc77e27a5d53b6b6cf;bpv=1;bpt=1;l=17 HpackEntryDecoder::Start(DecodeBuffer* db, HpackEntryDecoderListener* listener) https://source.chromium.org/chromium/chromium/src/+/main:net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_entry_decoder.cc;l=65;drc=f0cb0ae3f9b142a11fdc5efc77e27a5d53b6b6cf;bpv=1;bpt=1 ものすごく長いswitch文による最適化が行われている\u0026hellip; DecodeStatus HpackEntryTypeDecoder::Start(DecodeBuffer* db) https://source.chromium.org/chromium/chromium/src/+/main:net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_entry_type_decoder.cc;drc=f0cb0ae3f9b142a11fdc5efc77e27a5d53b6b6cf;bpv=1;bpt=1;l=35 HpackEntryDecoder::ResumeでListenerにDispatch https://source.chromium.org/chromium/chromium/src/+/main:net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_entry_decoder.cc;l=123;drc=f0cb0ae3f9b142a11fdc5efc77e27a5d53b6b6cf;bpv=1;bpt=1 HpackEntryDecoder::DispatchOnType https://source.chromium.org/chromium/chromium/src/+/main:net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_entry_decoder.cc;l=223;drc=f0cb0ae3f9b142a11fdc5efc77e27a5d53b6b6cf;bpv=1;bpt=1 HpackDecoderState::OnIndexedHeader(size_t index) https://source.chromium.org/chromium/chromium/src/+/main:net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_decoder_state.cc;l=81;drc=f0cb0ae3f9b142a11fdc5efc77e27a5d53b6b6cf;bpv=1;bpt=1 void HpackDecoderAdapter::ListenerAdapter::OnHeader(absl::string_view name, absl::string_view value) https://source.chromium.org/chromium/chromium/src/+/main:net/third_party/quiche/src/quiche/http2/hpack/hpack_decoder_adapter.cc;l=131;drc=f0cb0ae3f9b142a11fdc5efc77e27a5d53b6b6cf;bpv=1;bpt=1 void HeaderCoalescer::OnHeader(std::string_view key, std::string_view value) https://source.chromium.org/chromium/chromium/src/+/main:net/spdy/header_coalescer.cc;l=50;drc=f0cb0ae3f9b142a11fdc5efc77e27a5d53b6b6cf;bpv=1;bpt=1 bool HeaderCoalescer::AddHeader(std::string_view key, std::string_view value) https://source.chromium.org/chromium/chromium/src/+/main:net/spdy/header_coalescer.cc;l=64;drc=f0cb0ae3f9b142a11fdc5efc77e27a5d53b6b6cf;bpv=1;bpt=1 void HttpHeaderBlock::AppendValueOrAddHeader(const absl::string_view key, const absl::string_view value) https://source.chromium.org/chromium/chromium/src/+/main:net/third_party/quiche/src/quiche/common/http/http_header_block.cc;drc=f0cb0ae3f9b142a11fdc5efc77e27a5d53b6b6cf;bpv=1;bpt=1;l=287 https://source.chromium.org/chromium/chromium/src/+/main:net/spdy/buffered_spdy_framer.cc;l=109;drc=f0cb0ae3f9b142a11fdc5efc77e27a5d53b6b6cf;bpv=1;bpt=1 spdy::SpdyHeadersHandlerInterface* BufferedSpdyFramer::OnHeaderFrameStart( spdy::SpdyStreamId stream_id)\nvoid BufferedSpdyFramer::OnHeaderFrameEnd(spdy::SpdyStreamId stream_id) https://source.chromium.org/chromium/chromium/src/+/main:net/spdy/buffered_spdy_framer.cc;l=113;drc=f0cb0ae3f9b142a11fdc5efc77e27a5d53b6b6cf;bpv=1;bpt=1 void SpdySession::OnHeaders(spdy::SpdyStreamId stream_id, bool has_priority, int weight, spdy::SpdyStreamId parent_stream_id, bool exclusive, bool fin, quiche::HttpHeaderBlock headers, base::TimeTicks recv_first_byte_time) https://source.chromium.org/chromium/chromium/src/+/main:net/spdy/spdy_session.cc;l=3035;drc=f0cb0ae3f9b142a11fdc5efc77e27a5d53b6b6cf;bpv=1;bpt=1 Status Codeのパースなどしている。 void SpdyStream::OnHeadersReceived( const quiche::HttpHeaderBlock\u0026amp; response_headers, base::Time response_time, base::TimeTicks recv_first_byte_time) { https://source.chromium.org/chromium/chromium/src/+/main:net/spdy/spdy_stream.cc;drc=f0cb0ae3f9b142a11fdc5efc77e27a5d53b6b6cf;bpv=1;bpt=1;l=329 void SpdyStream::SaveResponseHeaders( const quiche::HttpHeaderBlock\u0026amp; response_headers, int status) https://source.chromium.org/chromium/chromium/src/+/main:net/spdy/spdy_stream.cc;drc=f0cb0ae3f9b142a11fdc5efc77e27a5d53b6b6cf;bpv=1;bpt=1;l=802 void SpdyHttpStream::OnHeadersReceived( const quiche::HttpHeaderBlock\u0026amp; response_headers) https://source.chromium.org/chromium/chromium/src/+/main:net/spdy/spdy_http_stream.cc;l=294;drc=f0cb0ae3f9b142a11fdc5efc77e27a5d53b6b6cf;bpv=1;bpt=1 最終的にここでSpdyHttpStream::ReadResponseHeadersにて設定された完了コールバックが呼ばれHttpNetworkTransaction::OnIOComplete経由でHttpNetworkTransaction::DoLoopに戻って処理が進行する https://source.chromium.org/chromium/chromium/src/+/main:net/spdy/spdy_http_stream.cc;drc=f0cb0ae3f9b142a11fdc5efc77e27a5d53b6b6cf;bpv=1;bpt=1;l=564 void SpdyHttpStream::DoResponseCallback(int rv) { CHECK_NE(rv, ERR_IO_PENDING); CHECK(!response_callback_.is_null());\n// Since Run may result in being called back, reset response_callback_ in // advance. std::move(response_callback_).Run(rv); }\nHttpNetworkTransaction::DoLoopに戻った後 HttpNetworkTransaction::DoReadBody https://source.chromium.org/chromium/chromium/src/+/main:net/http/http_network_transaction.cc;l=1549;drc=f0cb0ae3f9b142a11fdc5efc77e27a5d53b6b6cf;bpv=1;bpt=1 HttpNetworkTransaction::DoReadBodyComplete https://source.chromium.org/chromium/chromium/src/+/main:net/http/http_network_transaction.cc;l=1764;drc=f0cb0ae3f9b142a11fdc5efc77e27a5d53b6b6cf;bpv=1;bpt=1 最終的にHttpNetworkTransaction::DoCallbackで表に戻される。 https://source.chromium.org/chromium/chromium/src/+/main:net/http/http_network_transaction.cc;drc=f0cb0ae3f9b142a11fdc5efc77e27a5d53b6b6cf;bpv=1;bpt=1;l=965 そしてURLRequestHttpJob::OnStartCompletedが呼ばれる https://source.chromium.org/chromium/chromium/src/+/main:net/url_request/url_request_http_job.cc;drc=f0cb0ae3f9b142a11fdc5efc77e27a5d53b6b6cf;l=1249 URLRequestHttpJob::SaveCookiesAndNotifyHeadersComplete(int result) https://source.chromium.org/chromium/chromium/src/+/main:net/url_request/url_request_http_job.cc;drc=f0cb0ae3f9b142a11fdc5efc77e27a5d53b6b6cf;l=1008 URLRequestHttpJob::NotifyHeadersComplete() https://source.chromium.org/chromium/chromium/src/+/main:net/url_request/url_request_http_job.cc;drc=f0cb0ae3f9b142a11fdc5efc77e27a5d53b6b6cf;l=600 URLRequestJob::NotifyHeadersComplete() https://source.chromium.org/chromium/chromium/src/+/main:net/url_request/url_request_job.cc;drc=f0cb0ae3f9b142a11fdc5efc77e27a5d53b6b6cf;l=416 URLRequestJob::NotifyFinalHeadersReceived() https://source.chromium.org/chromium/chromium/src/+/main:net/url_request/url_request_job.cc;drc=f0cb0ae3f9b142a11fdc5efc77e27a5d53b6b6cf;l=484 void URLRequest::NotifyResponseStarted(int net_error) https://source.chromium.org/chromium/chromium/src/+/main:net/url_request/url_request.cc;drc=f0cb0ae3f9b142a11fdc5efc77e27a5d53b6b6cf;l=917 void URLRequest::NotifyRequestCompleted() https://source.chromium.org/chromium/chromium/src/+/main:net/url_request/url_request.cc;drc=f0cb0ae3f9b142a11fdc5efc77e27a5d53b6b6cf;l=1274 IPC経由でやっているっぽいことを見つけるまで # https://source.chromium.org/chromium/chromium/src/+/main:content/browser/storage_partition_impl.cc;l=1585;drc=4f289372eacd2b226e9d2830cc4a87b4ad621133?q=StoragePartitionImpl::CreateURLLoaderFactoryForBrowserProcessInternal\u0026ss=chromium%2Fchromium%2Fsrc network::mojom::NetworkContext* StoragePartitionImpl::GetNetworkContext() { DCHECK(initialized_); if (!network_context_owner_-\u0026gt;network_context.is_bound()) { InitNetworkContext(); } return network_context_owner_-\u0026gt;network_context.get(); } https://source.chromium.org/chromium/chromium/src/+/main:content/browser/storage_partition_impl.cc;drc=4f289372eacd2b226e9d2830cc4a87b4ad621133;l=3571?q=StoragePartitionImpl::CreateURLLoaderFactoryForBrowserProcessInternal\u0026ss=chromium%2Fchromium%2Fsrc void StoragePartitionImpl::InitNetworkContext() { network::mojom::NetworkContextParamsPtr context_params = network::mojom::NetworkContextParams::New(); cert_verifier::mojom::CertVerifierCreationParamsPtr cert_verifier_creation_params = cert_verifier::mojom::CertVerifierCreationParams::New(); GetContentClient()-\u0026gt;browser()-\u0026gt;ConfigureNetworkContextParams( browser_context_, is_in_memory(), relative_partition_path_, context_params.get(), cert_verifier_creation_params.get()); // Should be initialized with existing per-profile CORS access lists. DCHECK(context_params-\u0026gt;cors_origin_access_list.empty()) \u0026lt;\u0026lt; \u0026#34;NetworkContextParams::cors_origin_access_list should be populated \u0026#34; \u0026#34;via SharedCorsOriginAccessList\u0026#34;; context_params-\u0026gt;cors_origin_access_list = browser_context_-\u0026gt;GetSharedCorsOriginAccessList() -\u0026gt;GetOriginAccessList() .CreateCorsOriginAccessPatternsList(); devtools_instrumentation::ApplyNetworkContextParamsOverrides( browser_context_, context_params.get()); DCHECK(!context_params-\u0026gt;cert_verifier_params) \u0026lt;\u0026lt; \u0026#34;`cert_verifier_params` should not be set in the \u0026#34; \u0026#34;NetworkContextParams, as they will be replaced with a new pipe to \u0026#34; \u0026#34;the CertVerifierService.\u0026#34;; cert_verifier_service_updater_.reset(); context_params-\u0026gt;cert_verifier_params = GetCertVerifierParamsWithUpdater( std::move(cert_verifier_creation_params), cert_verifier_service_updater_.BindNewPipeAndPassReceiver()); // This mechanisms should be used only for legacy internal headers. You can // find a recommended alternative approach on URLRequest::cors_exempt_headers // at services/network/public/mojom/url_loader.mojom. context_params-\u0026gt;cors_exempt_header_list.push_back(blink::kPurposeHeaderName); context_params-\u0026gt;cors_exempt_header_list.push_back( GetCorsExemptRequestedWithHeaderName()); variations::UpdateCorsExemptHeaderForVariations(context_params.get()); cors_exempt_header_list_ = context_params-\u0026gt;cors_exempt_header_list; if (base::FeatureList::IsEnabled( network::features::kCompressionDictionaryTransportBackend) \u0026amp;\u0026amp; GetContentClient()-\u0026gt;browser()-\u0026gt;AllowCompressionDictionaryTransport( browser_context_)) { context_params-\u0026gt;shared_dictionary_enabled = true; if (!is_in_memory()) { // Some callers may already initialize NetworkContextFilePaths, and we // don\u0026#39;t want to overwrite them. if (!context_params-\u0026gt;file_paths) { context_params-\u0026gt;file_paths = network::mojom::NetworkContextFilePaths::New(); } context_params-\u0026gt;file_paths-\u0026gt;shared_dictionary_directory = partition_path_.Append(FILE_PATH_LITERAL(\u0026#34;Shared Dictionary\u0026#34;)); } if (context_params-\u0026gt;shared_dictionary_cache_max_size == 0u) { CalculateAndSetSharedDictionaryCacheMaxSize( GetWeakPtr(), is_in_memory() ? base::FilePath() : partition_path_); } } if (cookie_deprecation_label_manager_) { context_params-\u0026gt;cookie_deprecation_label = cookie_deprecation_label_manager_-\u0026gt;GetValue().value_or(\u0026#34;\u0026#34;); } network_context_owner_-\u0026gt;network_context.reset(); CreateNetworkContextInNetworkService( network_context_owner_-\u0026gt;network_context.BindNewPipeAndPassReceiver(), std::move(context_params)); DCHECK(network_context_owner_-\u0026gt;network_context); // Restore the saved network revocation nonces. This allows fenced frames\u0026#39; // untrusted network access states to be persisted in case of a // `NetworkService` crash. std::vector\u0026lt;base::UnguessableToken\u0026gt; nonces( std::begin(network_revocation_nonces_), std::end(network_revocation_nonces_)); network_context_owner_-\u0026gt;network_context-\u0026gt;RevokeNetworkForNonces( nonces, base::NullCallback()); network_context_client_receiver_.reset(); network_context_owner_-\u0026gt;network_context-\u0026gt;SetClient( network_context_client_receiver_.BindNewPipeAndPassRemote()); network_context_owner_-\u0026gt;network_context.set_disconnect_handler(base::BindOnce( \u0026amp;StoragePartitionImpl::InitNetworkContext, weak_factory_.GetWeakPtr())); } https://source.chromium.org/chromium/chromium/src/+/main:services/network/network_context.cc;l=1003 void NetworkContext::SetClient( mojo::PendingRemote\u0026lt;mojom::NetworkContextClient\u0026gt; client) { client_.reset(); client_.Bind(std::move(client)); } global関数CreateNetworkContextInNetworkServiceにて https://source.chromium.org/chromium/chromium/src/+/main:content/browser/network_service_instance_impl.cc;l=916?q=CreateNetworkContextInNetworkService\u0026ss=chromium%2Fchromium%2Fsrc void CreateNetworkContextInNetworkService( mojo::PendingReceiver\u0026lt;network::mojom::NetworkContext\u0026gt; context, network::mojom::NetworkContextParamsPtr params) { TRACE_EVENT0(\u0026#34;loading\u0026#34;, \u0026#34;CreateNetworkContextInNetworkService\u0026#34;); DCHECK(!BrowserThread::IsThreadInitialized(BrowserThread::UI) || BrowserThread::CurrentlyOn(BrowserThread::UI)); if (params-\u0026gt;http_cache_enabled \u0026amp;\u0026amp; params-\u0026gt;file_paths \u0026amp;\u0026amp; params-\u0026gt;file_paths-\u0026gt;http_cache_directory) { params-\u0026gt;file_paths-\u0026gt;http_cache_directory = params-\u0026gt;file_paths-\u0026gt;http_cache_directory-\u0026gt;path().Append( kCacheDataDirectoryName); } const bool has_valid_http_cache_path = params-\u0026gt;http_cache_enabled \u0026amp;\u0026amp; params-\u0026gt;file_paths \u0026amp;\u0026amp; params-\u0026gt;file_paths-\u0026gt;http_cache_directory \u0026amp;\u0026amp; !params-\u0026gt;file_paths-\u0026gt;http_cache_directory-\u0026gt;path().empty(); const bool brokering_is_enabled = IsOutOfProcessNetworkService() \u0026amp;\u0026amp; base::FeatureList::IsEnabled( features::kBrokerFileOperationsOnDiskCacheInNetworkService); if (has_valid_http_cache_path \u0026amp;\u0026amp; brokering_is_enabled) { mojo::MakeSelfOwnedReceiver( std::make_unique\u0026lt;HttpCacheBackendFileOperationsFactory\u0026gt;( params-\u0026gt;file_paths-\u0026gt;http_cache_directory-\u0026gt;path()), params-\u0026gt;http_cache_file_operations_factory .InitWithNewPipeAndPassReceiver()); } #if BUILDFLAG(IS_ANDROID) // On Android, if a cookie_manager pending receiver was passed then migration // should not be attempted as the cookie file is already being accessed by the // browser instance. if (params-\u0026gt;cookie_manager) { if (params-\u0026gt;file_paths) { // No migration should ever be attempted under this configuration. DCHECK(!params-\u0026gt;file_paths-\u0026gt;unsandboxed_data_path); } CreateNetworkContextInternal( std::move(context), std::move(params), SandboxGrantResult::kDidNotAttemptToGrantSandboxAccess); return; } // Note: This logic is duplicated from MaybeGrantAccessToDataPath to this fast // path. This should be kept in sync if there are any changes to the logic. SandboxGrantResult grant_result = SandboxGrantResult::kNoMigrationRequested; if (!params-\u0026gt;file_paths) { // No file paths (e.g. in-memory context) so nothing to do. grant_result = SandboxGrantResult::kDidNotAttemptToGrantSandboxAccess; } else { // If no `unsandboxed_data_path` is supplied, it means this is network // context has been created by Android Webview, which does not understand // the concept of `unsandboxed_data_path`. In this case, `data_directory` // should always be used, if present. if (!params-\u0026gt;file_paths-\u0026gt;unsandboxed_data_path) grant_result = SandboxGrantResult::kDidNotAttemptToGrantSandboxAccess; } // Create network context immediately without thread hops. CreateNetworkContextInternal(std::move(context), std::move(params), grant_result); #else // Restrict disk access to a certain path (on another thread) and continue // with network context creation. GrantSandboxAccessOnThreadPool( std::move(params), base::BindOnce(\u0026amp;CreateNetworkContextInternal, std::move(context))); #endif // BUILDFLAG(IS_ANDROID) } https://source.chromium.org/chromium/chromium/src/+/main:content/browser/network_service_instance_impl.cc;drc=4f289372eacd2b226e9d2830cc4a87b4ad621133;l=235?q=CreateNetworkContextInNetworkService\u0026ss=chromium%2Fchromium%2Fsrc void CreateNetworkContextInternal( mojo::PendingReceiver\u0026lt;network::mojom::NetworkContext\u0026gt; context, network::mojom::NetworkContextParamsPtr params, SandboxGrantResult grant_access_result) { TRACE_EVENT0(\u0026#34;loading\u0026#34;, \u0026#34;CreateNetworkContextInternal\u0026#34;); // These two histograms are logged from elsewhere, so don\u0026#39;t log them twice. DCHECK(grant_access_result != SandboxGrantResult::kFailedToCreateCacheDirectory); DCHECK(grant_access_result != SandboxGrantResult::kFailedToGrantSandboxAccessToCache); base::UmaHistogramEnumeration(\u0026#34;NetworkService.GrantSandboxResult\u0026#34;, grant_access_result); if (grant_access_result != SandboxGrantResult::kSuccess \u0026amp;\u0026amp; grant_access_result != SandboxGrantResult::kDidNotAttemptToGrantSandboxAccess \u0026amp;\u0026amp; grant_access_result != SandboxGrantResult::kNoMigrationRequested \u0026amp;\u0026amp; grant_access_result != SandboxGrantResult::kMigrationAlreadySucceeded) { PLOG(ERROR) \u0026lt;\u0026lt; \u0026#34;Encountered error while migrating network context data or \u0026#34; \u0026#34;granting sandbox access for \u0026#34; \u0026lt;\u0026lt; (params-\u0026gt;file_paths ? params-\u0026gt;file_paths-\u0026gt;data_directory.path() : base::FilePath()) \u0026lt;\u0026lt; \u0026#34;. Result: \u0026#34; \u0026lt;\u0026lt; static_cast\u0026lt;int\u0026gt;(grant_access_result); } if (!IsSafeToUseDataPath(grant_access_result)) { // Unsafe to use new `data_directory`. This means that a migration was // attempted, and `unsandboxed_data_path` contains the still-valid set of // data. Swap the parameters to instruct the network service to use this // path for the network context. This of course will mean that if the // network service is running sandboxed then this data might not be // accessible, but does provide a pathway to user recovery, as the sandbox // can just be disabled in this case. DCHECK(params-\u0026gt;file_paths-\u0026gt;unsandboxed_data_path.has_value()); params-\u0026gt;file_paths-\u0026gt;data_directory = *params-\u0026gt;file_paths-\u0026gt;unsandboxed_data_path; } if (network::TransferableDirectory::IsOpenForTransferRequired()) { if (params-\u0026gt;file_paths) { if (params-\u0026gt;file_paths-\u0026gt;http_cache_directory) { params-\u0026gt;file_paths-\u0026gt;http_cache_directory-\u0026gt;OpenForTransfer(); } if (params-\u0026gt;file_paths-\u0026gt;shared_dictionary_directory) { params-\u0026gt;file_paths-\u0026gt;shared_dictionary_directory-\u0026gt;OpenForTransfer(); } params-\u0026gt;file_paths-\u0026gt;data_directory.OpenForTransfer(); } } // This might recreate g_client if the network service needed to be restarted. auto* network_service = GetNetworkService(); #if BUILDFLAG(IS_WIN) // If the browser has started shutting down, it is possible that either a) // `g_client` was never created if shutdown started before the network service // was created, or b) the network service might have crashed meaning // `g_client` is the client for the already-crashed Network Service, and a new // network service never started. It\u0026#39;s not safe to bind the socket broker in // either of these cases so skip the binding since the browser is shutting // down anyway. if (!GetContentClient()-\u0026gt;browser()-\u0026gt;IsShuttingDown() \u0026amp;\u0026amp; GetContentClient()-\u0026gt;browser()-\u0026gt;ShouldSandboxNetworkService() \u0026amp;\u0026amp; !params-\u0026gt;socket_brokers) { params-\u0026gt;socket_brokers = network::mojom::SocketBrokerRemotes::New(); params-\u0026gt;socket_brokers-\u0026gt;client = g_client-\u0026gt;BindSocketBroker(); params-\u0026gt;socket_brokers-\u0026gt;server = g_client-\u0026gt;BindSocketBroker(); } #endif // BUILDFLAG(IS_WIN) network_service-\u0026gt;CreateNetworkContext(std::move(context), std::move(params)); } https://source.chromium.org/chromium/chromium/src/+/main:services/network/network_service.cc;l=710?q=CreateNetworkContext\u0026ss=chromium%2Fchromium%2Fsrc\u0026start=41 void NetworkService::CreateNetworkContext( mojo::PendingReceiver\u0026lt;mojom::NetworkContext\u0026gt; receiver, mojom::NetworkContextParamsPtr params) { owned_network_contexts_.emplace(std::make_unique\u0026lt;NetworkContext\u0026gt;( this, std::move(receiver), std::move(params), base::BindOnce(\u0026amp;NetworkService::OnNetworkContextConnectionClosed, base::Unretained(this)))); } https://source.chromium.org/chromium/chromium/src/+/main:services/network/network_context.h;drc=4f289372eacd2b226e9d2830cc4a87b4ad621133;bpv=1;bpt=1;l=152 // A NetworkContext creates and manages access to a URLRequestContext. // // When the network service is enabled, NetworkContexts are created through // NetworkService\u0026#39;s mojo interface and are owned jointly by the NetworkService // and the mojo::Remote\u0026lt;NetworkContext\u0026gt; used to talk to them, and the // NetworkContext is destroyed when either one is torn down. #if BUILDFLAG(ENABLE_REPORTING) class COMPONENT_EXPORT(NETWORK_SERVICE) NetworkContext : public mojom::NetworkContext, public net::ReportingCacheObserver { #else class COMPONENT_EXPORT(NETWORK_SERVICE) NetworkContext : public mojom::NetworkContext { #endif // BUILDFLAG(ENABLE_REPORTING) https://source.chromium.org/chromium/chromium/src/+/main:content/browser/network_service_instance_impl.cc;drc=4f289372eacd2b226e9d2830cc4a87b4ad621133;l=572?q=CreateNetworkContextInNetworkService\u0026ss=chromium%2Fchromium%2Fsrc network::mojom::NetworkService* GetNetworkService() { if (!g_network_service_remote) g_network_service_remote = new mojo::Remote\u0026lt;network::mojom::NetworkService\u0026gt;; if (!g_network_service_remote-\u0026gt;is_bound() || !g_network_service_remote-\u0026gt;is_connected()) { bool service_was_bound = g_network_service_remote-\u0026gt;is_bound(); g_network_service_remote-\u0026gt;reset(); if (GetContentClient()-\u0026gt;browser()-\u0026gt;IsShuttingDown()) { // This happens at system shutdown, since in other scenarios the network // process would only be torn down once the message loop stopped running. // We don\u0026#39;t want to start the network service again so just create message // pipe that\u0026#39;s not bound to stop consumers from requesting creation of the // service. auto receiver = g_network_service_remote-\u0026gt;BindNewPipeAndPassReceiver(); auto leaked_pipe = receiver.PassPipe().release(); } else { if (!g_force_create_network_service_directly) { mojo::PendingReceiver\u0026lt;network::mojom::NetworkService\u0026gt; receiver = g_network_service_remote-\u0026gt;BindNewPipeAndPassReceiver(); g_network_service_remote-\u0026gt;set_disconnect_handler( base::BindOnce(\u0026amp;OnNetworkServiceProcessGone, /*crashed=*/true)); if (IsInProcessNetworkService()) { CreateInProcessNetworkService(std::move(receiver)); } else { if (service_was_bound) LOG(ERROR) \u0026lt;\u0026lt; \u0026#34;Network service crashed, restarting service.\u0026#34;; ServiceProcessHost::Launch(std::move(receiver), ServiceProcessHost::Options() .WithDisplayName(u\u0026#34;Network Service\u0026#34;) .Pass()); } } else { DCHECK(IsInProcessNetworkService()) \u0026lt;\u0026lt; \u0026#34;If the network service is created directly, the test must not \u0026#34; \u0026#34;request an out of process network service.\u0026#34;; // This should only be reached in unit tests. if (BrowserThread::CurrentlyOn(BrowserThread::IO)) { CreateNetworkServiceOnIOForTesting( g_network_service_remote-\u0026gt;BindNewPipeAndPassReceiver(), /*completion_event=*/nullptr); } else { base::WaitableEvent event; GetIOThreadTaskRunner({})-\u0026gt;PostTask( FROM_HERE, base::BindOnce( CreateNetworkServiceOnIOForTesting, g_network_service_remote-\u0026gt;BindNewPipeAndPassReceiver(), base::Unretained(\u0026amp;event))); event.Wait(); } } delete g_client; // In case we\u0026#39;re recreating the network service. g_client = new NetworkServiceClient(); (*g_network_service_remote)-\u0026gt;SetParams(CreateNetworkServiceParams()); g_client-\u0026gt;OnNetworkServiceInitialized(g_network_service_remote-\u0026gt;get()); g_network_service_is_responding = false; g_network_service_remote-\u0026gt;QueryVersion(base::BindOnce( [](uint32_t) { g_network_service_is_responding = true; })); const base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); if (command_line-\u0026gt;HasSwitch(network::switches::kLogNetLog)) { base::FilePath log_path = command_line-\u0026gt;GetSwitchValuePath(network::switches::kLogNetLog); if (log_path.empty()) { log_path = GetContentClient()-\u0026gt;browser()-\u0026gt;GetNetLogDefaultDirectory(); if (!log_path.empty()) log_path = log_path.Append(FILE_PATH_LITERAL(\u0026#34;netlog.json\u0026#34;)); } base::File file = NetworkServiceInstancePrivate::BlockingOpenFile( log_path, base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE); if (!file.IsValid()) { LOG(ERROR) \u0026lt;\u0026lt; \u0026#34;Failed opening NetLog: \u0026#34; \u0026lt;\u0026lt; log_path.value(); } else { (*g_network_service_remote) -\u0026gt;StartNetLog( std::move(file), GetNetLogMaximumFileSizeFromCommandLine(*command_line), GetNetCaptureModeFromCommandLine(*command_line), GetContentClient()-\u0026gt;browser()-\u0026gt;GetNetLogConstants(), GetNetLogDurationFromCommandLine(*command_line)); } } base::FilePath ssl_key_log_path; if (command_line-\u0026gt;HasSwitch(network::switches::kSSLKeyLogFile)) { UMA_HISTOGRAM_ENUMERATION(kSSLKeyLogFileHistogram, SSLKeyLogFileAction::kSwitchFound); ssl_key_log_path = command_line-\u0026gt;GetSwitchValuePath(network::switches::kSSLKeyLogFile); LOG_IF(WARNING, ssl_key_log_path.empty()) \u0026lt;\u0026lt; \u0026#34;ssl-key-log-file argument missing\u0026#34;; } else { std::unique_ptr\u0026lt;base::Environment\u0026gt; env(base::Environment::Create()); std::optional\u0026lt;std::string\u0026gt; env_str = env-\u0026gt;GetVar(\u0026#34;SSLKEYLOGFILE\u0026#34;); if (env_str.has_value()) { UMA_HISTOGRAM_ENUMERATION(kSSLKeyLogFileHistogram, SSLKeyLogFileAction::kEnvVarFound); #if BUILDFLAG(IS_WIN) // base::Environment returns environment variables in UTF-8 on // Windows. ssl_key_log_path = base::FilePath(base::UTF8ToWide(*env_str)); #else ssl_key_log_path = base::FilePath(*env_str); #endif } } if (!ssl_key_log_path.empty()) { base::File file = NetworkServiceInstancePrivate::BlockingOpenFile( ssl_key_log_path, base::File::FLAG_OPEN_ALWAYS | base::File::FLAG_APPEND); if (!file.IsValid()) { LOG(ERROR) \u0026lt;\u0026lt; \u0026#34;Failed opening SSL key log file: \u0026#34; \u0026lt;\u0026lt; ssl_key_log_path.value(); } else { UMA_HISTOGRAM_ENUMERATION(kSSLKeyLogFileHistogram, SSLKeyLogFileAction::kLogFileEnabled); (*g_network_service_remote)-\u0026gt;SetSSLKeyLogFile(std::move(file)); } } if (FirstPartySetsHandlerImpl::GetInstance()-\u0026gt;IsEnabled()) { if (std::optional\u0026lt;net::GlobalFirstPartySets\u0026gt; sets = FirstPartySetsHandlerImpl::GetInstance()-\u0026gt;GetSets( base::BindOnce([](net::GlobalFirstPartySets sets) { GetNetworkService()-\u0026gt;SetFirstPartySets(std::move(sets)); })); sets.has_value()) { g_network_service_remote-\u0026gt;get()-\u0026gt;SetFirstPartySets( std::move(sets.value())); } } GetContentClient()-\u0026gt;browser()-\u0026gt;OnNetworkServiceCreated( g_network_service_remote-\u0026gt;get()); } } return g_network_service_remote-\u0026gt;get(); } おそらく以下でNetwork Serviceの実行が開始される\nServiceProcessHost::Launch(std::move(receiver), ServiceProcessHost::Options() .WithDisplayName(u\u0026#34;Network Service\u0026#34;) .Pass()); https://source.chromium.org/chromium/chromium/src/+/main:content/browser/loader/reconnectable_url_loader_factory.cc;l=293;drc=4f289372eacd2b226e9d2830cc4a87b4ad621133;bpv=1;bpt=1 ReconnectableURLLoaderFactoryForIOThreadWrapper:: ReconnectableURLLoaderFactoryForIOThreadWrapper( CreateCallback create_url_loader_factory_callback) : factory_(base::MakeRefCounted\u0026lt;ReconnectableURLLoaderFactory\u0026gt;( create_url_loader_factory_callback)), factory_for_io_thread_( base::MakeRefCounted\u0026lt;ReconnectableURLLoaderFactoryForIOThread\u0026gt;( create_url_loader_factory_callback)) { DCHECK_CURRENTLY_ON(BrowserThread::UI); } https://source.chromium.org/chromium/chromium/src/+/main:content/browser/loader/reconnectable_url_loader_factory.cc;drc=4f289372eacd2b226e9d2830cc4a87b4ad621133;l=177 void ReconnectableURLLoaderFactoryForIOThread::Initialize() { DCHECK_CURRENTLY_ON(BrowserThread::UI); // Create a mojo::PendingRemote\u0026lt;URLLoaderFactory\u0026gt; synchronously and push it to // the IO thread. If the pipe errors out later due to a network service crash, // the pipe is created on the IO thread, and the request send back to the UI // thread. // TODO(mmenke): Is one less thread hop on startup worth the extra complexity // of two different pipe creation paths? mojo::PendingRemote\u0026lt;network::mojom::URLLoaderFactory\u0026gt; network_factory; HandleNetworkFactoryRequestOnUIThread( network_factory.InitWithNewPipeAndPassReceiver()); GetIOThreadTaskRunner({})-\u0026gt;PostTask( FROM_HERE, base::BindOnce( \u0026amp;ReconnectableURLLoaderFactoryForIOThread::InitializeOnIOThread, this, std::move(network_factory))); } IOスレッド側 https://source.chromium.org/chromium/chromium/src/+/main:content/browser/loader/reconnectable_url_loader_factory.cc;drc=4f289372eacd2b226e9d2830cc4a87b4ad621133;l=251 void ReconnectableURLLoaderFactoryForIOThread::InitializeOnIOThread( mojo::PendingRemote\u0026lt;network::mojom::URLLoaderFactory\u0026gt; network_factory) { ReinitializeOnIOThread(mojo::Remote\u0026lt;network::mojom::URLLoaderFactory\u0026gt;( std::move(network_factory))); } void ReconnectableURLLoaderFactoryForIOThread::ReinitializeOnIOThread( mojo::Remote\u0026lt;network::mojom::URLLoaderFactory\u0026gt; network_factory) { DCHECK_CURRENTLY_ON(BrowserThread::IO); DCHECK(network_factory.is_bound()); // Set a disconnect handler so that connection errors on the pipes are // noticed, but the class doesn\u0026#39;t actually do anything when the error is // observed - instead, a new pipe is created in GetURLLoaderFactory() as // needed. This is to avoid incrementing the reference count of |this| in the // callback, as that could result in increasing the reference count from 0 to // 1 while there\u0026#39;s a pending task to delete |this|. See // https://crbug.com/870942 for more details. network_factory.set_disconnect_handler(base::DoNothing()); url_loader_factory_ = std::move(network_factory); } これは先程のReconnectableURLLoaderFactoryForIOThread::InitializeにてUIスレッド側から呼ばれていたやつ\nvoid ReconnectableURLLoaderFactoryForIOThread:: HandleNetworkFactoryRequestOnUIThread( mojo::PendingReceiver\u0026lt;network::mojom::URLLoaderFactory\u0026gt; network_factory_receiver) { DCHECK_CURRENTLY_ON(BrowserThread::UI); mojo::PendingRemote\u0026lt;network::mojom::URLLoaderFactory\u0026gt; factory_remote; create_url_loader_factory_callback_.Run(\u0026amp;factory_remote); if (!factory_remote) { // The underlying URLLoaderFactory has went away while // `ReconnectableURLLoaderFactoryForIOThread` is still held by consumers. return; } CHECK(mojo::FusePipes(std::move(network_factory_receiver), std::move(factory_remote))); } https://source.chromium.org/chromium/chromium/src/+/main:services/network/public/cpp/single_request_url_loader_factory.cc;l=29?q=CreateLoaderAndStart\u0026ss=chromium%2Fchromium%2Fsrc\u0026start=61 void CreateLoaderAndStart( mojo::PendingReceiver\u0026lt;mojom::URLLoader\u0026gt; loader, int32_t request_id, uint32_t options, const ResourceRequest\u0026amp; request, mojo::PendingRemote\u0026lt;mojom::URLLoaderClient\u0026gt; client, const net::MutableNetworkTrafficAnnotationTag\u0026amp; traffic_annotation) { if (!handler_task_runner_-\u0026gt;RunsTasksInCurrentSequence()) { handler_task_runner_-\u0026gt;PostTask( FROM_HERE, base::BindOnce(\u0026amp;HandlerState::CreateLoaderAndStart, this, std::move(loader), request_id, options, request, std::move(client), traffic_annotation)); return; } DCHECK(handler_); std::move(handler_).Run(std::move(loader), request_id, options, request, std::move(client), traffic_annotation); } https://source.chromium.org/chromium/chromium/src/+/main:content/browser/loader/reconnectable_url_loader_factory.cc;l=23;drc=4f289372eacd2b226e9d2830cc4a87b4ad621133;bpv=1;bpt=1 void ReconnectableURLLoaderFactory::CreateLoaderAndStart( mojo::PendingReceiver\u0026lt;network::mojom::URLLoader\u0026gt; receiver, int32_t request_id, uint32_t options, const network::ResourceRequest\u0026amp; url_request, mojo::PendingRemote\u0026lt;network::mojom::URLLoaderClient\u0026gt; client, const net::MutableNetworkTrafficAnnotationTag\u0026amp; traffic_annotation) { if (network::mojom::URLLoaderFactory* factory = GetURLLoaderFactory()) { factory-\u0026gt;CreateLoaderAndStart(std::move(receiver), request_id, options, url_request, std::move(client), traffic_annotation); } } https://source.chromium.org/chromium/chromium/src/+/main:content/browser/loader/reconnectable_url_loader_factory.cc;drc=4f289372eacd2b226e9d2830cc4a87b4ad621133;bpv=1;bpt=1;l=52 network::mojom::URLLoaderFactory* ReconnectableURLLoaderFactory::GetURLLoaderFactory() { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); // Create the URLLoaderFactory as needed, but make sure not to reuse a // previously created one if the test override has changed. if (url_loader_factory_ \u0026amp;\u0026amp; url_loader_factory_.is_connected() \u0026amp;\u0026amp; is_test_url_loader_factory_ == !!url_loader_factory::GetTestingInterceptor()) { return url_loader_factory_.get(); } is_test_url_loader_factory_ = !!url_loader_factory::GetTestingInterceptor(); url_loader_factory_.reset(); mojo::PendingRemote\u0026lt;network::mojom::URLLoaderFactory\u0026gt; url_loader_factory; create_url_loader_factory_callback_.Run(\u0026amp;url_loader_factory); if (!url_loader_factory) { return nullptr; } url_loader_factory_.Bind(std::move(url_loader_factory)); return url_loader_factory_.get(); } create_url_loader_factory_callback_ == StoragePartitionImpl::CreateURLLoaderFactoryForBrowserProcessInternal handler_の設定箇所はおそらくこれ? https://source.chromium.org/chromium/chromium/src/+/main:services/network/public/cpp/single_request_url_loader_factory.cc;l=89-101;drc=4f289372eacd2b226e9d2830cc4a87b4ad621133 SingleRequestURLLoaderFactory::SingleRequestURLLoaderFactory( RequestHandler handler) : state_(base::MakeRefCounted\u0026lt;HandlerState\u0026gt;(base::BindOnce( [](RequestHandler handler, mojo::PendingReceiver\u0026lt;network::mojom::URLLoader\u0026gt; loader, int32_t request_id, uint32_t options, const network::ResourceRequest\u0026amp; request, mojo::PendingRemote\u0026lt;network::mojom::URLLoaderClient\u0026gt; client, const net::MutableNetworkTrafficAnnotationTag\u0026amp; traffic_annotation) { std::move(handler).Run(request, std::move(loader), std::move(client)); }, std::move(handler)))) {} さらにここから呼ばれている? https://source.chromium.org/chromium/chromium/src/+/main:content/browser/loader/navigation_url_loader_impl.cc;l=153?q=SingleRequestURLLoaderFactory\u0026ss=chromium%2Fchromium%2Fsrc\u0026start=1 void MaybeCreateLoader( const network::ResourceRequest\u0026amp; tentative_resource_request, BrowserContext* browser_context, LoaderCallback callback, FallbackCallback fallback_callback) override { browser_interceptor_-\u0026gt;MaybeCreateLoader( tentative_resource_request, browser_context, base::BindOnce( [](LoaderCallback callback, URLLoaderRequestInterceptor::RequestHandler handler) { if (handler) { std::move(callback).Run(NavigationLoaderInterceptor::Result( base::MakeRefCounted\u0026lt; network::SingleRequestURLLoaderFactory\u0026gt;( std::move(handler)), /*subresource_loader_params=*/{})); } else { std::move(callback).Run(std::nullopt); } }, std::move(callback))); } 3. クライアントOS層(Windows) # 今回の仮定ではクライアントがWindowsのPCであるが残念ながらWindowsはOSSではないため直接的にソースコードを見ることは不可能である。 しかし例えば以下のスライドや記事等に示されるようにリバースエンジニアリングの知見からTCP/IPプロトコル・スタックが動いていることが推察できる。 https://i.blackhat.com/EU-23/Presentations/EU-23-Quan-Breaking-Theoretical-Limits_REV2.pdf https://doar-e.github.io/blog/2021/04/15/reverse-engineering-tcpipsys-mechanics-of-a-packet-of-the-death-cve-2021-24086/ https://ja.wikipedia.org/wiki/Network_Driver_Interface_Specification 残念ながらWindowsのカーネルデバッグとかについてはあまり詳しくないのとVirtualBox環境でWinDbgで動かすのを試みようとしたもののうまく繋がらずであったため深堀りができていない https://learn.microsoft.com/ja-jp/windows-hardware/drivers/debugger/getting-started-with-windbg--kernel-mode- 4. Wifiルーターまで # 自環境においては最初のルーターまではWi-Fiによる接続が行われている。 プロトコルとしてはWi-Fi6(ieee802.11ax)が使われており 暗号化方式はWPA2パーソナル(事前鍵共有方式)である。\n基本的な中身としてはデータリンク層(MPDU/PSDU)においては以下のようにデータが格納されており\nhttps://www.n-study.com/wlan-detail/802-11-frame-type-subtype/ https://github.com/on-keyday/brgen/blob/main/example/ieee802_11.bgn enum FrameType: :u2 Management = 0 Control = 1 Data = 2 Extension = 3 format FrameControl: # msb to lsb protocol_version :u2 frame_type: FrameType sub_type: u4 to_ds: u1 from_ds: u1 more_frag: u1 retry: u1 power_mgmt: u1 more_data: u1 protected_frame: u1 order: u1 format IEEE802_11FCS: frame_control: u16 duration_id: u16 addr1 : [6]u8 addr2 : [6]u8 addr3 : [6]u8 sequence_control: u16 addr4 : [6]u8 payload: [..]u8 fcs: u32 そして最下層のPPDU部分が\nhttps://www.rfwireless-world.com/terminology/wlan-802-11ax-frame-structure-ppdu-formats L-STF L-LTF L-SIGのレガシープリアンブルに続き\nHE SU PPDU RL-SIG HE-SIGA HE-STF HE-LTF1 HE SIG-B HE\n(途中)\nなお残念ながらIEEE802.11axの規格そのものは手に入れられなかった。\n5. Wifiルーターから宛先まで # Wifiルーターは光BBユニットでありそれをNTTのRT-500KIルーターに繋ぐ形となっている。\n光BBユニット https://www.softbank.jp/internet/support/connect/hikari/router/bbu24-menu/ RT-500KIルーター https://web116.jp/shop/hikari_r/guide/500ki/index/ 推測でしかないがSoftbankBBがBBIX社の技術を使っていると仮定すると OCX光を使っているのではないかと思われるが実際のところがどうなのかはいまいちわからない。 OCX 光(BBIX社の提供するVNEサービス) https://ocx-hikari.net/technical OCX 光の公式ページではIP in IPを利用していると言っているがおそらく4in6(RFC2473) https://en.wikipedia.org/wiki/IP_in_IP https://en.wikipedia.org/wiki/4in6 ただし、他のVNEでは明示的にRTシリーズルーターをサポートする旨が書いてあったのに対して OCX光には直接的な記述はなかった。 https://www.mfeed.ad.jp/transix/overview.html https://www.ntt.com/content/dam/nttcom/hq/jp/business/services/network/internet-connect/ocn-business/option/v-access-ipoe/pdf/bocn_vc_router.pdf また実際の自宅のルーターにspliterなり線をつなぐなりして確認できているわけではないため あくまでも推測に過ぎない。\nhttps://github.com/v6pc/v6mig-prov/blob/1.1/spec.md https://www.nextech.co.jp/business/nt4ov6kit/ https://qiita.com/metastable-void/items/74b49f3efc72eb6ca623 https://www.ntt-west.co.jp/ngn/business/index.html RT-500KIからはGE-PON FA C ONU タイプDに繋がれており光回線の速度自体は最大1Gbps相当ということになっている https://www.fujitsu.com/jp/products/network/carrier-router/archives/a-gepon/spec-2132/ そこでEthernetと光回線の変換が行われてNTT西日本のNGN網へ接続されている。\n今回の宛先はIPv4でルート指定されたが途中まではIPv6で運ばれているためその基盤部分のルートについても調査を試みた。\nRT-500KIにもWifiルーター機能があったためそのネットワークに直接繋いでIPv6レイヤーでtracertをしてみたところ\n$ tracert -w 300 google.com Tracing route to google.com [2404:6800:4004:808::200e] over a maximum of 30 hops: 1 1 ms 1 ms 1 ms (redacted for privacy) 2 * * * Request timed out. 3 * * * Request timed out. 4 * * * Request timed out. 5 14 ms 13 ms 14 ms 2400:2000:bb1a:3308::1 6 14 ms 14 ms 13 ms 2400:2000:2:0:1a:0:2:21 7 15 ms 13 ms 15 ms 2400:2000:0:201:1:5169:1011:2 8 18 ms 15 ms 15 ms 2001:4860:0:1::86eb 9 14 ms 14 ms 13 ms 2001:4860:0:1::86e6 10 28 ms 23 ms 23 ms 2001:4860::c:4001:389 11 22 ms 22 ms 22 ms 2001:4860::9:4001:388 12 25 ms 26 ms 22 ms 2001:4860:0:1::659 13 22 ms 22 ms 21 ms nrt20s08-in-x0e.1e100.net [2404:6800:4004:808::200e] Trace complete. そして光BBユニットを経由してtracertしてみたところ。\n$ tracert -w 300 google.com Tracing route to google.com [2404:6800:4004:818::200e] over a maximum of 30 hops: 1 1 ms \u0026lt;1 ms 2 ms (redacted for privacy) 2 2 ms 2 ms 2 ms (redacted for privacy) 3 * * * Request timed out. 4 * * * Request timed out. 5 * * * Request timed out. 6 * * * Request timed out. 7 16 ms 16 ms 15 ms 2400:2000:2:0:1a:0:2:21 8 15 ms 15 ms 15 ms 2400:2000:0:201:1:5169:1011:2 9 15 ms 15 ms 15 ms 2001:4860:0:1::39bd 10 16 ms 16 ms 15 ms 2001:4860:0:1::17d4 11 24 ms 24 ms 24 ms 2001:4860::c:4002:2166 12 24 ms 23 ms 23 ms 2001:4860::9:4001:388 13 22 ms 22 ms 23 ms 2001:4860:0:1::6ec5 14 23 ms 22 ms 22 ms nrt13s50-in-x0e.1e100.net [2404:6800:4004:818::200e] Trace complete. おそらくルートとして必ずBBIXのルートを通るように制御をかけられている。 また2400:2000:bb1a:3308::1が光BBユニットがあるときには非表示になっているためここでなんらかの特殊な処理、 例えばIPv4 over IPv6を引き剥がすなどがされているのかもしれない。(これは完全なる推測である) おそらく最初の空白地帯はNGN網内部であろうと推定される。\n他NGN網とVNEのつながりについては以下の記事があり、おそらくVNEまでのルートについては送信元のグローバルIPアドレスベースでどのVNEのネットワークに行くかが決定されているようだ。 https://www.geekpage.jp/blog/?id=2013/1/11/1 https://dforest.watch.impress.co.jp/library/p/proipv6/11948/ProfessionalIPv62ndedition-2023.pdf ここではIPv4パケットがRFC2473の方式で行っているものと仮定し事前調査のtracerouteの通りのルートを通って202.218.223.232まで到達したものとする。 おそらく各ルーター内では FIBを元にルーティングするとかあるいはFIBを設定するまでのところでもBGPなりあるいは管理者の設定したポリシーに基づく設定なりいろいろやってはいるであろうし IPv4 over IPv6 Gatewayの存在があるのでそれの挙動もあるであろうし あるいは帰りのパケットが行きとは全く別の経路を辿っているとかもありそうではあるし 他考慮漏れしている事項はいろいろあると思うが観測しようがないので割愛する。\n6. 宛先のOSレイヤー(Linux) # LLMに聞いて確認程度なので正確性については疑問が残るがコード量的に Linuxのライセンス GPL 2.0に従うとすると この箇所についてはソースコード公開義務があるかもしれないとなっているため別記事に移動 Linuxレイヤーの部分 7. Apache # Apache httpdが使われていると仮定する。観測できる範囲では前段に何が使われているかと行った情報は一切わからないためApacheが全部処理していると仮定する。またmpm_eventを使っていると仮定する。 https://github.com/apache/httpd https://github.com/apache/apr httpd\nLICENSE: https://github.com/apache/httpd/blob/trunk/LICENSE NOTICE: https://github.com/apache/httpd/blob/trunk/NOTICE APR\nLICENSE: https://github.com/apache/apr/blob/trunk/LICENSE NOTICE: https://github.com/apache/apr/blob/trunk/NOTICE またLLMに分析させた結果 ServerTokens Prod でサーバーバージョンを開示しないようにされておりまたmod_headersで Header always append X-Frame-Options SAMEORIGIN のようにクリックジャッキング攻撃を防ぐための設定などがされている。\n参照: https://qiita.com/bezeklik/items/1c4145652661cf5b2271 まずepoll_waitしているところから https://github.com/apache/apr/blob/5a6d5173c8b88767616e80ab33a711c4e0543e04/poll/unix/epoll.c#L271 epoll_waitはlistener_threadでapr_pollset_poll経由で呼び出されている。 https://github.com/apache/httpd/blob/af61c91eb967f28d22627090f58a287a7a5366b7/server/mpm/event/event.c#L2100 acceptする https://github.com/apache/httpd/blob/af61c91eb967f28d22627090f58a287a7a5366b7/server/mpm/event/event.c#L2259 ap_unixd_accept https://github.com/apache/httpd/blob/af61c91eb967f28d22627090f58a287a7a5366b7/os/unix/unixd.c#L293 apr_socket_acceptでaccept/accept4システムコール呼び出し及びユーザー空間のapr_socket構造体を作成 https://github.com/apache/apr/blob/5a6d5173c8b88767616e80ab33a711c4e0543e04/network_io/unix/sockets.c#L268 push2workerでworkerスレッドに送信 https://github.com/apache/httpd/blob/af61c91eb967f28d22627090f58a287a7a5366b7/server/mpm/event/event.c#L1496 https://github.com/apache/httpd/blob/af61c91eb967f28d22627090f58a287a7a5366b7/server/mpm_fdqueue.c#L382 /** * Push a new socket onto the queue. * * precondition: ap_queue_info_wait_for_idler has already been called * to reserve an idle worker thread */ apr_status_t ap_queue_push_socket(fd_queue_t *queue, apr_socket_t *sd, void *sd_baton, apr_pool_t *p) worker_threadで取得する https://github.com/apache/httpd/blob/af61c91eb967f28d22627090f58a287a7a5366b7/server/mpm/event/event.c#L2512 rv = ap_queue_pop_something(worker_queue, \u0026amp;csd, (void **)\u0026amp;cs, \u0026amp;ptrans, \u0026amp;te); process_socketで処理開始 https://github.com/apache/httpd/blob/af61c91eb967f28d22627090f58a287a7a5366b7/server/mpm/event/event.c#L1048 /* * process one connection in the worker */ static void process_socket(apr_thread_t *thd, apr_pool_t * p, apr_socket_t * sock, event_conn_state_t * cs, int my_child_num, int my_thread_num) https://github.com/apache/httpd/blob/af61c91eb967f28d22627090f58a287a7a5366b7/server/core.c#L5675 ap_pre_connection(conn_rec *c, void *csd) https://github.com/apache/httpd/blob/af61c91eb967f28d22627090f58a287a7a5366b7/server/core.c#L5626 static int core_pre_connection(conn_rec *c, void *csd) https://stackoverflow.com/questions/22167234/where-the-function-ap-run-pre-connection-in-apache-httpd-source-code#22171266 alpn callback処理 https://github.com/apache/httpd/blob/af61c91eb967f28d22627090f58a287a7a5366b7/modules/ssl/ssl_engine_kernel.c#L2826 https://github.com/apache/httpd/blob/af61c91eb967f28d22627090f58a287a7a5366b7/server/protocol.c#L2409 AP_DECLARE(const char *) ap_select_protocol(conn_rec *c, request_rec *r, server_rec *s, const apr_array_header_t *choices) 現在のconenctionのprotocolが異なる場合は変更する https://github.com/apache/httpd/blob/af61c91eb967f28d22627090f58a287a7a5366b7/modules/ssl/ssl_engine_kernel.c#L2843 https://github.com/apache/httpd/blob/af61c91eb967f28d22627090f58a287a7a5366b7/modules/http2/h2_switch.c#L154 static int h2_protocol_switch(conn_rec *c, request_rec *r, server_rec *s, const char *protocol) https://github.com/apache/httpd/blob/af61c91eb967f28d22627090f58a287a7a5366b7/modules/http2/h2_c1.c#L238 static int h2_c1_hook_process_connection(conn_rec* c) (時間切れ)\n【問３：自由アピール】 # 技術同人誌を書いたりしています。 https://techbookfest.org/product/rg6Y8rm2N2nVD4bkqBVv6i?productVariantID=iEp1TjzPyCrQramkN4Sx25 正直高速化とかキャッシュ戦略とかという領域が自分はあまりわかっていないというのを感じるのでそこらへんの実装とかがあるのであればぜひやりたいです。あとはDNSによる負荷分散とかあるいはanycastとかQUICとかを使った配信高速化みたいなのがあるのかどこまでやるのかなというのが楽しみです。\n【問４：事前学習について】 # 事前学習に同意する\nまとめ # とにかくソースコードを読んでコピペすればどうにか受かるようなので(違\nぜひソースコードを読みましょう\n正解を求めているわけではないらしいのでこんな拙くてほとんどソースコードが本文みたいなのでも 通してくれることもあるということがわかったので来年以降応募する方は一つの参考程度にしてもらえればと\u0026hellip;\nhttps://www.ipa.go.jp/jinzai/security-camp/2025/camp/zenkoku/message.html 真面目なこと言うと\n情報セキュリティに対する向き合い方（知識を深めたい意欲や、技術力を伸ばしたい熱意、課題に取り組む姿勢、考察の過程など） を見ていると書いてあるのでこれくらいやれば文章拙くても意欲が示せますよという例ではあるのかもしれない\u0026hellip;.\n6aeb805b-01b4-4741-a759-27a698561e7b ja ","date":"2025年 7月 15日","externalUrl":null,"permalink":"/static/blog/posts/2025_07_15_seccamp_2025_y2_application/","section":"Posts","summary":"","title":"セキュリティ・キャンプ2025 Y2 CDN自作ゼミ 応募課題さらし","type":"posts"},{"content":" 問題 # raw socketを使ったプロトコルスタックを作って以下のようなやつをdocker環境に閉じ込めようとしたものの\nclient \u0026lt;-client_router_network-\u0026gt; router \u0026lt;-router_server_network-\u0026gt; server ICMPパケットを1個送ると4つも返ってくるような代物になってしまっていた。\nネットワークは以下のようになっており\nd4e7c59005dc brstack_client_router bridge local d4203e5fc61a brstack_router_server bridge local 以下のような挙動が観察された。\nclient \u0026lt;-\u0026gt; router 03:21:41.914019 IP 192.168.30.10 \u0026gt; 192.168.31.10: ICMP echo request, id 0, seq 65, length 13 03:21:41.914336 IP 192.168.31.10 \u0026gt; 192.168.30.10: ICMP echo reply, id 0, seq 65, length 13 03:21:41.914521 IP 192.168.31.10 \u0026gt; 192.168.30.10: ICMP echo reply, id 0, seq 65, length 13 03:21:41.914532 IP 192.168.31.10 \u0026gt; 192.168.30.10: ICMP echo reply, id 0, seq 65, length 13 03:21:41.914715 IP 192.168.31.10 \u0026gt; 192.168.30.10: ICMP echo reply, id 0, seq 65, length 13 seqが65のICMP echo requestを1つ送ったのに対して同じくseqが65のICMP echo replyが4つほど返ってきている。\nまた以下はrouterとserverの間の挙動であるが途中で2倍になりそして帰りは送っただけ返っている。\nrouter \u0026lt;-\u0026gt;server 03:22:39.919549 IP 192.168.30.10 \u0026gt; 192.168.31.10: ICMP echo request, id 0, seq 94, length 13 03:22:39.919794 IP 192.168.30.10 \u0026gt; 192.168.31.10: ICMP echo request, id 0, seq 94, length 13 03:22:39.919850 IP 192.168.31.10 \u0026gt; 192.168.30.10: ICMP echo reply, id 0, seq 94, length 13 03:22:39.920096 IP 192.168.31.10 \u0026gt; 192.168.30.10: ICMP echo reply, id 0, seq 94, length 13 原因 # まず主要な原因はDockerがnet.ipv4.forwarding=1を設定しておりlinuxカーネルが他ホスト宛のICMPパケットを自動でforwardingする挙動をしていることが原因である。 つまり自分で書いたraw socket経由で処理してforwardした分とlinuxカーネルがforwardした分が重なってclient-\u0026gt;server方向で2倍にserver-\u0026gt;client方向でも2倍になって最終的に4倍になっているというわけである。\n以下の解決策を試す前にdocker-compose.ymlなどでsysctlsのnet.ipv4.icmp_echo_ignore_all: 1を指定してみるなども試してみたがおそらく挙動から推察するにserverに到達したもの自体は無視されたがforwardの挙動はどうにもならなかったようである。(おそらくこれも無効にしていた場合はさらにserver側でも2倍になることで8個ほどechoが返るというのが正解のような気もするが実験の際にはそこには気づいていなかった)\nまたnet.ipv4.forwarding: 0を設定した場合は以下のようなエラーになってしまった\nError response from daemon: failed to create task for container: failed to create shim task: OCI runtime create failed: runc create failed: unable to start container process: error during container init: open /proc/sys/net/ipv4/forwarding: no such file or directory 解決策 # nftablesを使うことで通常のネットワークパケットは遮断できる。 https://knowledge.sakura.ad.jp/22636/ この記事を参考に以下のようにnftablesのrulesetを設定し、 client,router,server全部に設定してみたところ 無事にOSのICMPのforwarding及び自動replyの挙動が遮断ができた。 またついでなのでarpなどの挙動も抑制した。\ntable inet inet_table { chain input { type filter hook input priority filter + 1; policy drop; } chain forward { type filter hook forward priority filter + 1; policy drop; } chain output { type filter hook output priority filter + 1; policy drop; } } table arp arp_table { chain input { type filter hook input priority filter + 1; policy drop; } chain output { type filter hook output priority filter + 1; policy drop; } } まずinputでそもそもOSのipスタックが自ホスト宛てのIPパケットを処理するのをpolicy dropで抑制し、forwardでは他ホスト宛のipパケットを転送するのを抑制しそしてoutputでは自ホストからのパケットを送信するのを抑制した。\nなおnet.ipv4.icmp_echo_ignore_all: 1を設定しなかった場合rulesetのchainがforwardだけだとserver側で受け取ったときに返す挙動が入ってrouter-server間ではreplyが以下のように二重になっていたためinput及びoutputにも指定した。(主にnftで全部制御するという一貫性重視の面が強い)\n$ sudo tcpdump -i br-d4203e5fc61a tcpdump: verbose output suppressed, use -v[v]... for full protocol decode listening on br-d4203e5fc61a, link-type EN10MB (Ethernet), snapshot length 262144 bytes 16:27:58.456736 IP 192.168.30.10 \u0026gt; 192.168.31.10: ICMP echo request, id 0, seq 34, length 13 16:27:58.456771 IP 192.168.31.10 \u0026gt; 192.168.30.10: ICMP echo reply, id 0, seq 34, length 13 16:27:58.457042 IP 192.168.31.10 \u0026gt; 192.168.30.10: ICMP echo reply, id 0, seq 34, length 13 将来的にtcpなどを実装する際にrstを出すのも止めるのにも有効かもしれない(未検証)\nおまけ(ちょっと長い) # 関連して以下のようなQ\u0026amp;Aを見つけたがnetfilterはraw socketには効かないといったような話を見た。\nhttps://serverfault.com/questions/1097938/does-iptables-rules-have-control-over-raw-socket-packets そこでLinuxカーネルを見てnetfilterがなぜsocket(AF_PACKET,　SOCK_RAW, htons(ETH_P_ALL))に効かないのか推測してみる。\nipv4のentryポイント及びnetfilterのpreroutingがかかっているのは以下の位置だと推測される。おそらくinputやforwardはpreroutingよりあとであるため一番早くともnetfilterはここから適用されているようである。 https://github.com/torvalds/linux/blob/1a33418a69cc801d48c59d7d803af5c9cd291be2/net/ipv4/ip_input.c#L558 /* * IP receive entry point */ int ip_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt, struct net_device *orig_dev) { struct net *net = dev_net(dev); skb = ip_rcv_core(skb, net); if (skb == NULL) return NET_RX_DROP; return NF_HOOK(NFPROTO_IPV4, NF_INET_PRE_ROUTING, net, NULL, skb, dev, NULL, ip_rcv_finish); } このip_rcvは以下のようにpacket_typeに設定されており https://github.com/torvalds/linux/blob/1a33418a69cc801d48c59d7d803af5c9cd291be2/net/ipv4/af_inet.c#L1886 static struct packet_type ip_packet_type __read_mostly = { .type = cpu_to_be16(ETH_P_IP), .func = ip_rcv, .list_func = ip_list_rcv, }; そしておそらくここら周辺のコードでip_rcvが呼び出されている。 https://github.com/torvalds/linux/blob/1a33418a69cc801d48c59d7d803af5c9cd291be2/net/core/dev.c#L5843 deliver_ptype_list_skb(skb, \u0026amp;pt_prev, orig_dev, type, \u0026amp;orig_dev-\u0026gt;ptype_specific); https://github.com/torvalds/linux/blob/1a33418a69cc801d48c59d7d803af5c9cd291be2/net/core/dev.c#L2422 static inline void deliver_ptype_list_skb(struct sk_buff *skb, struct packet_type **pt, struct net_device *orig_dev, __be16 type, struct list_head *ptype_list) { struct packet_type *ptype, *pt_prev = *pt; list_for_each_entry_rcu(ptype, ptype_list, list) { if (ptype-\u0026gt;type != type) continue; if (pt_prev) deliver_skb(skb, pt_prev, orig_dev); pt_prev = ptype; } *pt = pt_prev; } 一方でAF_PACKETの受信処理は以下であり https://github.com/torvalds/linux/blob/1a33418a69cc801d48c59d7d803af5c9cd291be2/net/packet/af_packet.c#L2168 /* * This function makes lazy skb cloning in hope that most of packets * are discarded by BPF. * * Note tricky part: we DO mangle shared skb! skb-\u0026gt;data, skb-\u0026gt;len * and skb-\u0026gt;cb are mangled. It works because (and until) packets * falling here are owned by current CPU. Output packets are cloned * by dev_queue_xmit_nit(), input packets are processed by net_bh * sequentially, so that if we return skb to original state on exit, * we will not harm anyone. */ static int packet_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt, struct net_device *orig_dev) { ここで受信用のhookが設定されており https://github.com/torvalds/linux/blob/1a33418a69cc801d48c59d7d803af5c9cd291be2/net/packet/af_packet.c#L3442 po-\u0026gt;prot_hook.func = packet_rcv; if (sock-\u0026gt;type == SOCK_PACKET) po-\u0026gt;prot_hook.func = packet_rcv_spkt; po-\u0026gt;prot_hook.af_packet_priv = sk; po-\u0026gt;prot_hook.af_packet_net = sock_net(sk); if (proto) { po-\u0026gt;prot_hook.type = proto; __register_prot_hook(sk); } https://github.com/torvalds/linux/blob/1a33418a69cc801d48c59d7d803af5c9cd291be2/net/packet/af_packet.c#L344 /* __register_prot_hook must be invoked through register_prot_hook * or from a context in which asynchronous accesses to the packet * socket is not possible (packet_create()). */ static void __register_prot_hook(struct sock *sk) { struct packet_sock *po = pkt_sk(sk); if (!packet_sock_flag(po, PACKET_SOCK_RUNNING)) { if (po-\u0026gt;fanout) __fanout_link(sk, po); else dev_add_pack(\u0026amp;po-\u0026gt;prot_hook); sock_hold(sk); packet_sock_flag_set(po, PACKET_SOCK_RUNNING, 1); } } https://github.com/torvalds/linux/blob/1a33418a69cc801d48c59d7d803af5c9cd291be2/net/core/dev.c#L603 /** *\tdev_add_pack - add packet handler *\t@pt: packet type declaration * *\tAdd a protocol handler to the networking stack. The passed \u0026amp;packet_type *\tis linked into kernel lists and may not be freed until it has been *\tremoved from the kernel lists. * *\tThis call does not sleep therefore it can not *\tguarantee all CPU\u0026#39;s that are in middle of receiving packets *\twill see the new packet type (until the next received packet). */ void dev_add_pack(struct packet_type *pt) { struct list_head *head = ptype_head(pt); if (WARN_ON_ONCE(!head)) return; spin_lock(\u0026amp;ptype_lock); list_add_rcu(\u0026amp;pt-\u0026gt;list, head); spin_unlock(\u0026amp;ptype_lock); } EXPORT_SYMBOL(dev_add_pack); https://github.com/torvalds/linux/blob/1a33418a69cc801d48c59d7d803af5c9cd291be2/net/core/dev.c#L573 /* *\tAdd a protocol ID to the list. Now that the input handler is *\tsmarter we can dispense with all the messy stuff that used to be *\there. * *\tBEWARE!!! Protocol handlers, mangling input packets, *\tMUST BE last in hash buckets and checking protocol handlers *\tMUST start from promiscuous ptype_all chain in net_bh. *\tIt is true now, do not change it. *\tExplanation follows: if protocol handler, mangling packet, will *\tbe the first on list, it is not able to sense, that packet *\tis cloned and should be copied-on-write, so that it will *\tchange it and subsequent readers will get broken packet. *\t--ANK (980803) */ static inline struct list_head *ptype_head(const struct packet_type *pt) { if (pt-\u0026gt;type == htons(ETH_P_ALL)) { if (!pt-\u0026gt;af_packet_net \u0026amp;\u0026amp; !pt-\u0026gt;dev) return NULL; return pt-\u0026gt;dev ? \u0026amp;pt-\u0026gt;dev-\u0026gt;ptype_all : \u0026amp;pt-\u0026gt;af_packet_net-\u0026gt;ptype_all; } if (pt-\u0026gt;dev) return \u0026amp;pt-\u0026gt;dev-\u0026gt;ptype_specific; return pt-\u0026gt;af_packet_net ? \u0026amp;pt-\u0026gt;af_packet_net-\u0026gt;ptype_specific : \u0026amp;ptype_base[ntohs(pt-\u0026gt;type) \u0026amp; PTYPE_HASH_MASK]; } 最終的にpt-\u0026gt;dev-\u0026gt;ptype_allに設定されている。そしておそらくこのあたりで配送されているため結果としてnetfilterの処理は丸々スキップされているのだと推定される。 https://github.com/torvalds/linux/blob/1a33418a69cc801d48c59d7d803af5c9cd291be2/net/core/dev.c#L5727 list_for_each_entry_rcu(ptype, \u0026amp;dev_net_rcu(skb-\u0026gt;dev)-\u0026gt;ptype_all, list) { if (pt_prev) ret = deliver_skb(skb, pt_prev, orig_dev); pt_prev = ptype; } list_for_each_entry_rcu(ptype, \u0026amp;skb-\u0026gt;dev-\u0026gt;ptype_all, list) { if (pt_prev) ret = deliver_skb(skb, pt_prev, orig_dev); pt_prev = ptype; } なおこれはinput側にしか焦点を当てていないのでoutput側がどうなのかはわからないがおそらく似たようにnetfilterはバイパスされているのではないかと推測される。(調査不足)\nそして更に調べていくついでにnetfilterのwikiを読んでみるとNETDEVのingress hookが一番最初にあるということが書かれている。 これを見てみればnetfilterでraw socketを止められそうかが分かるかなと思ったので調べてみた。 https://wiki.nftables.org/wiki-nftables/index.php/Netfilter_hooks ingress hookはおそらくここで https://github.com/torvalds/linux/blob/1a33418a69cc801d48c59d7d803af5c9cd291be2/include/linux/netfilter_netdev.h#L31 nf_hook_state_init(\u0026amp;state, NF_NETDEV_INGRESS, NFPROTO_NETDEV, skb-\u0026gt;dev, NULL, NULL, dev_net(skb-\u0026gt;dev), NULL); ここから呼ばれる経路にあり https://github.com/torvalds/linux/blob/1a33418a69cc801d48c59d7d803af5c9cd291be2/net/core/dev.c#L5652 static inline int nf_ingress(struct sk_buff *skb, struct packet_type **pt_prev, int *ret, struct net_device *orig_dev) { if (nf_hook_ingress_active(skb)) { int ingress_retval; if (*pt_prev) { *ret = deliver_skb(skb, *pt_prev, orig_dev); *pt_prev = NULL; } rcu_read_lock(); ingress_retval = nf_hook_ingress(skb); rcu_read_unlock(); return ingress_retval; } return 0; } それはここで呼ばれている。 https://github.com/torvalds/linux/blob/1a33418a69cc801d48c59d7d803af5c9cd291be2/net/core/dev.c#L5747 #ifdef CONFIG_NET_INGRESS if (static_branch_unlikely(\u0026amp;ingress_needed_key)) { bool another = false; nf_skip_egress(skb, true); skb = sch_handle_ingress(skb, \u0026amp;pt_prev, \u0026amp;ret, orig_dev, \u0026amp;another); if (another) goto another_round; if (!skb) goto out; nf_skip_egress(skb, false); if (nf_ingress(skb, \u0026amp;pt_prev, \u0026amp;ret, orig_dev) \u0026lt; 0) goto out; } #endif 位置関係で見るとptype_allに先にdeliverされたあとnf_ingressによるチェックが入っている\u0026hellip; つまりここでもnetfilterをスキップできるようだ\u0026hellip;\nしかし前段でgeneric XDP処理がされているのを見つけた\u0026hellip;\ngeneric XDP https://yunazuno.hatenablog.com/entry/2017/06/12/094101 (最初からwikipediaのXDPのページを見れば一目瞭然であったということに最後の最後になって気付いた。) https://en.wikipedia.org/wiki/Express_Data_Path https://github.com/torvalds/linux/blob/1a33418a69cc801d48c59d7d803af5c9cd291be2/net/core/dev.c#L5698 if (static_branch_unlikely(\u0026amp;generic_xdp_needed_key)) { int ret2; migrate_disable(); ret2 = do_xdp_generic(rcu_dereference(skb-\u0026gt;dev-\u0026gt;xdp_prog), \u0026amp;skb); migrate_enable(); if (ret2 != XDP_PASS) { ret = NET_RX_DROP; goto out; } } https://github.com/torvalds/linux/blob/1a33418a69cc801d48c59d7d803af5c9cd291be2/net/core/dev.c#L5338 int do_xdp_generic(const struct bpf_prog *xdp_prog, struct sk_buff **pskb) { https://github.com/torvalds/linux/blob/1a33418a69cc801d48c59d7d803af5c9cd291be2/net/core/dev.c#L5258 static u32 netif_receive_generic_xdp(struct sk_buff **pskb, struct xdp_buff *xdp, const struct bpf_prog *xdp_prog) { https://github.com/torvalds/linux/blob/1a33418a69cc801d48c59d7d803af5c9cd291be2/net/core/dev.c#L5133 u32 bpf_prog_run_generic_xdp(struct sk_buff *skb, struct xdp_buff *xdp, const struct bpf_prog *xdp_prog) { 最終的にeBPFの実行箇所まで言ったがこれ以上進むと沼が深すぎるのでとりあえず今回はここまでとする。 https://github.com/torvalds/linux/blob/1a33418a69cc801d48c59d7d803af5c9cd291be2/include/net/xdp.h#L646 static __always_inline u32 bpf_prog_run_xdp(const struct bpf_prog *prog, struct xdp_buff *xdp) { /* Driver XDP hooks are invoked within a single NAPI poll cycle and thus * under local_bh_disable(), which provides the needed RCU protection * for accessing map entries. */ u32 act = __bpf_prog_run(prog, xdp, BPF_DISPATCHER_FUNC(xdp)); if (static_branch_unlikely(\u0026amp;bpf_master_redirect_enabled_key)) { if (act == XDP_TX \u0026amp;\u0026amp; netif_is_bond_slave(xdp-\u0026gt;rxq-\u0026gt;dev)) act = xdp_master_redirect(xdp); } return act; } ということで結論としてはAF_PACKET socketにも渡したくない場合はXDPを使えということである。\n(正しいのかどうかはデバッグしたわけではないため本当にそうかまではわからない部分もあるので有識者の方これ違うとかあったら教えて下さい\u0026hellip;)\n追記(5/18): その後送信側を調べてみたところAF_PACKETでも送信側の方は netfilterのnetdevファミリーのegressフックを使えば抑制できる事がわかった。\nhttps://github.com/torvalds/linux/blob/205b2bd7939cc126f445ce3010af22858c18ef1f/net/packet/af_packet.c#L273 static int packet_xmit(const struct packet_sock *po, struct sk_buff *skb) { if (!packet_sock_flag(po, PACKET_SOCK_QDISC_BYPASS)) return dev_queue_xmit(skb); #ifdef CONFIG_NETFILTER_EGRESS if (nf_hook_egress_active()) { skb = nf_hook_direct_egress(skb); if (!skb) return NET_XMIT_DROP; } #endif return dev_direct_xmit(skb, packet_pick_tx_queue(skb)); } 48d2610e-3147-4a87-aa5d-7fc101156359 ja ","date":"2025年 5月 10日","externalUrl":null,"permalink":"/static/blog/posts/2025_05_10_raw_socket_nftables/","section":"Posts","summary":"","title":"AF_PACKET SOCK_RAWソケットのためにnftablesでLinux kernelの挙動を抑制した話","type":"posts"},{"content":"","date":"2025年 5月 10日","externalUrl":null,"permalink":"/static/blog/tags/docker/","section":"Tags","summary":"","title":"Docker","type":"tags"},{"content":"","date":"2025年 5月 10日","externalUrl":null,"permalink":"/static/blog/tags/nftables/","section":"Tags","summary":"","title":"Nftables","type":"tags"},{"content":"","date":"2025年 5月 10日","externalUrl":null,"permalink":"/static/blog/tags/raw-socket/","section":"Tags","summary":"","title":"Raw Socket","type":"tags"},{"content":"","date":"2025年 5月 10日","externalUrl":null,"permalink":"/static/blog/categories/%E6%8A%80%E8%A1%93%E8%A8%98%E4%BA%8B/","section":"Categories","summary":"","title":"技術記事","type":"categories"},{"content":"","date":"2025年 5月 6日","externalUrl":null,"permalink":"/static/blog/tags/black-hat-asia-2025/","section":"Tags","summary":"","title":"Black Hat Asia 2025","type":"tags"},{"content":" はじめに # 前編 からの続きである。読んでいない方はそちらから読むことをおすすめする。\n4/1 # シンガポールはアジア一の金融の街として知られている。そこで金融関係の会社が大集合しているRaffles Place と呼ばれている場所に散策に向かった。asu_paraさんは勤めているところがFintechの会社であるというのもあってだいぶ興奮していた。\nasu_paraさん提供の写真 その後その近くにあったアジア文明博物館 を見物するなどする。\nそしてBlack Hat Asia 2025の会場にたどり着く。\nここでBlack Hat Asiaの会場はマリーナ・ベイ・サンズであるのだが、 マリーナ・ベイ・サンズというとおそらく大半の人は3つのビルと屋上のプールの組み合わさった様子を思い浮かべると思われる。\nしかしこういったイベントの開催場所となっているのはその有名な建物の前にあるショッピングモールが併設された建物である。ということに現地で気づいてちょっとびっくりしていた。\nさてたどり着いて上がってみたもののなんと既に入場時間を過ぎておりasu_paraさんは粘ってたもののセキュリティが厳しかったので結局会場には入れずじまいであった。\nそこでその近くにあったGardens by the Bay に行って OCBCスカイウェイを歩くなどする。\nその他夜にはマーライオンのところに行くなどする。昼間は遠目から見てもわかるほど激混みしていたのだが夜になると意外と空いていた。\nさてこの日に至っても発表資料ができていないということで帰ったあとsisakulintといろいろ作業していたら 有効になっているはずの機能が無効になっていると判明して直す作業をしていた。\nhttps://github.com/ultra-supara/sisakulint/pull/143 4/2 # この日はasu_paraさんはFinantial Service Summit に行っていた。 筆者は午前中はホテルでのんびりと過ごし午後はNetwork Operation Center に見学に行くなどしていた。\nなお大多数の人にとってはどうでもいい話であろうがこの写真について一言述べておく。 このNOCのためのOpen Network Detection and Resposneを提供しているcorelightが今回のやつで 提供していたZeekというソフトウェアがある。 実のところ筆者はSecHack365時代にbrgenというものを作っていたときに このZeekに付随しているSpicyという言語について調べていた時期がありそれもあって このZeekが使われているのを見ておおぉ使われてるんだぁとすごく興奮した。\nGitHub - zeek/zeek: Zeek is a powerful network analysis framework that is much different from the typical IDS you may know. Zeek is a powerful network analysis framework that is much different from the typical IDS you may know. - zeek/zeek\ngithub.com GitHub - zeek/spicy: C\u0026#43;\u0026#43; parser generator for dissecting protocols \u0026amp; files. C\u0026#43;\u0026#43; parser generator for dissecting protocols \u0026amp; files. - zeek/spicy\ngithub.com なおこちら筆者が作っているbrgen(唐突な宣伝)。 GitHub - on-keyday/brgen: brgen, short for BinaRy encoder/decoder GENerator brgen, short for BinaRy encoder/decoder GENerator. Contribute to on-keyday/brgen development by creating an account on GitHub.\ngithub.com 4/3 # さていよいよこの日からがイベント本編といったところである。\n午前中はBriefingsを1つ聞いたのとArsenalのあたりをうろちょろしていた。\nまずはArsenalの会場の様子 そしてプログラム表(3:25 PMのところにsisakulintがある) Think Inside the Box # まずBriefingsを聞いた。\nhttps://www.blackhat.com/asia-25/briefings/schedule/index.html#think-inside-the-box-in-the-wild-abuse-of-windows-sandbox-in-targeted-attacks-44095 日本人発表者の講演であった。 要約するとWindows Sandboxの脆弱性をついたバックドアの事例についてとそれを検出する方法について述べられていた。\nなおBlack Hatでは新規性というのが結構重要視されるらしいのだが応募してから講演までそれなりに間が空いているため その間に日本の警視庁及びNISCがこの事例について公表してしまっているという状態であったので探せば他にも記事がいくつか見つかる。\n例: https://blog.itochuci.co.jp/entry/2025/03/12/140000 まず前提として現在Proエディション以上のWindows10/11では\n別途VMなどをインストールしなくても使える ハイパーバイザーベースの 軽量な 分離された デスクトップ環境を提供する Windows Sandboxという機能が使える。\nhttps://learn.microsoft.com/en-us/windows/security/application-security/application-isolation/windows-sandbox/ またその設定は.wsbファイルというxmlベースの設定ファイルで記述することができる。例えばネットワークを有効化するか否かを記述するNetoworkingやサンドボックス内(Guest)と外(Host)のフォルダを共有するためのMappdedFolders、Windows Sandbox起動時に実行するコマンドを設定するLoginCommandといったものがある。(概念的にはdocker composeのdocker-compose.ymlに似ている)\nしかし例えばいくらか制約もありたとえばHost環境でアンチウイルスソフトを動かしていてもGuest内では動かないといったような制約もある。\nこの前提を踏まえて実際のシチュエーションを見ると まず暗号化/圧縮されたmalwareをHostにどうにかして置く。(本事例ではANELバックドア を使用して設置したとしていた) その後Windows Sandboxが表示するUIによって気づかれるのを防ぐためにSYSTEMアカウントを使用してWindowsSandbox.exeを起動するようにschtasks を使って指定。 その時渡す.wsbファイルにはC2サーバーと通信するためにNetworksを有効にし、さらにHostに置いてあるユーザー情報など及び先程置いたmalwareが入ったディレクトリをMappedFoldersで指定してマルウェアの解凍用batchファイルをLoginCommandに設定して起動する。\nSandboxが起動するとマルウェアが多段階にわけて解凍され、NOOPDOOR が出現しそしてSandbox環境内で起動される。\nこのようにするとEndpoint Protection Platform(EPP)やEndpoint Detection and Response(EDR)の検知をかいくぐれてしまったのである。\nまたこの事例では.wbsファイルがファイルベースであったが最近はwsb.exeというコマンドラインベースのツールが登場したらしくコマンドラインオプションで.wbsに書いてある設定を直接渡せるなどメモリベースでより起動フローをわかりにくくさせるということもできるようになりつつある。\nでは対策はあるのかと言うとまずログをきちんと取ればsigma rulesなどで不正な実行を検知したりすることができる。\nGitHub - SigmaHQ/sigma: Main Sigma Rule Repository Main Sigma Rule Repository. Contribute to SigmaHQ/sigma development by creating an account on GitHub.\ngithub.com またWindowsのGroup Policyを使ってNetworkやMappdedFoldersを無効化するなどができる。\nhttps://learn.microsoft.com/ja-jp/windows/client-management/mdm/policy-csp-windowssandbox#allowmappedfolders さらにはWindows Sandbox内のリソースはHostからはvmmemSandbox.exeなどのプロセスのメモリ領域として見れるためそれをスキャンすることでmalwareの実行を検出することもできると述べられていた。\nr0fuzz # ここからはArsenalの話である。\nGitHub - traboda/r0fuzz Contribute to traboda/r0fuzz development by creating an account on GitHub.\ngithub.com https://www.blackhat.com/asia-25/arsenal/schedule/index.html#r0fuzz-a-collaborative-fuzzer-43908 発電所などで使われているIndstrual Control System(ICS)で使われているMODBUS,OPC,UA,DNP3等といったハードウェアネットワークプロトコルをfuzzingするためのツールである。\nMutation based fuzzing,Generation based fuzzing,AI driven coupus generationなどといった機能が統合されている。\nなおこれ自体とはあんまり関係ないが現地では発電所に対してハッキングするCTFのようなものが行われていた。\nhttps://www.blackhat.com/asia-25/arsenal/schedule/index.html#circuit-breaker-ctf-43220 といってもほとんど手順書通りにコマンドを打ち込むだけだったのだが なんというかリモート経由で発電所内に設置されている特定のレジスタの値を書き換えることで 発電所の挙動をいじったりショートさせたりするという感じであった。 以下のツールを使っていた。 GitHub - sanny32/OpenModScan: Open ModScan is a Free Modbus Master (Client) Utility Open ModScan is a Free Modbus Master (Client) Utility - sanny32/OpenModScan\ngithub.com kntrl # GitHub - kondukto-io/kntrl: kntrl is an eBPF based runtime agent that monitors and prevents anomalous behaviour defined by you on your pipeline. kntrl achieves this by monitoring kernel calls, and denying access as soon as your defined behaviour is detected. For more: https://kntrl.dev kntrl is an eBPF based runtime agent that monitors and prevents anomalous behaviour defined by you on your pipeline. kntrl achieves this by monitoring kernel calls, and denying access as soon as yo...\ngithub.com https://www.blackhat.com/asia-25/arsenal/schedule/#kntrl---securing-cicd-runners-through-ebpf-agent-43153 kntrlはCI/CD上でeBPFベースで不審な通信を検出し拒否するようにできるツールである。 CI/CDではしばしばサードパーティのアクションというのものを使ったりするが、そこが乗っ取られて意図しない通信が行われ情報が抜き取られるなどといった攻撃をされるという事例は枚挙にいとまがない。さらにCI/CDは開発環境とみなされしばしば過剰な権限を付与されておりそれが脆弱性を引き起こすこともある。またそもそもとしてCI/CD上での通信を完全に把握するのは難しい。 そこでこのツールが役に立つ。\nOpen Policy Agent のrego言語を使ってpolicyを定義することができるためこういったツールの利用に慣れている人にはわかりやすい。\nmantis # GitHub - PhonePe/mantis: Mantis is a security framework that automates the workflow of discovery, reconnaissance, and vulnerability scanning. Mantis is a security framework that automates the workflow of discovery, reconnaissance, and vulnerability scanning. - PhonePe/mantis\ngithub.com https://www.blackhat.com/asia-25/arsenal/schedule/#mantis---asset-discovery-at-scale-43534 mantisは攻撃対象の発見、偵察、スキャンといった作業を自動化しさらにはDashBoardで表示すると言ったことまでできるツールである。 autorecon という似たようなツールを聞いたことはあったがこちらは分散ノードでスキャンしたりとかアラート機能がついてたりとかしている。 攻撃面の洗い出しと言った作業がより効率的にできるようになる可能性がある。\njary # GitHub - CTRLRLTY/JARY: An opinionated extensible language for rule creation! (UNDER DEVELOPMENT) An opinionated extensible language for rule creation! (UNDER DEVELOPMENT) - CTRLRLTY/JARY\ngithub.com https://www.blackhat.com/asia-25/arsenal/schedule/#jary---a-modular-data-correlation-engine-43640 JARYはYARA に影響を受けたルール定義言語でありログデータなどを受け取ってそれを分析したり変更したりと言ったことができるエンジンである。開発当初はログデータの解析が主な目的ではあったらしいもののログの直接的な解析自体はユーザーの責務と割り切った設計になっているため例えば入力にhttp requestのjson bodyをパースしたものを入れて判定させるなどすることで入力検証などといった用途にも使えるとしている。\n個人的には発表の流れが面白いなあと印象に残っている。あとリポジトリのreadmeとかにところどころ思想の強さを感じる\u0026hellip;\nnimplant # GitHub - chvancooten/NimPlant: A light-weight first-stage C2 implant written in Nim (and Rust). A light-weight first-stage C2 implant written in Nim (and Rust). - chvancooten/NimPlant\ngithub.com https://www.blackhat.com/asia-25/arsenal/schedule/#nimplant-43204 nim言語とRustとPythonで書かれたCommand and Control(C2)を実行するためのimplant(攻撃対象のコンピュータで実行されるソフトウェア)及びそのサーバーである。 構成ファイルを記述することでimplantの挙動の書き換えができるようになっている。\nリポジトリを見てみると面白そうなんだけど正直あんまり印象に残ってない\u0026hellip;.\nsql-data-gurad # GitHub - ThalesGroup/sql-data-guard: Safety Layer for LLM Database Interactions Safety Layer for LLM Database Interactions. Contribute to ThalesGroup/sql-data-guard development by creating an account on GitHub.\ngithub.com https://www.blackhat.com/asia-25/arsenal/schedule/#sql-data-guard-safety-layer-for-llm-database-interactions-46259 sql data guardはSQL Injection等を防ぐためにsqlのアクセスしようとしているデータと条件に応じて検証しさらに可能であれば修正まで行うツールである。例えば SELECT id,customer_name FROM orders WHERE 1 = 1のようなQueryが来ても例えばクエリにidとproduct_nameだけ許可するとかWHERE節にaccount_id=3がなきゃだめとかいう条件を記述することでこういったのを検知しさらにはSELECT id,product_name FROM orders WHERE account_id = 3に直したものも提供すると言った機能を備えている。\nLLMによるSQL生成というのをひとつの焦点にしており、 既存のDBにはfine-grantedなcolmuns levelやrow levelのアクセス制御とかいうのがないのは 問題である的な話をしておりこのツールを使うことでそ言ったきめ細やかなアクセス制御が可能になると言ったようなことが述べられていた。\nこのセッションは3日と4日両方でやっていた。\n他にも見たのがある全部書くとと長いので割愛\u0026hellip;(気が向いたら追記されるかも\u0026hellip;)\nsisakulint # そして午後は自分たちの発表であった。\nGitHub - ultra-supara/sisakulint: CI-Friendly static linter with autofix, SAST, semantic analysis for GitHub Actions CI-Friendly static linter with autofix, SAST, semantic analysis for GitHub Actions - ultra-supara/sisakulint\ngithub.com https://www.blackhat.com/asia-25/arsenal/schedule/#sisakulint---ci-friendly-static-linter-with-sast-semantic-analysis-for-github-actions-43229 asu_paraさんのslide https://speakerdeck.com/4su_para/sisakulint-ci-friendly-static-linter-with-sast-semantic-analysis-for-github-actions 発表の様子 # 写真をいくらか撮ったものの普段そういった撮影をする技術を研鑽していないのがあだとなって 人が多少多いが発表者の顔が見切れているやつか人が少ないがそれなりの構図というパターンしか撮れていないので写真を統合してなんとなーく想像してほしい。 なお発表内容自体は上記のスライドを参照していただきたい　バッサリ切っていくスタイル\n触りだけ言うとGitHub Actions WorkflowのyamlファイルのlinterでありさらにはSAST的な機能やAutofixなどといった機能が盛り込まれている。\n構図的にはスピーカーが写りつつ一番良かったやつ 人が多めなやつ asu_paraさんはカンペを見るためにXREAL を持っていっていたものの会場ではどうも文字が小さすぎて見えず結局カンペ無しで喋っていた。 スライドを見ず即興で喋っていたが聴衆の様子を見るにちゃんと伝わっていたようだ(なお筆者の英語に限らないがリスニング能力はだいぶ低いので本当に伝わっていたのかどうかは現地にいた人のみぞ知るである)\n正直に言って自分は全くもって発表するって面では貢献できていないのでそちらについては割愛。\n時間割当が75分の中で2回ほどサイクルを回してasu_paraさんがスライドを見せつつ喋ってという感じであっという間に終わりを迎えた。\nなお日本人参加者もちらほら見かけることが多かった。\nなお終わったあとこのArsenalのレビューボードの方と写真を取らせてもらったがあいにくそちらの写真はasu_paraさんしか持ってないので見たい方は彼に聞いて下さい。\nその後 # Think Inside the Boxの講演をしていた原さんに遭遇して少し話を聞く。 BriefingsではSpeaker Coaching Program なるものが発表前にあるらしくJSACの選考委員 などをやっていてそういった仕組みをJSACにも取り入れたいと思ったといったような話を聞く。\n4/4 # とりあえず発表は終わったのでArsenalとBriefingsを見ていった。\nDecoy Mutex # Arsenalの話からする。\nGitHub - ScarredMonk/Decoy-Mutex: Decoy-Mutex: A Windows tool for creating decoy mutexes (Fake Infection Markers) associated with ransomware simulations. Ransomware checks for the presence of its related mutex to determine whether the system is already infected. It doesn’t infect the system if it locates the mutex. Decoy-Mutex: A Windows tool for creating decoy mutexes (Fake Infection Markers) associated with ransomware simulations. Ransomware checks for the presence of its related mutex to determine whether ...\ngithub.com Decoy MutexはWindows環境においてマルウェアが同じマルウェアを複数同時に実行しないようにするためにある特定の名前のMutexを使うことを利用してあらかじめDecoy MutexによってMutexを保持しておくことでマルウェアの実行を防ぐというソフトウェアである。\n正直これだけでも実際にマルウェアを阻止できているのを見ると結構有効な手段そうだなあと思った。\nSilver Saml Forger # GitHub - Semperis/SilverSamlForger: Silver SAML forgery tool Silver SAML forgery tool. Contribute to Semperis/SilverSamlForger development by creating an account on GitHub.\ngithub.com Silver SAML ForgerはSAMLのレスポンスを作成するツールでありSilver SAML Attackの実装等にも使えるツールである。\nSilver SAML AttackとはSAMLのIdPを侵害することで署名をつけた任意のSAMLレスポンス(Assertion)を生成できてしまう脆弱性であるGolden SAML Attackに対して署名を検証する側のService Providerを侵害して検証用の公開鍵を書き換えることで攻撃者による署名を付けたSAMLレスポンスを正常なものとして認識させてしまうという脆弱性につけられた名称である。企業内部でしばしばやりがちなといっても筆者は実際のところを知らないんですが自前の証明書チェーンをimportしてSAMLで使うというシナリオにおいてそのimportのプロセスで攻撃されて入るというような話であった。\n実際に攻撃の様子を実演してみたりしていた\nCasino Heist # GitHub - Kiinzu/Casino-Heist: Casino Heist is a playground for learning Solidity smart contracts security, we have it ready for you to play at https://casinoheist.xyz Casino Heist is a playground for learning Solidity smart contracts security, we have it ready for you to play at https://casinoheist.xyz - Kiinzu/Casino-Heist\ngithub.com https://www.blackhat.com/asia-25/arsenal/schedule/index.html#casino-heist-master-the-art-of-solidity-smart-contract-security-43224 Ethereumというスマートコントラクトを実装したブロックチェーン上でそのスマートコントラクトを記述するための言語であるSolidityなどの脆弱性について学べるplaygroundである。これを作っている人がホスティングしているサイトもあればdockerで動かせるバージョンもある。\nこのplaygroundの動機はなんにもわからない完全初心者には既存のブロックチェーンのセキュリティの話を扱うコンテンツ(Walletが,Etherの送信が\u0026hellip;,こういうツールを使え\u0026hellip;WriteUpを読め\u0026hellip;etc)がハードルが高すぎるという問題意識からきており初心者Lv1の人に対して初心者Lv1のためのPlaygroundを提供するという説明をされていた。\n前に何かのCTFに一回参加したときにSolodityの脆弱性を使うやつを見たのだが大体わからんかったのでこういうのを見て勉強したい\u0026hellip;\nこちらも他にも見たのがあるが全部書くとと長いので割愛\u0026hellip;(気が向いたら追記されるかも\u0026hellip;)\nBriefings # CDN CannonとByzRPのBriefingsを聞く。 といってもほとんど聞き取れもせずだったのだが\u0026hellip; ということで以下の内容は振り返りついでに公開資料を読んでまとめている。\nCDN Cannon # https://www.blackhat.com/asia-25/briefings/schedule/index.html#cdn-cannon-exploiting-cdn-back-to-origin-strategies-for-amplification-attacks-43932 CDN Cannonの方は簡単に言うとCDNを使ってAmplification攻撃が出来てしまうという話であった。 例えばCDNの中にはimageを取ってくるときにCDN側で画像を切り取ったりしてサイズを変更してクライアントに送信できるという機能があるものがある。\n-\u0026gt; GET /image.png?crop=100,100 -\u0026gt; -\u0026gt; GET /image.png -\u0026gt; Client CDN Origin Server \u0026lt;- 200 OK (100x100の画像) \u0026lt;- crop \u0026lt;- 200 OK (300x300の画像)\u0026lt;- このとき例えば攻撃側がcropの値を1,1とすると\n-\u0026gt; GET /image.png?crop=1,1 -\u0026gt; -\u0026gt; GET /image.png -\u0026gt; Client CDN Origin Server \u0026lt;- 200 OK (1x1の画像,数百byte) \u0026lt;- crop \u0026lt;- 200 OK (300x300の画像,数kB/数MB)\u0026lt;- このようにOrigin ServerからCDNへの転送量がCDNからクライアントの転送量に対してだいぶ多くなる。 他にもドメインの所有についてきちんと検証しないヘッダの書き換えができるCDNを使って巨大なヘッダを含むリクエストを送りつけたり HEADをGETに変換する挙動をするCDNを使ってHEADリクエストを送りつけて大きな容量のデータをOrigin ServerからCDNに送らせるとか。あるいはクライアントからリクエストを送っておいてコネクションを切断するがOrigin ServerからCDNへはデータが送られるようにするとか。 もちろんこれら一つ一つだけでは大した事なさそうではあるが塵も積もれば山となるようで 検証した結果Client-CDN間が数kbpsレベルの転送量でCDN-Origin間に数Gbpsレベルの転送量を与えることが出来たそうである。\nもちろんネットワーク帯域に対してかなりの負荷をかけるという面があるというだけではなく 発表自体では直接的な言及はなかったものの 例えばOrigin ServerがAWSなどのクラウド環境に置かれているとき そのOutbound転送には課金がされていることが大半である。それがCDNを使ってこのように大量の転送を引き起こすことができてしまうとコストを無駄にかけさせるという攻撃ができてしまうのである。\n対策としてはRFCに準拠してHEADはHEADでProxyするようにするとか クライアントが切断したらCDN-Origin間も切断するようにするとかといったことが述べられていた。\nByzRP # https://www.blackhat.com/asia-25/briefings/schedule/#the-byzrp-solution-a-global-operational-shield-for-rpki-validators-44176 ByzRPの方はBGP(Border Gateway Protocol)のRPKIの仕組みを改善するためのアーキテクチャを提案しているものである。 まず前提としてBGP自体はスケールし効率的で素早くルーティング情報をネットワーク間に伝播させることを目的としている。しかしその設計自体にはセキュリティというのが組み込まれていなかったため 例えばルーティング情報の出どころが正しいことを保証するメカニズムがなく 過去には間違ったルーティング情報が流れて大規模障害が起こったりあるいはパケットを吸い上げて盗聴しようと意図的に間違ったルーティング情報を流されたりといったことが起こったりしている。(BGPハイジャック)\nそこでRPKI(Resource Public Key Infrastructure) という仕組みを導入しルーティング情報の出どころを検証できるような仕組みが広まっている。 具体的には各地域レジストリ(RIR)などがPublication Point(PP)にてリソース証明書(IPアドレスやAS番号などの保有を証明するCA証明書やルーティング情報を広告する認可を表すRoute Origin Authorization(ROA)に署名するための秘密鍵に対応するEE証明書などがある)やCRL(失効リスト)などを配布しておりそれらをRelying Party(RP,ISP等及びそれらが使用するPPからの収集/検証/ルータへの適用を行うソフトウェア)がそれぞれ収集してROAによるRoute Origin Validation(ROV)を行いそれを各自の持っているBGPルーターのフィルタリングルール等に適用するなどすることで実現している。\n一見良さそうではあるがこの仕組みには問題がいくつかある。\nまずRPKI自体はoptionalな機能であるためRPがPPからROAなどを取得できないとその情報はないものとして扱われるという点がある。\n例えばRPとPPの間の通信を妨害するあるいはPP自体を乗っ取ってレスポンスに長い遅延を含めてタイムアウトさせるなどすれば検証自体を無効化出来てしまうのである。\nあるいはRPの実装の問題としてRPに不正な入力を入れてクラッシュさせてBGP Routerに情報を行かせないということもできてしまう。\nそうでなくてもネットワークの障害等でコネクションエラー等が発生するとやはり無効化されてしまう。\n総じてセキュリティの三要素の可用性に問題があるといえる。\nそこでこのByzRPではWatchDogの追加と分散コンセンサスアルゴリズムに基づいたRPの実装を提案している。\nまずWatchdog機構ではPPとRPの間のやり取りを監視しRPがクラッシュしたりあるいは通信遅延が異常に長かったりするのを検知するとそのPPをブラックリストに入れるといったことを行う。 これによってPPの乗っ取りやあるいはRPのクラッシュに対応ができるようになる。\nまたネットワークエラーによる無効化の対策にはビザンチンフォールトトレラント(ByzRPのByzはByzantionから来ている)な分散コンセンサスアルゴリズムを使って複数のRPで合意を取ることでたとえ1つのRPがネットワークエラーでPPからROAを拾って来れなかったとしても全体で見ればROAを拾って検証することができるようになる。\nまたこの仕組みを様々な人で共有してRP-as-a-Serviceのような形にすればより多くの人が個別でRPを用意するよりもより少ないトラッフィク量でより信頼性の高いRPが実現できるようになりよりRPKIの実装がしやすくなると主張されていた。\nその他 # 企業ブースのあたりをうろついてみたりNOC Presentationとか聞いていたりしたがあいにくリスニング能力なさすぎとかとかバイタリティが足りなすぎてあんまり聞けたりはしなかった\u0026hellip;もったいなかったような気もする。\n最後に終了講演を聞いてこの日が終わり こんな感じでBlack Hat Asia 2025は終わった。\n4/5 # この日は朝チャンギ空港に向かって荷物を預けそして Tampinesあたりまで言ってそこのフードコートで板麺 を食べるなどする。 その後シンガポール国立大学 を見に行くなどしているうちにあっという間に時間が過ぎそして深夜の飛行機で日本へと帰っていったのであった\u0026hellip;\nまとめ # 1週間近くシンガポールに行って美味しいものを食っていろんなものを見て聞いてなかなか貴重な体験をしたんだろうなぁというのを感じる。いろんなセキュリティ系の技術を見てセキュリティの幅は広いなぁということを実感した。\nなお他にもいい景色を見たり人に会ったりドリアンを食べたりと言ったエピソードもあるのだがそちらは省略されている。聞きたい人は現実の筆者に会ったときにでも聞いてほしい。\nあとこの記事自体ではあまり触れていないが今回の一連の旅程ではasu_paraさんの行動力の高さに非常に感心した。\n自分はなんというか性根が\u0026quot;Ask for forgiveness, not permission\u0026quot;ではなく\u0026quot;Ask for permission before acting\u0026quot;という感じであるため、まずダメそうかなぁという判断をしがちであったがasu_paraさんは臆することなく行動していて自分にはそういうとりあえず行動という精神が大いに欠けているなというのを思ったのであった。\nなおこの話をasu_paraさん本人にしたところ「行動よりもコミュニケーション能力の問題だと思っていて、英語がペラペラであることよりも、伝わる英語が話せるかを意識している」と言っていたがあいにく筆者の英語リスニング能力が壊滅的なので実際伝わっていたのかどうかは判断できない(多分伝わってはいたと思うけれども)。しかしこのとにかく臆さず会話をしに行っていた姿勢は是非真似していきたいところである。\n宣伝 # こういった経験ができたのもすべてasu_paraさんのお陰であると同時に元をたどせばSecHack365に参加したおかげである。ありがとうSecHack365。\nセキュリティに興味がある人やなにかを開発したい人あるいは強強な技術者達に会いたい人はぜひぜひSecHack365に参加してほしい。\n一年かけてものづくりしたりあるいは人と交流したりする中できっとなにか人生の糧になるような経験ができるであろう。\n2025年度の締切は5月13日(火) 12:00までです。\n詳しくはこちら👇️👇️👇️👇️👇️👇️\nhttps://sechack365.nict.go.jp/requirements/ ぼやき # ⚠️ネガティブ節全開なボヤキなので見たくない人は飛ばしてください⚠️ 一応隠していない部分についてはできる限りポジティブめのことを書いたつもりではあるが 一方である種の本心(ポジティブな部分が本心でないというわけではない)としてこういう感情を持っていた面もあるということを残しておく。 この記録を書いていてこの経験をしたことについて振り返ったとき 正直外に出ること自体があまりないのと人に興味がないのといろいろあって体験していることが貴重なのかどうかという判断が本質的な意味ではそもそもつかないという感じがしている。\n自分自身の判断として価値があると信じられているかと言うと「客観的な指標を参照すればおそらく価値があるであろうと推定される」みたいな捉え方に近いような認識になっているような主観(回りくどい)というのが正直なところでなんかもったいないなぁということを感じている。\nあとこれらの発表されていた技術群を並べて見せられたとしても正直適切に評価するのは出来ていないだろうなぁというのを感じたりもする。無知であると情報がすべて等価に見えるというなんかで聞いたような言葉を強く実感する。後からするのが後悔だというがまさにそんな感じである。\n感受性も解像度も低いしInputを言語化したり体系化するのが億劫でなにかと理由を付けては先延ばしにし4月の頭に行ったはずなのにもはや5月である。\n他にも聞く気になれば翻訳アプリとか活用したりで聞けばよかったじゃんとも思ったりしたが後の祭りである。\nあとその場にいても訊きたいことが思い浮かばなくてあとからああこれ訊きたいような気がするなぁと思ったりする。\n事前に読み込んだりとか言う準備的なのをしておけばもっと楽しめたのかなぁというのをひしひしと感じたりもする\u0026hellip;\n26a2c06b-38de-4c10-8280-6f49feb0ef29 ja ","date":"2025年 5月 6日","externalUrl":null,"permalink":"/static/blog/posts/2025_05_06_blackhat_asia_2/","section":"Posts","summary":"","title":"Black Hat Asia 2025 その2","type":"posts"},{"content":"","date":"2025年 5月 6日","externalUrl":null,"permalink":"/static/blog/categories/%E6%97%A5%E8%A8%98/","section":"Categories","summary":"","title":"日記","type":"categories"},{"content":" はじまり # 少し前の記事 で触れているように筆者はBlackhat Asia 2025に参加しに行くことになった。この記事はその記録である。なお記事内に登場する人物と筆者の関わりについては先の記事に乗っているのでそちらを参照してもらいたい。\nといってもこの記事(その1)はほとんどシンガポール(もとい出発前から)であたふたしていたのと飯食って美味かったとかいうだけの話である。Black Hat Asia 2025本編については次の記事 を参照してほしい。 なお写真についてだが筆者は普段写真を撮る文化圏の人間ではない。そのため撮っている写真が少ない。これらの記事に乗っている写真を見て「えーこんな写真?もっといいのなかったの?」と思ったとしてもそれは載せれそうな写真がそれしかなかったということでご理解いただきたい。\n3/29 # Black Hat Asia 2025自体は4/1-4/4であったもののこの日に東京に移動していた。 一度asu_paraさんと合流したところ、asu_paraさんにシンガポールは物価が高いから万一飯屋が高くて無理だったとして飢えないようにしよう、といったようなことを言われてとドン・キホーテでプロテインバー的なものをしこたま買う。なお結果はどうだったか先に言っておくと言ってもそこまで法外な値段というわけでもなく逼迫することもなかったこともあって結局自分たちではあまり消費することはなくもっぱらasu_paraさんがBlack Hatの会場で配ってなかなかの好評を得ていたそうである\u0026hellip;\n3/30 # この日は飛行機が朝だいぶ早くから空港にてそわそわしていた。 そこでふとそういえば飛行機7時間くらい飛ぶけど暇を潰せそうな本とか持ってないなと思い立った。 そこでKindleというものの存在を思い出し、そうだKindleで本を買えばいいんだと思って 空港にてAmazonをポチポチし早速9000円くらいした本 を あ、そういえばクレカ登録してなかったなとその場で登録してポチッとやった。 すると、どうなるかというとAmazonに完全に不正利用扱いされてアカウント利用停止である。 後先考えないで行動するとこうなるという良い見本であった\u0026hellip;。(なお後日復旧しました)Amazonは優秀だね!\nAmazonのことはとりあえず置いて搭乗手続きをして荷物検査を終えさあ出国ゲート。 これがもう最近は全自動でパスポートと顔認証で行けてしまう 。 あいにく自分が最後にして最初海外に行ったのはもう13年くらい前の話でありその頃はこんな感じではなかったため\nうおーーすげーーみらーーい\nという小学生のような感想を抱いて通過した。見た目がちょっとワクワクしてしまった。\nそして搭乗して狭い機内に乗り込みasu_paraさんに本を借りたりしつつ7時間をしのいで到着。\nまず第一印象は蒸し暑いであった。ついた瞬間の天気はあんまり良くなかったのであるがとにかくジメジメした感じであった。\nチャンギ空港を出て併設のショッピングモールで目にしたものといえば資生堂が出資してつくったらしいでっかいグリーンガーデンと滝のようなやつであった。ついた矢先に日本の企業名を見るとは\u0026hellip;\n滝の様子 他にもチャンギ空港いろいろ日本のものがあって例えば\u0026hellip;\ndo do do donki~ donki~ ho-te-\nいやドン・キホーテあるんかいと言う感じでチャンギ空港にもドン・キホーテがあったのである。もちろん中で売っているものは日本とはだいぶ違うしだいぶいいお値段していたが\u0026hellip;。他にもすき家があったりとんかつ屋があったり\u0026hellip;.。なんというか結構日本のチェーン店とかが進出しているんだなぁというのを感じた。\nその後重い荷物を持ってひいこらひいこら言いながらバスに乗り込んでホテルに向かう。 なおシンガポールのバス及び地下鉄ではJCBは使えない。そんなこととはつゆ知らずJCBを使う気満々だった筆者はバス入口で運転手が\u0026quot;change your card\u0026quot;と言っているのにも気づかずタッチしても通らないとあたふた慌てていた。そしてふと財布を見て見つかった青山のMaster Cardでえいっとやって急場をしのいだ。青山には感謝してもしきれない。\n夜はムスタファ・センター に行って移動時に飲む用の水を買い そしてその帰りにインド料理店 で夕食を食べる。 とにかくガーリックのナンとマサラの組み合わせがとてもとても美味しかったということだけ記しておく。\n3/31 # 午前中はシンガポール国立博物館 へ行く。 この日はなんのタイミングだったのかはわからないがなんと入場料が無料であった。(参考 )\n中の展示は古代の人々が使っていた船のような展示からはじまりテマセック、そしてライオンの街を意味するシンガプーラといった名前になりその後ジョホール王国のスルタンによる統治、そしてイギリスによる植民地時代と展示が続きそして太平洋戦争期の日本による統治,昭南島時代その後のシンガポールの独立から現代のシンガポールまでとかなり充実した内容の展示であった。とにかく多文化とかの由来がぎゅっと歴史に詰まっている感があって言葉では言い表せないのでぜひ行ってほしい。I ABSOLUTELY RECOMMEND IT!\n午後はセントーサ島へ観光へ繰り出す。 モノレールに乗って到着してから\nビーチを延々と歩いたりといった調子であった。\nその後カジノやらUniversal Studio Singaporeを入口だけみてS.E.A. Aquarium でいろんな魚やらクラゲやらを見る。 なかなか日本にはいないような魚が多くて面白かったもっともそもそもそんなに魚に詳しいわけではないのので何見ても新鮮ではあるのだが\u0026hellip;\nその後夜はアラブストリートのあたりをうろちょろしてモスク を見たりした。 本当に通り一つ隔てると相当様相が異なり1つの国で3カ国くらい観光した気分になれるのがだいぶお得だなというのを感じた。\n夕食は肉骨茶 を食べる。(https://www.foodadvisor.com.sg/restaurant/new-manlee-bak-kut-teh-bugis/ ) なんというか中華風で薬味が効いた感じできのこやら骨付き肉やら謎の野菜やらが鍋に入っていて美味しかった。\n7fd3c42d-c9e5-4bc3-bb99-704d6fb86bcc ja ","date":"2025年 5月 6日","externalUrl":null,"permalink":"/static/blog/posts/2025_05_06_blackhat_asia_1/","section":"Posts","summary":"","title":"Black Hat Asia 2025 その1","type":"posts"},{"content":"","date":"2025年 4月 26日","externalUrl":null,"permalink":"/static/blog/tags/cline/","section":"Tags","summary":"","title":"Cline","type":"tags"},{"content":" TL;DR # 回帰テストは大事だよね\n経緯 # 巷で話題のFastAPI-MCPというものを使ってみようとしてとりあえず作ってみたらいい感じに出来たのでインターネット経由で使えるようにしようとした(公開用ではなく個人用)\nhttps://qiita.com/takuya77088/items/4db3bcaedd8ad721b077 インターネット経由なので認証が必要だよなというのでとりあえずBasic認証をかけようと思ったが自分が使っているclineでBasic認証は使えるんだろうかと思い、ドキュメントを見てみると以下のような記述を見つけた。\nhttps://docs.cline.bot/mcp-servers/configuring-mcp-servers#sse-transport { \u0026#34;mcpServers\u0026#34;: { \u0026#34;remote-server\u0026#34;: { \u0026#34;url\u0026#34;: \u0026#34;https://your-server-url.com/mcp\u0026#34;, \u0026#34;headers\u0026#34;: { \u0026#34;Authorization\u0026#34;: \u0026#34;Bearer your-token\u0026#34; }, \u0026#34;alwaysAllow\u0026#34;: [\u0026#34;tool3\u0026#34;], \u0026#34;disabled\u0026#34;: false } } } おお、Basic認証もいけそうじゃんということでcline_mcp_settings.jsonを\n{ \u0026#34;mcpServers\u0026#34;: { \u0026#34;mcp-server\u0026#34;: { \u0026#34;disabled\u0026#34;: false, \u0026#34;url\u0026#34;: \u0026#34;https://example.com/mcp\u0026#34;, \u0026#34;headers\u0026#34;: { \u0026#34;Authorization\u0026#34;: \u0026#34;Basic \u0026lt;base64\u0026gt;\u0026#34; } } } } 上みたいな感じ(イメージ)に編集しさあ早速使ってみようとやってみると\n\u0026hellip;え? いやいやちゃんと設定したよね? と確認してみるも設定に異常はなさそうだ。\nそこでサーバーのログを確認してみると\n(マスキング済み)\nLog Message ID 1160193 Level ERROR Start Time 2025-04-25T16:11:36.576410265Z End Time 2025-04-25T16:11:36.579863591Z Status Code 401 Unauthorized Request Method GET Request URL **** Host **** Remote Address **** Private Remote Address **** Private Local Address **** User Agent node Request Headers Accept: text/event-stream Accept-Encoding: gzip, br Accept-Language: * Cache-Control: no-cache Cdn-Loop: cloudflare; loops=1 Cf-Connecting-Ip: ******** Cf-Ipcontinent: **** Cf-Ipcountry: **** Cf-Iplatitude: **** Cf-Iplongitude: **** Cf-Ray: **** Cf-Region: **** Cf-Region-Code: **** Cf-Timezone: **** Cf-Visitor: {\u0026#34;scheme\u0026#34;:\u0026#34;https\u0026#34;} Cf-Warp-Tag-Id: **** Connection: keep-alive Hs-Connecting-Port: **** Pragma: no-cache Sec-Fetch-Mode: cors User-Agent: node X-Forwarded-For: **** X-Forwarded-Proto: https Response Headers Content-Security-Policy: default-src \u0026#39;self\u0026#39;; upgrade-insecure-requests; block-all-mixed-content Referrer-Policy: strict-origin-when-cross-origin Strict-Transport-Security: max-age=31536000; includeSubDomains Vary: Origin Www-Authenticate: Basic X-Content-Type-Options: nosniff X-Frame-Options: SAMEORIGIN X-Powered-By: PHP/7.1.32 X-Xss-Protection: 1; mode=block Response Body Size 0 Taken Time 127.685µs Taken Time (Nano) 127685 Log {\u0026#34;time\u0026#34;:\u0026#34;2025-04-25T16:11:36.576482085Z\u0026#34;,\u0026#34;level\u0026#34;:\u0026#34;ERROR\u0026#34;,\u0026#34;file\u0026#34;:\u0026#34;*\u0026#34;,\u0026#34;line\u0026#34;:\u0026#34;*\u0026#34;,\u0026#34;message\u0026#34;:\u0026#34;access denied deny: default (basic_auth)\u0026#34;} \u0026hellip;あれ?Authorizationヘッダ送られてないっぽい????\nということで調査に乗り出した\n調査 # まずissueを探してみると早速ヒット\nIt seems that setting the header does not work effect in MCP SEE mode? · Issue #2652 · cline/cline What happened? I developed a Spring WebFlux MCP Server, everything is working fine, but I want to pass a header through the Client: AccessKey. I found that the server side cannot obtain the custom ...\ngithub.com どうやらMcpの設定の読み込み箇所にバグがあったようだ 下の方にPRが確認できたので見てみると\nfix:The POST requests of MCP\u0026#39;s SSE server support setting headers. by suntp · Pull Request #2969 · cline/cline …652) Description Add headers config when requesting sse server in McpHub.ts Test Procedure After the fix, sse server support headers configuration. Type of Change 🐛 Bug fix (non-breaking change...\ngithub.com fix:The POST requests of MCP\u0026#39;s SSE server support setting headers.(#2… · suntp/cline@49766bf …652)\ngithub.com const SseConfigSchema = BaseConfigSchema.extend({ url: z.string().url(), + headers: z.record(z.string()).optional(), といったような記述が発見された。ではこれは元からなかったのかというとそうでもないようで記録をたどってみるとこの時点では存在したことが確認できる\nBlaming cline/src/services/mcp/McpHub.ts at 0204396b37d5219e12710e5df5c3c3b6e4ed508d · cline/cline Autonomous coding agent right in your IDE, capable of creating/editing files, executing commands, using the browser, and more with your permission every step of the way. - Blaming cline/src/service...\ngithub.com ではどこで消えたかと言うとzod を使ったスキーマに書き直した際に消失したようだ。\nMcpHub.ts - Better Zod Types and Function Refactor (#2497) · cline/cline@8c4b642 * refactor types and functions * fix timeout validation type error * changeset\ngithub.com Blaming cline/src/services/mcp/McpHub.ts at 8c4b642859eb6d7a9a372c30ef9d360aaa5e44b4 · cline/cline Autonomous coding agent right in your IDE, capable of creating/editing files, executing commands, using the browser, and more with your permission every step of the way. - Blaming cline/src/service...\ngithub.com なおこのzodに変更する際には普通にPRでレビューをしているが通過している。AIツールもついぞ指摘することはなかったようだ\nMcpHub.ts - Better Zod Types and Function Refactor by celestial-vault · Pull Request #2497 · cline/cline Description Add zod type transforms to discriminate between sse and stdio config schemas. Refactor out MCP Settings Config reading and validation to make the code easier to understand and be more ...\ngithub.com なおスキーマ以外にもこの修正PRではMCPのtypescript-sdk のSSEClientTransportの初期化部分も正確に初期化されていなかったことが示唆されている 。時系列的には以下のSSEトランスポートを実装した時点で既にスキーマからはheadersが消えた状態であったためSSEトランスポート実装者から見落とされた可能性がある。\nSSE Transport by celestial-vault · Pull Request #2520 · cline/cline Description Add SSE transport connection Test Procedure Tested with the Zapier MCP server Checked that existing local servers work Type of Change 🐛 Bug fix (non-breaking change which fixes an ...\ngithub.com Blaming cline/src/services/mcp/McpHub.ts at 6abf0be8d15889ef0c96b50c26c5c1121fedc37a · cline/cline Autonomous coding agent right in your IDE, capable of creating/editing files, executing commands, using the browser, and more with your permission every step of the way. - Blaming cline/src/service...\ngithub.com 結論 # とりあえずこのバグが直るまではインターネット経由で自作mcpサーバーを使うのはお預けである..\nあとAIのレビューを鵜呑みにするのは危ないということと仕様を書いたらテストを書くのは大事ということが改めて感じられた\u0026hellip;\n追記: mcp-remote というライブラリで普通に行けた\u0026hellip; みんなこっち使うからバグが表に出なかったのかな\u0026hellip;\n1f3b313a-d6b2-4e0f-b266-52e97b7e21bb ja ","date":"2025年 4月 26日","externalUrl":null,"permalink":"/static/blog/posts/2025_04_26_cline_headers_problem/","section":"Posts","summary":"","title":"ClineのMCPで認証が使えなかった話","type":"posts"},{"content":"","date":"2025年 4月 26日","externalUrl":null,"permalink":"/static/blog/tags/mcp/","section":"Tags","summary":"","title":"MCP","type":"tags"},{"content":" はじまり # 某氏があるリポジトリにStarを付けていたのを見かけたので見てみたらハイパーバイザを1日で作って学べるぜ的なことが書いてあったのでハイパーバイザを作ってみようとしていた。\nGitHub - tandasat/Hypervisor-101-in-Rust: The materials of \u0026#34;Hypervisor 101 in Rust\u0026#34;, a one-day long course, to quickly learn hardware-assisted virtualization technology and its application for high-performance fuzzing on Intel/AMD processors. github.com ※実験的にここだけOGPで拾ってきたカードリンク風\nだが以下の最初のところにたどり着くまでにすでに1日(嘘数時間程度)かかって詰まったのでメモを残す。\nhttps://tandasat.github.io/Hypervisor-101-in-Rust/hypervisor-setup-and-operation-cycle/exercise-preparation-building-and-running-the-hypervisor-and-navigating-code.html なおHypervisor-101-in-Rustは以下のコミット時点のものを参照しており、また筆者の環境はArch Linux(kernel version 6.14.2,x86_64)である。\nhttps://github.com/tandasat/Hypervisor-101-in-Rust/tree/00cdd3dcc6c46a96aff22e732bee5dc15cf681ed Bochsが起動できない # まず最初にBochs を使うことに決めて早速書いてあったとおりに\ncargo xtask bochs-intel と打ってみると\n00000000000i[ ] debugger using rc file \u0026#39;./bochs/dbg_command.txt\u0026#39;. 00000000000i[ ] BXSHARE not set. using compile time default \u0026#39;/usr/share/bochs\u0026#39; 00000000000i[ ] reading configuration from ./bochs/linux_intel.bxrc 00000000000p[SIM ] \u0026gt;\u0026gt;PANIC\u0026lt;\u0026lt; Plugin \u0026#39;busmouse\u0026#39; not found 00000000000e[SIM ] notify called, but no bxevent_callback function is registered 00000000000e[SIM ] notify called, but no bxevent_callback function is registered ======================================================================== Bochs is exiting with the following message: [SIM ] Plugin \u0026#39;busmouse\u0026#39; not found ======================================================================== 00000000000i[SIM ] quit_sim called with exit code 1 ======================================================================== Bochs x86 Emulator 3.0 Built from GitHub snapshot on February 16, 2025 Timestamp: Sun Feb 16 10:00:00 CET 2025 ======================================================================== といったエラーが出た。まずそもそもこのbochsを呼び出している箇所はxtask/src/bochs.rs内にあり 以下のようになっている。\nlet bxrc = format!(\u0026#34;./bochs/{}_{cpu_type}.bxrc\u0026#34;, env::consts::OS); let output = Command::new(bochs) .args([\u0026#34;-q\u0026#34;, \u0026#34;-unlock\u0026#34;, \u0026#34;-rc\u0026#34;, DBG_CMD, \u0026#34;-f\u0026#34;, \u0026amp;bxrc,\u0026#34;-dbglog\u0026#34;,\u0026#34;./bochs/debug.log\u0026#34;]) .current_dir(Path::new(\u0026#34;./tests\u0026#34;)) .stdout(Stdio::piped()) .spawn() .unwrap(); ということでtests/bochs/linux_intel.bxrcを開いてbusmouseという記述を探すと以下のような行が見つかった。\nplugin_ctrl: biosdev=true, busmouse=false, e1000=false, es1370=false, extfpuirq=true, parallel=true, sb16=false, serial=true, speaker=false, unmapped=true, usb_ehci=false, usb_ohci=false, usb_uhci=false, usb_xhci=false, voodoo=false ちゃんとbusmouse=falseと書いてあるくせにnot foundとはこれいかにと思ってウンウン悩んだ末、とりあえずxxx=falseとなっている記述を消して\nplugin_ctrl: biosdev=true, extfpuirq=true, parallel=true, serial=true, unmapped=true にしてみたところ別のエラーになった。とりあえずこのpluginに関する問題は解決したようだ。(根本的にはしてないかもしれないがとりあえず動くからいいのだ\u0026hellip;)\nnoguiが使えない # 続いて出てきたエラーは以下のようなものであった。\n00000000000i[ ] debugger using rc file \u0026#39;./bochs/dbg_command.txt\u0026#39;. 00000000000i[ ] BXSHARE not set. using compile time default \u0026#39;/usr/share/bochs\u0026#39; 00000000000i[ ] reading configuration from ./bochs/linux_intel.bxrc 00000000000p[ ] \u0026gt;\u0026gt;PANIC\u0026lt;\u0026lt; ./bochs/linux_intel.bxrc:4: display library \u0026#39;nogui\u0026#39; not available 00000000000e[SIM ] notify called, but no bxevent_callback function is registered 00000000000e[SIM ] notify called, but no bxevent_callback function is registered ======================================================================== Bochs is exiting with the following message: [ ] ./bochs/linux_intel.bxrc:4: display library \u0026#39;nogui\u0026#39; not available あれー?存在しないやつかなぁと思ったがドキュメントを見てもあるっぽい(4.3.3節) https://bochs.sourceforge.io/doc/docbook/user/bochsrc.html ということでChatGPTに聞いたりしてみると--with-noguiなるビルドオプションをつければいい的なことを言われた。 結局\n$ yay bochs --editmenu 5 aur/bochs-gdb-stub 2.8-1 (+1 0.00) A portable x86 PC emulation software package with gdbstub 4 aur/bochs-gdb 2.6.11-3 (+1 0.00) (Out-of-date: 2022-06-17) A portable x86 PC emulation software package with gdbstub 3 aur/bochs-sdl2 2.8-2 (+0 0.00) A portable x86 PC emulation software package, including GUI debugger, with SDL2 support 2 aur/bochs-sdl 2.7-1 (+2 0.00) (Orphaned) A portable x86 PC emulation software package, including GUI debugger, with sdl support 1 aur/bochs 3.0-1 (+4 0.00) (Installed) A portable x86 PC emulation software package, including GUI debugger ==\u0026gt; Packages to install (eg: 1 2 3, 1-3 or ^4) ==\u0026gt; 1 AUR Explicit (1): bochs-3.0-1 :: PKGBUILD up to date, skipping download: bochs 1 bochs (Installed) (Build Files Exist) ==\u0026gt; Packages to cleanBuild? ==\u0026gt; [N]one [A]ll [Ab]ort [I]nstalled [No]tInstalled or (1 2 3, 1-3, ^4) ==\u0026gt; 1 :: Deleting (1/1): ~/.cache/yay/bochs HEAD is now at ecd9a06 updated to 3.0 Removing bochs-3.0-1-x86_64.pkg.tar.zst Removing bochs-3.0.tar.gz Removing bochs-debug-3.0-1-x86_64.pkg.tar.zst 1 bochs (Installed) (Build Files Exist) ==\u0026gt; Diffs to show? ==\u0026gt; [N]one [A]ll [Ab]ort [I]nstalled [No]tInstalled or (1 2 3, 1-3, ^4) ==\u0026gt; 1 bochs (Installed) (Build Files Exist) ==\u0026gt; PKGBUILDs to edit? ==\u0026gt; [N]one [A]ll [Ab]ort [I]nstalled [No]tInstalled or (1 2 3, 1-3, ^4) ==\u0026gt; 1 -\u0026gt; $EDITOR is not set -\u0026gt; Add $EDITOR or $VISUAL to your environment variables ==\u0026gt; Edit PKGBUILD with? ==\u0026gt; code そして以下のように編集\n(前略) ./configure \\ --prefix=/usr \\ --without-wx \\ --with-x11 \\ --with-x \\ --with-term \\ --disable-docbook \\ --enable-cpu-level=6 \\ --enable-fpu \\ --enable-3dnow \\ --enable-smp \\ --enable-x86-64 \\ --enable-avx \\ --enable-evex \\ --enable-long-phy-address \\ --enable-pcidev \\ --enable-usb \\ --enable-debugger \\ + --with-nogui #--with-sdl (後略) そして\n:: Proceed with install? [Y/n] y とした これによりbochs自体は起動した。\n最後のファイル # だが次に\n[MEM0 ]p| \u0026gt;\u0026gt;PANIC\u0026lt;\u0026lt; ROM: couldn\u0026#39;t open ROM image file \u0026#39;/usr/share/ovmf/OVMF.fd\u0026#39; というエラーが出た。 再度tests/bochs/linux_intel.bxrcを見に行くと以下のようになっていた。\nromimage: file=\u0026#34;/usr/share/ovmf/OVMF.fd\u0026#34;, address=0xffe00000, options=none vgaromimage: file=\u0026#34;/usr/local/share/bochs/VGABIOS-lgpl-latest\u0026#34; これはtests/bochs/bios内に同じ名前のファイルがそれぞれ存在していたためそちらのパスに差し替えてみたところ解決した。\nそして\n5: #0:ERROR: panicked at \u0026#39;not yet implemented: E#1-1\u0026#39;, hypervisor/src/hardware_vt/vmx.rs:71:9 無事最初のところまでたどり着いたのであった。\nおわり # あとは指示に従って作っていきましょう\u0026hellip;\n蛇足 # ソースコードを探しに行った無駄足メモの記録\nエラー箇所 https://github.com/bochs-emu/Bochs/blob/d41c0fb4ce96beb4aab5831911d6921d484505b2/bochs/config.cc#L2535 コメントは追記\n// ここでguiライブラリ名を指定して設定しているようだがおそらく失敗しi=1;となる if (SIM-\u0026gt;get_param_enum(BXPN_SEL_DISPLAY_LIBRARY)-\u0026gt;set_by_name(params[1])) { i = 2; } else { i = 1; } // 個々の条件でi=1に当てはまるので入る。 if ((num_params == 3) || (i == 1)) { if (!strncmp(params[i], \u0026#34;options=\u0026#34;, 8)) { // この条件は今回は当てはまらないのでskip SIM-\u0026gt;get_param_string(BXPN_DISPLAYLIB_OPTIONS)-\u0026gt;set(\u0026amp;params[i][8]); } else if (i == 1) { // この条件に当てはまりエラーになる PARSE_ERR((\u0026#34;%s: display library \u0026#39;%s\u0026#39; not available\u0026#34;, context, params[1])); } else { PARSE_ERR((\u0026#34;%s: display_library directive malformed\u0026#34;, context)); } } 66327bb0-84e2-44e0-89cb-bfd40650d5f6 ja ","date":"2025年 4月 20日","externalUrl":null,"permalink":"/static/blog/posts/2025_04_20_hypervisor_bochs/","section":"Posts","summary":"","title":"Hypervisor-101-in-Rustの最初にたどり着くまで","type":"posts"},{"content":"","date":"2025年 4月 20日","externalUrl":null,"permalink":"/static/blog/tags/rust/","section":"Tags","summary":"","title":"Rust","type":"tags"},{"content":"","date":"2025年 4月 20日","externalUrl":null,"permalink":"/static/blog/tags/%E3%83%8F%E3%82%A4%E3%83%91%E3%83%BC%E3%83%90%E3%82%A4%E3%82%B6/","section":"Tags","summary":"","title":"ハイパーバイザ","type":"tags"},{"content":" TL;DR # ブログの更新が一瞬でできるようになった\n経緯 # もともとこのブログはサーバー上にgo:embedするという形で格納されていた。 当初このサーバーを作り始めたときにはそれでも十分に早いビルドで表に出すことが出来ていた。 しかしサーバーの機能増大に伴ってついにはビルドだけで15分ほどかかり このビルドとともに更新するという手間をいれるとかなりブログを更新するのが億劫であった。\nそこで今回ブログの部分をgo:embedしている静的リソースと切り離し更新を早くできるようにした。 具体的にはブログ読み取り用のコンテナを一つ立ててそこにビルドされたブログのディレクトリをreadonlyマウントする。 そしてホスト側で更新をかけるとコンテナ側でも更新が反映されWebからアクセスできるといった次第である。\nこれをもって今年はもっと頻繁にブログを更新できるようにしていきたいものである。\n2c3c4be9-dd86-404f-a07b-a55f270ca930 ja ","date":"2025年 4月 19日","externalUrl":null,"permalink":"/static/blog/posts/2025_04_19_new_blog_system/","section":"Posts","summary":"","title":"ブログを更新しやすくした話","type":"posts"},{"content":"","date":"2025年 4月 19日","externalUrl":null,"permalink":"/static/blog/tags/%E8%87%AA%E5%AE%85%E3%82%B5%E3%83%BC%E3%83%90%E3%83%BC/","section":"Tags","summary":"","title":"自宅サーバー","type":"tags"},{"content":"","date":"2025年 4月 19日","externalUrl":null,"permalink":"/static/blog/categories/%E5%A0%B1%E5%91%8A/","section":"Categories","summary":"","title":"報告","type":"categories"},{"content":" お断り # この文章は本来3月に書いて結局公開していなかったものを4月になってから 公開しているものである。よって記事の日付は3月になっていることをご了承願いたい。 なおこの記事は次回以降出すシンガポールに行った話の前座である。\nはじまり # ことの発端はsisakulint (https://github.com/ultra-supara/sisakulint ) というツールの開発に関わっていたことに遡る。 もともと開発者のasu_paraさんとはSecHack365'23の同期でありその際にsisakulintの開発を手伝ったりしており終わった後もたまに開発を手伝っていた。\nSecHack365が終わった後昨年2024年の10月頃から asu_paraさんはこのsisakulintをBlack Hat Asia 2025のArsenalに応募することに決めており その際もし受かったら一緒にシンガポールまで来てほしいと言われていた。 筆者はまあ受かったら行きましょかと言いつつあんまり内心は受からんやろと思っていたのであった(筆者は原則として期待値を低く持っておく主義である) しかし、なんとBlack Hat Asia 2025に通ったのである。筆者の見る目がない(おそらくそうだろうな)のか審査員の目が節穴なのかはわからんが通ってしまった以上は行かねばならぬ。\nそこで本来Arsenalへ行くだけなら無料のビジネスパスというものが存在するためそれを使えばよいのだが、 asu_paraさんにせっかく行くのだからBriefingsを見たほうがいいと説得されたため そのパスを買おうと思ったもののなんと一般価格で最大2500シンガポールドル,学生価格としても1000シンガポールドル(2025/3/22時点為替レート1S$=111.69円とすると27万円と11万円ほど)でありだいぶ高い。\nhttps://www.blackhat.com/asia-25/registration.html うーん経済的に痛いなぁ痛いなぁと思っていたところasu_paraさんが以下のページを見つけて scholarshipに応募してみてはどうかと言ってきた。\nhttps://www.blackhat.com/asia-25/scholarships.html ということでとりあえず送ってみたところ通ったという次第である。 そのことを報告したところ送った内容を公開しておくれと言われたのでここに公開する次第である。\n実際に送ったもの # 質問内容と肝心の翻訳して実際に送った内容そのものををメモってなかったのでもはや参照できないのだが おおよそ以下のような質問に対して 以下のような回答をgoogle翻訳に突っ込んだりした(で一応文章的におかしいところを補う形で若干の修正をした)やつを送った\n締切の2/28に送ってないことを思い出し ダメ元で慌てて書いて調べて送ったのであんまり練られてもいないし あとおそらく内容が自分個人の話すぎて他人の参考にならんとは思うが この程度の文章で行けるのであればおそらくもっと多くの人が行ける可能性があるんじゃないだろうかということで供養しておく。\nなぜBlackHat Asia 2025に参加する必要があるかを述べなさい 開発を手伝っていたツールの開発者がBlackHat Arsenalに応募したところ通ってしまい、さらになぜだか私の名前もspeaker扱いで登録されてしまっているため このスカラーシップに通ろうが通らまいがどちらにせよ行くことになった。正直個人的には無料で取得できるBusiness Passでよいのですが主開発者のasu_paraさんにせっかく行くのなら Briefings Passをもって行ってBrieingsを聴くべきだと説得されたものの正直学生価格の1000S$でもとても高いのでどうしようか迷っていたところこのスカラーシップを見つけ このスカラーシッププログラムに受かったら1000S$のBriefings Pass代が0S$になるなら経済的に楽になるなと思い応募しました。 Reference: https://www.blackhat.com/asia-25/arsenal/schedule/index.html#sisakulint---ci-friendly-static-linter-with-sast-semantic-analysis-for-github-actions-43229 あなたの普段やっていることで参加するにblackhat参加に値すると思う点は? バイナリフォーマットを表現するのDSLから構造体/エンコーダ/デコーダの組を任意の言語に対して(今のところあまり対応言語数は多くないが)自動生成するツールを個人的に開発している。 (ref: https://github.com/on-keyday/brgen ) 日本のSecHack365 2023年度において優秀修了生となった(ref: https://sechack365.nict.go.jp/achievement/2023/ https://sechack365.nict.go.jp/achievement/2023/pdf/31Dn.pdf (only in Japanese))\n過去のblackhatのコンテンツの中からあなたが特に興味を持ったものを3つ以上あなたの経験と絡めて説明しなさい まずThe Cost of Complexity: Different Vulnerabilities While Implementing the Same RFCはRFCの複雑さと曖昧さによって生じる脆弱性について検証したものである。 https://www.blackhat.com/asia-21/briefings/schedule/#the-cost-of-complexity-different-vulnerabilities-while-implementing-the-same-rfc-22276 私自身もRFCを読むときにしばしば記述の曖昧性に惑わされることが多いためこの検証事例は興味深い。 私自身の開発しているツールはこういった曖昧性や手書きによる脆弱性をなくすことも目標とするためこれらの現実の事例は実際にツールを作成する際に大いに参考になる。\nPISE: Automatic Protocol Reverse Engineeringもまた興味深い。この手法ではシンボリック実行とL*アルゴリズムによって プロトコルのパケットを解析して種類を判定しさらに状態遷移まで自動で判定できるというものである。 https://www.blackhat.com/us-22/briefings/schedule/#automatic-protocol-reverse-engineering-27238 例えばこの技術で解析したパケットを元に自分が作っているDSLの雛形を生成するようにできれば解析したパケットを分析するのがより簡単になり さらに様々な言語にexportして試すことも容易になるであろうというのが考えられるためこういった技術は興味深い\nBreaking Theoretical Limits: The Gap Between Virtual NICs and Physical Network Cardsでは物理nicと仮想nicの差異に起因するネットワークプロトコルスタックでの脆弱性について述べられている。 https://www.blackhat.com/eu-23/briefings/schedule/#breaking-theoretical-limits-the-gap-between-virtual-nics-and-physical-network-cards-35116 整数演算オーバーフローや超過読み取りによるメモリリークなど私が開発しているツールにおいても対策として重要な視点が得られる。\n結果 # ということで無事タダで行けることとなった。ありがたやありがたや\nbfcd3250-27f2-4871-b849-2cfa842a2945 ja ","date":"2025年 3月 22日","externalUrl":null,"permalink":"/static/blog/posts/2025_03_22_blackhat_scholarship_accepted/","section":"Posts","summary":"","title":"BlackHat Asiaのscholarshipは意外と通るものだという話","type":"posts"},{"content":"","date":"2025年 3月 22日","externalUrl":null,"permalink":"/static/blog/categories/%E3%83%9D%E3%82%A8%E3%83%A0/","section":"Categories","summary":"","title":"ポエム","type":"categories"},{"content":" TL;DR # docker buildx bake を使っているときに docker-compose.yaml でおんなじサービスの複製を作るときは build で同じ Dockerfile を指定してはいけない\n内容 # いつものように自宅サーバーを再起動しようとしたら\nINFO: failed to resume the status from path .buildx-cache/ingest/39e3ffd26acfcd29cffe52b4e0f3ed668c06daf8a81552cb090ef701d17fa756: failed reading status of resume write: stat .buildx-cache/ingest/39e3ffd26acfcd29cffe52b4e0f3ed668c06daf8a81552cb090ef701d17fa756/data: no such file or directory: not found. will recreate them (中略) ERROR: (*service).Write failed: rpc error: code = Canceled desc = context canceled ERROR: target server1: failed to solve: error writing layer blob: rpc error: code = Unknown desc = rename tmp file: rename .buildx-cache/ingest/39e3ffd26acfcd29cffe52b4e0f3ed668c06daf8a81552cb090ef701d17fa756/startedat.tmp .buildx-cache/ingest/39e3ffd26acfcd29cffe52b4e0f3ed668c06daf8a81552cb090ef701d17fa756/startedat: no such file or directory: unknown といったようなエラーで落ちてしまった。\n一体何なんだと思って調べたら\n#151 [server3] exporting cache to client directory #151 writing layer sha256:1f3e46996e2966e4faa5846e56e76e3748b7315e2ded61476c24403d592134f0 done #151 writing layer sha256:51463de4e5797bcc36a1a19b8f6309c079b129cc677aeb5655c1669e75c39318 #151 writing layer sha256:51463de4e5797bcc36a1a19b8f6309c079b129cc677aeb5655c1669e75c39318 1.1s done #151 writing layer sha256:95101ac40e2ff0ac79fc615bcf401be1514c8f9eb0157bf7a9bcdd3bdb70f6a8 #151 preparing build cache for export 1.2s done #151 writing layer sha256:95101ac40e2ff0ac79fc615bcf401be1514c8f9eb0157bf7a9bcdd3bdb70f6a8 done #151 ERROR: error writing layer blob: rpc error: code = Unknown desc = rename tmp file: rename .buildx-cache/ingest/fbcff690bc06459030780f486d15a593ecd86fb27dac52f951f6268f4ad9b832/startedat.tmp .buildx-cache/ingest/fbcff690bc06459030780f486d15a593ecd86fb27dac52f951f6268f4ad9b832/startedat: no such file or directory: unknown #153 [server1] exporting cache to client directory #153 preparing build cache for export 1.2s done #153 writing layer sha256:1f3e46996e2966e4faa5846e56e76e3748b7315e2ded61476c24403d592134f0 done #153 writing layer sha256:51463de4e5797bcc36a1a19b8f6309c079b129cc677aeb5655c1669e75c39318 1.1s done #153 writing layer sha256:95101ac40e2ff0ac79fc615bcf401be1514c8f9eb0157bf7a9bcdd3bdb70f6a8 done #153 ERROR: error writing layer blob: rpc error: code = Unknown desc = rename tmp file: rename .buildx-cache/ingest/fbcff690bc06459030780f486d15a593ecd86fb27dac52f951f6268f4ad9b832/startedat.tmp .buildx-cache/ingest/fbcff690bc06459030780f486d15a593ecd86fb27dac52f951f6268f4ad9b832/startedat: no such file or directory: unknown #154 [server2] exporting cache to client directory #154 preparing build cache for export 1.3s done #154 writing layer sha256:1f3e46996e2966e4faa5846e56e76e3748b7315e2ded61476c24403d592134f0 done #154 writing layer sha256:51463de4e5797bcc36a1a19b8f6309c079b129cc677aeb5655c1669e75c39318 1.1s done #154 writing layer sha256:95101ac40e2ff0ac79fc615bcf401be1514c8f9eb0157bf7a9bcdd3bdb70f6a8 0.0s done #154 writing layer sha256:b1f77e77296a02790b70f8ed8cfd6f310025ee050000230d1ddd51a0c9d5b2c2 0.0s done #154 writing layer sha256:f08ee5bac4d0300b89d29658a05711bdc4ddebd37821ae36f6b2662aee51998d done #154 writing layer sha256:f57fe6a295e506ba6fb8aea87751853a2e0b1593d74cac42ca7516871179837e 0.0s done #154 writing config sha256:8237ad0dcb6ae6a79225202401f7b89cf9e7a1eeeb94248481c656eec97c7b98 done #154 writing cache manifest sha256:5df95bcd761912ba0665b2b56adc7aa1e54b09d21409c486929ae790852ca776 done #154 DONE 1.3s どうも複数のコンテナのビルドで同じハッシュ値のイメージレイヤーを書き込もうとしてエラーになっているようだ。 最後のだけは成功していることからおそらく最後のやつが始めた rename 処理は正常に走ったが並列で動いていた他のやつではエラーになってしまっているようだ。\nということで、docker-compose.yaml を見直してみると、\nservices: server1: container_name: ${HOMESERVER_BUILD_PREFIX}server1 hostname: server1 build: context: . tags: - ${HOMESERVER_BUILD_PREFIX}server1:latest target: final dockerfile: ./docker/server.Dockerfile # 略 server2: container_name: ${HOMESERVER_BUILD_PREFIX}server2 hostname: server2 build: context: . tags: - ${HOMESERVER_BUILD_PREFIX}server2:latest target: final dockerfile: ./docker/server.Dockerfile # 略 server3: container_name: ${HOMESERVER_BUILD_PREFIX}server3 hostname: server3 build: context: . tags: - ${HOMESERVER_BUILD_PREFIX}server3:latest target: final dockerfile: ./docker/server.Dockerfile # 略 \u0026hellip;どう見ても\nbuild: context: . tags: - ${HOMESERVER_BUILD_PREFIX}serverN:latest target: final dockerfile: ./docker/server.Dockerfile という記述が重複している。これで並列でビルドが走りったとして結果としてできるものは全くおなじになり当然 Hash 値も同じになり その 同じファイルに並列でアクセスしようとすることで競合が起こり、結果として先程のエラーになっていたと推測される。\nおそらく設計的にハッシュ値で一意になるはずだということでロックが取られていないのが原因であろうがまあ単純に効率が悪いのでこんなことをするやつがいるとは思っていなかったのだろう。\n解決策を o3-mini に絞らせてみたところいかのように profiles 属性を使う形がよいと提案された。\nserver-base: image: ${HOMESERVER_BUILD_PREFIX}server-base:latest build: context: . dockerfile: ./docker/server.Dockerfile target: final tags: - ${HOMESERVER_BUILD_PREFIX}server-base:latest profiles: - build-only server1: container_name: ${HOMESERVER_BUILD_PREFIX}server1 hostname: server1 image: ${HOMESERVER_BUILD_PREFIX}server-base:latest # 略 server2: container_name: ${HOMESERVER_BUILD_PREFIX}server2 hostname: server2 image: ${HOMESERVER_BUILD_PREFIX}server-base:latest # 略 server3: container_name: ${HOMESERVER_BUILD_PREFIX}server3 hostname: server3 image: ${HOMESERVER_BUILD_PREFIX}server-base:latest # 略 なお、profiles というのは docker compose のサービスを分割するための機能でありこれを利用することで server-base が起動しないでビルドでのみ使われるようにしている。 https://docs.docker.jp/compose/profiles.html これにより無事、サーバーは起動できたのであった。\n感想 # 今まで動いていたのは偶然だったということです。並列ビルドの思わぬ落とし穴にハマってしまった\u0026hellip;\n89752672-acb8-4624-96e0-dd9bbc884d79 ja ","date":"2025年 2月 3日","externalUrl":null,"permalink":"/static/blog/posts/2025_02_03_docker_buildx_bake_parallel_build_problem/","section":"Posts","summary":"","title":"docker-compose.yaml の書き方が間違っていた話","type":"posts"},{"content":"","date":"2024年 12月 26日","externalUrl":null,"permalink":"/static/blog/tags/go%E8%A8%80%E8%AA%9E/","section":"Tags","summary":"","title":"Go言語","type":"tags"},{"content":" TL;DR # 最近はRtlGenRandomじゃなくてProcessPrngが使われている。\n本文 # Go 言語で暗号学的に安全な乱数を生成するためのパッケージとしてcrypto/randがあるが それの Windows 版の内部実装のコードを見ていたらいつの間にか RtlGenRandom から ProcessPrngという関数に変わっていたということを今更見つけた。\n具体的にはこのコミットで変更されたらしい https://github.com/golang/go/commit/693def151adff1af707d82d28f55dba81ceb08e1 RtlGenRandom は advapi32.dll から SystemFunction036 という名前でエクスポートされる関数で ドキュメント化されているが互換性は保証されない、みたいな機能であった。 https://learn.microsoft.com/ja-jp/windows/win32/api/ntsecapi/nf-ntsecapi-rtlgenrandom 一方変更された ProcessPrngはBCryptPrimitives.dllからエクスポートされており 仕様上も常に TRUE を返すとなっている。 https://learn.microsoft.com/ja-jp/windows/win32/seccng/processprng Microsoft は CryptoAPI を次世代 API に置き換える方向に向かっておりおそらくその流れを汲んで 移行してっているようだ。 https://learn.microsoft.com/ja-jp/windows/win32/seccng/cng-portal なお、私が知っていたのはRtlCryptGenRandomが使われていることだけだったのだが crypto/rand の Windows 上の の乱数生成器の歴史を blame して見ていくと 最初に実装された頃(2010 年,14 年前!)は CryptGenRandomを使っていたようだ\nhttps://github.com/golang/go/commit/ccd28e8eb6f81f21093deb730ea70982cb381514 https://learn.microsoft.com/ja-jp/windows/win32/api/wincrypt/nf-wincrypt-cryptgenrandom その後非推奨化されたのに伴って代替としてRtlGenRandomになった。\nhttps://github.com/golang/go/issues/33542 https://github.com/golang/go/commit/333e90448a0e55f2e1161853caecf3d30ef3a74a その前後ではBCryptGenRandomやGenRandomなどいくつかの変更候補が現れたりしていた。 いずれも最終的にRtlGenRandomを呼ぶだけのラッパーだから遅いとか他のライブラリや言語の実装がみんなRtlGenRandomを使っているとかと言う理由で却下されている。\n議論の中にはRtlGenRandomの内部をリバースエンジニアリングした結果ProcessPrngが呼ばれているという報告なども存在した。\nそして、最近(2023 年)になってRtlGenRandomの内部で呼ばれていたProcessPrngを直接呼び出すように 変更されていったという流れのようだ。\n感想 # Windows API って抽象化層が厚いけどこういうシステムのベースのところだと邪魔だから 段々リバースエンジニアリングとかで内部構造の理解が深まっていくにつれて どんどん下のほうの関数を呼ぶようになっていてちょっとでもオーバーヘッドを減らそうという努力を感じた。\nあと次世代 API は Windows 2008 Server とか Vista から使えるみたいなことが Microsoft のドキュメントには書いてあるけど普及するまでには時間がかかるんだなぁとも感じた。\nc9c07d84-7f0f-4c9d-8023-46edfaebe6da ja ","date":"2024年 12月 26日","externalUrl":null,"permalink":"/static/blog/posts/2024_12_26_golang_rand_was_changed/","section":"Posts","summary":"","title":"Go言語のWindows実装のcrypto/randのベースのAPIが変わっていてへぇと思った話","type":"posts"},{"content":"","date":"2024年 12月 26日","externalUrl":null,"permalink":"/static/blog/tags/windows/","section":"Tags","summary":"","title":"Windows","type":"tags"},{"content":" TL;DR # docker image の unstable とかをむやみに使うな\n内容 # 自宅サーバー用に https://apt.llvm.org/llvm.sh というのを使って C++環境をセットアップしていたのだがこれを走らせていた Docker 上でエラーが出た\n10.89 File \u0026#34;/usr/bin/add-apt-repository\u0026#34;, line 361, in \u0026lt;module\u0026gt; 10.89 addaptrepo = AddAptRepository() 10.89 ^^^^^^^^^^^^^^^^^^ 10.89 File \u0026#34;/usr/bin/add-apt-repository\u0026#34;, line 37, in __init__ 10.89 self.distro = get_distro() 10.89 ^^^^^^^^^^^^ 10.89 File \u0026#34;/usr/lib/python3/dist-packages/aptsources/distro.py\u0026#34;, line 547, in get_distro 10.89 release = os_release[\u0026#34;VERSION_ID\u0026#34;] 10.89 ~~~~~~~~~~^^^^^^^^^^^^^^ 10.89 KeyError: \u0026#39;VERSION_ID\u0026#39; llvm.sh のソースコードを見てみると\nadd-apt-repository -y \u0026#34;${REPO_NAME}\u0026#34; という形で使われていた。\nadd-apt-repository 自体のソースコードは以下に見つかった1 https://git.launchpad.net/software-properties/tree/add-apt-repository?id=3bab9302813c894ff851cb576e69ff686c793195 addaptrepo = AddAptRepository() get_distro 関数は以下で見つかった\nhttps://salsa.debian.org/apt-team/python-apt/-/blob/main/aptsources/distro.py?ref_type=heads os_release = platform.freedesktop_os_release() id = os_release[\u0026#34;ID\u0026#34;] codename = os_release[\u0026#34;VERSION_CODENAME\u0026#34;] description = os_release[\u0026#34;PRETTY_NAME\u0026#34;] release = os_release[\u0026#34;VERSION_ID\u0026#34;] is_like = os_release.get(\u0026#34;ID_LIKE\u0026#34;, []) この中で platform.freedesktop_os_release()が使われているがこれは python の標準ライブラリの一部である\nhttps://docs.python.org/3/library/platform.html#platform.freedesktop_os_release /etc/os-releaseを読んでいるらしいので中身を見てみると2\n#95 7.666 PRETTY_NAME=\u0026#34;Debian GNU/Linux trixie/sid\u0026#34; #95 7.666 NAME=\u0026#34;Debian GNU/Linux\u0026#34; #95 7.666 VERSION_CODENAME=trixie #95 7.666 ID=debian #95 7.666 HOME_URL=\u0026#34;https://www.debian.org/\u0026#34; #95 7.666 SUPPORT_URL=\u0026#34;https://www.debian.org/support\u0026#34; #95 7.666 BUG_REPORT_URL=\u0026#34;https://bugs.debian.org/\u0026#34; たしかにVERSION_IDがない。\nman ページに os-release の説明が合ったので読んでいると以下の記述を発見 https://www.man7.org/linux/man-pages/man5/os-release.5.html VERSION_ID= A lower-case string (mostly numeric, no spaces or other characters outside of 0–9, a–z, \u0026#34;.\u0026#34;, \u0026#34;_\u0026#34; and \u0026#34;-\u0026#34;) identifying the operating system version, excluding any OS name information or release code name, and suitable for processing by scripts or usage in generated filenames. This field is optional. Examples: \u0026#34;VERSION_ID=17\u0026#34;, \u0026#34;VERSION_ID=11.04\u0026#34;. This field is optional.\u0026hellip;はっもしやと\nDockerfile を見てみるとFROM debian:unstable-slimとの記述が\u0026hellip;.\nlatestと直したところこのエラーは出なくなった。\n結論 # unstableバージョンは unstable なのでちゃんと調べてから使うか 大人しく安定バージョンを使いましょう。\n782302fc-2fbc-414d-9dc3-488dfccf1454 ja ただし行数が異なるためおそらくバージョンがちがうが\u0026#160;\u0026#x21a9;\u0026#xfe0e;\ncat /etc/os-release.なお前半の#95 7.666は docker のログの一部である\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","date":"2024年 11月 27日","externalUrl":null,"permalink":"/static/blog/posts/2024_11_27_add_apt_repository/","section":"Posts","summary":"","title":"add-apt-repositoryがVERSION_IDがなくてこけた話","type":"posts"},{"content":"","date":"2024年 11月 27日","externalUrl":null,"permalink":"/static/blog/tags/apt/","section":"Tags","summary":"","title":"Apt","type":"tags"},{"content":"python の pip の upgrade をしようとしたら、消えることは以下のように往々にして起こりがちである。 筆者は既に 2 回は遭遇したしおそらくこれからも遭遇するであろう。\n$ python.exe -m pip install --upgrade pip Requirement already satisfied: pip in c:\\python312\\lib\\site-packages (24.2) Collecting pip Downloading pip-24.3.1-py3-none-any.whl.metadata (3.7 kB) Downloading pip-24.3.1-py3-none-any.whl (1.8 MB) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 1.8/1.8 MB 16.8 MB/s eta 0:00:00 Installing collected packages: pip Attempting uninstall: pip Found existing installation: pip 24.2 Uninstalling pip-24.2: ERROR: Could not install packages due to an OSError: [WinError 5] アクセスが拒否されました。: \u0026#39;c:\\\\python312\\\\scripts\\\\pip.exe\u0026#39; Consider using the `--user` option or check the permissions. WARNING: There was an error checking the latest version of pip. $ python.exe -m pip install --upgrade pip C:\\Python312\\python.exe: No module named pip そんなときには https://github.com/pypa/get-pip を使えばよい。\n$ curl -sSL https://bootstrap.pypa.io/get-pip.py -o get-pip.py $ python get-pip.py これで pip は復活する。\n09adca49-6423-41e2-9903-21120706c5a2 ja ","date":"2024年 10月 30日","externalUrl":null,"permalink":"/static/blog/posts/2024_10_30_pip_is_easy_deletable/","section":"Posts","summary":"","title":"pipが消えってしまったときの対処法","type":"posts"},{"content":"","date":"2024年 10月 30日","externalUrl":null,"permalink":"/static/blog/tags/python/","section":"Tags","summary":"","title":"Python","type":"tags"},{"content":"ふと自宅サーバーの監視ログを見ていたらストレージ使用量が少し多いような気がした。 ncdu を使って調べてみると 普段は見かけない以下の２つのディレクトリが残っていた\n~/.local/share/docker/volumes/buildx_buildkit_youthful_cray0_state ~/.local/share/docker/volumes/buildx_buildkit_boring_dubinsky0_state 先日自宅サーバーをビルド途中に電源が落ちるというアクシデント1がありおそらくその際に残ってしまったのだろう\u0026hellip; ChatGPT に聞いてdocker volume rm \u0026lt;volume name\u0026gt;で消せるらしいとわかったので消そうとしたら 片方は消せたがもう片方は以下のようなエラーになった\nError response from daemon: remove buildx_buildkit_youthful_cray0_state: volume is in use - [45af4aa4b062d8501725844ba1d998ae07b009b38d559d97e395725aa091c2c0] そこでdocker ps -a --filter volume=buildx_buildkit_youthful_cray0_state --no-truncするとなぜか buildkitd が２日前から走りっぱなしになっていた。\nCONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 45af4aa4b062d8501725844ba1d998ae07b009b38d559d97e395725aa091c2c0 moby/buildkit:buildx-stable-1 \u0026#34;buildkitd --allow-insecure-entitlement=network.host\u0026#34; 2 days ago Up 2 days buildx_buildkit_youthful_cray0 おそらくビルド中に落ちた結果正常に消されなかったのだろう\u0026hellip; docker stop 45af4aa4b062d8501725844ba1d998ae07b009b38d559d97e395725aa091c2c0 docker rm 45af4aa4b062d8501725844ba1d998ae07b009b38d559d97e395725aa091c2c0をして無事削除できた。\nf8912e9a-fc18-4295-a502-fc232ef76df5 ja うっかり電源ボタンを押してしまったのである\u0026hellip;\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","date":"2024年 10月 13日","externalUrl":null,"permalink":"/static/blog/posts/2024_10_13_remaining_docker_buildx_state/","section":"Posts","summary":"","title":"Dockerのbuildxのstateが残っていた話","type":"posts"},{"content":" 訂正 # 2024/10/07: タイトルと TL;DR、本文中などに技術的に不正確な表現、意図とは異なる表現になっている部分が含まれていたため訂正(✕: ed25519 で署名した →◯: ed25519 鍵で作成された)1\nTL;DR # ed25519 鍵で作成した証明書をサーバー証明書として使うのはまだ早い\n動機 # BoringSSL2 を使って https サーバー制作中3にブラウザ(Brave(Chromium ベースのブラウザ)4)からテストしようとしたら\n2024-10-06 09:56:30 [::1]:56185 method=\u0026#34;SSL_read\u0026#34; ssl_error_code=1 ssl_error_desc=\u0026#34;SSL\u0026#34; lib_error=error:100000fd:SSL routines::NO_COMMON_SIGNATURE_ALGORITHMS というエラーが出た。 多分証明書の署名関連が悪いんだろうとは思いとりあえず調べていったら 必要以上に深堀ってしまったのでまとめておく。\n調査概要 # BoringSSL のエラー箇所を特定したところエラーの原因は サーバー側とクライアント側で signature algorithm 5 の候補が一致しなかったためであった。 その原因はクライアントサイド(Chromium)では ed255196 のサポートが有効化されていないからであった。\n調査過程詳細 # 以下コードのようになっている箇所は関数名や変数名などの識別子を表す。\nBoringSSL のデバッグ # まず NO_COMMON_SIGNATURE_ALGORITHMS の文字列を探しブレークポイントを貼ってデバッグをしてみたら tls1_choose_signature_algorithm のコードで失敗していて ssl_private_key_supports_signature_algorithm で候補が弾かれてしまっているようだ7\nfor (uint16_t sigalg : sigalgs) { if (!ssl_private_key_supports_signature_algorithm(hs, sigalg)) { continue; } for (uint16_t peer_sigalg : peer_sigalgs) { if (sigalg == peer_sigalg) { *out = sigalg; return true; } } } OPENSSL_PUT_ERROR(SSL, SSL_R_NO_COMMON_SIGNATURE_ALGORITHMS); return false; 唯一 ssl_private_key_supports_signature_algorithm で弾かれないのが ed25519 のときだけであった。\n筆者はこれまで quic-go8 を使って実験をしていた。 そのときにはサーバー証明書を ed25519 鍵で作成しており9、今回使ったサーバー証明書もそれを流用したもののため おそらくこのあたりが原因ではあろうと推測した。\n以下がブラウザからきた peer_sigalgsの中身をデバッガーで表示したもの、\n0x000001f1a338e548 {1027, 2052, 1025, 1283, 2053, 1281, 2054, 1537} 以下が sigalgs の中身をデバッガーで表示したものである。\n0x00007ffa28f01210 {ssl.dll!const unsigned short bssl::kSignSignatureAlgorithms[12]} {2055, 1027, 2052, ...} 以下それぞれの値がどこから来たものなのかを探っていく。\nBoringSSL のコードリーディング 1 # デバッガーのsigalgs のところに表示された kSignSignatureAlgorithms の表記から調べるとそれは以下のように署名アルゴリズムの一覧が定義された配列であった。10\nstatic const uint16_t kSignSignatureAlgorithms[] = { // List our preferred algorithms first. SSL_SIGN_ED25519, SSL_SIGN_ECDSA_SECP256R1_SHA256, SSL_SIGN_RSA_PSS_RSAE_SHA256, SSL_SIGN_RSA_PKCS1_SHA256, // If needed, sign larger hashes. // // TODO(davidben): Determine which of these may be pruned. SSL_SIGN_ECDSA_SECP384R1_SHA384, SSL_SIGN_RSA_PSS_RSAE_SHA384, SSL_SIGN_RSA_PKCS1_SHA384, SSL_SIGN_ECDSA_SECP521R1_SHA512, SSL_SIGN_RSA_PSS_RSAE_SHA512, SSL_SIGN_RSA_PKCS1_SHA512, // If the peer supports nothing else, sign with SHA-1. SSL_SIGN_ECDSA_SHA1, SSL_SIGN_RSA_PKCS1_SHA1, }; Chromium のコードリーディング # peer_algs の値が Chromium のソースを辿ってどうしてそうなっているのかを探る。\nTLS というワードから辿って調べていると ここで署名方式が設定されているようだということがわかった。11\n// Disable SHA-1 server signatures. // TODO(crbug.com/boringssl/699): Once the default is flipped in BoringSSL, we // no longer need to override it. static const uint16_t kVerifyPrefs[] = { SSL_SIGN_ECDSA_SECP256R1_SHA256, SSL_SIGN_RSA_PSS_RSAE_SHA256, SSL_SIGN_RSA_PKCS1_SHA256, SSL_SIGN_ECDSA_SECP384R1_SHA384, SSL_SIGN_RSA_PSS_RSAE_SHA384, SSL_SIGN_RSA_PKCS1_SHA384, SSL_SIGN_RSA_PSS_RSAE_SHA512, SSL_SIGN_RSA_PKCS1_SHA512, }; if (!SSL_set_verify_algorithm_prefs(ssl_.get(), kVerifyPrefs, std::size(kVerifyPrefs))) { return ERR_UNEXPECTED; } BoringSSL のコードリーディング 2 # Chromium も同じく boringssl を使用しているため boringssl 内を再度探していく。\nまずサーバー側のpeer_sigalgsはhs-\u0026gt;peer_sigalgsから取得されたものである12\nSpan\u0026lt;const uint16_t\u0026gt; peer_sigalgs; if (cred-\u0026gt;type == SSLCredentialType::kDelegated) { peer_sigalgs = hs-\u0026gt;peer_delegated_credential_sigalgs; } else { peer_sigalgs = hs-\u0026gt;peer_sigalgs; if (peer_sigalgs.empty() \u0026amp;\u0026amp; version == TLS1_2_VERSION) { // If the client didn\u0026#39;t specify any signature_algorithms extension, it is // interpreted as SHA-1. See // http://tools.ietf.org/html/rfc5246#section-7.4.1.4.1 static const uint16_t kTLS12Default[] = {SSL_SIGN_RSA_PKCS1_SHA1, SSL_SIGN_ECDSA_SHA1}; peer_sigalgs = kTLS12Default; } } hs-\u0026gt;peer_sigalgsはtls1_parse_peer_sigalgs から来ているようだ13\nbool tls1_parse_peer_sigalgs(SSL_HANDSHAKE *hs, const CBS *in_sigalgs) { // Extension ignored for inappropriate versions if (ssl_protocol_version(hs-\u0026gt;ssl) \u0026lt; TLS1_2_VERSION) { return true; } // In all contexts, the signature algorithms list may not be empty. (It may be // omitted by clients in TLS 1.2, but then the entire extension is omitted.) return CBS_len(in_sigalgs) != 0 \u0026amp;\u0026amp; parse_u16_array(in_sigalgs, \u0026amp;hs-\u0026gt;peer_sigalgs); } tls1_parse_peer_sigalgs は ext_sigalgs_parse_clienthello14から呼ばれていて15\nstatic bool ext_sigalgs_parse_clienthello(SSL_HANDSHAKE *hs, uint8_t *out_alert, CBS *contents) { hs-\u0026gt;peer_sigalgs.Reset(); if (contents == NULL) { return true; } CBS supported_signature_algorithms; if (!CBS_get_u16_length_prefixed(contents, \u0026amp;supported_signature_algorithms) || CBS_len(contents) != 0 || !tls1_parse_peer_sigalgs(hs, \u0026amp;supported_signature_algorithms)) { return false; } return true; } ext_sigalgs_parse_clienthello は TLS extension のパース用に kExtension 配列内に設定されているようだ16\n{ TLSEXT_TYPE_signature_algorithms, ext_sigalgs_add_clienthello, forbid_parse_serverhello, ext_sigalgs_parse_clienthello, dont_add_serverhello, }, TLSEXT_TYPE_signature_algorithms14の定義は以下の通り17\n// ExtensionType values from RFC 5246 #define TLSEXT_TYPE_signature_algorithms 13 この TLSEXT_TYPE_signature_algorithms が入っている kExtension 配列は tls_extension_find で参照されており18\nstatic const struct tls_extension *tls_extension_find(uint32_t *out_index, uint16_t value) { unsigned i; for (i = 0; i \u0026lt; kNumExtensions; i++) { if (kExtensions[i].value == value) { *out_index = i; return \u0026amp;kExtensions[i]; } } return NULL; } ssl_scan_clienthello_tlsext内で TLS extension パース時に呼ばれているようだ。 19\nunsigned ext_index; const struct tls_extension *const ext = tls_extension_find(\u0026amp;ext_index, type); if (ext == NULL) { continue; } hs-\u0026gt;extensions.received |= (1u \u0026lt;\u0026lt; ext_index); uint8_t alert = SSL_AD_DECODE_ERROR; if (!ext-\u0026gt;parse_clienthello(hs, \u0026amp;alert, \u0026amp;extension)) { *out_alert = alert; OPENSSL_PUT_ERROR(SSL, SSL_R_ERROR_PARSING_EXTENSION); ERR_add_error_dataf(\u0026#34;extension %u\u0026#34;, (unsigned)type); return false; } で Chromium の側(=client)ではこれの逆側なのでそれを調べていく\n書き込み時にはssl_add_clienthello_tlsext_innerでkExtension.add_clientheloが呼ばれている20\nif (!kExtensions[i].add_clienthello(hs, \u0026amp;extensions, compressed.get(), ssl_client_hello_inner)) { OPENSSL_PUT_ERROR(SSL, SSL_R_ERROR_ADDING_EXTENSION); ERR_add_error_dataf(\u0026#34;extension %u\u0026#34;, (unsigned)kExtensions[i].value); return false; } kExtensionはtls_extensionの配列でありtls_extensionの定義は以下である。 21\nstruct tls_extension { uint16_t value; bool (*add_clienthello)(const SSL_HANDSHAKE *hs, CBB *out, CBB *out_compressible, ssl_client_hello_type_t type); bool (*parse_serverhello)(SSL_HANDSHAKE *hs, uint8_t *out_alert, CBS *contents); bool (*parse_clienthello)(SSL_HANDSHAKE *hs, uint8_t *out_alert, CBS *contents); bool (*add_serverhello)(SSL_HANDSHAKE *hs, CBB *out); }; よってext_sigalgs_add_clienthello14を調べる22\nstatic bool ext_sigalgs_add_clienthello(const SSL_HANDSHAKE *hs, CBB *out, CBB *out_compressible, ssl_client_hello_type_t type) { if (hs-\u0026gt;max_version \u0026lt; TLS1_2_VERSION) { return true; } CBB contents, sigalgs_cbb; if (!CBB_add_u16(out_compressible, TLSEXT_TYPE_signature_algorithms) || !CBB_add_u16_length_prefixed(out_compressible, \u0026amp;contents) || !CBB_add_u16_length_prefixed(\u0026amp;contents, \u0026amp;sigalgs_cbb) || !tls12_add_verify_sigalgs(hs, \u0026amp;sigalgs_cbb) || !CBB_flush(out_compressible)) { return false; } return true; } ext_sigalgs_add_clienthelloはtls12_add_verify_sigalgsで取得したデータを追加している23\nbool tls12_add_verify_sigalgs(const SSL_HANDSHAKE *hs, CBB *out) { for (uint16_t sigalg : tls12_get_verify_sigalgs(hs)) { if (!CBB_add_u16(out, sigalg)) { return false; } } return true; } tls12_add_verify_sigalgsはverify_sigalgsかkVerifySignatureAlgorithmsを使うようだ24\nstatic Span\u0026lt;const uint16_t\u0026gt; tls12_get_verify_sigalgs(const SSL_HANDSHAKE *hs) { if (hs-\u0026gt;config-\u0026gt;verify_sigalgs.empty()) { return Span\u0026lt;const uint16_t\u0026gt;(kVerifySignatureAlgorithms); } return hs-\u0026gt;config-\u0026gt;verify_sigalgs; } kVerifySignatureAlgorithmsのほうにも ED25519 はない\u0026hellip;25\n// kVerifySignatureAlgorithms is the default list of accepted signature // algorithms for verifying. static const uint16_t kVerifySignatureAlgorithms[] = { // List our preferred algorithms first. SSL_SIGN_ECDSA_SECP256R1_SHA256, SSL_SIGN_RSA_PSS_RSAE_SHA256, SSL_SIGN_RSA_PKCS1_SHA256, // Larger hashes are acceptable. SSL_SIGN_ECDSA_SECP384R1_SHA384, SSL_SIGN_RSA_PSS_RSAE_SHA384, SSL_SIGN_RSA_PKCS1_SHA384, SSL_SIGN_RSA_PSS_RSAE_SHA512, SSL_SIGN_RSA_PKCS1_SHA512, // For now, SHA-1 is still accepted but least preferable. SSL_SIGN_RSA_PKCS1_SHA1, }; だがこちらは 9 個あるのに対してpeer_sigalgsでは 8 個しか来ていなかった、ので多分違う。 そして、verify_sigalgsはSSL_set_verify_algorithm_prefsから呼ばれたで設定されており ここで Chromium のソースコードで設定されていたkVerifyPrefsとつながる26\nint SSL_set_verify_algorithm_prefs(SSL *ssl, const uint16_t *prefs, size_t num_prefs) { if (!ssl-\u0026gt;config) { OPENSSL_PUT_ERROR(SSL, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED); return 0; } return set_sigalg_prefs(\u0026amp;ssl-\u0026gt;config-\u0026gt;verify_sigalgs, MakeConstSpan(prefs, num_prefs)); } 結論 # 結論としてはとりあえず別の署名アルゴリズム27を使ってテストを進めることにした。 quic-go とかは対応してくれているんだけどな ed25519\u0026hellip;28\n余談 # chatgpt にめっちゃ推敲してもらった\u0026hellip;けど多分読みづらいところがまだあるんだろうな\nおまけ # 探索中に見つけたが本筋とは関係ないリンクとか\nなんだこいつ https://github.com/google/boringssl/blob/76968bb3d53982560bcf08bcd0ba3e1865fe15cd/ssl/handshake_server.cc#L426 static bool is_probably_jdk11_with_tls13(const SSL_CLIENT_HELLO *client_hello) { Chromium のソースコードを辿った履歴:\nhttps://github.com/chromium/chromium/blob/main/net/socket/tls_stream_attempt.h#L108 https://github.com/chromium/chromium/blob/c37978c399439b0626603234bf5c40b9debb578f/net/socket/ssl_client_socket.cc#L150 https://github.com/chromium/chromium/blob/c37978c399439b0626603234bf5c40b9debb578f/net/socket/ssl_client_socket_impl.h#L4 https://github.com/chromium/chromium/blob/c37978c399439b0626603234bf5c40b9debb578f/net/socket/ssl_client_socket_impl.cc#L848 SSL_CONTEXT - SSL_CTX_new はここで https://github.com/chromium/chromium/blob/c37978c399439b0626603234bf5c40b9debb578f/net/socket/ssl_client_socket_impl.cc#L187 SSLClientSocketImpl https://github.com/chromium/chromium/blob/c37978c399439b0626603234bf5c40b9debb578f/net/socket/ssl_client_socket_impl.cc#L271 SSLConfig - not SSL_CONTEXT of openssl https://github.com/chromium/chromium/blob/4d6151c6d142dae91e9868946f2464b37db35c34/net/ssl/ssl_config.h#L4 SSL_new 呼び出し https://github.com/chromium/chromium/blob/c37978c399439b0626603234bf5c40b9debb578f/net/socket/ssl_client_socket_impl.cc#L630 openssl の BIO に接続 https://github.com/chromium/chromium/blob/c37978c399439b0626603234bf5c40b9debb578f/net/socket/socket_bio_adapter.cc#L61 耐量子暗号?\u0026hellip; https://github.com/chromium/chromium/blob/c37978c399439b0626603234bf5c40b9debb578f/net/socket/ssl_client_socket_impl.cc#L647 上位鍵による署名も ed25519 であるにはあるが今回の文脈では誤りであった\u0026#160;\u0026#x21a9;\u0026#xfe0e;\ngoogle が openssl からフォークしてメンテナンスしている TLS 実装 https://github.com/google/boringssl 4194b7e6-be19-4190-8775-625d7fde821e ja \u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttp/1,http/2,http/3 を統合したものを作ろうとしている。エラーが出たのは TCP の https の文脈であるが筆者が QUIC の話を入れたりしているのはこういう背景による\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nなぜ Brave かというと普段使っているのがそれだからである\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n署名アルゴリズム。この文脈ではサーバーがクライアントに「自分は某というサーバーだよ。ほらこれが公開鍵と証拠の署名ね」とするときにサーバーがする署名の方法。具体的にいうと CertificateVerify で使うやつ。 厳密な定義や使い方は自分で調べてね。\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nエドワーズ曲線デジタル署名アルゴリズムの一種。短い鍵で RSA 並の安全性と高速化が図られている https://ja.wikipedia.org/wiki/%E3%82%A8%E3%83%89%E3%83%AF%E3%83%BC%E3%82%BA%E6%9B%B2%E7%B7%9A%E3%83%87%E3%82%B8%E3%82%BF%E3%83%AB%E7%BD%B2%E5%90%8D%E3%82%A2%E3%83%AB%E3%82%B4%E3%83%AA%E3%82%BA%E3%83%A0 \u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://github.com/google/boringssl/blob/76968bb3d53982560bcf08bcd0ba3e1865fe15cd/ssl/extensions.cc#L4154 \u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://github.com/quic-go/quic-go \u0026#160;\u0026#x21a9;\u0026#xfe0e;\nなんか安全で高速っていうのをみて「じゃあそれでいいんじゃない?」という安易な発想に基づく\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://github.com/google/boringssl/blob/76968bb3d53982560bcf08bcd0ba3e1865fe15cd/ssl/extensions.cc#L405 \u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://github.com/chromium/chromium/blob/c37978c399439b0626603234bf5c40b9debb578f/net/socket/ssl_client_socket_impl.cc#L750 https://issues.chromium.org/issues/42290575 \u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://github.com/google/boringssl/blob/76968bb3d53982560bcf08bcd0ba3e1865fe15cd/ssl/extensions.cc#L4139 \u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://github.com/google/boringssl/blob/76968bb3d53982560bcf08bcd0ba3e1865fe15cd/ssl/extensions.cc#L4090 \u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://www.rfc-editor.org/rfc/rfc5246#section-7.4.1.4 で定義されている定数。 https://www.rfc-editor.org/rfc/rfc5246#section-7.4.1.4.1 でフォーマットが定義され ext_sigalgs_add_clienthello や ext_sigalgs_parse_clienthello と対応している\u0026#160;\u0026#x21a9;\u0026#xfe0e;\u0026#160;\u0026#x21a9;\u0026#xfe0e;\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://github.com/google/boringssl/blob/76968bb3d53982560bcf08bcd0ba3e1865fe15cd/ssl/extensions.cc#L1045 \u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://github.com/google/boringssl/blob/76968bb3d53982560bcf08bcd0ba3e1865fe15cd/ssl/extensions.cc#L3118 (kExtension 先頭) https://github.com/google/boringssl/blob/76968bb3d53982560bcf08bcd0ba3e1865fe15cd/ssl/extensions.cc#L3185 \u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://github.com/google/boringssl/blob/76968bb3d53982560bcf08bcd0ba3e1865fe15cd/include/openssl/tls1.h#L192 \u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://github.com/google/boringssl/blob/76968bb3d53982560bcf08bcd0ba3e1865fe15cd/ssl/extensions.cc#L3333 \u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://github.com/google/boringssl/blob/76968bb3d53982560bcf08bcd0ba3e1865fe15cd/ssl/extensions.cc#L3643 \u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://github.com/google/boringssl/blob/76968bb3d53982560bcf08bcd0ba3e1865fe15cd/ssl/extensions.cc#L3394 \u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://github.com/google/boringssl/blob/76968bb3d53982560bcf08bcd0ba3e1865fe15cd/ssl/extensions.cc#L485 \u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://github.com/google/boringssl/blob/76968bb3d53982560bcf08bcd0ba3e1865fe15cd/ssl/extensions.cc#L1037 \u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://github.com/google/boringssl/blob/76968bb3d53982560bcf08bcd0ba3e1865fe15cd/ssl/extensions.cc#L436 \u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://github.com/google/boringssl/blob/76968bb3d53982560bcf08bcd0ba3e1865fe15cd/ssl/extensions.cc#L428 \u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://github.com/google/boringssl/blob/76968bb3d53982560bcf08bcd0ba3e1865fe15cd/ssl/extensions.cc#L385 \u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://github.com/google/boringssl/blob/76968bb3d53982560bcf08bcd0ba3e1865fe15cd/ssl/ssl_privkey.cc#L954 \u0026#160;\u0026#x21a9;\u0026#xfe0e;\nECDSA あたりかな\u0026#160;\u0026#x21a9;\u0026#xfe0e;\ngo の標準ライブラリが以下の通りデフォルトの署名アルゴリズムを使ってるからですね多分 https://github.com/golang/go/blob/2f507985dc24d198b763e5568ebe5c04d788894f/src/crypto/tls/defaults.go#L30 // defaultSupportedSignatureAlgorithms contains the signature and hash algorithms that // the code advertises as supported in a TLS 1.2+ ClientHello and in a TLS 1.2+ // CertificateRequest. The two fields are merged to match with TLS 1.3. // Note that in TLS 1.2, the ECDSA algorithms are not constrained to P-256, etc. var defaultSupportedSignatureAlgorithms = []SignatureScheme{ PSSWithSHA256, ECDSAWithP256AndSHA256, Ed25519, PSSWithSHA384, PSSWithSHA512, PKCS1WithSHA256, PKCS1WithSHA384, PKCS1WithSHA512, ECDSAWithP384AndSHA384, ECDSAWithP521AndSHA512, PKCS1WithSHA1, ECDSAWithSHA1, } \u0026#160;\u0026#x21a9;\u0026#xfe0e; ","date":"2024年 10月 6日","externalUrl":null,"permalink":"/static/blog/posts/2024_10_06_using_certificate_signed_with_ed25519_is_early/","section":"Posts","summary":"","title":"ed25519 のサーバー証明書はブラウザがまだ対応していないという話","type":"posts"},{"content":"","date":"2024年 10月 6日","externalUrl":null,"permalink":"/static/blog/tags/tls/","section":"Tags","summary":"","title":"TLS","type":"tags"},{"content":"","date":"17 8月 2024","externalUrl":null,"permalink":"/static/blog/en/tags/home-server/","section":"Tags","summary":"","title":"Home Server","type":"tags"},{"content":"","date":"17 8月 2024","externalUrl":null,"permalink":"/static/blog/en/categories/notice/","section":"Categories","summary":"","title":"Notice","type":"categories"},{"content":"","date":"2024年 8月 17日","externalUrl":null,"permalink":"/static/blog/tags/wordpress/","section":"Tags","summary":"","title":"WordPress","type":"tags"},{"content":"WordPress を使ったことがないので使ってみようとした。 リバースプロキシを間に挟んで特定のサブディレクトリに置いてみたのだが なぜか\n/xxxx/wp-admin/index.php とかにリクエストしたはずなのにブラウザの検索ウィンドウの表示が\n/wp-admin/index.php になっていた。\n\u0026hellip;History API を誰かが使っているなと思って調査。 このようなコードを見つける。(URL は別物化してある)\n\u0026lt;link id=\u0026#34;wp-admin-canonical\u0026#34; rel=\u0026#34;canonical\u0026#34; href=\u0026#34;https://example.com/wp-admin/\u0026#34; /\u0026gt; \u0026lt;script\u0026gt; if (window.history.replaceState) { window.history.replaceState( null, null, document.getElementById(\u0026#34;wp-admin-canonical\u0026#34;).href + window.location.hash ); } \u0026lt;/script\u0026gt; wp-admin-canonical\u0026hellip;? ChatGPT に投げたら SEO 対策だの一貫性だの説明がついたが本当に何なんだ\u0026hellip;\nで調べてみたら以下にたどり着いた https://core.trac.wordpress.org/ticket/35561 そして最終的に今(2024/8/17)から二週間前の時点からして問題が解決してねえことがわかっただけだった。\nふざけてやがるぜ\n仕方がないのでフォーラムとかにあった wp-admin-canonical を抹消してくれるプラグインを入れるつもりである\n追記:\nインストールではまったのでこれを追記。 https://wordpress.org/support/topic/why-this-plugin-cannot-install/ 48958544-c207-4041-b1b6-d55d5367671c ja ","date":"2024年 8月 17日","externalUrl":null,"permalink":"/static/blog/posts/2024_08_17_wordpress_admin_canonical_bug/","section":"Posts","summary":"","title":"WordPressのwp-admin-canonicalのバグ","type":"posts"},{"content":"go-webauthn を使っていたところ以下のエラーに遭遇した。\nUnable to validate attestation signature statement during attestation validation: invalid certificate chain from MDS: x509: unhandled critical extension ここでエラーを追跡してみた。\n前半の文面からここがエラーだとわかる。どうやら証明書の検証エラーのようだ https://github.com/go-webauthn/webauthn/blob/cf1758ab00a77bbe97e9315946f78e6874a8d708/protocol/attestation.go#L257-L259 ここで検証に使っているx5cの出どころはここ https://github.com/go-webauthn/webauthn/blob/cf1758ab00a77bbe97e9315946f78e6874a8d708/protocol/attestation.go#L240-L250 x5csの出どころはここ https://github.com/go-webauthn/webauthn/blob/cf1758ab00a77bbe97e9315946f78e6874a8d708/protocol/attestation.go#L169 formatHandlerの出どころであるattestationRegistryに登録されているのはRegisterAttestationFormat関数 https://github.com/go-webauthn/webauthn/blob/cf1758ab00a77bbe97e9315946f78e6874a8d708/protocol/attestation.go#L87-L89 今回は Windows Hello の tpm 形式であるため tpm のハンドラーがRegisterAttestationFormatで登録されているのはここで、formatHandlerの実態はverifyTPMFormatだとわかる https://github.com/go-webauthn/webauthn/blob/cf1758ab00a77bbe97e9315946f78e6874a8d708/protocol/attestation_tpm.go#L18-L20 verifyTPMFormat 内では証明書(x5cs)がある場合、独自に extension を解析しているようだ Subject Alternative Name extension https://github.com/go-webauthn/webauthn/blob/9ca2faef6e4bbc88bfbaaccca846ee420b142e17/protocol/attestation_tpm.go#L175-L182 Extended Key Usage https://github.com/go-webauthn/webauthn/blob/9ca2faef6e4bbc88bfbaaccca846ee420b142e17/protocol/attestation_tpm.go#L199 Basic Constraints extension https://github.com/go-webauthn/webauthn/blob/9ca2faef6e4bbc88bfbaaccca846ee420b142e17/protocol/attestation_tpm.go#L222 なお標準では以下の processExtensions 関数でハンドリングされる https://github.com/golang/go/blob/72735094660a475a69050b7368c56b25346f5406/src/crypto/x509/parser.go#L664 ここでデバッグ用のサーバーを立ち上げ tpm 形式の attestation object を取得してその中からx5cにあたる証明書をダンプして、 python でをパースして x509 証明書の extension を確認したところ以下のようであった。\nSignature Algorithm: \u0026lt;ObjectIdentifier(oid=1.2.840.113549.1.1.11, name=sha256WithRSAEncryption)\u0026gt; Extensions: Type: \u0026lt;ObjectIdentifier(oid=2.5.29.15, name=keyUsage)\u0026gt; Value: \u0026lt;KeyUsage(digital_signature=True, content_commitment=False, key_encipherment=False, data_encipherment=False, key_agreement=False, key_cert_sign=False, crl_sign=False, encipher_only=False, decipher_only=False)\u0026gt; Critical: True Type: \u0026lt;ObjectIdentifier(oid=2.5.29.19, name=basicConstraints)\u0026gt; Value: \u0026lt;BasicConstraints(ca=False, path_length=None)\u0026gt; Critical: True Type: \u0026lt;ObjectIdentifier(oid=2.5.29.32, name=certificatePolicies)\u0026gt; Value: \u0026lt;CertificatePolicies([\u0026lt;PolicyInformation(policy_identifier=\u0026lt;ObjectIdentifier(oid=1.3.6.1.4.1.311.21.31, name=Unknown OID)\u0026gt;, policy_qualifiers=[\u0026lt;UserNotice(notice_reference=None, explicit_text=\u0026#39;TCPA Trusted Platform Identity\u0026#39;)\u0026gt;])\u0026gt;])\u0026gt; Critical: True Type: \u0026lt;ObjectIdentifier(oid=2.5.29.37, name=extendedKeyUsage)\u0026gt; Value: \u0026lt;ExtendedKeyUsage([\u0026lt;ObjectIdentifier(oid=2.23.133.8.3, name=Unknown OID)\u0026gt;])\u0026gt; Critical: False Type: \u0026lt;ObjectIdentifier(oid=2.5.29.17, name=subjectAltName)\u0026gt; Value: \u0026lt;SubjectAlternativeName(\u0026lt;GeneralNames([\u0026lt;DirectoryName(value=\u0026lt;Name(2.23.133.2.1=id:494E5443,2.23.133.2.2=ADL,2.23.133.2.3=id:02580012)\u0026gt;)\u0026gt;])\u0026gt;)\u0026gt; Critical: True Type: \u0026lt;ObjectIdentifier(oid=2.5.29.35, name=authorityKeyIdentifier)\u0026gt; Value: \u0026lt;AuthorityKeyIdentifier(key_identifier=b\u0026#39;0\\xa3\\x12\\xbc\\x94$q\\xc8\\xcb#\\x9c\\xf3\\xc2,@N\\x19\\x1e\\x8f\\xa9\u0026#39;, authority_cert_issuer=None, authority_cert_serial_number=None)\u0026gt; Critical: False Type: \u0026lt;ObjectIdentifier(oid=2.5.29.14, name=subjectKeyIdentifier)\u0026gt; Value: \u0026lt;SubjectKeyIdentifier(digest=b\u0026#39;\\xab#\\xb6\\xd1z\\xb92\\x16\\xd3\\xcf\\x92%\\xa1\\xdct\\xd2\\x96\\x961t\u0026#39;)\u0026gt; Critical: False Type: \u0026lt;ObjectIdentifier(oid=1.3.6.1.5.5.7.1.1, name=authorityInfoAccess)\u0026gt; Value: \u0026lt;AuthorityInformationAccess([\u0026lt;AccessDescription(access_method=\u0026lt;ObjectIdentifier(oid=1.3.6.1.5.5.7.48.2, name=caIssuers)\u0026gt;, access_location=\u0026lt;UniformResourceIdentifier(value=\u0026#39;http://azcsprodncuaikpublish.blob.core.windows.net/ncu-intc-keyid-134d03d6581dabea3bf82eb2e34bf98192826962/e069c79b-9b7d-4ae6-afc6-23d396df1a4c.cer\u0026#39;)\u0026gt;)\u0026gt;])\u0026gt; Critical: False 比較してみるとわかるのだが実は標準でハンドリングされるものばかりなのである。 では何が原因かとデバッガを使って調べてみると以下のコードに到達した。\nhttps://github.com/golang/go/blob/72735094660a475a69050b7368c56b25346f5406/src/crypto/x509/parser.go#L691 if len(out.DNSNames) == 0 \u0026amp;\u0026amp; len(out.EmailAddresses) == 0 \u0026amp;\u0026amp; len(out.IPAddresses) == 0 \u0026amp;\u0026amp; len(out.URIs) == 0 { // If we didn\u0026#39;t parse anything then we do the critical check, below. unhandled = true } つまり証明書がドメイン名にもメールアドレスにも IP アドレスにも URI にも関連していないのでハンドリングされない扱いになっているのであった。 ちなみに tpm 用の独自のロジックのほうでは tpm 用のメタデータ(manufacturer,model,version)などが解析されていた https://github.com/go-webauthn/webauthn/blob/9ca2faef6e4bbc88bfbaaccca846ee420b142e17/protocol/attestation_tpm.go#L320-L328 そしてこの unhandled フラグにより以下で UnhandledCriticalExtensions が設定されえる。 https://github.com/golang/go/blob/72735094660a475a69050b7368c56b25346f5406/src/crypto/x509/parser.go#L819-L821 そしてこの箇所で UnhandledCriticalExtensions との比較が行われエラーが発生する。 https://github.com/golang/go/blob/72735094660a475a69050b7368c56b25346f5406/src/crypto/x509/verify.go#L565 なお UnhandledCriticalExtension エラー型の定義は以下でありこれはx509: unhandled critical extensionの文面に一致する。 https://github.com/golang/go/blob/72735094660a475a69050b7368c56b25346f5406/src/crypto/x509/x509.go#L969-L973 なおこの一連の検証を行うかは GetValidateTrustAnchor によって判定されている。 https://github.com/go-webauthn/webauthn/blob/cf1758ab00a77bbe97e9315946f78e6874a8d708/protocol/attestation.go#L229 GetValidateTrustAnchor の実態は anchors である。 https://github.com/go-webauthn/webauthn/blob/cf1758ab00a77bbe97e9315946f78e6874a8d708/metadata/providers/memory/provider.go#L71-72 anchors はここでデフォルトの true に設定され https://github.com/go-webauthn/webauthn/blob/cf1758ab00a77bbe97e9315946f78e6874a8d708/metadata/providers/memory/provider.go#L17 ここのオプションで変更可能であると思われ https://github.com/go-webauthn/webauthn/blob/master/metadata/providers/memory/provider.go#L23 そしてこの関数をオプションとして与えることで検証をスキップさせとりあえずの成功にはできそうである。 https://github.com/go-webauthn/webauthn/blob/master/metadata/providers/memory/options.go#L44 またもうひとつの手段としてはブラウザの publicKey オブジェクトに渡す attestation プロパティの値を none に設定することで tpm 形式の証明書ではないものにするという手もある。\nhttps://developer.mozilla.org/ja/docs/Web/API/CredentialsContainer/create#attestation だが検証できるのに検証できないのはもどかしいので Issue を立てた。どうなるかは知らない。\nhttps://github.com/go-webauthn/webauthn/issues/275 c428750e-c332-4252-8c3b-c774f51b8e24 ja ","date":"2024年 8月 13日","externalUrl":null,"permalink":"/static/blog/posts/2024_08_13_go_webauthn_11_tpm_bug/","section":"Posts","summary":"","title":"go-webauthn version 11でのエラー","type":"posts"},{"content":"","date":"2024年 8月 13日","externalUrl":null,"permalink":"/static/blog/tags/webauthn/","section":"Tags","summary":"","title":"WebAuthn","type":"tags"},{"content":"","date":"2024年 8月 9日","externalUrl":null,"permalink":"/static/blog/tags/raspberry-pi-5/","section":"Tags","summary":"","title":"Raspberry Pi 5","type":"tags"},{"content":"ラズベリーパイ 5 で GPIO を mmap するときにハマったので覚え書き。\n生成 AI による要約 # 問題の背景 mmap 関数を使用して GPIO にアクセスしようとすると、EINVAL エラーが返され、操作が失敗する。 原因としては、mmap の第 2 引数である長さ (len) が適切でない可能性があると考えた。 解決策の探求 Raspberry Pi の公式ユーティリティ pinctrl のコードを調査し、どのように mmap を使用しているかを確認。 特定の GPIO_CHIP_T 構造体の size フィールドが mmap の len 引数に使用されていることを突き止めた。 Raspberry Pi 5 の RP1 チップの場合、この size フィールドは 0x30000 となっていることを確認。 mmap の len 引数に 0x30000 を指定した結果、成功した。 結論 Raspberry Pi 5 で mmap を使用する際、/dev/gpiomem ではなく/dev/gpiomemN（N は 0〜4）が使用される。 mmap の長さ引数に、適切な値（RP1 の場合は 0x30000）を指定する必要がある。 以下駄文、もとい詳細 # まず最初は以下の rasbperry-gpio-python のコードを元に作っていたのだが、mmapは EINVAL を返し、動かなかった。 なお、Raspberry Pi 5 環境では/dev/gpiomemではなく/dev/gpiomemN(N は 0 から 4(筆者環境))であった。\nreaspberry-gpio-python 2024/8/9 22:00 閲覧\n// try /dev/gpiomem first - this does not require root privs if ((mem_fd = open(\u0026#34;/dev/gpiomem\u0026#34;, O_RDWR|O_SYNC)) \u0026gt; 0) { if ((gpio_map = (uint32_t *)mmap(NULL, BLOCK_SIZE, PROT_READ|PROT_WRITE, MAP_SHARED, mem_fd, 0)) == MAP_FAILED) { return SETUP_MMAP_FAIL; } else { return SETUP_OK; } } man によるとmmapでEINVALになる条件は以下のようであったので、おそらく長さ(BLOCK_SIZE)があっていないのだろうと推測した。(なお筆者は実際には addr か offset の可能性もいくらか検討した) なおこのコードではBLOCK_SIZEは4096であった。\nhttps://ja.manpages.org/mmap/2 EINVAL addr か length か offset が適切でない (例えば、大きすぎるとか、ページ境界にアラインメントされていない)。 EINVAL (Linux 2.6.12 以降) length が 0 であった。 EINVAL flags に MAP_PRIVATE と MAP_SHARED のどちらも含まれていなかった、もしくは その両方が含まれていた。 その後解決策を探したところ フォーラム に respberrypi/utils/pinctrl を参考にしろという情報があったのでそれを見る。\nまずmmapをする箇所は以下のようになっていた。 https://github.com/raspberrypi/utils/blob/a1d522f0f1b50858a44fac80523a2bd80098e789/pinctrl/gpiolib.c#L664 gpio_map = mmap( NULL, /* Any address in our space will do */ chip-\u0026gt;size + align, /* Map length */ PROT_READ | PROT_WRITE, /* Enable reading \u0026amp; writing */ MAP_SHARED, /* Shared with other processes */ inst-\u0026gt;mem_fd, /* File to map */ 0 /* Offset to GPIO peripheral */ ); このうちの第二引数の指定に使われているchip変数は以下のように初期化されている。\nhttps://github.com/raspberrypi/utils/blob/a1d522f0f1b50858a44fac80523a2bd80098e789/pinctrl/gpiolib.c#L657-L658 inst = \u0026amp;gpio_chips[i]; chip = inst-\u0026gt;chip; gpio_chipsはgpio_create_instance関数内で初期化が行われており\nhttps://github.com/raspberrypi/utils/blob/a1d522f0f1b50858a44fac80523a2bd80098e789/pinctrl/gpiolib.c#L51 static GPIO_CHIP_INSTANCE_T *gpio_create_instance(const GPIO_CHIP_T *chip, uint64_t phys_addr, const char *name, const char *dtnode) { GPIO_CHIP_INSTANCE_T *inst = \u0026amp;gpio_chips[num_gpio_chips]; gpio_create_instance関数はgpio_init関数内で呼び出されている。\nhttps://github.com/raspberrypi/utils/blob/a1d522f0f1b50858a44fac80523a2bd80098e789/pinctrl/gpiolib.c#L481 inst = gpio_create_instance(chip, phys_addr, NULL, alias); そしてこのgpio_create_instanceに渡されているchip引数はgpio_find_chip関数で取得されており\nhttps://github.com/raspberrypi/utils/blob/a1d522f0f1b50858a44fac80523a2bd80098e789/pinctrl/gpiolib.c#L464 chip = gpio_find_chip(compatible); そしてgpio_find_chip関数内では以下のように検索されている。\nhttps://github.com/raspberrypi/utils/blob/a1d522f0f1b50858a44fac80523a2bd80098e789/pinctrl/gpiolib.c#L399 for (chip = \u0026amp;__start_gpiochips; name \u0026amp;\u0026amp; chip \u0026lt; \u0026amp;__stop_gpiochips; chip++) { そして、__start_gpiochipsと__stop_gpiochipsはgpiochip.hで定義されている。\nhttps://github.com/raspberrypi/utils/blob/a1d522f0f1b50858a44fac80523a2bd80098e789/pinctrl/gpiochip.h#L39-L40 extern const GPIO_CHIP_T __start_gpiochips; extern const GPIO_CHIP_T __stop_gpiochips; GPIO_CHIP_Tは以下の構造体で、mmap箇所ではこれのsizeフィールドが使われていた。\nhttps://github.com/raspberrypi/utils/blob/master/pinctrl/gpiochip.h#L12-L19 typedef struct GPIO_CHIP_ { const char *name; const char *compatible; const GPIO_CHIP_INTERFACE_T *interface; int size; uintptr_t data; } GPIO_CHIP_T; ここで、そのすぐ近くにDECLARE_GPIO_CHIPというマクロがあり、どうやら特定の CHIP ごとの情報を入れた変数が gpiochipsセクションに配置されているらしいとわかる。おそらく、__start_gpiochipsと__stop_gpiochipsは このセクションを指しているものであろう。\nhttps://github.com/raspberrypi/utils/blob/master/pinctrl/gpiochip.h#L6-L8 #define DECLARE_GPIO_CHIP(name, compatible, iface, size, data) \\ GPIO_CHIP_T name ## _chip __attribute__ ((section (\u0026#34;gpiochips\u0026#34;))) __attribute__ ((used)) = \\ { #name, compatible, iface, size, data } 今回は Raspberry Pi 5 を使っているので Raspberry Pi 5 の peripheral controller である RP1 のコード箇所を見ると、 sizeフィールド部分が0x30000となっていた。\nhttps://github.com/raspberrypi/utils/blob/a1d522f0f1b50858a44fac80523a2bd80098e789/pinctrl/gpiochip_rp1.c#L503-L504 DECLARE_GPIO_CHIP(rp1, \u0026#34;raspberrypi,rp1-gpio\u0026#34;, \u0026amp;rp1_gpio_interface, 0x30000, 0); そこで試しにmmapのlen引数に0x30000を指定して実行したところアドレスを取得できた。\n戯言 # ブログにコード切り貼りは疲れるからなにか自動でコードを挿入する機構を作りたいですね\u0026hellip;\n6a98bea8-a6f8-4dcd-9d79-9e8b6898d803 ja ","date":"2024年 8月 9日","externalUrl":null,"permalink":"/static/blog/posts/2024_08_09_raspi5_gpio_map/","section":"Posts","summary":"","title":"ラズベリーパイ5のGPIOのmmapについて","type":"posts"},{"content":"ラズベリーパイ 5 をセットアップ。\nまず、Raspberry Pi Imager をダウンロードする。\nインストールが終わると以下のような画面になる。\n買ったデバイス(今回は RASPBERRY PI 5)を選択し、 OS を選択(今回は RASPBERRY PI OS(64-bit))し、 SD カードの刺さったストレージを選択する。\nその後「次へ」を選択すると「Would you like to apply OS customization settings?」と出て、 編集する、はい、いいえの選択肢が出る。 (写真はすで編集したあとなので設定をクリアするの選択肢も入っている)\n今回は編集するを押した。\nすると次のような画面が出るので適宜、埋めていく。\nssh 鍵を ssh-keygen などで生成して公開鍵をここに設定する。 (鍵は実際のものとは別のものを使用しています)\nこれが終わると再度先程の画面に戻されるので「はい」を押す。\nそしてもう一度 「(ストレージ名)に存在するすべてのデータは完全に削除されます。本当に続けますか?」と出るのでストレージ名が間違っていなかったら「はい」を押す。\nすると OS のインストールが始まるので暫く待つ。\n完了したら PC から抜いて Raspberry Pi 5 の micro SD カード差込口に 挿す。\nそして電源につなぎ、起動させてしばらくしたあと\nnmap -A -vv 192.168.xx.00/24 などのように nmap をかけると1\nDiscovered open port 22/tcp on 192.168.xx.yy ssh port(22 番)が開いている\nip アドレスが見つかるのでそこに\nssh ssh://(ユーザー名)@(IPアドレス) -i ~/.ssh/(鍵の名前) とすれば入れる。普通に nmap とかせずに HDMI ケーブルでモニターに繋いで正攻法で入って ip aとかをして知ることもできる。 そこはお好みでどうぞ。 かくしてとりあえず OS を入れるまではできた\u0026hellip;\naf0712f2-38b3-40cd-9c4e-bb00f0926910 ja ネットワークアドレスはip aなどを使って適宜調べてください\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","date":"2024年 8月 7日","externalUrl":null,"permalink":"/static/blog/posts/2024_08_07_setup_rasberry_pi/","section":"Posts","summary":"","title":"ラズベリーパイ5をセットアップ","type":"posts"},{"content":" translated by Google Translate # I often see criticism that \u0026ldquo;you only think about yourself,\u0026rdquo; but who on earth is going to think about you if you don\u0026rsquo;t put yourself first?\nIf you only think about others and desperately suppress your own feelings, who on earth is going to think about you?\nNo one will assert their rights on your behalf.\nI don\u0026rsquo;t understand.\nAristotle said that moderation is important, so I think it\u0026rsquo;s important to practice everything in moderation.\n273ec8ba-7ee2-4579-99dd-0166340c31ef en ","date":"29 7月 2024","externalUrl":null,"permalink":"/static/blog/en/posts/2024_07_29_who_claim_my_rights/","section":"Posts","summary":"","title":"2024_07_29_who_claim_my_rights","type":"posts"},{"content":"","date":"29 7月 2024","externalUrl":null,"permalink":"/static/blog/en/tags/philosophy/","section":"Tags","summary":"","title":"Philosophy","type":"tags"},{"content":"","date":"29 7月 2024","externalUrl":null,"permalink":"/static/blog/en/categories/poem/","section":"Categories","summary":"","title":"Poem","type":"categories"},{"content":"このサーバーは筆者が趣味で開発している自宅サーバーです。 (中途半端にライブラリに頼りつつ)自力でいろいろ作っています。\nこのサーバーの更新機構は shell script で動いておりまた CI/CD によって一応最低限 container が起動して動くところまでは確認していますが 自宅サーバー上の構成によって普通にデプロイに失敗して死ぬ場合や、 サーバーの調子がおかしくなって強制再起動する場合も結構あるため 落ちていたとしても責任はおえません。\nGoogle Analytics 等は使用しておりませんが 管理の目的のため IP アドレスや User-Agent 等の情報は収集されています(詳細後述)のであらかじめご了承ください。\nなお admin ページに入りたい方は比較的簡単に入口まではいけますのでパスをいじるとか試行錯誤してください。 さすがにちょっとまずいかなと思いわかりにくくはしました。adminページを見に行くまでならべつに咎めはしないので見たい方はどうぞがんばってください。(入口までは行けるけど突破はさせません)\nなお、このブログにコメントしたい方は最初にコメント記入位置の下の Register User を展開してユーザー登録してください。 本ブログのコメント欄は書き込む際にパスキーによる認証を必要としておりますので パスキーが使えない端末の場合は諦めてください。パスキーについてわからない方は別途調べてください。 現状では削除用 API がまだ実装されていないため登録されたらされっぱなしです。ご了承ください。(削除したい場合は以下連絡先にお問い合わせください)(サーバー運用も素人なのである日突然データが消えるとかもありえますのでそこも了承したうえでご利用ください) 以下に簡易的な個人情報保護方針を書いておきます。とはいえ法律の専門家ではないためそこのところはご了承ください。\n25/03/22 更新\n25/07/17 更新: 第三者サービス項追加、不正確な記述を修正\n## 個人情報保護方針 当ウェブサイトのコンテンツをご利用いただき、ありがとうございます。以下は、当ウェブサイトにおける個人情報の取り扱いについての方針です。個人情報の取り扱いに関してご理解とご協力をお願い申し上げます。 ### 個人情報の定義 個人情報とは、特定の個人を識別できる情報を指します。例えば、氏名、住所、電話番号、メールアドレス、その他個人を特定できる情報が含まれます。厳密な定義は日本の個人情報の保護に関する法律の定義に従います。 ### コメント欄の利用について コメント欄には、個人を特定できる情報の記入を控えていただくようお願い申し上げます。 具体的には、以下の情報は記入しないでください。記入された場合、削除の対象となります。 なお現在コメント欄は管理者の承認を経ない設計となっているため 投稿されてから暫くの間は情報が表示されたままになってしまう可能性があることをお断り申し上げます。 場合によってはコメント欄を無効化する処置を取らせていただきます。 自分や他人の氏名 住所や電話番号 メールアドレス その他個人を特定できる情報 ### 個人情報の収集と利用 本サーバーではセキュリティや情報開示請求、不正アクセス検知などの対応及びアクセス分析の目的のため各 HTTP リクエストについてヘッダー情報(User-Agent など ,他 Cloudflare により追加される情報含む,Cookie の中身 を除く)や IP アドレスなどを記録しております。ログは数年間保存される可能性があります。 これらの情報は将来にわたって何らかの営利目的及び個人の追跡をする目的等での使用をすることはありません。 コメント欄に書き込まれた情報は、以下の目的で使用することがあります。 コメントの表示および管理 不適切なコメントの削除 コメントの内容に対する返信 当ウェブサイトでは、コメント情報については第三者に提供することはありません。 ただし、法令に基づく要請があった場合や、サービスの提供に必要な場合には、必要な範囲で提供することがあります。 ### 第三者サービス 本ブログを提供するに当たり以下のサービスを利用しています。各規定について同意したものとみなします。 DDoS対策等のためCloudflareが利用されているためその利用については別途[Cloudflare社のプライバシーポリシー](https://www.cloudflare.com/ja-jp/privacypolicy/)に同意したものとみなします。 また管理者のみがアクセス可能な非公開Discordサーバー上において不正アクセス等への管理者の迅速な対応を目的としてHTTP リクエストのヘッダー情報ならびにIPアドレス情報をWebHookを使用して共有しています。そのため[Discord社のプライバシーポリシー](https://discord.com/privacy)に同意したものとみなします。 さらにはログバックアップのためAWSのap-northeast-1リージョン内にHTTPリクエストのヘッダー情報及びIPアドレス情報についてのバックアップを1年ごとに行っております。ログ保存期間については個人情報の収集と利用節の記述に従います。[AWSのプライバシーポリシー](https://aws.amazon.com/jp/compliance/japan-data-privacy/)についても同意したものとみなします。 ### コメントの監視と管理 コメント欄に投稿された内容は、当サイトの管理者によって監視および管理される場合があります。法令に違反する内容や不適切な内容が発見された場合には、削除することがあります。 ### 個人情報保護の取り組み 当サーバーでは、個人情報を保護するために適切な対策を講じ改善を続けております。 - WebAuthn ベースの認証による管理者ログインの制限 - cloudflare tunnel の使用による攻撃面の低減 - 管理者による定期的な監視 以上のようなセキュリティ対策などは行っていますが、素人の対策であることには留意が必要です。 大企業ばりのセキュリティレベルには届いていないことは留意ください。 # ログ削除に関するご案内と注意事項 関連するログ情報の削除をご希望の場合は、下記お問い合わせ先までご連絡ください。 ご注意事項： 完全な削除の保証について： 素人による対応のため、ログが完全に残っていない場合や、削除に時間がかかる場合があります。また、ご提供いただいた情報だけでは特定が困難な場合もございます。 個人特定の可能性について： 削除依頼によって、本来管理者がその人物に紐づいているかどうかわからなかったログが、特定の人物に紐づいていることが管理者に判明してしまう可能性があります。この点については十分ご留意ください。 管理者による情報の利用について： 管理者は、これらの情報を知ったとしても、将来にわたって営利目的や個人の追跡を目的として使用することはありません。削除の遂行に必要な範囲においてのみこれを用います。 ### Cookie 等の使用 コメントを記入する際に認証を行うときに Cookie が使用されます。Cookie はサーバー上のユーザー ID などのセッションデータと紐づきますが Cookie 自体には意味のあるデータは含まれません。Google アナリティクスや第三者サービスによる Cookie による追跡等は行っておりませんが 管理者が IP アドレスや User-Agent,Referer などの情報を使ってアクセスログの解析を行う場合がございます。 また WebAuthn の使用によりお使いのデバイスにユーザー名やドメイン名などのメタデータと認証用の秘密鍵が記録されます。 ### ポリシーの変更 この方針は、法令の改正、管理者による誤認があった場合や当サイトの運営方針に応じて変更されることがあります。変更があった場合は、当サイトにおいてその旨を通知いたします。 ### お問い合わせ 個人情報の取り扱いに関してご質問やご不明点がある場合は、下記に連絡いただくかむやみに登録しないようにお願いします。 このサイトについてのお問い合わせはBlueSky @kforfk.bsky.social または mixi2 @onkeyday まで DM いただきますようよろしくお願いします。 返信には時間がかかる場合がありますので、ご了承ください。(現在管理者はあまりBlueSkyの方は見ていないのでmixi2のほうが返信される蓋然性が高いです) アーキテクチャ? # 簡易的な?アーキテクチャ\nGitHub Repository(private) | | ┌----------HCVI-----Cloudflare pull to update | ┌Host Machine --------------┼-------↑-----------------------------┐ | ┌--manage by script--┴-----SRMS ssh proxy --\u0026gt;ssh server | | ↓ ↑ ↑ from Internet | | ┌-rootless docker ---------------|---------|-------------┐ | | | └---------┤ | | | | ┌internal network -----------------┬----|-----------┐ | | | | | ┌----db-┬------┐----------┼--broker | | | | | | server1 server2 server3 | | | | | | |- - - - | - - - | - - - - | - - - ┼-broker network-┘ | | | | | load_balancer(distroless) | | | | | ├----------|-----------------------| | | | | | cloudflared------┐ | | | | | └----app network--------|----------┘ | | | └--------------------------|-----------------------------┘ | └----------------------------|Internet----------------------------┘ | Cloudflare | | ├---\u0026lt;-normal requests comes | | ↑ send reboot command ┌-\u0026gt;GitHub Actions GitHub Repository(private) | push | Dev Environment * SRMS - Server Reboot Management System (written in Go and Shell Script). Go program watches the server via the broker and if reboot command comes, calls shell script to reboot. Shell script pulls the updated code, build, and restart the container * HCVI - Health Check Via Internet. if fail, SRMS try to reboot cloudflared in the container * \u0026#34;GitHub Repository(private)\u0026#34; refers same object * \u0026#34;Cloudflare\u0026#34; refers same object d3b0e14c-2795-4467-bf3e-8f6d587f61e4 ja ","date":"2024年 7月 29日","externalUrl":null,"permalink":"/static/blog/posts/2024_07_29_notice_about_this_server/","section":"Posts","summary":"","title":"このサーバーについて","type":"posts"},{"content":"このサイトにアクセスコントロール機能が実装された。 ブラウザから特定の API を叩くことでコントロールできるようになっている。 アクセスコントロールの変更の際には WebAuthn による認証を 必須としている。万が一 API が公開されてしまったとしても WebAuthn の鍵を登録していないと変更は出来ないため、比較的強固になっていると思われる。\n内部では(どうせ同一ホスト上にあるのであんま意味ない気がするが) ロードバランサー挟んでサーバー 3 つという構成である。 そのため 3 つのサーバーで状態を同期させる必要がある。 そのために別途裏側でイベントドリブンにアクセス制御状態を同期する仕組みが 構築されている。\nb62e48a2-e327-4e4c-8159-8707fc46e6f5 ja ","date":"2024年 7月 21日","externalUrl":null,"permalink":"/static/blog/posts/2024_07_21_access_control/","section":"Posts","summary":"","title":"アクセスコントロール機能が実装された","type":"posts"},{"content":"","date":"2024年 7月 21日","externalUrl":null,"permalink":"/static/blog/tags/%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0/","section":"Tags","summary":"","title":"プログラミング","type":"tags"},{"content":" リファクタリングについて # 自分は設計が下手くそである。 設計が下手くそなんでたいてい最初の設計は残念極まりなく、密結合なものが出来あがる。\nだが、時間を置いて見てみると、「あ、これは密結合だ」というセンサーが光ってくる。\n密結合である場合というのは大体\n関係ない(なにかしらの代入だとか演算だとかメソッド呼び出しの引数とかの関係が一切ない)変数が一緒にいる フラグ変数とかが奥深くまで引き回されている 共通コードのはずなのに、ある場合のときの例外みたいな処理が入っている 必要以上にコンテキスト変数?が引き回されている ひとつのクラスに色々情報詰め込みすぎ これは主観だが最近リファクタリングしたコードはこんなかんじであった。\nこれらのことは大体時間を置いてみると客観視ができるようになるので 時間を置いてみるというのは非常に大事である。\nまたリファクタリングというのは処理の内容を変えずにコードを整理することである。 この処理の内容を変えないというのが肝であり、処理が変わってしまうとせっかくの修正も 思わぬ影響を与えかねない。そのために重要なのはテストである。 テストを書いていないのにリファクタリングをするというのは 命綱を付けないでやる綱渡りみたいなもんで危険極まりないと筆者は思っている。 ただ、一方で、これは密結合で作り始めたときは若干分が悪い。 密結合なものは往々にしてテストがし辛いためここにトレードオフがある。\n他方でこのようなものを一切合切排除しているコードにもデメリットはあると思われる。\nひとつはコードリーディングの難しさという問題がある 初期の開発者はおそらく(想像でしかないが)この具体的で密結合だとかで分離しきれていないという状態を たどったうえで抽象化、構造化をしているためどこに何があるかがわかる状態である。 しかし、後から入ってきた初見の人やまったくの部外者には一体どこでその具体的な処理がなされているのか わからなくなってしまう。 たいてい OSS とかのコードはこのような抽象化が進んでいるため処理の詳細を追うのに苦労する。 こういう OSS にはドキュメンテーションなどはあれど大体表に出ている部分の説明であることが多い(そして大抵の人にはそれで事足りる)。 そのためとくに実装の参考にしたいなどといってに詳細部分を探すのは骨が折れる (まあ最近はエディターとか GitHub だとかの検索機能が優秀だったり生成 AI もあるのである程度は読めるであろう)\n総じて思うのは最初はそれこそ密結合でプロトタイプくらいの気持ちで書いておいて、 一段落したらある程度分離の目処がつくのでそのときに分離をし、テストを書くようにすればよいのではと思うのである。最初から下手な設計でガッチガチに決めてしまうとあとから変えるのは難しい。 あとから想定していなかった状態が出てくるのは開発では日常茶飯事である。 そしてソフトウェアは生き物のようなものであると筆者は考える。 ホメオスタシスを保つかのごとく外からの状態は一定でありつつも内部はどんどんより良く変えていけるのが メンテナンスのしやすさの向上に繋がり、また外とのインターフェイスを変更したくなったときなどでも変更にも強くなると筆者は思うのである。\nただし、これはあくまでボッチで書く場合の話である。 大量の人が関わるプロダクトの場合、あとから引っ剥がすのは全箇所についてある程度 当たりをつけられないと難しいためまともに設計をしてから書いたほうがいいかもしれない。 と思ったが、筆者は万年ボッチ開発者なので実際のところはどうなのかは不明である。\n7baa9bde-52bf-4e37-85fe-6f39281da8f3 ja ","date":"2024年 7月 21日","externalUrl":null,"permalink":"/static/blog/posts/2024_07_20_about_refactor/","section":"Posts","summary":"","title":"リファクタリングについて","type":"posts"},{"content":"このサーバーについて(プライバシーポリシー) Music\n","date":"2024年 5月 23日","externalUrl":null,"permalink":"/static/blog/","section":"on-keydayのブログ","summary":"","title":"on-keydayのブログ","type":"page"},{"content":"","externalUrl":null,"permalink":"/static/blog/series/","section":"Series","summary":"","title":"Series","type":"series"}]