通过客户机接入的一些小操作

现在在某客户项目上,一些资源只能通过客户电脑上的VPN访问。客户机是一台Windows。而且并没有管理员权限。而我平常开发用的是Mac,使用起来两台电脑切换非常不方便。 远程桌面 因为客户机防火墙拒绝所有接入连接,所以直接通过RDP连接到客户机是不现实的。 有的同事甚至通过把两台机器加入到同一个zoom meeting,然后再分享桌面,申请控制权来实现远程桌面。 而我选择了端口转发,客户机有安全审计,像frp是立马报毒的。所以能用到的只有ssh,毕竟谁能说自带的ssh是危险应用呢? ssh -R 8080:127.0.0.1:3389 user@remote.host 将远端的8080端口映射到本机的3389端口,经过尝试,发现机器直接拒绝远程登入。所以必须寻找一个远程桌面的代替品,这里直接使用了RustDesk,可以在无UAC的环境下执行,开启IP直接登录和密码访问,就能解决第一个问题,远程桌面。 SSH Server采用了在日常使用机器上的Gost gost -L forward+ssh://username:password@0.0.0.0:2222/ 断线重连 putty的衍生版本Kitty(http://www.9bis.net/kitty/) 支持记录密码 支持忽略Host Key变更 自动锁屏 客户机设置了自动锁屏,而且锁屏之后需要按Ctrl+Alt+Del才能进入输密码的界面。我的账户也没有权限修改,一旦锁屏,远程桌面(RustDesk)只能看着锁屏界面发呆。这里用到了两个小软件(因为只用一个会偶尔失效),分别是 Caffeine,模拟定时按一个不存在的F15按键。 StayAwake,鼠标不停移动1px。 代理 这就是一个更加敏感的操作了,尝试在客户机跑gost,还是因为操作审计,立马被删了,而且第二天客户IT就找上门来了。 所以,什么软件可以起到代理的作用,但大家又会觉得这是无害的呢? 答案是:抓包软件 所以我在客户机上装了一个fiddler。 连接 最后一个问题了,怎么样才能让Mac和Windows互相访问呢?公司规定客户设备是不能接入办公室网络的,只能接入访客网络。而访客网络是AP隔离的。 答案就是一根网线。都配上169.254.xxx.xxx网段,就可以互通了。 一同组合拳下来,操作体验上升了1251223%。

August 24, 2022

iptables + tproxy 实现透明代理

先看一下iptables,正经图 路由设置 ip rule add fwmark 1 table 100 #带有mark1的包都发到table 100 ip route add local 0.0.0.0/0 dev lo table 100 #所有包都走lo(本地回环) 不同场景 内网机器访问国内服务 A的请求到达路由器的PREROUTING链,根据链上规则 -m set --match-set reserved_ip dst -j RETURN 直接返回原来的PREROUTING连,走FORWARD以及后续的操作 内网机器访问国外服务 A的请求到达路由器的PREROUTING链,根据链上规则 -p tcp -j TPROXY --on-port 1081 --tproxy-mark 0x1/0x1 给TCP请求加上mark,然后重定向到1081,根据前面的路由设置,会直接到本地回环,请求会到达Transparent Proxy 判断需要走Proxy,那接下来的流程就是,包装原请求并发出,带上了0xff(即255)的mark 按照OUTPUT链上的规则 -m mark --mark 0xff -j RETURN `` 直接返回OUTPUT链,走POSTROUTING链出去 判断为直连,直接原封不动的请求真实服务器,也是带上0xff的mark,后面和情况1一样 路由器访问国内服务 路由器的请求会从OUTPUT开始,会匹配上OUTPUT链上规则 --match-set chnroute dst -j RETURN 直接返回OUTPUT链,走POSTROUTING链出去...

February 25, 2022

一个可以接受的BFF

公司几乎所有项目都会加上一个BFF,没有为什么,就是想要一个BFF,经历过几个项目,无一例外,BFF都成了一坨屎。包括不限于以下问题: request和response明明都不需要做任何修改,但就是要重新写一遍给客户端的API和调用上游的Client 需要把返回包上code/data/message,然后在API层面调用无数次的ResponseData.ok(……) 背锅侠,任何问题都会被第一时间找到,需要找各种日志自证清白 在最近的一个项目中,我在客户的限制下搭了一套不太完善的BFF架子,目的就是避免上面的部分问题,尽可能减少一些模板代码。 客户限制:必须要Java+Spring Boot,而且必须要WebMVC,不能是Webflux/Reactor 全局 最终成品如下 从上往下 Sleuth Tracing 这是一个servlet filter,用来给req加transactionId 自定义的Log Filter,用来记录所有的Request和Response Reverse Proxy Filter,会处理特定的path,直接进入转发流程。 Spring Mvc的Filter,进入Spring的处理范围。 Proxy 对于Proxy部分,会有一串Interceptor,这个interceptor chain是金字塔形状的, 如果对request做操作,order越优先,越接近原始,越往后,越接近转发给上游的状态 如果对response做操作,order越优先,越接近返回给客户端的状态,越往后,越接近从上游拿到的原始状态 所以 Logger的优先级放最低,拿到发给上游和从上游拿到的结果。 ErrorCatch放次低,在第二顺位拿到上游的错误,包装后抛出 ErrorHandler放最高,在最外层捕获到所有错误,记录后给客户端友好提示 Spring ResponseDataBodyAdvice负责对API包内的返回值进行包装,判断如果returnType是由Jackson处理,那就包一层。 @Slf4j @ControllerAdvice(basePackages = "com.example.bff.api") public class ResponseDataBodyAdvice<T> implements ResponseBodyAdvice<T> { @Override public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) { return MappingJackson2HttpMessageConverter.class.isAssignableFrom(converterType); } @Override @SneakyThrows public T beforeBodyWrite(T body, MethodParameter returnType, MediaType selectedContentType, Class<?...

December 13, 2021

递归到Y组合子

前面已经得到一个非递归版本的“递归”求列表长度的函数了。 ((λ (mk-length) (mk-length mk-length)) (λ (mk-length) (λ (l) (if (null? l) 0 (+ 1 ((mk-length mk-length) (cdr l))))))) 再次回想一下,一般所需要的递归形式 \[\displaylines{ f(l)=\begin{cases} 0, & \text{if $l=0$} \\ 1+f(l-1) \end{cases}\\ } \] 所以,需要在上面的函数中,将(mk-length mk-length)给想办法替换掉。得到一个如下的版本。 ((λ (mk-length) (mk-length mk-length)) (λ (mk-length) ((λ (length) (λ (l) ;最内层 (if (null? l) 0 (+ 1 (length (cdr l))))) ) (mk-length mk-length)) )) 去执行一下代码就能知道,是无法正常工作的,原因就在于: 传进去的length 是由(mk-length mk-length)计算而来。 之前的计算是放在最内层,一旦l满足要求,就不会在内层计算(mk-length mk-length) 现在被抽到外面作为值传入,无法得知函数何时该终止,只能无限计算(mk-length mk-length) 所以,还是需要想办法把这部分操作延迟到内层进行。有个很简单的延迟计算办法,就是将其用lambda裹一层。如下: ((λ (mk-length) (mk-length mk-length)) (λ (mk-length) ((λ (length) ;length不再提前计算 (λ (l) ;最内层 (if (null?...

December 12, 2021

不常见k8s设置+不合理DNS服务=大坑

现象 在K8s的集群中,有部分容器内部(其实大部分,也许是所有),可以正常nslookup域名,但ping一个域名的时候,会报错 / # nslookup baidu.com Server: 10.43.0.10 Address: 10.43.0.10:53 Non-authoritative answer: Non-authoritative answer: Name: baidu.com Address: 220.181.38.148 Name: baidu.com Address: 220.181.38.251 / # ping baidu.com ping: bad address 'baidu.com' 但是在k8s各个节点上用docker运行同样的镜像,却没有这个问题。 最后几经搜索,发现了问题。 奇怪的配置(其实不算坑 k8s会替换容器内的/etc/resolv.conf。 If a Pod’s dnsPolicy is set to default, it inherits the name resolution configuration from the node that the Pod runs on. The Pod’s DNS resolution should behave the same as the node. But see Known issues....

December 10, 2021

匿名函数的递归

一个简单的递归求列表长度: (define length (λ (l) (if (null? l) 0 (+ 1 (length (cdr l)))))) 如果不允许使用define,如何实现? 先实现一个最简单版本,当列表为空时成立。 (λ (l) (if (null? l) 0 (+ 1 (...... (cdr l))))) 先忽略......处,显然,当l为空的时候,能得到正确返回0。就叫它lenght0吧。 依托已有的这个函数,来实现当list为1的时候,也能正确返回的函数: (λ (l) (if (null? l) 0 (+ 1 (length0 (cdr l))))) 可以看出 和空列表版本几乎一致,唯一的区别是......和length0 由于不让使用define,所以无法直接使用lenght0,需要代换 最终length1如下: (λ (l) (if (null? l) 0 (+ 1 ((λ (l) (if (null? l) 0 (+ 1 (...... (cdr l))))) (cdr l))))) 再进一步,得到length2,如下: (λ (l) (if (null?...

December 10, 2021

一次失败的couchbase troubleshooting

前情提要 大约1年半前,我在某咖啡项目上做一些后端业务,数据库是用的couchbase。(谁选的,为啥选的,一概不知。 在使用过程中发现,并发高了之后,Couchbase在平均响应时间/95%时间依旧非常美观的情况下,后端API反倒会夹杂着一些请求超时,并发越高越明显。 这个问题一直没有被解决,最近机缘巧合,开始尝试解决这个问题。 先通过非Java版本(就是Go)的Couchbase SDK做同样的测试,得出问题应该是出在Client端,而非Server端。1 根据Couchbase SDK的源码梳理了一下整个查询的流程。 尝试回答之前的问题 为什么会超时? 在couchbaseNode那一块逻辑的时候,没有足够的Endpoint,会直接进入重试,重试直至超时。 怎么解决Endpoint不够的问题? 最直接的办法就是调大Endpoint的连接数,Spring Data默认1,官方推荐6-10,在实际项目中甚至都被调到30-50了。数值调大之后确实缓解了出错情况。 为什么Endpoint调这么大了,依然会出错? 因为没来得及释放Endpoint,负责释放Endpoint的IO线程被Block住了。后面的业务逻辑实际上是在IO线程上执行。 图上publish response给客户端,是在Computation线程池,为什么会出现IO线程的问题? 按照设计,业务确实应该都是跑在Computation线程池上(或后期切换到其他线程),IO线程对SDK的使用者来说,应该是不可见的。 问题出在这个代码块: //org/springframework/data/couchbase/core/RxJavaCouchbaseTemplate.java:392 @Override public <T>Observable<T> findByN1QL(N1qlQuery query, Class<T> entityClass) { return queryN1QL(query) .flatMap(asyncN1qlQueryResult -> asyncN1qlQueryResult.errors() .flatMap(error -> Observable.error(new CouchbaseQueryExecutionException("Unable to execute n1ql query due to error:" + error.toString()))) .switchIfEmpty(asyncN1qlQueryResult.rows())) .map(row -> { JsonObject json = ((AsyncN1qlQueryRow)row).value(); String id = json.getString(TemplateUtils.SELECT_ID); Long cas = json....

November 29, 2021

K3S@Home

VM Cluster 在PVE机器上创建三台虚拟机。如果用CT,k3s启动会有一点问题,新手为了避免不必要的麻烦,还是安装VM,首先需要的就是VM Template,不然每次都重新安装系统,也太麻烦了。 Cloud-Init Support 操作中间磁盘id可能会有点小区别 文中需要的Cloud Image可以从系统官网下载,比如 Cloud-Init Support k3s 安装官网就是一句话 curl -sfL https://get.k3s.io | sh - 但是最好不要照着广告走,还是得照着文档走。虽然k3s可以用其他runtime,但为了避免麻烦,还是安装&使用docker。 安装Docker Install Docker Engine on Ubuntu 安装k3s 接下来就是安装k3s #server curl -sfL https://get.k3s.io | sh -s - --docker cat /var/lib/rancher/k3s/server/node-token #agent curl -sfL https://get.k3s.io | K3S_URL=https://myserver:6443 K3S_TOKEN=mynodetoken sh -s - --docker K3s Server Configuration Reference K3s Agent Configuration Reference 这个时候,服务端的kubectl 就可以正常使用了,文档后面是安装dashboard,那个dashboard对新手来说,没用。先不装。 客户端登陆 在自己电脑使用kubectl 显然是比每次都要ssh到服务器上更方便,在自己电脑上装好kubectl ,然后把k3s server上的配置文件下载到~/.kube/config A kubeconfig file will be written to /etc/rancher/k3s/k3s....

September 17, 2021

字典顺序

字典顺序,实际上就是一个递增的顺序。字典为 \(dic\) ,可以得到:\(dic[n] 将 \(dic[n]\) 记为 \(d_n\) , \(dic[n+1]\) 记为 \(d_{n+1}\) ,相邻的两个元素,可能存在前N位相同的项(N可以为0),我们将前N项去掉。得到 \(S_n\) 和 \(S_{n+1}\) 。 由于是递增顺序,显而易见: \(S_n[0] 。 由于 \(S_n\) 的下一项是 \(S_{n+1}\) ,所以 \(S_n[1:]\) 的所有元素无法排列出比现有更大的组合。 \(S_n[1:]\) 为递减排列(即能排列出的最大值)。 同样的道理, \(S_{n+1}\) 的上一项是 \(S_n\) ,所以 \(S_{n-1}[1:]\) 的所有元素无法排列出比现有更小的组合。 \(S_{n+1}[1:]\) 为递增排列。 如果能排列出来,那与“ \(S_n\) 的下一项是 \(S_{n+1}\) ”相冲突 对于 \(S_n[0]\) 和 \(S_{n+1}[0]\) ,除了已经知道的 \(S_n[0] 。还有另外一个规律: \(S_{n+1}[0]\) 为 \(S_n\) 所有元素中 \(S_n[0]\) 的下一项。(依然是因为 \(S_n\) 的下一项就是 \(S_{n+1}\) ) 所以,整体的思路是:...

March 12, 2021

Leetcode#45 Jump Game II

给定一个数组,从下标为0开始,往右跳,下标对应的值表示其能跳的最远距离。求最少X步,能跳到最后一个元素? 简单思路 最近看SICP看的比较多,第一个想到的办法,就是递归。 对于数组\([a,a_1...a_n]\),找到能跳到最后的最小下标\(m\) 对于数组\([a,a_1...a_m]\),找到能跳到最后的最小下标\(l\) …… 当最后数组长度为1的时候,说明已经到头了 func jump(nums []int) int { return jumpIter(nums, 0) } func findMin(nums []int) int { for i := 0; i < len(nums); i++ { if i+nums[i] >= len(nums)-1 { return i } } return len(nums) - 1 } func jumpIter(nums []int, count int) int { if len(nums) == 1 { return count } return jumpIter(nums[:findMin(nums)+1], count+1) } 改进 在上面的算法中,从左到右, 很多位置(\(i\))能达到的最远距离(\(nums[i]+i\)),都被计算了多次。 就算是每个位置的最远距离缓存/保存起来,迭代次数并不会减少。 改进的思路,依然是从左往右遍历func jump(nums []int) int { if len(nums) == 1 { return 0 } var limit, next, steps int for i := 0; i <= limit; i++ { //从左到limit if i+nums[i] > next { //更新最大的next next = i + nums[i] } if i == limit { //如果已经移动到了limit steps++ limit = next //从0-limit之间选出最远的next if limit >= len(nums)-1 { //如果current-limit之间的最远距离已经达到目标 break } } } return steps } 整理一下...

November 27, 2020