carlos / platform Public

Clone
git clone https://amadan.net/carlos/platform

Plain git — no account needed to clone.

Download

Download this file

1package host
2
3import (
4 "context"
5 "encoding/binary"
6 "fmt"
7 "net"
8 "net/http"
9 "os"
10 "path/filepath"
11 "strconv"
12 "strings"
13 "time"
14
15 "github.com/carlosframework/platform/internal/instancectl"
16)
17
18func claimProcTCPAddress(a *net.TCPAddr, ipv6 bool) string {
19 ip := a.IP.To4()
20 if ipv6 {
21 ip = a.IP.To16()
22 }
23 if ip == nil {
24 return ""
25 }
26 var b strings.Builder
27 for i := 0; i < len(ip); i += 4 {
28 fmt.Fprintf(&b, "%08X", binary.NativeEndian.Uint32(ip[i:i+4]))
29 }
30 return fmt.Sprintf("%s:%04X", b.String(), a.Port)
31}
32
33func claimTCPPeer(conn net.Conn, group string) error {
34 local, ok := conn.LocalAddr().(*net.TCPAddr)
35 if !ok {
36 return os.ErrPermission
37 }
38 remote, ok := conn.RemoteAddr().(*net.TCPAddr)
39 if !ok {
40 return os.ErrPermission
41 }
42 pids, err := os.ReadFile(filepath.Join("/sys/fs/cgroup", group, "cgroup.procs"))
43 if err != nil {
44 return err
45 }
46 inodes := map[string]bool{}
47 for _, network := range []string{"tcp", "tcp6"} {
48 raw, err := os.ReadFile("/proc/net/" + network)
49 if err != nil {
50 return err
51 }
52 for _, line := range strings.Split(string(raw), "\n") {
53 fields := strings.Fields(line)
54 if len(fields) > 9 && fields[1] == claimProcTCPAddress(remote, network == "tcp6") && fields[2] == claimProcTCPAddress(local, network == "tcp6") && fields[3] == "01" && fields[9] != "0" {
55 inodes["socket:["+fields[9]+"]"] = true
56 }
57 }
58 }
59 for _, pid := range strings.Fields(string(pids)) {
60 n, err := strconv.Atoi(pid)
61 if err != nil || n <= 1 {
62 return os.ErrPermission
63 }
64 dir := filepath.Join("/proc", pid, "fd")
65 entries, err := os.ReadDir(dir)
66 if err != nil {
67 return err
68 }
69 for _, entry := range entries {
70 target, err := os.Readlink(filepath.Join(dir, entry.Name()))
71 if err == nil && inodes[target] {
72 return nil
73 }
74 }
75 }
76 return os.ErrPermission
77}
78
79func claimBoundUnitReady(ctx context.Context, s instancectl.ClaimUnitStarted, shape claimUnitShape) error {
80 transport := &http.Transport{DisableKeepAlives: true, DialContext: func(ctx context.Context, _, _ string) (net.Conn, error) {
81 network, endpoint := "unix", claimSocketTarget(shape)
82 if shape.Addr != "" {
83 network, endpoint = "tcp", shape.Addr
84 }
85 conn, err := (&net.Dialer{}).DialContext(ctx, network, endpoint)
86 if err != nil {
87 return nil, err
88 }
89 if network == "unix" {
90 err = claimProbePeer(conn, s.MainPID, "0::/"+s.ControlGroup)
91 } else {
92 for until := time.Now().Add(500 * time.Millisecond); ; {
93 err = claimTCPPeer(conn, s.ControlGroup)
94 if err == nil || time.Now().After(until) {
95 break
96 }
97 select {
98 case <-ctx.Done():
99 conn.Close()
100 return nil, ctx.Err()
101 case <-time.After(10 * time.Millisecond):
102 }
103 }
104 }
105 if err != nil {
106 conn.Close()
107 return nil, err
108 }
109 return conn, nil
110 }}
111 defer transport.CloseIdleConnections()
112 request, err := http.NewRequestWithContext(ctx, http.MethodGet, "http://carlos/", nil)
113 if err != nil {
114 return err
115 }
116 client := &http.Client{Transport: transport, Timeout: time.Second, CheckRedirect: func(*http.Request, []*http.Request) error { return http.ErrUseLastResponse }}
117 response, err := client.Do(request)
118 if err != nil {
119 return err
120 }
121 defer response.Body.Close()
122 if response.StatusCode >= 500 {
123 return os.ErrPermission
124 }
125 return nil
126}
127