blob: 856526d07961b290a2f08f7e9e50e395c349e474 [file] [log] [blame]
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +04001package welcome
2
3import (
Giorgi Lekveishvili5c1b06e2024-03-28 15:19:44 +04004 "embed"
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +04005 "encoding/json"
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +04006 "errors"
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +04007 "fmt"
Giorgi Lekveishvili5c1b06e2024-03-28 15:19:44 +04008 "html/template"
Giorgi Lekveishvili081f18f2023-11-07 14:58:10 +04009 "io"
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +040010 "io/fs"
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +040011 "log"
Giorgi Lekveishvili46743d42023-12-10 15:47:23 +040012 "net"
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +040013 "net/http"
Giorgi Lekveishvili9d5e3f52024-03-13 15:02:50 +040014 "net/netip"
Giorgi Lekveishvili1caed362023-12-13 16:29:43 +040015 "strings"
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +040016
Giorgi Lekveishviliab7ff6e2024-03-29 13:11:30 +040017 "github.com/gomarkdown/markdown"
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +040018 "github.com/gorilla/mux"
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +040019
20 "github.com/giolekva/pcloud/core/installer"
gioe72b54f2024-04-22 10:44:41 +040021 "github.com/giolekva/pcloud/core/installer/dns"
22 phttp "github.com/giolekva/pcloud/core/installer/http"
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +040023 "github.com/giolekva/pcloud/core/installer/soft"
Giorgi Lekveishvili46743d42023-12-10 15:47:23 +040024 "github.com/giolekva/pcloud/core/installer/tasks"
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +040025)
26
Giorgi Lekveishvili5c1b06e2024-03-28 15:19:44 +040027//go:embed env-manager-tmpl/*
28var tmpls embed.FS
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +040029
Giorgi Lekveishvili5c1b06e2024-03-28 15:19:44 +040030var tmplsParsed templates
31
32func init() {
33 if t, err := parseTemplates(tmpls); err != nil {
34 panic(err)
35 } else {
36 tmplsParsed = t
37 }
38}
39
40type templates struct {
41 form *template.Template
42 status *template.Template
43}
44
45func parseTemplates(fs embed.FS) (templates, error) {
46 base, err := template.ParseFS(fs, "env-manager-tmpl/base.html")
47 if err != nil {
48 return templates{}, err
49 }
50 parse := func(path string) (*template.Template, error) {
51 if b, err := base.Clone(); err != nil {
52 return nil, err
53 } else {
54 return b.ParseFS(fs, path)
55 }
56 }
57 form, err := parse("env-manager-tmpl/form.html")
58 if err != nil {
59 return templates{}, err
60 }
61 status, err := parse("env-manager-tmpl/status.html")
62 if err != nil {
63 return templates{}, err
64 }
65 return templates{form, status}, nil
66}
Giorgi Lekveishvili081f18f2023-11-07 14:58:10 +040067
68type Status string
69
70const (
71 StatusActive Status = "ACTIVE"
72 StatusAccepted Status = "ACCEPTED"
73)
74
75// TODO(giolekva): add CreatedAt and ValidUntil
76type invitation struct {
77 Token string `json:"token"`
78 Status Status `json:"status"`
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +040079}
80
Giorgi Lekveishvili081f18f2023-11-07 14:58:10 +040081type EnvServer struct {
82 port int
gioe72b54f2024-04-22 10:44:41 +040083 ss soft.Client
84 repo soft.RepoIO
85 repoClient soft.ClientGetter
Giorgi Lekveishvili081f18f2023-11-07 14:58:10 +040086 nsCreator installer.NamespaceCreator
Giorgi Lekveishvilicd9e42c2023-12-13 09:49:44 +040087 dnsFetcher installer.ZoneStatusFetcher
Giorgi Lekveishvili081f18f2023-11-07 14:58:10 +040088 nameGenerator installer.NameGenerator
gioe72b54f2024-04-22 10:44:41 +040089 httpClient phttp.Client
90 dnsClient dns.Client
91 Tasks map[string]tasks.Task
Giorgi Lekveishviliab7ff6e2024-03-29 13:11:30 +040092 envInfo map[string]template.HTML
gioe72b54f2024-04-22 10:44:41 +040093 dns map[string]installer.EnvDNS
Giorgi Lekveishviliab7ff6e2024-03-29 13:11:30 +040094 dnsPublished map[string]struct{}
Giorgi Lekveishvili081f18f2023-11-07 14:58:10 +040095}
96
Giorgi Lekveishvilicd9e42c2023-12-13 09:49:44 +040097func NewEnvServer(
98 port int,
gioe72b54f2024-04-22 10:44:41 +040099 ss soft.Client,
100 repo soft.RepoIO,
101 repoClient soft.ClientGetter,
Giorgi Lekveishvilicd9e42c2023-12-13 09:49:44 +0400102 nsCreator installer.NamespaceCreator,
103 dnsFetcher installer.ZoneStatusFetcher,
104 nameGenerator installer.NameGenerator,
gioe72b54f2024-04-22 10:44:41 +0400105 httpClient phttp.Client,
106 dnsClient dns.Client,
Giorgi Lekveishvilicd9e42c2023-12-13 09:49:44 +0400107) *EnvServer {
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400108 return &EnvServer{
109 port,
110 ss,
111 repo,
gioe72b54f2024-04-22 10:44:41 +0400112 repoClient,
Giorgi Lekveishvili7fb28bf2023-06-24 19:51:16 +0400113 nsCreator,
Giorgi Lekveishvilicd9e42c2023-12-13 09:49:44 +0400114 dnsFetcher,
Giorgi Lekveishvili081f18f2023-11-07 14:58:10 +0400115 nameGenerator,
gioe72b54f2024-04-22 10:44:41 +0400116 httpClient,
117 dnsClient,
Giorgi Lekveishvili46743d42023-12-10 15:47:23 +0400118 make(map[string]tasks.Task),
Giorgi Lekveishviliab7ff6e2024-03-29 13:11:30 +0400119 make(map[string]template.HTML),
gioe72b54f2024-04-22 10:44:41 +0400120 make(map[string]installer.EnvDNS),
Giorgi Lekveishviliab7ff6e2024-03-29 13:11:30 +0400121 make(map[string]struct{}),
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400122 }
123}
124
125func (s *EnvServer) Start() {
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400126 r := mux.NewRouter()
127 r.PathPrefix("/static/").Handler(http.FileServer(http.FS(staticAssets)))
Giorgi Lekveishvili46743d42023-12-10 15:47:23 +0400128 r.Path("/env/{key}").Methods("GET").HandlerFunc(s.monitorTask)
Giorgi Lekveishvili1caed362023-12-13 16:29:43 +0400129 r.Path("/env/{key}").Methods("POST").HandlerFunc(s.publishDNSRecords)
Giorgi Lekveishvili123a3672023-12-04 13:01:29 +0400130 r.Path("/").Methods("GET").HandlerFunc(s.createEnvForm)
131 r.Path("/").Methods("POST").HandlerFunc(s.createEnv)
Giorgi Lekveishvili081f18f2023-11-07 14:58:10 +0400132 r.Path("/create-invitation").Methods("GET").HandlerFunc(s.createInvitation)
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400133 http.Handle("/", r)
134 log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", s.port), nil))
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400135}
136
Giorgi Lekveishvili46743d42023-12-10 15:47:23 +0400137func (s *EnvServer) monitorTask(w http.ResponseWriter, r *http.Request) {
138 vars := mux.Vars(r)
Giorgi Lekveishvilicd9e42c2023-12-13 09:49:44 +0400139 key, ok := vars["key"]
140 if !ok {
141 http.Error(w, "Task key not provided", http.StatusBadRequest)
142 return
143 }
gioe72b54f2024-04-22 10:44:41 +0400144 t, ok := s.Tasks[key]
Giorgi Lekveishvili46743d42023-12-10 15:47:23 +0400145 if !ok {
146 http.Error(w, "Task not found", http.StatusBadRequest)
147 return
148 }
Giorgi Lekveishviliab7ff6e2024-03-29 13:11:30 +0400149 dnsRecords := ""
150 if _, ok := s.dnsPublished[key]; !ok {
151 dnsRef, ok := s.dns[key]
152 if !ok {
153 http.Error(w, "Task dns configuration not found", http.StatusInternalServerError)
154 return
155 }
gioe72b54f2024-04-22 10:44:41 +0400156 if records, err := s.dnsFetcher.Fetch(dnsRef.Address); err == nil {
157 dnsRecords = records
Giorgi Lekveishviliab7ff6e2024-03-29 13:11:30 +0400158 }
Giorgi Lekveishvilicd9e42c2023-12-13 09:49:44 +0400159 }
Giorgi Lekveishviliab7ff6e2024-03-29 13:11:30 +0400160 data := map[string]any{
Giorgi Lekveishvilicd9e42c2023-12-13 09:49:44 +0400161 "Root": t,
Giorgi Lekveishviliab7ff6e2024-03-29 13:11:30 +0400162 "EnvInfo": s.envInfo[key],
163 "DNSRecords": dnsRecords,
164 }
165 if err := tmplsParsed.status.Execute(w, data); err != nil {
Giorgi Lekveishvili46743d42023-12-10 15:47:23 +0400166 http.Error(w, err.Error(), http.StatusInternalServerError)
167 return
168 }
169}
170
Giorgi Lekveishvili1caed362023-12-13 16:29:43 +0400171func (s *EnvServer) publishDNSRecords(w http.ResponseWriter, r *http.Request) {
172 vars := mux.Vars(r)
173 key, ok := vars["key"]
174 if !ok {
175 http.Error(w, "Task key not provided", http.StatusBadRequest)
176 return
177 }
178 dnsRef, ok := s.dns[key]
179 if !ok {
180 http.Error(w, "Task dns configuration not found", http.StatusInternalServerError)
181 return
182 }
gioe72b54f2024-04-22 10:44:41 +0400183 records, err := s.dnsFetcher.Fetch(dnsRef.Address)
184 if err != nil {
185 http.Error(w, "Task dns configuration not found", http.StatusInternalServerError)
186 return
Giorgi Lekveishvili1caed362023-12-13 16:29:43 +0400187 }
188 r.ParseForm()
189 if apiToken, err := getFormValue(r.PostForm, "api-token"); err != nil {
190 http.Error(w, err.Error(), http.StatusBadRequest)
191 return
192 } else {
193 p := NewGandiUpdater(apiToken)
gioe72b54f2024-04-22 10:44:41 +0400194 zone := strings.Join(strings.Split(dnsRef.Zone, ".")[1:], ".") // TODO(gio): this is not gonna work with no subdomain case
195 if err := p.Update(zone, strings.Split(records, "\n")); err != nil {
Giorgi Lekveishvili1caed362023-12-13 16:29:43 +0400196 http.Error(w, err.Error(), http.StatusInternalServerError)
197 return
198 }
199 }
Giorgi Lekveishviliab7ff6e2024-03-29 13:11:30 +0400200 s.envInfo[key] = "Successfully published DNS records, waiting to propagate."
201 s.dnsPublished[key] = struct{}{}
Giorgi Lekveishvili1eec3e12023-12-18 21:12:29 +0400202 http.Redirect(w, r, fmt.Sprintf("/env/%s", key), http.StatusSeeOther)
Giorgi Lekveishvili1caed362023-12-13 16:29:43 +0400203}
204
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400205func (s *EnvServer) createEnvForm(w http.ResponseWriter, r *http.Request) {
Giorgi Lekveishvili5c1b06e2024-03-28 15:19:44 +0400206 if err := tmplsParsed.form.Execute(w, nil); err != nil {
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400207 http.Error(w, err.Error(), http.StatusInternalServerError)
208 }
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400209}
210
Giorgi Lekveishvili081f18f2023-11-07 14:58:10 +0400211func (s *EnvServer) createInvitation(w http.ResponseWriter, r *http.Request) {
212 invitations, err := s.readInvitations()
213 if err != nil {
214 http.Error(w, err.Error(), http.StatusInternalServerError)
215 return
216 }
217 token, err := installer.NewFixedLengthRandomNameGenerator(100).Generate() // TODO(giolekva): use cryptographic tokens
218 if err != nil {
219 http.Error(w, err.Error(), http.StatusInternalServerError)
220 return
221
222 }
223 invitations = append(invitations, invitation{token, StatusActive})
224 if err := s.writeInvitations(invitations); err != nil {
225 http.Error(w, err.Error(), http.StatusInternalServerError)
226 return
227 }
228 if _, err := w.Write([]byte("OK")); err != nil {
229 http.Error(w, err.Error(), http.StatusInternalServerError)
230 return
231 }
232}
233
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400234type createEnvReq struct {
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400235 Name string
236 ContactEmail string `json:"contactEmail"`
237 Domain string `json:"domain"`
238 AdminPublicKey string `json:"adminPublicKey"`
239 SecretToken string `json:"secretToken"`
Giorgi Lekveishvili081f18f2023-11-07 14:58:10 +0400240}
241
242func (s *EnvServer) readInvitations() ([]invitation, error) {
243 r, err := s.repo.Reader("invitations")
244 if err != nil {
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400245 if errors.Is(err, fs.ErrNotExist) {
246 return make([]invitation, 0), nil
247 }
Giorgi Lekveishvili081f18f2023-11-07 14:58:10 +0400248 return nil, err
249 }
250 defer r.Close()
251 dec := json.NewDecoder(r)
252 invitations := make([]invitation, 0)
253 for {
254 var i invitation
255 if err := dec.Decode(&i); err == io.EOF {
256 break
257 }
258 invitations = append(invitations, i)
259 }
260 return invitations, nil
261}
262
263func (s *EnvServer) writeInvitations(invitations []invitation) error {
264 w, err := s.repo.Writer("invitations")
265 if err != nil {
266 return err
267 }
268 defer w.Close()
269 enc := json.NewEncoder(w)
270 for _, i := range invitations {
271 if err := enc.Encode(i); err != nil {
272 return err
273 }
274 }
275 return s.repo.CommitAndPush("Generated new invitation")
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400276}
277
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400278func extractRequest(r *http.Request) (createEnvReq, error) {
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400279 var req createEnvReq
280 if err := func() error {
281 var err error
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400282 if err = r.ParseForm(); err != nil {
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400283 return err
284 }
Giorgi Lekveishvili081f18f2023-11-07 14:58:10 +0400285 if req.SecretToken, err = getFormValue(r.PostForm, "secret-token"); err != nil {
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400286 return err
287 }
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400288 if req.Domain, err = getFormValue(r.PostForm, "domain"); err != nil {
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400289 return err
290 }
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400291 if req.ContactEmail, err = getFormValue(r.PostForm, "contact-email"); err != nil {
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400292 return err
293 }
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400294 if req.AdminPublicKey, err = getFormValue(r.PostForm, "admin-public-key"); err != nil {
295 return err
296 }
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400297 return nil
298 }(); err != nil {
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400299 if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400300 return createEnvReq{}, err
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400301 }
302 }
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400303 return req, nil
304}
305
306func (s *EnvServer) acceptInvitation(token string) error {
Giorgi Lekveishvili081f18f2023-11-07 14:58:10 +0400307 invitations, err := s.readInvitations()
308 if err != nil {
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400309 return err
Giorgi Lekveishvili081f18f2023-11-07 14:58:10 +0400310 }
311 found := false
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400312 for i := range invitations {
313 if invitations[i].Token == token && invitations[i].Status == StatusActive {
314 invitations[i].Status = StatusAccepted
Giorgi Lekveishvili081f18f2023-11-07 14:58:10 +0400315 found = true
316 break
317 }
318 }
319 if !found {
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400320 return fmt.Errorf("Invitation not found")
321 }
322 return s.writeInvitations(invitations)
323}
324
325func (s *EnvServer) createEnv(w http.ResponseWriter, r *http.Request) {
326 req, err := extractRequest(r)
327 if err != nil {
328 http.Error(w, err.Error(), http.StatusInternalServerError)
Giorgi Lekveishvili081f18f2023-11-07 14:58:10 +0400329 return
330 }
gio3cdee592024-04-17 10:15:56 +0400331 mgr, err := installer.NewInfraAppManager(s.repo, s.nsCreator)
332 if err != nil {
333 http.Error(w, err.Error(), http.StatusInternalServerError)
334 return
335 }
336 var infra installer.InfraConfig
gioe72b54f2024-04-22 10:44:41 +0400337 if err := soft.ReadYaml(s.repo, "config.yaml", &infra); err != nil {
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400338 http.Error(w, err.Error(), http.StatusInternalServerError)
339 return
340 }
Giorgi Lekveishvili77ee2dc2023-12-11 16:51:10 +0400341 // if err := s.acceptInvitation(req.SecretToken); err != nil {
342 // http.Error(w, err.Error(), http.StatusInternalServerError)
343 // return
344 // }
Giorgi Lekveishvili081f18f2023-11-07 14:58:10 +0400345 if name, err := s.nameGenerator.Generate(); err != nil {
346 http.Error(w, err.Error(), http.StatusInternalServerError)
347 return
348 } else {
349 req.Name = name
350 }
Giorgi Lekveishvili9d5e3f52024-03-13 15:02:50 +0400351 var cidrs installer.EnvCIDRs
gioe72b54f2024-04-22 10:44:41 +0400352 if err := soft.ReadYaml(s.repo, "env-cidrs.yaml", &cidrs); err != nil {
Giorgi Lekveishvili9d5e3f52024-03-13 15:02:50 +0400353 http.Error(w, err.Error(), http.StatusInternalServerError)
354 return
355 }
356 startIP, err := findNextStartIP(cidrs)
357 if err != nil {
358 http.Error(w, err.Error(), http.StatusInternalServerError)
359 return
360 }
361 cidrs = append(cidrs, installer.EnvCIDR{req.Name, startIP})
gioe72b54f2024-04-22 10:44:41 +0400362 if err := soft.WriteYaml(s.repo, "env-cidrs.yaml", cidrs); err != nil {
Giorgi Lekveishvili9d5e3f52024-03-13 15:02:50 +0400363 http.Error(w, err.Error(), http.StatusInternalServerError)
364 return
365 }
Giorgi Lekveishvili5c1b06e2024-03-28 15:19:44 +0400366 if err := s.repo.CommitAndPush(fmt.Sprintf("Allocate CIDR for %s", req.Name)); err != nil {
367 http.Error(w, err.Error(), http.StatusInternalServerError)
368 return
369 }
gioe72b54f2024-04-22 10:44:41 +0400370 envNetwork, err := installer.NewEnvNetwork(startIP)
371 if err != nil {
372 http.Error(w, err.Error(), http.StatusInternalServerError)
373 return
374 }
375 env := installer.EnvConfig{
376 Id: req.Name,
377 InfraName: infra.Name,
378 Domain: req.Domain,
379 PrivateDomain: fmt.Sprintf("p.%s", req.Domain),
380 ContactEmail: req.ContactEmail,
381 AdminPublicKey: req.AdminPublicKey,
382 PublicIP: infra.PublicIP,
383 NameserverIP: infra.PublicIP,
384 NamespacePrefix: fmt.Sprintf("%s-", req.Name),
385 Network: envNetwork,
386 }
Giorgi Lekveishviliab7ff6e2024-03-29 13:11:30 +0400387 key := func() string {
388 for {
389 key, err := s.nameGenerator.Generate()
390 if err == nil {
391 return key
392 }
393 }
394 }()
395 infoUpdater := func(info string) {
396 s.envInfo[key] = template.HTML(markdown.ToHTML([]byte(info), nil, nil))
397 }
Giorgi Lekveishvilicd9e42c2023-12-13 09:49:44 +0400398 t, dns := tasks.NewCreateEnvTask(
gioe72b54f2024-04-22 10:44:41 +0400399 env,
Giorgi Lekveishvili46743d42023-12-10 15:47:23 +0400400 s.nsCreator,
gioe72b54f2024-04-22 10:44:41 +0400401 s.dnsFetcher,
402 s.httpClient,
403 s.dnsClient,
Giorgi Lekveishvili46743d42023-12-10 15:47:23 +0400404 s.repo,
gioe72b54f2024-04-22 10:44:41 +0400405 s.repoClient,
gio3cdee592024-04-17 10:15:56 +0400406 mgr,
Giorgi Lekveishviliab7ff6e2024-03-29 13:11:30 +0400407 infoUpdater,
Giorgi Lekveishvili46743d42023-12-10 15:47:23 +0400408 )
gioe72b54f2024-04-22 10:44:41 +0400409 s.Tasks[key] = t
Giorgi Lekveishvili1eec3e12023-12-18 21:12:29 +0400410 s.dns[key] = dns
Giorgi Lekveishvili77ee2dc2023-12-11 16:51:10 +0400411 go t.Start()
Giorgi Lekveishvilic85504d2023-12-20 19:29:47 +0400412 http.Redirect(w, r, fmt.Sprintf("/env/%s", key), http.StatusSeeOther)
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400413}
Giorgi Lekveishvili9d5e3f52024-03-13 15:02:50 +0400414
415func findNextStartIP(cidrs installer.EnvCIDRs) (net.IP, error) {
416 m, err := netip.ParseAddr("10.0.0.0")
417 if err != nil {
418 return nil, err
419 }
420 for _, cidr := range cidrs {
421 i, err := netip.ParseAddr(cidr.IP.String())
422 if err != nil {
423 return nil, err
424 }
425 if i.Compare(m) > 0 {
426 m = i
427 }
428 }
429 sl := m.AsSlice()
430 sl[2]++
431 if sl[2] == 0b11111111 {
432 sl[2] = 0
433 sl[1]++
434 }
435 if sl[1] == 0b11111111 {
436 return nil, fmt.Errorf("Can not allocate")
437 }
438 ret, ok := netip.AddrFromSlice(sl)
439 if !ok {
440 return nil, fmt.Errorf("Must not reach")
441 }
442 return net.ParseIP(ret.String()), nil
443}