深挖 docker 默认网络 | 为什么 docker 默认网络能上外网

为什么默认配置创建出来的 docker 容器可以访问外网,为什么监听对应端口就能对外暴露docker服务,一张图搞清楚。

首先 Docker 有四中网络模式, 分别是 Bridge、Host、Container、None,默认使用 Bridge,今天就来讲讲 Bridge。

https://imagehost-cdn.frytea.com/images/2021/12/28/drawf92673ecda5420ba.jpg

创建的容器默认使用 bridge 的方式联网,因此默认就可以docker间互通,该网桥名叫 bridge0,通过 nat 的方式与物理网卡相连,每创建一个该模式下的容器,就自动创建一对 veth-pair 挂上去。

# brctl show 
bridge name bridge id       STP enabled interfaces
br-1061a9012b5a     8000.0242998bddbb   no      
docker0     8000.0242f9c2fd4f   no      veth4fc1b46
                            veth86c0817
                            vethe510127
vmbr0       8000.b496916544ad   no      enp59s0f1

通过 nat 的方式,docker可以自由的通过宿主机网卡访问外网,如果映射端口,也是通过 nat 的方式将对应流量送入docker:

# docker ps
CONTAINER ID   IMAGE          COMMAND              CREATED        STATUS        PORTS                                       NAMES
398907d00b97   001b9ea10452   "/sbin/init start"   5 hours ago    Up 5 hours    192.168.226.140:8206->8006/tcp              hci-nos3
475b07e8faa4   001b9ea10452   "/sbin/init start"   5 hours ago    Up 5 hours    192.168.226.139:8206->8006/tcp              hci-nos2
e9955db2132c   001b9ea10452   "/sbin/init"         29 hours ago   Up 29 hours   0.0.0.0:8106->8006/tcp, :::8106->8006/tcp   hci-nos
# iptables -n -L -t nat
Chain PREROUTING (policy ACCEPT)
target     prot opt source               destination         
DOCKER     all  --  0.0.0.0/0            0.0.0.0/0            ADDRTYPE match dst-type LOCAL

Chain INPUT (policy ACCEPT)
target     prot opt source               destination         

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination         
DOCKER     all  --  0.0.0.0/0           !127.0.0.0/8          ADDRTYPE match dst-type LOCAL

Chain POSTROUTING (policy ACCEPT)
target     prot opt source               destination         
MASQUERADE  all  --  172.18.0.0/16        0.0.0.0/0           
MASQUERADE  all  --  172.17.0.0/16        0.0.0.0/0           
MASQUERADE  tcp  --  172.17.0.2           172.17.0.2           tcp dpt:8006
MASQUERADE  tcp  --  172.17.0.3           172.17.0.3           tcp dpt:8006
MASQUERADE  tcp  --  172.17.0.4           172.17.0.4           tcp dpt:8006

Chain DOCKER (2 references)
target     prot opt source               destination         
RETURN     all  --  0.0.0.0/0            0.0.0.0/0           
RETURN     all  --  0.0.0.0/0            0.0.0.0/0           
DNAT       tcp  --  0.0.0.0/0            0.0.0.0/0            tcp dpt:8106 to:172.17.0.2:8006
DNAT       tcp  --  0.0.0.0/0            192.168.226.139      tcp dpt:8206 to:172.17.0.3:8006
DNAT       tcp  --  0.0.0.0/0            192.168.226.140      tcp dpt:8206 to:172.17.0.4:8006

以上面几个docker为例,分别直接监听和指定ip,会发现创建了对应的规则在 iptables 中。

至此,为什么docker访问外网及访问docker的原理讲解完毕,有问题欢迎留言。

参考文献

理解网桥、集线器、交换机、路由器的本质

一直弄不清楚这几者的区别,加之现代的三层交换机,就更搞不清楚几者的关系了。

后来发现试图以现代产品的思维理解这几个概念是极其容易混淆的,必须回归到几者最本质的样子,再去拓展才行。

后来在知乎上找到一段描述,感觉说的很好,在这里记录一下:

  • 集线器
    • 工作在物理层(L1)。一个口收到的信号,原封不动的发送给所有其他的口,由其他的口上的设备自己决定是否接收信号。
  • 网桥
    • 工作在数据链路层(L2)。以太网中,数据链路层地址就是mac地址,网桥与hub的区别在于,网桥会过滤mac,只有目的mac地址匹配的数据才会发送到出口。一个bridge指的是一个输入到一个输出的桥接。
  • 交换机
    • 工作在数据链路层(L2)。早期的switch,其实可以看成多个bridge的集成设备,因此也工作在数据链路层。一个交换机口的输入到另一个交换机口的输出,可以认为是一个bridging。交换机中的MAC table,实际是为了bridge能工作而存在。
  • 路由器
    • 工作在网络层(L3),基于IP地址做转发。

回归本质,有助于更好地理解。至此我终于理解了什么是网桥。

参考文献

Python 实战项目解决循环依赖问题

问题重现

在一次简单的代码合并后,发现 Python 项目跑不起来了,报错如下:

ssh://root@192.168.226.76:22/usr/bin/python3 -B -u /tmp/pycharm_project_882/mimic_daemon_server/helper_main.py
Traceback (most recent call last):
  File "/tmp/pycharm_project_882/mimic_daemon_server/helper_main.py", line 1, in 
<module>
    from mimic_daemon_server import create_app
  File "/tmp/pycharm_project_882/mimic_daemon_server/__init__.py", line 14, in 
<module>
    from mimic_daemon_server.nodes import nodes
  File "/tmp/pycharm_project_882/mimic_daemon_server/nodes/__init__.py", line 2, in 
<module>
    from . import route
  File "/tmp/pycharm_project_882/mimic_daemon_server/nodes/route.py", line 5, in 
<module>
    from mimic_daemon_server.guest_common import ReplicationConfig
  File "/tmp/pycharm_project_882/mimic_daemon_server/guest_common/__init__.py", line 3, in 
<module>
    from .abstract_config import AbstractConfig
  File "/tmp/pycharm_project_882/mimic_daemon_server/guest_common/abstract_config.py", line 16, in 
<module>
    import mimic_daemon_server.qemu_server.Drive
  File "/tmp/pycharm_project_882/mimic_daemon_server/qemu_server/__init__.py", line 25, in 
<module>
    from .QemuConfig import QemuConfig
  File "/tmp/pycharm_project_882/mimic_daemon_server/qemu_server/QemuConfig.py", line 12, in 
<module>
    from mimic_daemon_server.guest_common import AbstractConfig
ImportError: cannot import name 'AbstractConfig' from 'mimic_daemon_server.guest_common' (/tmp/pycharm_project_882/mimic_daemon_server/guest_common/__init__.py)

进程已结束,退出代码1

由于我在 Python 领域还是个初学者,没有遇到类似问题,但是根据分析,问题应该是出在了 循环依赖

a------->b-------->c
         ^         |
         |         |
         +----d<---+

报错分析

何为循环依赖呢?首先要搞清楚一点,python模块是天然的单例类,就是说第一次导入时会产生一个实例,后面再导入都会直接返回该实例。

模块的初始化时被导入时进行的,就是说每一次 import 都会执行 __init__.py 内的代码。

一旦 Python 的模块导入规划不合理,造成了多个互相依赖的模块均在第一次进入时开始实例化,就会产生 循环依赖 问题。

总结一下,Python 模块是可以循环依赖的,但是必须避免循环实例化

解决方法

解决循环依赖的方法也有很多:

(1)打破循环实例化态

如果想快速解决,在报错日志中找到循环实例化的两个模块,打破其循环实例化的状态即可。

例如观察文首的一段日志,会很明显的发现调用栈中前后引入了同一个模块,很容易定位到问题。

(2) 强制指定实例化顺序

可以在整个项目的根模块中的 __init__.py 中将所有子模块依赖一遍,调整可能出现循环依赖的顺序,就可以避免后期模块间依赖时产生不可预知的循环依赖问题。

也可以在模块中提供一个 init 方法,在需要的地方手动控制实例化顺序,避免导入时意外的循环依赖。

总结

Python 模块间可以随意引用,但是必须注意实例化的前后顺序,否则就会出现这种循环实例化问题,让人摸不着头脑。

参考文献

解决 perl: warning: Setting locale failed.

使用 Ubuntu 主机远程 PVE 服务器执行命令时报错:

# qm list
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
    LANGUAGE = (unset),
    LC_ALL = (unset),
    LC_ADDRESS = "zh_CN.UTF-8",
    LC_NAME = "zh_CN.UTF-8",
    LC_MONETARY = "zh_CN.UTF-8",
    LC_PAPER = "zh_CN.UTF-8",
    LC_IDENTIFICATION = "zh_CN.UTF-8",
    LC_TELEPHONE = "zh_CN.UTF-8",
    LC_MEASUREMENT = "zh_CN.UTF-8",
    LC_TIME = "zh_CN.UTF-8",
    LC_NUMERIC = "zh_CN.UTF-8",
    LANG = "en_US.UTF-8"
    are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").

查看字符集配置发现有报错:

# locale
locale: Cannot set LC_ALL to default locale: No such file or directory
LANG=en_US.UTF-8
LANGUAGE=
LC_CTYPE="en_US.UTF-8"
LC_NUMERIC=zh_CN.UTF-8
LC_TIME=zh_CN.UTF-8
LC_COLLATE="en_US.UTF-8"
LC_MONETARY=zh_CN.UTF-8
LC_MESSAGES="en_US.UTF-8"
LC_PAPER=zh_CN.UTF-8
LC_NAME=zh_CN.UTF-8
LC_ADDRESS=zh_CN.UTF-8
LC_TELEPHONE=zh_CN.UTF-8
LC_MEASUREMENT=zh_CN.UTF-8
LC_IDENTIFICATION=zh_CN.UTF-8
LC_ALL=

经过分析错误为 LC_ALL 没设置值,LC_CTYPELC_MESSAGES 的值 zh_CN.UTF-8 系统未安装。

远程主机默认仅支持 en_US.UTF-8 ,而我的客户端默认配置为 zh_CN.UTF-8 ,结果远程时服务端继承了客户端配置,导致字符集报错。

此时无需在远程做任何配置,只需断开远程连接,将下面一行配置写入我的 Ubuntu 主机中的 ~./bashrc 即可:

export LC_ALL=C

原理

在设定主机 locale 时存在一个优先级,可总结为 LC_ALL > LC_* >LANG ,可以说 LC_ALL最上级设定或者强制设定,而 LANG 是默认设定值。

若需了解详情可拓展阅读参考文献。

参考文献

Perl 调试打印 HASH 内容

在调试 Perl 程序时常常需要打印哈希表内容,虽然可以直接使用 foreach 打印,但数据复杂了就难办了,此时可以将 Hash 表转换为 json 文本再打印:

use JSON;

my $data = {'info'=> "test", 'struct' => {'test1'=>'test1', 'test2'=>'test2'}};
my $json = new JSON;
#$json->sort_by(sub { ncmp($JSON::PP::a, $JSON::PP::b) });
my $json_text = $json->pretty->encode ($data);
print $json_text;

如果没有 json 包需要安装一下:

cpan -i JSON

如果下载太慢,可以使用 tuna 提供的 cpan 国内镜像源:

# 若tuna cpan不在镜像列表中则将其加入列表首位
if ! (
    perl -MCPAN -e 'CPAN::HandleConfig->load();' \
        -e 'CPAN::HandleConfig->prettyprint("urllist")' |
    grep -qF 'https://mirrors.tuna.tsinghua.edu.cn/CPAN/'
); then
    perl -MCPAN -e 'CPAN::HandleConfig->load();' \
        -e 'CPAN::HandleConfig->edit("urllist", "unshift", "https://mirrors.tuna.tsinghua.edu.cn/CPAN/");' \
        -e 'CPAN::HandleConfig->commit()'
fi

测试一下,效果还可以:

$ perl -e 'use JSON;
> 
> my $data = {'info'=> "test", 'struct' => {'test1'=>'test1', 'test2'=>'test2'}};
> my $json = new JSON;
> #$json->sort_by(sub { ncmp($JSON::PP::a, $JSON::PP::b) });
> my $json_text = $json->pretty->encode ($data);
> print $json_text;'
{
   "struct" : {
      "test2" : "test2",
      "test1" : "test1"
   },
   "info" : "test"
}

参考文献

分享一个快捷内网、漏洞扫描工具 fscan

fscan 是一款 go 语言写成 “一款内网综合扫描工具,方便一键自动化、全方位漏扫扫描”。是我在刷 kon9chunkit 发起的 GitHub中文排行榜 时发现的。

该工具支持主机存活探测、端口扫描、常见服务的爆破、ms17010、redis批量写公钥、计划任务反弹shell、读取win网卡信息、web指纹识别、web漏洞扫描、netbios探测、域控识别等功能。

用来快速扫描局域网主机存活也是很好的,下面简单介绍使用方法:

安装很简单,装好 go 环境编译一下就行:

$ git clone https://github.com/shadow1ng/fscan.git
$ cd facan
$ go build -ldflags="-s -w " -trimpath

之后就可以进行扫描了,快速进行局域网段扫描:

$ ./fscan -h 192.168.123.0/24 

   ___                              _    
  / _ \     ___  ___ _ __ __ _  ___| | __ 
 / /_\/____/ __|/ __| '__/ _` |/ __| |/ /
/ /_\\_____\__ \ (__| | | (_| | (__|   <    
\____/     |___/\___|_|  \__,_|\___|_|\_\   
                     fscan version: 1.6.3
start infoscan
已完成 0/0 listen ip4:icmp 0.0.0.0: socket: operation not permitted
The current user permissions unable to send icmp packets
start ping
(icmp) Target '192.168.123.9' is alive
(icmp) Target '192.168.123.4' is alive
...
icmp alive hosts len is: 7
192.168.123.3:443 open
...
alive ports len is: 19
start vulscan
[*] WebTitle:http://192.168.123.1      code:401 len:140    title:401 Unauthorized
.....

完整支持参数如下:

    -c string
        ssh命令执行
  -cookie string
        设置cookie
  -debug int
        多久没响应,就打印当前进度(default 60)
  -domain string
        smb爆破模块时,设置域名
  -h string
        目标ip: 192.168.11.11 | 192.168.11.11-255 | 192.168.11.11,192.168.11.12
  -hf string
        读取文件中的目标
  -hn string
        扫描时,要跳过的ip: -hn 192.168.1.1/24
  -m string
        设置扫描模式: -m ssh (default "all")
  -no
        扫描结果不保存到文件中
  -nobr
        跳过sql、ftp、ssh等的密码爆破
  -nopoc
        跳过web poc扫描
  -np
        跳过存活探测
  -num int
        web poc 发包速率  (default 20)
  -o string
        扫描结果保存到哪 (default "result.txt")
  -p string
        设置扫描的端口: 22 | 1-65535 | 22,80,3306 (default "21,22,80,81,135,139,443,445,1433,3306,5432,6379,7001,8000,8080,8089,9000,9200,11211,27017")
  -pa string
        新增需要扫描的端口,-pa 3389 (会在原有端口列表基础上,新增该端口)
  -path string
        fcgi、smb romote file path
  -ping
        使用ping代替icmp进行存活探测
  -pn string
        扫描时要跳过的端口,as: -pn 445
  -pocname string
        指定web poc的模糊名字, -pocname weblogic
  -proxy string
        设置代理, -proxy http://127.0.0.1:8080
  -user string
        指定爆破时的用户名
  -userf string
        指定爆破时的用户名文件
  -pwd string
        指定爆破时的密码
  -pwdf string
        指定爆破时的密码文件
  -rf string
        指定redis写公钥用模块的文件 (as: -rf id_rsa.pub)
  -rs string
        redis计划任务反弹shell的ip端口 (as: -rs 192.168.1.1:6666)
  -silent
        静默扫描,适合cs扫描时不回显
  -sshkey string
        ssh连接时,指定ssh私钥
  -t int
        扫描线程 (default 600)
  -time int
        端口扫描超时时间 (default 3)
  -u string
        指定Url扫描
  -uf string
        指定Url文件扫描
  -wt int
        web访问超时时间 (default 5)

总之是一款很实用的工具,平时有很多场景可以使用到。

参考文献

Windows 禁用 AppXSVC 解决 CPU 占用高问题

在一台虚拟机上安装了 Windows 10 lstc 2021 版,启动后发现 CPU 占用率一直居高不下,查看任务管理器发现是一个叫 wsapp 的系统服务服务占用 CPU 过高,搜索后发现对应的是 AppX Deployment Service 系统服务,是用来提供 微软应用商店服务服务的。

这就很奇怪了,lstc 没有预装微软应用商店呀,因此也不能使用常规方法将其关闭,实测在服务管理器中也无法直接关闭。

解决方法

后面我发现其实可以通过修改注册表的方式去禁用该服务,方法很简单:

Step1 打开注册表编辑器

两种方法可以打开:

  1. 在任务栏上的搜索框中,键入regedit,然后选择”注册表编辑器 (桌面应用) 结果。
  2. 右键单击”开始”, 然后选择”运行”。“打开:”框中键入 regedit,然后选择”确定“。

Step2 禁用服务

首先在注册表编辑器中找到这一项:

HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Services\AppXSvc

之后在右侧找到 Start 将值由原本的 3 改为 4 即可。

https://imagehost-cdn.frytea.com/images/2021/12/16/image46a917cd3ecedee5.png

大概像图片中这样。

最后重启即可!

Step3 查看效果

重启后查看任务管理器,发现再也没有长期占用 CPU 50% 以上的 AppXSVC 服务了。

https://imagehost-cdn.frytea.com/images/2021/12/16/image0e48aeaeec65f56a.png

结束。

参考文献