16
"github.com/gorilla/mux"
18
"github.com/lxc/lxd/shared"
21
type devLxdResponse struct {
27
func okResponse(ct interface{}, ctype string) *devLxdResponse {
28
return &devLxdResponse{ct, http.StatusOK, ctype}
31
type devLxdHandler struct {
35
* This API will have to be changed slightly when we decide to support
36
* websocket events upgrading, but since we don't have events on the
37
* server side right now either, I went the simple route to avoid
40
f func(c container, r *http.Request) *devLxdResponse
43
var configGet = devLxdHandler{"/1.0/config", func(c container, r *http.Request) *devLxdResponse {
44
filtered := []string{}
45
for k, _ := range c.ExpandedConfig() {
46
if strings.HasPrefix(k, "user.") {
47
filtered = append(filtered, fmt.Sprintf("/1.0/config/%s", k))
50
return okResponse(filtered, "json")
53
var configKeyGet = devLxdHandler{"/1.0/config/{key}", func(c container, r *http.Request) *devLxdResponse {
54
key := mux.Vars(r)["key"]
55
if !strings.HasPrefix(key, "user.") {
56
return &devLxdResponse{"not authorized", http.StatusForbidden, "raw"}
59
value, ok := c.ExpandedConfig()[key]
61
return &devLxdResponse{"not found", http.StatusNotFound, "raw"}
64
return okResponse(value, "raw")
67
var metadataGet = devLxdHandler{"/1.0/meta-data", func(c container, r *http.Request) *devLxdResponse {
68
value := c.ExpandedConfig()["user.meta-data"]
69
return okResponse(fmt.Sprintf("#cloud-config\ninstance-id: %s\nlocal-hostname: %s\n%s", c.Name(), c.Name(), value), "raw")
72
var handlers = []devLxdHandler{
73
devLxdHandler{"/", func(c container, r *http.Request) *devLxdResponse {
74
return okResponse([]string{"/1.0"}, "json")
76
devLxdHandler{"/1.0", func(c container, r *http.Request) *devLxdResponse {
77
return okResponse(shared.Jmap{"api_version": shared.APIVersion}, "json")
85
func hoistReq(f func(container, *http.Request) *devLxdResponse, d *Daemon) func(http.ResponseWriter, *http.Request) {
86
return func(w http.ResponseWriter, r *http.Request) {
87
conn := extractUnderlyingConn(w)
88
cred, ok := pidMapper.m[conn]
90
http.Error(w, pidNotInContainerErr.Error(), 500)
94
c, err := findContainerForPid(cred.pid, d)
96
http.Error(w, err.Error(), 500)
103
idmapset, err := c.LastIdmapSet()
104
if err == nil && idmapset != nil {
105
uid, _ := idmapset.ShiftIntoNs(0, 0)
109
if rootUid != cred.uid {
110
http.Error(w, "Access denied for non-root user", 401)
115
if resp.code != http.StatusOK {
116
http.Error(w, fmt.Sprintf("%s", resp.content), resp.code)
117
} else if resp.ctype == "json" {
118
w.Header().Set("Content-Type", "application/json")
119
WriteJSON(w, resp.content)
121
w.Header().Set("Content-Type", "application/octet-stream")
122
fmt.Fprintf(w, resp.content.(string))
127
func createAndBindDevLxd() (*net.UnixListener, error) {
128
sockFile := path.Join(shared.VarPath("devlxd"), "sock")
131
* If this socket exists, that means a previous lxd died and didn't
132
* clean up after itself. We assume that the LXD is actually dead if we
133
* get this far, since StartDaemon() tries to connect to the actual lxd
134
* socket to make sure that it is actually dead. So, it is safe to
135
* remove it here without any checks.
137
* Also, it would be nice to SO_REUSEADDR here so we don't have to
138
* delete the socket, but we can't:
139
* http://stackoverflow.com/questions/15716302/so-reuseaddr-and-af-unix
141
* Note that this will force clients to reconnect when LXD is restarted.
143
if err := os.Remove(sockFile); err != nil && !os.IsNotExist(err) {
147
unixAddr, err := net.ResolveUnixAddr("unix", sockFile)
152
unixl, err := net.ListenUnix("unix", unixAddr)
157
if err := os.Chmod(sockFile, 0666); err != nil {
164
func devLxdServer(d *Daemon) *http.Server {
167
for _, handler := range handlers {
168
m.HandleFunc(handler.path, hoistReq(handler.f, d))
173
ConnState: pidMapper.ConnStateHandler,
178
* Everything below here is the guts of the unix socket bits. Unfortunately,
179
* golang's API does not make this easy. What happens is:
181
* 1. We install a ConnState listener on the http.Server, which does the
182
* initial unix socket credential exchange. When we get a connection started
183
* event, we use SO_PEERCRED to extract the creds for the socket.
185
* 2. We store a map from the connection pointer to the pid for that
186
* connection, so that once the HTTP negotiation occurrs and we get a
187
* ResponseWriter, we know (because we negotiated on the first byte) which
188
* pid the connection belogs to.
190
* 3. Regular HTTP negotiation and dispatch occurs via net/http.
192
* 4. When rendering the response via ResponseWriter, we match its underlying
193
* connection against what we stored in step (2) to figure out which container
198
* We keep this in a global so that we can reference it from the server and
199
* from our http handlers, since there appears to be no way to pass information
202
var pidMapper = ConnPidMapper{m: map[*net.UnixConn]*ucred{}}
210
type ConnPidMapper struct {
211
m map[*net.UnixConn]*ucred
214
func (m *ConnPidMapper) ConnStateHandler(conn net.Conn, state http.ConnState) {
215
unixConn := conn.(*net.UnixConn)
218
cred, err := getCred(unixConn)
220
shared.Debugf("Error getting ucred for conn %s", err)
224
case http.StateActive:
228
case http.StateHijacked:
230
* The "Hijacked" state indicates that the connection has been
231
* taken over from net/http. This is useful for things like
232
* developing websocket libraries, who want to upgrade the
233
* connection to a websocket one, and not use net/http any
234
* more. Whatever the case, we want to forget about it since we
235
* won't see it either.
237
delete(m.m, unixConn)
238
case http.StateClosed:
239
delete(m.m, unixConn)
241
shared.Debugf("Unknown state for connection %s", state)
246
* I also don't see that golang exports an API to get at the underlying FD, but
247
* we need it to get at SO_PEERCRED, so let's grab it.
249
func extractUnderlyingFd(unixConnPtr *net.UnixConn) int {
250
conn := reflect.Indirect(reflect.ValueOf(unixConnPtr))
251
netFdPtr := conn.FieldByName("fd")
252
netFd := reflect.Indirect(netFdPtr)
253
fd := netFd.FieldByName("sysfd")
257
func getCred(conn *net.UnixConn) (*ucred, error) {
258
fd := extractUnderlyingFd(conn)
260
uid, gid, pid, err := getUcred(fd)
265
return &ucred{pid, int64(uid), int64(gid)}, nil
269
* As near as I can tell, there is no nice way of extracting an underlying
270
* net.Conn (or in our case, net.UnixConn) from an http.Request or
271
* ResponseWriter without hijacking it [1]. Since we want to send and recieve
272
* unix creds to figure out which container this request came from, we need to
275
* [1]: https://groups.google.com/forum/#!topic/golang-nuts/_FWdFXJa6QA
277
func extractUnderlyingConn(w http.ResponseWriter) *net.UnixConn {
278
v := reflect.Indirect(reflect.ValueOf(w))
279
connPtr := v.FieldByName("conn")
280
conn := reflect.Indirect(connPtr)
281
rwc := conn.FieldByName("rwc")
283
netConnPtr := (*net.Conn)(unsafe.Pointer(rwc.UnsafeAddr()))
284
unixConnPtr := (*netConnPtr).(*net.UnixConn)
289
var pidNotInContainerErr = fmt.Errorf("pid not in container?")
291
func findContainerForPid(pid int32, d *Daemon) (container, error) {
293
* Try and figure out which container a pid is in. There is probably a
294
* better way to do this. Based on rharper's initial performance
295
* metrics, looping over every container and calling newLxdContainer is
296
* expensive, so I wanted to avoid that if possible, so this happens in
297
* a two step process:
299
* 1. Walk up the process tree until you see something that looks like
300
* an lxc monitor process and extract its name from there.
302
* 2. If this fails, it may be that someone did an `lxc exec foo bash`,
303
* so the process isn't actually a decendant of the container's
304
* init. In this case we just look through all the containers until
305
* we find an init with a matching pid namespace. This is probably
306
* uncommon, so hopefully the slowness won't hurt us.
312
cmdline, err := ioutil.ReadFile(fmt.Sprintf("/proc/%d/cmdline", pid))
317
if strings.HasPrefix(string(cmdline), "[lxc monitor]") {
318
// container names can't have spaces
319
parts := strings.Split(string(cmdline), " ")
320
name := strings.TrimSuffix(parts[len(parts)-1], "\x00")
322
return containerLoadByName(d, name)
325
status, err := ioutil.ReadFile(fmt.Sprintf("/proc/%d/status", pid))
330
re := regexp.MustCompile("PPid:\\s*([0-9]*)")
331
for _, line := range strings.Split(string(status), "\n") {
332
m := re.FindStringSubmatch(line)
333
if m != nil && len(m) > 1 {
334
result, err := strconv.Atoi(m[1])
345
origPidNs, err := os.Readlink(fmt.Sprintf("/proc/%d/ns/pid", origpid))
350
containers, err := dbContainersList(d.db, cTypeRegular)
355
for _, container := range containers {
356
c, err := containerLoadByName(d, container)
365
initpid := c.InitPID()
366
pidNs, err := os.Readlink(fmt.Sprintf("/proc/%d/ns/pid", initpid))
371
if origPidNs == pidNs {
376
return nil, pidNotInContainerErr