forked from KNEMC/KLALB
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dde54d0929
|
||
|
|
3dae7dd876
|
||
|
|
b6acc6d509
|
||
|
|
be82f55277
|
||
|
|
7896ee6638
|
||
|
|
ed94423855
|
||
|
|
c3172a04b1
|
||
|
|
cc803f47a7 | ||
|
|
3bb95988fc | ||
|
|
e87e3bb953 | ||
|
|
41db17f1ec | ||
|
|
61868fe93e
|
||
|
|
9cea5ed493
|
||
|
|
e0296e0ccd
|
||
|
|
9cea74943c
|
||
|
|
35c430114e
|
||
|
|
6c7017bc75
|
||
|
|
041cc31c5f
|
||
|
|
cc811eddfe
|
||
|
|
984031c6e2
|
||
|
|
75ff49a9d1
|
||
|
|
d6ac0da75d
|
||
|
|
455139dfe9
|
||
|
|
f0efa6d041 | ||
|
|
314f90b5f3 | ||
|
|
053450429c | ||
|
|
8dcdb08755 | ||
|
|
ba61d3b5ae | ||
|
|
74e4360d71 |
@@ -0,0 +1,277 @@
|
||||
---
|
||||
name: shadcn
|
||||
description: Manages shadcn components and projects — adding, searching, fixing, debugging, styling, and composing UI, including chat interfaces. Provides project context, component docs, and usage examples. Applies when working with shadcn/ui, component registries, presets, --preset codes, or any project with a components.json file. Also triggers for "shadcn init", "create an app with --preset", or "switch to --preset".
|
||||
user-invocable: false
|
||||
allowed-tools: Bash(npx shadcn@latest *), Bash(pnpm dlx shadcn@latest *), Bash(bunx --bun shadcn@latest *)
|
||||
---
|
||||
|
||||
# shadcn/ui
|
||||
|
||||
A framework for building ui, components and design systems. Components are added as source code to the user's project via the CLI.
|
||||
|
||||
> **IMPORTANT:** Run all CLI commands using the project's package runner: `npx shadcn@latest`, `pnpm dlx shadcn@latest`, or `bunx --bun shadcn@latest` — based on the project's `packageManager`. Examples below use `npx shadcn@latest` but substitute the correct runner for the project.
|
||||
|
||||
## Current Project Context
|
||||
|
||||
```json
|
||||
!`npx shadcn@latest info --json`
|
||||
```
|
||||
|
||||
The JSON above contains the project config and installed components. Use `npx shadcn@latest docs <component>` to get documentation and example URLs for any component.
|
||||
|
||||
## Principles
|
||||
|
||||
1. **Use existing components first.** Use `npx shadcn@latest search` to check registries before writing custom UI. Check community registries too.
|
||||
2. **Compose, don't reinvent.** Settings page = Tabs + Card + form controls. Dashboard = Sidebar + Card + Chart + Table.
|
||||
3. **Use built-in variants before custom styles.** `variant="outline"`, `size="sm"`, etc.
|
||||
4. **Use semantic colors.** `bg-primary`, `text-muted-foreground` — never raw values like `bg-blue-500`.
|
||||
|
||||
## Critical Rules
|
||||
|
||||
These rules are **always enforced**. Each links to a file with Incorrect/Correct code pairs.
|
||||
|
||||
### Styling & Tailwind → [styling.md](./rules/styling.md)
|
||||
|
||||
- **`className` for layout, not styling.** Never override component colors or typography.
|
||||
- **No `space-x-*` or `space-y-*`.** Use `flex` with `gap-*`. For vertical stacks, `flex flex-col gap-*`.
|
||||
- **Use `size-*` when width and height are equal.** `size-10` not `w-10 h-10`.
|
||||
- **Use `truncate` shorthand.** Not `overflow-hidden text-ellipsis whitespace-nowrap`.
|
||||
- **No manual `dark:` color overrides.** Use semantic tokens (`bg-background`, `text-muted-foreground`).
|
||||
- **Use `cn()` for conditional classes.** Don't write manual template literal ternaries.
|
||||
- **No manual `z-index` on overlay components.** Dialog, Sheet, Popover, etc. handle their own stacking.
|
||||
|
||||
### Forms & Inputs → [forms.md](./rules/forms.md)
|
||||
|
||||
- **Forms use `FieldGroup` + `Field`.** Never use raw `div` with `space-y-*` or `grid gap-*` for form layout.
|
||||
- **`InputGroup` uses `InputGroupInput`/`InputGroupTextarea`.** Never raw `Input`/`Textarea` inside `InputGroup`.
|
||||
- **Buttons inside inputs use `InputGroup` + `InputGroupAddon`.**
|
||||
- **Option sets (2–7 choices) use `ToggleGroup`.** Don't loop `Button` with manual active state.
|
||||
- **`FieldSet` + `FieldLegend` for grouping related checkboxes/radios.** Don't use a `div` with a heading.
|
||||
- **Field validation uses `data-invalid` + `aria-invalid`.** `data-invalid` on `Field`, `aria-invalid` on the control. For disabled: `data-disabled` on `Field`, `disabled` on the control.
|
||||
|
||||
### Component Structure → [composition.md](./rules/composition.md)
|
||||
|
||||
- **Items always inside their Group.** `SelectItem` → `SelectGroup`. `DropdownMenuItem` → `DropdownMenuGroup`. `CommandItem` → `CommandGroup`.
|
||||
- **Use `asChild` (radix) or `render` (base) for custom triggers.** Check `base` field from `npx shadcn@latest info`. → [base-vs-radix.md](./rules/base-vs-radix.md)
|
||||
- **Dialog, Sheet, and Drawer always need a Title.** `DialogTitle`, `SheetTitle`, `DrawerTitle` required for accessibility. Use `className="sr-only"` if visually hidden.
|
||||
- **Use full Card composition.** `CardHeader`/`CardTitle`/`CardDescription`/`CardContent`/`CardFooter`. Don't dump everything in `CardContent`.
|
||||
- **Button has no `isPending`/`isLoading`.** Compose with `Spinner` + `data-icon` + `disabled`.
|
||||
- **`TabsTrigger` must be inside `TabsList`.** Never render triggers directly in `Tabs`.
|
||||
- **`Avatar` always needs `AvatarFallback`.** For when the image fails to load.
|
||||
|
||||
### Use Components, Not Custom Markup → [composition.md](./rules/composition.md)
|
||||
|
||||
- **Use existing components before custom markup.** Check if a component exists before writing a styled `div`.
|
||||
- **Callouts use `Alert`.** Don't build custom styled divs.
|
||||
- **Empty states use `Empty`.** Don't build custom empty state markup.
|
||||
- **Toast follows the project base.** Use `toast` from the `toast` component for
|
||||
Base UI projects. Use `toast()` from `sonner` for Radix and React Aria
|
||||
projects.
|
||||
- **Use `Separator`** instead of `<hr>` or `<div className="border-t">`.
|
||||
- **Use `Skeleton`** for loading placeholders. No custom `animate-pulse` divs.
|
||||
- **Use `Badge`** instead of custom styled spans.
|
||||
|
||||
### Icons → [icons.md](./rules/icons.md)
|
||||
|
||||
- **Icons in `Button` use `data-icon`.** `data-icon="inline-start"` or `data-icon="inline-end"` on the icon.
|
||||
- **No sizing classes on icons inside components.** Components handle icon sizing via CSS. No `size-4` or `w-4 h-4`.
|
||||
- **Pass icons as objects, not string keys.** `icon={CheckIcon}`, not a string lookup.
|
||||
|
||||
### Chat & Messaging → [chat.md](./rules/chat.md)
|
||||
|
||||
- **Chat UI composes the chat primitives.** Conversations use `MessageScroller`, rows use `Message`, surfaces use `Bubble`. Never hand-rolled bubble `div`s or a raw scroll container.
|
||||
- **`MessageScroller` owns scroll behavior.** Streaming follow, anchoring, and jump-to-latest (`MessageScrollerButton`) are built in. Don't write a `useStickToBottom`/`ResizeObserver` hook.
|
||||
- **Attachments use `Attachment`; system notes and dividers use `Marker`.** Not `Item` cards or `Separator` + a label.
|
||||
|
||||
### CLI
|
||||
|
||||
- **Never decode preset codes or build preset URLs manually.** Use `npx shadcn@latest preset decode <code>`, `preset url <code>`, or `preset open <code>`. For project-aware preset detection, use `npx shadcn@latest preset resolve`.
|
||||
- **Apply preset codes directly with the CLI.** Use `npx shadcn@latest apply <code>` for existing projects, or `npx shadcn@latest init --preset <code>` when initializing.
|
||||
|
||||
## Key Patterns
|
||||
|
||||
These are the most common patterns that differentiate correct shadcn/ui code. For edge cases, see the linked rule files above.
|
||||
|
||||
```tsx
|
||||
// Form layout: FieldGroup + Field, not div + Label.
|
||||
<FieldGroup>
|
||||
<Field>
|
||||
<FieldLabel htmlFor="email">Email</FieldLabel>
|
||||
<Input id="email" />
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
|
||||
// Validation: data-invalid on Field, aria-invalid on the control.
|
||||
<Field data-invalid>
|
||||
<FieldLabel>Email</FieldLabel>
|
||||
<Input aria-invalid />
|
||||
<FieldDescription>Invalid email.</FieldDescription>
|
||||
</Field>
|
||||
|
||||
// Icons in buttons: data-icon, no sizing classes.
|
||||
<Button>
|
||||
<SearchIcon data-icon="inline-start" />
|
||||
Search
|
||||
</Button>
|
||||
|
||||
// Spacing: gap-*, not space-y-*.
|
||||
<div className="flex flex-col gap-4"> // correct
|
||||
<div className="space-y-4"> // wrong
|
||||
|
||||
// Equal dimensions: size-*, not w-* h-*.
|
||||
<Avatar className="size-10"> // correct
|
||||
<Avatar className="w-10 h-10"> // wrong
|
||||
|
||||
// Status colors: Badge variants or semantic tokens, not raw colors.
|
||||
<Badge variant="secondary">+20.1%</Badge> // correct
|
||||
<span className="text-emerald-600">+20.1%</span> // wrong
|
||||
```
|
||||
|
||||
## Component Selection
|
||||
|
||||
| Need | Use |
|
||||
| -------------------------- | --------------------------------------------------------------------------------------------------- |
|
||||
| Button/action | `Button` with appropriate variant |
|
||||
| Form inputs | `Input`, `Select`, `Combobox`, `Switch`, `Checkbox`, `RadioGroup`, `Textarea`, `InputOTP`, `Slider` |
|
||||
| Toggle between 2–5 options | `ToggleGroup` + `ToggleGroupItem` |
|
||||
| Data display | `Table`, `Card`, `Badge`, `Avatar` |
|
||||
| Navigation | `Sidebar`, `NavigationMenu`, `Breadcrumb`, `Tabs`, `Pagination` |
|
||||
| Overlays | `Dialog` (modal), `Sheet` (side panel), `Drawer` (bottom sheet), `AlertDialog` (confirmation) |
|
||||
| Feedback | `toast` (Base UI), `sonner` (Radix/Aria), `Alert`, `Progress`, `Skeleton`, `Spinner` |
|
||||
| Command palette | `Command` inside `Dialog` |
|
||||
| Charts | `Chart` (wraps Recharts) |
|
||||
| Layout | `Card`, `Separator`, `Resizable`, `ScrollArea`, `Accordion`, `Collapsible` |
|
||||
| Empty states | `Empty` |
|
||||
| Menus | `DropdownMenu`, `ContextMenu`, `Menubar` |
|
||||
| Tooltips/info | `Tooltip`, `HoverCard`, `Popover` |
|
||||
| Chat / conversation UI | `MessageScroller`, `Message`, `Bubble`, `Attachment`, `Marker` |
|
||||
|
||||
## Key Fields
|
||||
|
||||
The injected project context contains these key fields:
|
||||
|
||||
- **`aliases`** → use the actual alias prefix for imports (e.g. `@/`, `~/`), never hardcode.
|
||||
- **`isRSC`** → when `true`, components using `useState`, `useEffect`, event handlers, or browser APIs need `"use client"` at the top of the file. Always reference this field when advising on the directive.
|
||||
- **`tailwindVersion`** → `"v4"` uses `@theme inline` blocks; `"v3"` uses `tailwind.config.js`.
|
||||
- **`tailwindCssFile`** → the global CSS file where custom CSS variables are defined. Always edit this file, never create a new one.
|
||||
- **`style`** → component visual treatment (e.g. `nova`, `vega`).
|
||||
- **`base`** → primitive library (`radix` or `base`). Affects component APIs and available props.
|
||||
- **`iconLibrary`** → determines icon imports. Use `lucide-react` for `lucide`, `@tabler/icons-react` for `tabler`, etc. Never assume `lucide-react`.
|
||||
- **`resolvedPaths`** → exact file-system destinations for components, utils, hooks, etc.
|
||||
- **`framework`** → routing and file conventions (e.g. Next.js App Router vs Vite SPA).
|
||||
- **`packageManager`** → use this for any non-shadcn dependency installs (e.g. `pnpm add date-fns` vs `npm install date-fns`).
|
||||
- **`preset`** → resolved preset code and values for the current project. Use `npx shadcn@latest preset resolve --json` when you only need preset information.
|
||||
|
||||
See [cli.md — `info` command](./cli.md) for the full field reference.
|
||||
|
||||
## Component Docs, Examples, and Usage
|
||||
|
||||
Run `npx shadcn@latest docs <component>` to get the URLs for a component's documentation, examples, and API reference. Fetch these URLs to get the actual content.
|
||||
|
||||
```bash
|
||||
npx shadcn@latest docs button dialog select
|
||||
```
|
||||
|
||||
**When creating, fixing, debugging, or using a component, always run `npx shadcn@latest docs` and fetch the URLs first.** This ensures you're working with the correct API and usage patterns rather than guessing.
|
||||
|
||||
## Workflow
|
||||
|
||||
1. **Get project context** — already injected above. Run `npx shadcn@latest info` again if you need to refresh.
|
||||
2. **Check installed components first** — before running `add`, always check the `components` list from project context or list the `resolvedPaths.ui` directory. Don't import components that haven't been added, and don't re-add ones already installed.
|
||||
3. **Find components** — `npx shadcn@latest search`.
|
||||
4. **Get docs and examples** — run `npx shadcn@latest docs <component>` to get URLs, then fetch them. Use `npx shadcn@latest view` to browse registry items you haven't installed. To preview changes to installed components, use `npx shadcn@latest add --diff`.
|
||||
5. **Install or update** — `npx shadcn@latest add`. When updating existing components, use `--dry-run` and `--diff` to preview changes first (see [Updating Components](#updating-components) below).
|
||||
6. **Fix imports in third-party components** — After adding components from community registries (e.g. `@bundui`, `@magicui`), check the added non-UI files for hardcoded import paths like `@/components/ui/...`. These won't match the project's actual aliases. Use `npx shadcn@latest info` to get the correct `ui` alias (e.g. `@workspace/ui/components`) and rewrite the imports accordingly. The CLI rewrites imports for its own UI files, but third-party registry components may use default paths that don't match the project.
|
||||
7. **Review added components** — After adding a component or block from any registry, **always read the added files and verify they are correct**. Check for missing sub-components (e.g. `SelectItem` without `SelectGroup`), missing imports, incorrect composition, or violations of the [Critical Rules](#critical-rules). Also replace any icon imports with the project's `iconLibrary` from the project context (e.g. if the registry item uses `lucide-react` but the project uses `hugeicons`, swap the imports and icon names accordingly). Fix all issues before moving on.
|
||||
8. **Registry must be explicit** — When the user asks to add a block or component, **do not guess the registry**. If no registry is specified (e.g. user says "add a login block" without specifying `@shadcn`, `@tailark`, `owner/repo`, etc.), ask which registry to use. Never default to a registry on behalf of the user.
|
||||
9. **Switching presets** — Ask the user first: **overwrite**, **partial**, **merge**, or **skip**?
|
||||
- **Inspect current preset**: `npx shadcn@latest preset resolve`. Use `--json` when you need structured values.
|
||||
- **Inspect incoming preset**: `npx shadcn@latest preset decode <code>`. Use `preset url <code>` or `preset open <code>` to share or open the preset builder.
|
||||
- **Overwrite**: `npx shadcn@latest apply <code>`. Overwrites detected components, fonts, and CSS variables.
|
||||
- **Partial**: `npx shadcn@latest apply <code> --only theme,font`. Updates only the selected preset parts without reinstalling UI components. Supported values are `theme` and `font`; comma-separated combinations are allowed. `icon` is intentionally not supported, because icon changes may require full component reinstall and transforms.
|
||||
- **Merge**: `npx shadcn@latest init --preset <code> --force --no-reinstall`, then run `npx shadcn@latest info` to list installed components, then for each installed component use `--dry-run` and `--diff` to [smart merge](#updating-components) it individually.
|
||||
- **Skip**: `npx shadcn@latest init --preset <code> --force --no-reinstall`. Only updates config and CSS, leaves components as-is.
|
||||
- **Important**: Always run preset commands inside the user's project directory. `apply` only works in an existing project with a `components.json` file. The CLI automatically preserves the current base (`base` vs `radix`) from `components.json`. If you must use a scratch/temp directory (e.g. for `--dry-run` comparisons), pass `--base <current-base>` explicitly — preset codes do not encode the base.
|
||||
|
||||
## Updating Components
|
||||
|
||||
When the user asks to update a component from upstream while keeping their local changes, use `--dry-run` and `--diff` to intelligently merge. **NEVER fetch raw files from GitHub manually — always use the CLI.**
|
||||
|
||||
1. Run `npx shadcn@latest add <component> --dry-run` to see all files that would be affected.
|
||||
2. For each file, run `npx shadcn@latest add <component> --diff <file>` to see what changed upstream vs local.
|
||||
3. Decide per file based on the diff:
|
||||
- No local changes → safe to overwrite.
|
||||
- Has local changes → read the local file, analyze the diff, and apply upstream updates while preserving local modifications.
|
||||
- User says "just update everything" → use `--overwrite`, but confirm first.
|
||||
4. **Never use `--overwrite` without the user's explicit approval.**
|
||||
|
||||
## Quick Reference
|
||||
|
||||
```bash
|
||||
# Create a new project.
|
||||
npx shadcn@latest init --name my-app --preset base-nova
|
||||
npx shadcn@latest init --name my-app --preset a2r6bw --template vite
|
||||
|
||||
# Create a monorepo project.
|
||||
npx shadcn@latest init --name my-app --preset base-nova --monorepo
|
||||
npx shadcn@latest init --name my-app --preset base-nova --template next --monorepo
|
||||
|
||||
# Initialize existing project.
|
||||
npx shadcn@latest init --preset base-nova
|
||||
npx shadcn@latest init --defaults # shortcut: --template=next --preset=nova (base style implied)
|
||||
|
||||
# Apply a preset to an existing project.
|
||||
npx shadcn@latest apply a2r6bw
|
||||
npx shadcn@latest apply a2r6bw --only theme
|
||||
npx shadcn@latest apply a2r6bw --only font
|
||||
npx shadcn@latest apply a2r6bw --only theme,font
|
||||
|
||||
# Inspect preset codes and project preset state.
|
||||
npx shadcn@latest preset decode a2r6bw
|
||||
npx shadcn@latest preset url a2r6bw
|
||||
npx shadcn@latest preset open a2r6bw
|
||||
npx shadcn@latest preset resolve
|
||||
npx shadcn@latest preset resolve --json
|
||||
|
||||
# Add components.
|
||||
npx shadcn@latest add button card dialog
|
||||
npx shadcn@latest add @magicui/shimmer-button
|
||||
npx shadcn@latest add owner/repo/item
|
||||
npx shadcn@latest add --all
|
||||
|
||||
# Preview changes before adding/updating.
|
||||
npx shadcn@latest add button --dry-run
|
||||
npx shadcn@latest add button --diff button.tsx
|
||||
npx shadcn@latest add @acme/form --view button.tsx
|
||||
npx shadcn@latest add owner/repo/item --dry-run
|
||||
|
||||
# Search registries.
|
||||
npx shadcn@latest search @shadcn -q "sidebar"
|
||||
npx shadcn@latest search @tailark -q "stats"
|
||||
npx shadcn@latest search owner/repo -q "login"
|
||||
npx shadcn@latest search # all configured registries
|
||||
npx shadcn@latest search @shadcn -q "menu" -t ui # filter by item type
|
||||
|
||||
# Get component docs and example URLs.
|
||||
npx shadcn@latest docs button dialog select
|
||||
|
||||
# View registry item details (for items not yet installed).
|
||||
npx shadcn@latest view @shadcn/button
|
||||
npx shadcn@latest view owner/repo/item
|
||||
```
|
||||
|
||||
**Named presets:** `nova`, `vega`, `maia`, `lyra`, `mira`, `luma`
|
||||
**Templates:** `next`, `vite`, `start`, `react-router`, `astro` (all support `--monorepo`) and `laravel` (not supported for monorepo)
|
||||
**Preset codes:** Version-prefixed base62 strings (e.g. `a2r6bw` or `b0`), from [ui.shadcn.com](https://ui.shadcn.com).
|
||||
|
||||
## Detailed References
|
||||
|
||||
- [rules/forms.md](./rules/forms.md) — FieldGroup, Field, InputGroup, ToggleGroup, FieldSet, validation states
|
||||
- [rules/composition.md](./rules/composition.md) — Groups, overlays, Card, Tabs, Avatar, Alert, Empty, Toast, Separator, Skeleton, Badge, Button loading
|
||||
- [rules/chat.md](./rules/chat.md) — MessageScroller, Message, Bubble, Attachment, Marker; streaming, anchoring, jump-to-latest
|
||||
- [rules/icons.md](./rules/icons.md) — data-icon, icon sizing, passing icons as objects
|
||||
- [rules/styling.md](./rules/styling.md) — Semantic colors, variants, className, spacing, size, truncate, dark mode, cn(), z-index
|
||||
- [rules/base-vs-radix.md](./rules/base-vs-radix.md) — asChild vs render, Select, ToggleGroup, Slider, Accordion
|
||||
- [cli.md](./cli.md) — Commands, flags, presets, templates
|
||||
- [registry.md](./registry.md) — Authoring source registries, `include`, item definitions, dependencies, GitHub registry rules
|
||||
- [customization.md](./customization.md) — Theming, CSS variables, extending components
|
||||
@@ -0,0 +1,5 @@
|
||||
interface:
|
||||
display_name: "shadcn/ui"
|
||||
short_description: "Manages shadcn/ui components — adding, searching, fixing, debugging, styling, and composing UI."
|
||||
icon_small: "./assets/shadcn-small.png"
|
||||
icon_large: "./assets/shadcn.png"
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 1.0 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 3.8 KiB |
@@ -0,0 +1,290 @@
|
||||
# shadcn CLI Reference
|
||||
|
||||
Configuration is read from `components.json`.
|
||||
|
||||
> **IMPORTANT:** Always run commands using the project's package runner: `npx shadcn@latest`, `pnpm dlx shadcn@latest`, or `bunx --bun shadcn@latest`. Check `packageManager` from project context to choose the right one. Examples below use `npx shadcn@latest` but substitute the correct runner for the project.
|
||||
|
||||
> **IMPORTANT:** Only use the flags documented below. Do not invent or guess flags — if a flag isn't listed here, it doesn't exist. The CLI auto-detects the package manager from the project's lockfile; there is no `--package-manager` flag.
|
||||
|
||||
## Contents
|
||||
|
||||
- Commands: init, apply, add (dry-run, smart merge), search, view, docs, info, build
|
||||
- Templates: next, vite, start, react-router, astro
|
||||
- Presets: named, code, URL formats and fields
|
||||
- Switching presets
|
||||
|
||||
---
|
||||
|
||||
## Commands
|
||||
|
||||
### `init` — Initialize or create a project
|
||||
|
||||
```bash
|
||||
npx shadcn@latest init [components...] [options]
|
||||
```
|
||||
|
||||
Initializes shadcn/ui in an existing project or creates a new project (when `--name` is provided). Optionally installs components in the same step.
|
||||
|
||||
| Flag | Short | Description | Default |
|
||||
| ----------------------- | ----- | --------------------------------------------------------- | ------- |
|
||||
| `--template <template>` | `-t` | Template (next, start, vite, next-monorepo, react-router) | — |
|
||||
| `--preset [name]` | `-p` | Preset configuration (named, code, or URL) | — |
|
||||
| `--yes` | `-y` | Skip confirmation prompt | `true` |
|
||||
| `--defaults` | `-d` | Use defaults (`--template=next --preset=base-nova`) | `false` |
|
||||
| `--force` | `-f` | Force overwrite existing configuration | `false` |
|
||||
| `--cwd <cwd>` | `-c` | Working directory | current |
|
||||
| `--name <name>` | `-n` | Name for new project | — |
|
||||
| `--silent` | `-s` | Mute output | `false` |
|
||||
| `--rtl` | | Enable RTL support | — |
|
||||
| `--reinstall` | | Re-install existing UI components | `false` |
|
||||
| `--monorepo` | | Scaffold a monorepo project | — |
|
||||
| `--no-monorepo` | | Skip the monorepo prompt | — |
|
||||
|
||||
`npx shadcn@latest create` is an alias for `npx shadcn@latest init`.
|
||||
|
||||
### `apply` — Apply a preset to an existing project
|
||||
|
||||
```bash
|
||||
npx shadcn@latest apply [preset] [options]
|
||||
```
|
||||
|
||||
Applies a preset to an existing project, overwriting preset-driven config, fonts, CSS variables, and detected UI components.
|
||||
|
||||
| Flag | Short | Description | Default |
|
||||
| ------------------- | ----- | ------------------------------------------ | ------- |
|
||||
| `--preset <preset>` | — | Preset configuration (named, code, or URL) | — |
|
||||
| `--yes` | `-y` | Skip confirmation prompt | `false` |
|
||||
| `--cwd <cwd>` | `-c` | Working directory | current |
|
||||
| `--silent` | `-s` | Mute output | `false` |
|
||||
|
||||
`[preset]` is a shorthand for `--preset <preset>`. If both are provided, they must match.
|
||||
If no preset is provided, the CLI offers to open the custom preset builder on `ui.shadcn.com/create`.
|
||||
|
||||
### `add` — Add components
|
||||
|
||||
> **IMPORTANT:** To compare local components against upstream or to preview changes, ALWAYS use `npx shadcn@latest add <component> --dry-run`, `--diff`, or `--view`. NEVER fetch raw files from GitHub or other sources manually. The CLI handles registry resolution, file paths, and CSS diffing automatically.
|
||||
|
||||
```bash
|
||||
npx shadcn@latest add [components...] [options]
|
||||
```
|
||||
|
||||
Accepts component names, registry-prefixed names (`@magicui/shimmer-button`),
|
||||
GitHub item addresses (`owner/repo/item`), URLs, or local paths.
|
||||
|
||||
| Flag | Short | Description | Default |
|
||||
| --------------- | ----- | -------------------------------------------------------------------------------------------------------------------- | ------- |
|
||||
| `--yes` | `-y` | Skip confirmation prompt | `false` |
|
||||
| `--overwrite` | `-o` | Overwrite existing files | `false` |
|
||||
| `--cwd <cwd>` | `-c` | Working directory | current |
|
||||
| `--all` | `-a` | Add all available components | `false` |
|
||||
| `--path <path>` | `-p` | Target path for the component | — |
|
||||
| `--silent` | `-s` | Mute output | `false` |
|
||||
| `--dry-run` | | Preview all changes without writing files | `false` |
|
||||
| `--diff [path]` | | Show diffs. Without a path, shows the first 5 files. With a path, shows that file only (implies `--dry-run`) | — |
|
||||
| `--view [path]` | | Show file contents. Without a path, shows the first 5 files. With a path, shows that file only (implies `--dry-run`) | — |
|
||||
|
||||
#### Dry-Run Mode
|
||||
|
||||
Use `--dry-run` to preview what `add` would do without writing any files. `--diff` and `--view` both imply `--dry-run`.
|
||||
|
||||
```bash
|
||||
# Preview all changes.
|
||||
npx shadcn@latest add button --dry-run
|
||||
|
||||
# Show diffs for all files (top 5).
|
||||
npx shadcn@latest add button --diff
|
||||
|
||||
# Show the diff for a specific file.
|
||||
npx shadcn@latest add button --diff button.tsx
|
||||
|
||||
# Show contents for all files (top 5).
|
||||
npx shadcn@latest add button --view
|
||||
|
||||
# Show the full content of a specific file.
|
||||
npx shadcn@latest add button --view button.tsx
|
||||
|
||||
# Works with URLs too.
|
||||
npx shadcn@latest add https://api.npoint.io/abc123 --dry-run
|
||||
|
||||
# Works with public GitHub registries too.
|
||||
npx shadcn@latest add owner/repo/item --dry-run
|
||||
|
||||
# CSS diffs.
|
||||
npx shadcn@latest add button --diff globals.css
|
||||
```
|
||||
|
||||
**When to use dry-run:**
|
||||
|
||||
- When the user asks "what files will this add?" or "what will this change?" — use `--dry-run`.
|
||||
- Before overwriting existing components — use `--diff` to preview the changes first.
|
||||
- When the user wants to inspect component source code without installing — use `--view`.
|
||||
- When checking what CSS changes would be made to `globals.css` — use `--diff globals.css`.
|
||||
- When the user asks to review or audit third-party registry code before installing — use `--view` to inspect the source.
|
||||
|
||||
> **`npx shadcn@latest add --dry-run` vs `npx shadcn@latest view`:** Prefer `npx shadcn@latest add --dry-run/--diff/--view` over `npx shadcn@latest view` when the user wants to preview changes to their project. `npx shadcn@latest view` only shows raw registry metadata. `npx shadcn@latest add --dry-run` shows exactly what would happen in the user's project: resolved file paths, diffs against existing files, and CSS updates. Use `npx shadcn@latest view` only when the user wants to browse registry info without a project context.
|
||||
|
||||
#### Smart Merge from Upstream
|
||||
|
||||
See [Updating Components in SKILL.md](./SKILL.md#updating-components) for the full workflow.
|
||||
|
||||
### `search` — Search registries
|
||||
|
||||
```bash
|
||||
npx shadcn@latest search [registries...] [options]
|
||||
```
|
||||
|
||||
Fuzzy search across registries. Also aliased as `npx shadcn@latest list`.
|
||||
Supports namespaces (`@acme`), public GitHub registry sources (`owner/repo`),
|
||||
and registry catalog URLs. Without `-q`, lists all items. When no registries are
|
||||
passed, searches every registry configured in `components.json`.
|
||||
|
||||
| Flag | Short | Description | Default |
|
||||
| ------------------- | ----- | ------------------------------------------------- | ------- |
|
||||
| `--query <query>` | `-q` | Search query | — |
|
||||
| `--type <type>` | `-t` | Filter by item type (e.g. `ui`, `block`, `hook`); comma-separated | — |
|
||||
| `--limit <number>` | `-l` | Max items to display | `100` |
|
||||
| `--offset <number>` | `-o` | Items to skip | `0` |
|
||||
| `--json` | | Output as JSON | `false` |
|
||||
| `--cwd <cwd>` | `-c` | Working directory | current |
|
||||
|
||||
### `view` — View item details
|
||||
|
||||
```bash
|
||||
npx shadcn@latest view <items...> [options]
|
||||
```
|
||||
|
||||
Displays item info including file contents. Examples:
|
||||
`npx shadcn@latest view @shadcn/button`,
|
||||
`npx shadcn@latest view owner/repo/item`.
|
||||
|
||||
### `docs` — Get component documentation URLs
|
||||
|
||||
```bash
|
||||
npx shadcn@latest docs <components...> [options]
|
||||
```
|
||||
|
||||
Outputs resolved URLs for component documentation, examples, and API references. Accepts one or more component names. Fetch the URLs to get the actual content.
|
||||
|
||||
Example output for `npx shadcn@latest docs input button`:
|
||||
|
||||
```
|
||||
base radix
|
||||
|
||||
input
|
||||
docs https://ui.shadcn.com/docs/components/radix/input
|
||||
examples https://raw.githubusercontent.com/.../examples/input-example.tsx
|
||||
|
||||
button
|
||||
docs https://ui.shadcn.com/docs/components/radix/button
|
||||
examples https://raw.githubusercontent.com/.../examples/button-example.tsx
|
||||
```
|
||||
|
||||
Some components include an `api` link to the underlying library (e.g. `cmdk` for the command component).
|
||||
|
||||
### `diff` — Check for updates
|
||||
|
||||
Do not use this command. Use `npx shadcn@latest add --diff` instead.
|
||||
|
||||
### `info` — Project information
|
||||
|
||||
```bash
|
||||
npx shadcn@latest info [options]
|
||||
```
|
||||
|
||||
Displays project info and `components.json` configuration. Run this first to discover the project's framework, aliases, Tailwind version, and resolved paths.
|
||||
|
||||
| Flag | Short | Description | Default |
|
||||
| ------------- | ----- | ----------------- | ------- |
|
||||
| `--cwd <cwd>` | `-c` | Working directory | current |
|
||||
|
||||
**Project Info fields:**
|
||||
|
||||
| Field | Type | Meaning |
|
||||
| -------------------- | --------- | ------------------------------------------------------------------ |
|
||||
| `framework` | `string` | Detected framework (`next`, `vite`, `react-router`, `start`, etc.) |
|
||||
| `frameworkVersion` | `string` | Framework version (e.g. `15.2.4`) |
|
||||
| `isSrcDir` | `boolean` | Whether the project uses a `src/` directory |
|
||||
| `isRSC` | `boolean` | Whether React Server Components are enabled |
|
||||
| `isTsx` | `boolean` | Whether the project uses TypeScript |
|
||||
| `tailwindVersion` | `string` | `"v3"` or `"v4"` |
|
||||
| `tailwindConfigFile` | `string` | Path to the Tailwind config file |
|
||||
| `tailwindCssFile` | `string` | Path to the global CSS file |
|
||||
| `aliasPrefix` | `string` | Import alias prefix (e.g. `@`, `~`, `@/`) |
|
||||
| `packageManager` | `string` | Detected package manager (`npm`, `pnpm`, `yarn`, `bun`) |
|
||||
|
||||
**Components.json fields:**
|
||||
|
||||
| Field | Type | Meaning |
|
||||
| -------------------- | --------- | ------------------------------------------------------------------------------------------ |
|
||||
| `base` | `string` | Primitive library (`radix` or `base`) — determines component APIs and available props |
|
||||
| `style` | `string` | Visual style (e.g. `nova`, `vega`) |
|
||||
| `rsc` | `boolean` | RSC flag from config |
|
||||
| `tsx` | `boolean` | TypeScript flag |
|
||||
| `tailwind.config` | `string` | Tailwind config path |
|
||||
| `tailwind.css` | `string` | Global CSS path — this is where custom CSS variables go |
|
||||
| `iconLibrary` | `string` | Icon library — determines icon import package (e.g. `lucide-react`, `@tabler/icons-react`) |
|
||||
| `aliases.components` | `string` | Component import alias (e.g. `@/components`) |
|
||||
| `aliases.utils` | `string` | Utils import alias (e.g. `@/lib/utils`) |
|
||||
| `aliases.ui` | `string` | UI component alias (e.g. `@/components/ui`) |
|
||||
| `aliases.lib` | `string` | Lib alias (e.g. `@/lib`) |
|
||||
| `aliases.hooks` | `string` | Hooks alias (e.g. `@/hooks`) |
|
||||
| `resolvedPaths` | `object` | Absolute file-system paths for each alias |
|
||||
| `registries` | `object` | Configured custom registries |
|
||||
|
||||
**Links fields:**
|
||||
|
||||
The `info` output includes a **Links** section with templated URLs for component docs, source, and examples. For resolved URLs, use `npx shadcn@latest docs <component>` instead.
|
||||
|
||||
### `build` — Build a custom registry
|
||||
|
||||
```bash
|
||||
npx shadcn@latest build [registry] [options]
|
||||
```
|
||||
|
||||
Builds `registry.json` into individual JSON files for distribution. Default input: `./registry.json`, default output: `./public/r`.
|
||||
|
||||
For authoring rules, `include`, item definitions, `registryDependencies`, and
|
||||
GitHub registry behavior, see [registry.md](./registry.md).
|
||||
|
||||
| Flag | Short | Description | Default |
|
||||
| ----------------- | ----- | ----------------- | ------------ |
|
||||
| `--output <path>` | `-o` | Output directory | `./public/r` |
|
||||
| `--cwd <cwd>` | `-c` | Working directory | current |
|
||||
|
||||
---
|
||||
|
||||
## Templates
|
||||
|
||||
| Value | Framework | Monorepo support |
|
||||
| -------------- | -------------- | ---------------- |
|
||||
| `next` | Next.js | Yes |
|
||||
| `vite` | Vite | Yes |
|
||||
| `start` | TanStack Start | Yes |
|
||||
| `react-router` | React Router | Yes |
|
||||
| `astro` | Astro | Yes |
|
||||
| `laravel` | Laravel | No |
|
||||
|
||||
All templates support monorepo scaffolding via the `--monorepo` flag. When passed, the CLI uses a monorepo-specific template directory (e.g. `next-monorepo`, `vite-monorepo`). When neither `--monorepo` nor `--no-monorepo` is passed, the CLI prompts interactively. Laravel does not support monorepo scaffolding.
|
||||
|
||||
---
|
||||
|
||||
## Presets
|
||||
|
||||
Three ways to specify a preset via `--preset`:
|
||||
|
||||
1. **Named:** `--preset nova` or `--preset lyra`
|
||||
2. **Code:** `--preset a2r6bw` (version-prefixed base62 string, e.g. `a2r6bw` or `b0`)
|
||||
3. **URL:** `--preset "https://ui.shadcn.com/init?base=radix&style=nova&..."`
|
||||
|
||||
> **IMPORTANT:** Never try to decode, fetch, or resolve preset codes manually. Preset codes are opaque — pass them directly to `npx shadcn@latest init --preset <code>` and let the CLI handle resolution.
|
||||
> Use `npx shadcn@latest apply --preset <code>` when overwriting an existing project's preset.
|
||||
|
||||
## Switching Presets
|
||||
|
||||
Ask the user first: **overwrite**, **merge**, or **skip** existing components?
|
||||
|
||||
- **Overwrite / Re-install** → `npx shadcn@latest apply --preset <code>`. Overwrites all detected component files with the new preset styles. Use when the user hasn't customized components.
|
||||
- **Merge** → `npx shadcn@latest init --preset <code> --force --no-reinstall`, then run `npx shadcn@latest info` to get the list of installed components and use the [smart merge workflow](./SKILL.md#updating-components) to update them one by one, preserving local changes. Use when the user has customized components.
|
||||
- **Skip** → `npx shadcn@latest init --preset <code> --force --no-reinstall`. Only updates config and CSS variables, leaves existing components as-is.
|
||||
|
||||
Always run preset commands inside the user's project directory. `apply` only works in an existing project with a `components.json` file. The CLI automatically preserves the current base (`base` vs `radix`) from `components.json`. If you must use a scratch/temp directory (e.g. for `--dry-run` comparisons), pass `--base <current-base>` explicitly — preset codes do not encode the base.
|
||||
@@ -0,0 +1,209 @@
|
||||
# Customization & Theming
|
||||
|
||||
Components reference semantic CSS variable tokens. Change the variables to change every component.
|
||||
|
||||
## Contents
|
||||
|
||||
- How it works (CSS variables → Tailwind utilities → components)
|
||||
- Color variables and OKLCH format
|
||||
- Dark mode setup
|
||||
- Changing the theme (presets, CSS variables)
|
||||
- Adding custom colors (Tailwind v3 and v4)
|
||||
- Border radius
|
||||
- Customizing components (variants, className, wrappers)
|
||||
- Checking for updates
|
||||
|
||||
---
|
||||
|
||||
## How It Works
|
||||
|
||||
1. CSS variables defined in `:root` (light) and `.dark` (dark mode).
|
||||
2. Tailwind maps them to utilities: `bg-primary`, `text-muted-foreground`, etc.
|
||||
3. Components use these utilities — changing a variable changes all components that reference it.
|
||||
|
||||
---
|
||||
|
||||
## Color Variables
|
||||
|
||||
Every color follows the `name` / `name-foreground` convention. The base variable is for backgrounds, `-foreground` is for text/icons on that background.
|
||||
|
||||
| Variable | Purpose |
|
||||
| -------------------------------------------- | -------------------------------- |
|
||||
| `--background` / `--foreground` | Page background and default text |
|
||||
| `--card` / `--card-foreground` | Card surfaces |
|
||||
| `--primary` / `--primary-foreground` | Primary buttons and actions |
|
||||
| `--secondary` / `--secondary-foreground` | Secondary actions |
|
||||
| `--muted` / `--muted-foreground` | Muted/disabled states |
|
||||
| `--accent` / `--accent-foreground` | Hover and accent states |
|
||||
| `--destructive` / `--destructive-foreground` | Error and destructive actions |
|
||||
| `--border` | Default border color |
|
||||
| `--input` | Form input borders |
|
||||
| `--ring` | Focus ring color |
|
||||
| `--chart-1` through `--chart-5` | Chart/data visualization |
|
||||
| `--sidebar-*` | Sidebar-specific colors |
|
||||
| `--surface` / `--surface-foreground` | Secondary surface |
|
||||
|
||||
Colors use OKLCH: `--primary: oklch(0.205 0 0)` where values are lightness (0–1), chroma (0 = gray), and hue (0–360).
|
||||
|
||||
---
|
||||
|
||||
## Dark Mode
|
||||
|
||||
Class-based toggle via `.dark` on the root element. In Next.js, use `next-themes`:
|
||||
|
||||
```tsx
|
||||
import { ThemeProvider } from "next-themes"
|
||||
|
||||
<ThemeProvider attribute="class" defaultTheme="system" enableSystem>
|
||||
{children}
|
||||
</ThemeProvider>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Changing the Theme
|
||||
|
||||
```bash
|
||||
# Apply a preset code from ui.shadcn.com.
|
||||
npx shadcn@latest apply --preset a2r6bw
|
||||
|
||||
# Positional shorthand also works.
|
||||
npx shadcn@latest apply a2r6bw
|
||||
|
||||
# Switch to a named preset and overwrite existing components.
|
||||
npx shadcn@latest apply --preset nova
|
||||
|
||||
# Preserve existing components instead.
|
||||
npx shadcn@latest init --preset nova --force --no-reinstall
|
||||
|
||||
# Use a custom theme URL.
|
||||
npx shadcn@latest apply --preset "https://ui.shadcn.com/init?base=radix&style=nova&theme=blue&..."
|
||||
```
|
||||
|
||||
Or edit CSS variables directly in `globals.css`.
|
||||
|
||||
---
|
||||
|
||||
## Adding Custom Colors
|
||||
|
||||
Add variables to the file at `tailwindCssFile` from `npx shadcn@latest info` (typically `globals.css`). Never create a new CSS file for this.
|
||||
|
||||
```css
|
||||
/* 1. Define in the global CSS file. */
|
||||
:root {
|
||||
--warning: oklch(0.84 0.16 84);
|
||||
--warning-foreground: oklch(0.28 0.07 46);
|
||||
}
|
||||
.dark {
|
||||
--warning: oklch(0.41 0.11 46);
|
||||
--warning-foreground: oklch(0.99 0.02 95);
|
||||
}
|
||||
```
|
||||
|
||||
```css
|
||||
/* 2a. Register with Tailwind v4 (@theme inline). */
|
||||
@theme inline {
|
||||
--color-warning: var(--warning);
|
||||
--color-warning-foreground: var(--warning-foreground);
|
||||
}
|
||||
```
|
||||
|
||||
When `tailwindVersion` is `"v3"` (check via `npx shadcn@latest info`), register in `tailwind.config.js` instead:
|
||||
|
||||
```js
|
||||
// 2b. Register with Tailwind v3 (tailwind.config.js).
|
||||
module.exports = {
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
warning: "oklch(var(--warning) / <alpha-value>)",
|
||||
"warning-foreground":
|
||||
"oklch(var(--warning-foreground) / <alpha-value>)",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
```tsx
|
||||
// 3. Use in components.
|
||||
<div className="bg-warning text-warning-foreground">Warning</div>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Border Radius
|
||||
|
||||
`--radius` controls border radius globally. Components derive values from it (`rounded-lg` = `var(--radius)`, `rounded-md` = `calc(var(--radius) - 2px)`).
|
||||
|
||||
---
|
||||
|
||||
## Customizing Components
|
||||
|
||||
See also: [rules/styling.md](./rules/styling.md) for Incorrect/Correct examples.
|
||||
|
||||
Prefer these approaches in order:
|
||||
|
||||
### 1. Built-in variants
|
||||
|
||||
```tsx
|
||||
<Button variant="outline" size="sm">
|
||||
Click
|
||||
</Button>
|
||||
```
|
||||
|
||||
### 2. Tailwind classes via `className`
|
||||
|
||||
```tsx
|
||||
<Card className="mx-auto max-w-md">...</Card>
|
||||
```
|
||||
|
||||
### 3. Add a new variant
|
||||
|
||||
Edit the component source to add a variant via `cva`:
|
||||
|
||||
```tsx
|
||||
// components/ui/button.tsx
|
||||
warning: "bg-warning text-warning-foreground hover:bg-warning/90",
|
||||
```
|
||||
|
||||
### 4. Wrapper components
|
||||
|
||||
Compose shadcn/ui primitives into higher-level components:
|
||||
|
||||
```tsx
|
||||
export function ConfirmDialog({ title, description, onConfirm, children }) {
|
||||
return (
|
||||
<AlertDialog>
|
||||
<AlertDialogTrigger asChild>{children}</AlertDialogTrigger>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>{title}</AlertDialogTitle>
|
||||
<AlertDialogDescription>{description}</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
||||
<AlertDialogAction onClick={onConfirm}>Confirm</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Checking for Updates
|
||||
|
||||
```bash
|
||||
npx shadcn@latest add button --diff
|
||||
```
|
||||
|
||||
To preview exactly what would change before updating, use `--dry-run` and `--diff`:
|
||||
|
||||
```bash
|
||||
npx shadcn@latest add button --dry-run # see all affected files
|
||||
npx shadcn@latest add button --diff button.tsx # see the diff for a specific file
|
||||
```
|
||||
|
||||
See [Updating Components in SKILL.md](./SKILL.md#updating-components) for the full smart merge workflow.
|
||||
@@ -0,0 +1,77 @@
|
||||
{
|
||||
"skill_name": "shadcn",
|
||||
"evals": [
|
||||
{
|
||||
"id": 1,
|
||||
"prompt": "I'm building a Next.js app with shadcn/ui (base-nova preset, lucide icons). Create a settings form component with fields for: full name, email address, and notification preferences (email, SMS, push notifications as toggle options). Add validation states for required fields.",
|
||||
"expected_output": "A React component using FieldGroup, Field, ToggleGroup, data-invalid/aria-invalid validation, gap-* spacing, and semantic colors.",
|
||||
"files": [],
|
||||
"expectations": [
|
||||
"Uses FieldGroup and Field components for form layout instead of raw div with space-y",
|
||||
"Uses Switch for independent on/off notification toggles (not looping Button with manual active state)",
|
||||
"Uses data-invalid on Field and aria-invalid on the input control for validation states",
|
||||
"Uses gap-* (e.g. gap-4, gap-6) instead of space-y-* or space-x-* for spacing",
|
||||
"Uses semantic color tokens (e.g. bg-background, text-muted-foreground, text-destructive) instead of raw colors like bg-red-500",
|
||||
"No manual dark: color overrides"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"prompt": "Create a dialog component for editing a user profile. It should have the user's avatar at the top, input fields for name and bio, and Save/Cancel buttons with appropriate icons. Using shadcn/ui with radix-nova preset and tabler icons.",
|
||||
"expected_output": "A React component with DialogTitle, Avatar+AvatarFallback, data-icon on icon buttons, no icon sizing classes, tabler icon imports.",
|
||||
"files": [],
|
||||
"expectations": [
|
||||
"Includes DialogTitle for accessibility (visible or with sr-only class)",
|
||||
"Avatar component includes AvatarFallback",
|
||||
"Icons on buttons use the data-icon attribute (data-icon=\"inline-start\" or data-icon=\"inline-end\")",
|
||||
"No sizing classes on icons inside components (no size-4, w-4, h-4, etc.)",
|
||||
"Uses tabler icons (@tabler/icons-react) instead of lucide-react",
|
||||
"Uses asChild for custom triggers (radix preset)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"prompt": "Create a dashboard component that shows 4 stat cards in a grid. Each card has a title, large number, percentage change badge, and a loading skeleton state. Using shadcn/ui with base-nova preset and lucide icons.",
|
||||
"expected_output": "A React component with full Card composition, Skeleton for loading, Badge for changes, semantic colors, gap-* spacing.",
|
||||
"files": [],
|
||||
"expectations": [
|
||||
"Uses full Card composition with CardHeader, CardTitle, CardContent (not dumping everything into CardContent)",
|
||||
"Uses Skeleton component for loading placeholders instead of custom animate-pulse divs",
|
||||
"Uses Badge component for percentage change instead of custom styled spans",
|
||||
"Uses semantic color tokens instead of raw color values like bg-green-500 or text-red-600",
|
||||
"Uses gap-* instead of space-y-* or space-x-* for spacing",
|
||||
"Uses size-* when width and height are equal instead of separate w-* h-*"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"prompt": "I'm building a Next.js app with shadcn/ui (base-nova preset, lucide icons). Build a chat conversation view: a scrollable thread of messages from two different people, each with an avatar, sender name, timestamp, and message bubble. A couple of messages include an image attachment and a PDF file attachment, and there's a 'Today' divider separating the days.",
|
||||
"expected_output": "A React component composing MessageScroller, Message, Bubble, Attachment, and Marker from the registry instead of hand-rolled bubble/divider/attachment markup.",
|
||||
"files": [],
|
||||
"expectations": [
|
||||
"Uses MessageScroller (MessageScrollerProvider, MessageScrollerViewport, MessageScrollerContent, MessageScrollerItem) for the scrollable thread instead of a raw overflow-y-auto div or ScrollArea",
|
||||
"Wraps each row in MessageScrollerItem inside MessageScrollerContent",
|
||||
"Uses Message with MessageAvatar/MessageContent/MessageHeader for row layout instead of custom flex divs",
|
||||
"Uses Bubble + BubbleContent for the message surface instead of a styled div with bg-muted/bg-primary",
|
||||
"Uses Attachment (AttachmentMedia, AttachmentContent, AttachmentTitle, AttachmentDescription) for the file and image attachments instead of Item or a custom card",
|
||||
"Uses Marker (variant=\"separator\") for the 'Today' divider instead of Separator plus a centered label",
|
||||
"Uses semantic color tokens and gap-* spacing; no raw colors like bg-emerald-500 and no space-y-*",
|
||||
"Includes \"use client\" when the component uses state or event handlers (isRSC)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"prompt": "Using shadcn/ui (base-nova preset, lucide icons), build a streaming AI chat UI. The assistant's reply streams in while it generates, the view auto-scrolls to follow the latest content but stops following if the user scrolls up to read earlier messages, a 'jump to latest' button appears when the user has scrolled away from the bottom, and a subtle 'thinking…' shimmer shows while the model is generating.",
|
||||
"expected_output": "A React component that delegates scroll/anchor behavior to MessageScroller and uses MessageScrollerButton for jump-to-latest and the shimmer utility for the thinking indicator — no hand-rolled scroll logic or custom shimmer keyframes.",
|
||||
"files": [],
|
||||
"expectations": [
|
||||
"Uses MessageScroller with MessageScrollerProvider (autoScroll) and scrollAnchor on message items for the stick-to-bottom/follow behavior instead of a custom useStickToBottom hook or ResizeObserver/scrollTop wiring",
|
||||
"Uses MessageScrollerButton for the jump-to-latest control instead of a hand-built conditional button driven by manual scroll-position state",
|
||||
"Uses the shimmer utility class for the 'thinking…' indicator instead of a custom @keyframes or bg-clip-text gradient animation",
|
||||
"Wraps each message row in MessageScrollerItem inside MessageScrollerContent",
|
||||
"Uses Message + Bubble + BubbleContent for the conversation rows instead of hand-rolled bubble divs",
|
||||
"Uses semantic color tokens and gap-* spacing; includes \"use client\" (isRSC)"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
# shadcn MCP Server
|
||||
|
||||
The CLI includes an MCP server that lets AI assistants search, browse, view, and install items from registries.
|
||||
|
||||
---
|
||||
|
||||
## Setup
|
||||
|
||||
```bash
|
||||
shadcn mcp # start the MCP server (stdio)
|
||||
shadcn mcp init # write config for your editor
|
||||
```
|
||||
|
||||
Editor config files:
|
||||
|
||||
| Editor | Config file |
|
||||
| ----------- | ------------------------------- |
|
||||
| Claude Code | `.mcp.json` |
|
||||
| Cursor | `.cursor/mcp.json` |
|
||||
| VS Code | `.vscode/mcp.json` |
|
||||
| OpenCode | `opencode.json` |
|
||||
| Codex | `~/.codex/config.toml` (manual) |
|
||||
|
||||
---
|
||||
|
||||
## Tools
|
||||
|
||||
> **Tip:** MCP tools handle registry operations (search, view, install). For project configuration (aliases, framework, Tailwind version), use `npx shadcn@latest info` — there is no MCP equivalent.
|
||||
|
||||
### `shadcn:get_project_registries`
|
||||
|
||||
Returns registry names from `components.json`. Errors if no `components.json` exists.
|
||||
|
||||
**Input:** none
|
||||
|
||||
### `shadcn:list_items_in_registries`
|
||||
|
||||
Lists all items from one or more registries. Registries can be configured
|
||||
namespaces such as `@acme`, public GitHub sources such as `owner/repo`, or
|
||||
registry catalog URLs. Omit `registries` to list from every registry configured
|
||||
in `components.json`.
|
||||
|
||||
**Input:** `registries` (string[], optional — omit for all configured), `types` (string[], optional — e.g. `["ui", "block"]`), `limit` (number, optional, defaults to 100), `offset` (number, optional)
|
||||
|
||||
### `shadcn:search_items_in_registries`
|
||||
|
||||
Fuzzy search across registries. Registries can be configured namespaces, public
|
||||
GitHub sources, or registry catalog URLs. Omit `registries` to search every
|
||||
registry configured in `components.json` — e.g. "find me a hero" across all
|
||||
configured registries.
|
||||
|
||||
**Input:** `registries` (string[], optional — omit for all configured), `query` (string), `types` (string[], optional — e.g. `["ui", "block"]`), `limit` (number, optional, defaults to 100), `offset` (number, optional)
|
||||
|
||||
### `shadcn:view_items_in_registries`
|
||||
|
||||
View item details including full file contents.
|
||||
|
||||
**Input:** `items` (string[]) — e.g.
|
||||
`["@shadcn/button", "@shadcn/card", "owner/repo/item"]`
|
||||
|
||||
### `shadcn:get_item_examples_from_registries`
|
||||
|
||||
Find usage examples and demos with source code. Omit `registries` to search
|
||||
every registry configured in `components.json`.
|
||||
|
||||
**Input:** `registries` (string[], optional — omit for all configured), `query` (string) — e.g. `"accordion-demo"`, `"button example"`
|
||||
|
||||
### `shadcn:get_add_command_for_items`
|
||||
|
||||
Returns the CLI install command.
|
||||
|
||||
**Input:** `items` (string[]) — e.g. `["@shadcn/button"]`
|
||||
|
||||
### `shadcn:get_audit_checklist`
|
||||
|
||||
Returns a checklist for verifying components (imports, deps, lint, TypeScript).
|
||||
|
||||
**Input:** none
|
||||
|
||||
---
|
||||
|
||||
## Configuring Registries
|
||||
|
||||
Namespaced and authenticated registries are set in `components.json`. The
|
||||
`@shadcn` registry is always built-in. Public GitHub registries can also be used
|
||||
directly as `owner/repo` registry sources when the repository has a root
|
||||
`registry.json`; they do not need `components.json` configuration.
|
||||
|
||||
```json
|
||||
{
|
||||
"registries": {
|
||||
"@acme": "https://acme.com/r/{name}.json",
|
||||
"@private": {
|
||||
"url": "https://private.com/r/{name}.json",
|
||||
"headers": { "Authorization": "Bearer ${MY_TOKEN}" }
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- Names must start with `@`.
|
||||
- URLs must contain `{name}`.
|
||||
- `${VAR}` references are resolved from environment variables.
|
||||
|
||||
Community registry index: `https://ui.shadcn.com/r/registries.json`
|
||||
@@ -0,0 +1,277 @@
|
||||
# Registry Authoring and Addresses
|
||||
|
||||
Use this reference when the user wants to create, fix, publish, or reason about
|
||||
a shadcn registry.
|
||||
|
||||
## Mental Model
|
||||
|
||||
A registry has two forms:
|
||||
|
||||
- **Source registry**: an authored `registry.json` in a project or repository.
|
||||
It may use `include` and file paths that point at source files.
|
||||
- **Built registry**: generated JSON files served to CLI consumers, usually
|
||||
from `public/r`. Use `npx shadcn@latest build` to create this form.
|
||||
|
||||
The CLI installer consumes registry item payloads. A source registry is a way to
|
||||
author those payloads from real files.
|
||||
|
||||
Registry items are not limited to React components. They can distribute
|
||||
components, hooks, utilities, design tokens, pages, config files, docs, rules,
|
||||
workflows, templates, MCP files, and other project files.
|
||||
|
||||
## Root `registry.json`
|
||||
|
||||
The root registry file should define registry metadata and either `items` or
|
||||
`include`.
|
||||
|
||||
```json
|
||||
{
|
||||
"$schema": "https://ui.shadcn.com/schema/registry.json",
|
||||
"name": "acme",
|
||||
"homepage": "https://acme.com",
|
||||
"items": [
|
||||
{
|
||||
"name": "absolute-url",
|
||||
"type": "registry:lib",
|
||||
"title": "Absolute URL",
|
||||
"description": "A utility to turn any path into an absolute URL.",
|
||||
"files": [
|
||||
{
|
||||
"path": "lib/absolute-url.ts",
|
||||
"type": "registry:lib"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Root registry rules:
|
||||
|
||||
- Root `registry.json` must include `name` and `homepage`.
|
||||
- `items` is an array of registry item definitions.
|
||||
- `include` may be used to split the source registry into multiple files.
|
||||
- Included registry files may omit `name` and `homepage`.
|
||||
|
||||
## Include
|
||||
|
||||
Use `include` to keep large registries modular.
|
||||
|
||||
```json
|
||||
{
|
||||
"$schema": "https://ui.shadcn.com/schema/registry.json",
|
||||
"name": "acme",
|
||||
"homepage": "https://acme.com",
|
||||
"include": ["registry/ui/registry.json", "registry/blocks/registry.json"]
|
||||
}
|
||||
```
|
||||
|
||||
Include rules:
|
||||
|
||||
- Include paths are relative to the `registry.json` that declares them.
|
||||
- Include paths must explicitly point to a `registry.json` file.
|
||||
- Do not use remote URLs, absolute paths, or parent traversal (`..`).
|
||||
- Item file paths are relative to the registry file that declares the item.
|
||||
- Duplicate item names fail across the resolved registry.
|
||||
|
||||
Example included file:
|
||||
|
||||
```json
|
||||
{
|
||||
"items": [
|
||||
{
|
||||
"name": "button",
|
||||
"type": "registry:ui",
|
||||
"files": [
|
||||
{
|
||||
"path": "button.tsx",
|
||||
"type": "registry:ui"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
If this file is at `registry/ui/registry.json`, then `button.tsx` is read from
|
||||
`registry/ui/button.tsx`, and the built item path is emitted relative to the
|
||||
root registry.
|
||||
|
||||
## Item Definitions
|
||||
|
||||
Common item fields:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "login-form",
|
||||
"type": "registry:block",
|
||||
"title": "Login Form",
|
||||
"description": "A login form with email and password fields.",
|
||||
"dependencies": ["zod"],
|
||||
"registryDependencies": ["button", "input", "label"],
|
||||
"files": [
|
||||
{
|
||||
"path": "blocks/login-form.tsx",
|
||||
"type": "registry:block"
|
||||
}
|
||||
],
|
||||
"cssVars": {
|
||||
"light": {
|
||||
"brand": "oklch(0.62 0.18 250)"
|
||||
},
|
||||
"dark": {
|
||||
"brand": "oklch(0.72 0.16 250)"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Important fields:
|
||||
|
||||
- `name`: the installable item name. It is not necessarily a file path.
|
||||
- `type`: one of the registry item types, such as `registry:ui`,
|
||||
`registry:block`, `registry:lib`, `registry:hook`, `registry:file`,
|
||||
`registry:page`, `registry:theme`, `registry:style`, `registry:font`, or
|
||||
`registry:item`.
|
||||
- `files`: source files copied or generated by the item.
|
||||
- `dependencies`: npm runtime dependencies.
|
||||
- `devDependencies`: npm development dependencies.
|
||||
- `registryDependencies`: other registry items required by this item.
|
||||
- `cssVars`, `css`, `tailwind`, `envVars`, and `docs`: optional install-time
|
||||
additions.
|
||||
|
||||
File rules:
|
||||
|
||||
- File paths are relative to the declaring `registry.json`.
|
||||
- `registry:file` and `registry:page` files require a `target`.
|
||||
- Do not use remote file URLs in source registry file paths.
|
||||
- Keep source files copy-pasteable: no hidden app-only imports.
|
||||
|
||||
## Registry Dependencies
|
||||
|
||||
`registryDependencies` entries are item addresses, not file paths.
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "login-form",
|
||||
"type": "registry:block",
|
||||
"registryDependencies": ["button", "@acme/input", "acme/ui/card#v1.2.0"],
|
||||
"files": [
|
||||
{
|
||||
"path": "blocks/login-form.tsx",
|
||||
"type": "registry:block"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Dependency rules:
|
||||
|
||||
- Bare names such as `"button"` mean official shadcn items.
|
||||
- Bare names never mean same-registry or same-repository items.
|
||||
- Namespaced dependencies use `@namespace/item-name`.
|
||||
- GitHub dependencies use `owner/repo/item-name`.
|
||||
- Pin GitHub dependencies with `owner/repo/item-name#ref` when needed.
|
||||
- Refs are not inherited. If `owner/repo/foo#v2` depends on `bar` from the same
|
||||
repo at `v2`, write `owner/repo/bar#v2`.
|
||||
- Do not use relative dependencies such as `"./bar"`.
|
||||
|
||||
## Address Schemes
|
||||
|
||||
When reasoning about a registry item string, classify it first.
|
||||
|
||||
| Address | Scheme | Meaning |
|
||||
| ----------------------------------- | --------- | ------------------------------------------------------------ |
|
||||
| `button` | shadcn | Official shadcn item named `button`. |
|
||||
| `@acme/button` | namespace | Item `button` from configured registry `@acme`. |
|
||||
| `@acme/ui/button` | namespace | Item `ui/button` from configured registry `@acme`. |
|
||||
| `https://example.com/r/button.json` | url | Built registry item JSON at that URL. |
|
||||
| `./button.json` | file | Built registry item JSON on disk. |
|
||||
| `acme/ui/button` | github | Item `button` from GitHub repo `acme/ui`. |
|
||||
| `acme/ui/forms/login#main` | github | Item `forms/login` from GitHub repo `acme/ui` at ref `main`. |
|
||||
|
||||
For namespace and GitHub addresses, slashful item names are allowed and are item
|
||||
names, not file paths. Addresses ending in `.json` keep file-address
|
||||
precedence, so `acme/ui/data/schema.json` is treated as a file path, not a
|
||||
GitHub item address.
|
||||
|
||||
## GitHub Registries
|
||||
|
||||
A public GitHub repository can act as a source registry when it has a root
|
||||
`registry.json`.
|
||||
|
||||
```txt
|
||||
owner/repo/item-name[#ref]
|
||||
```
|
||||
|
||||
Rules:
|
||||
|
||||
- The first two path segments are GitHub owner and repo.
|
||||
- All remaining path segments are the registry item name.
|
||||
- The source entrypoint is always root `registry.json`.
|
||||
- GitHub registries are source registries consumed directly by the CLI. They do
|
||||
not require `shadcn build` or generated item JSON files.
|
||||
- `include` follows the same source-registry rules as local registries.
|
||||
- Currently, GitHub addresses support public `github.com` repositories only.
|
||||
- Private repos and GitHub Enterprise require explicit product decisions.
|
||||
|
||||
When implementing GitHub registry fetching, resolve refs to a commit SHA before
|
||||
reading source files. Do not read moving refs directly from
|
||||
`raw.githubusercontent.com`, because branch-like refs can be cached for several
|
||||
minutes.
|
||||
|
||||
Preferred flow:
|
||||
|
||||
```txt
|
||||
owner/repo[#ref]
|
||||
-> resolve ref with git ls-remote
|
||||
-> commit SHA
|
||||
-> read https://raw.githubusercontent.com/{owner}/{repo}/{sha}/registry.json
|
||||
-> read includes and item files from the same SHA
|
||||
```
|
||||
|
||||
This keeps a command on one consistent repository snapshot.
|
||||
|
||||
Full 40-character commit SHAs are already stable and can be used directly.
|
||||
Branches, tags, and short refs require Git so the CLI can resolve them to a
|
||||
commit SHA first.
|
||||
|
||||
## Build and Verify
|
||||
|
||||
Use the CLI to build source registries:
|
||||
|
||||
```bash
|
||||
npx shadcn@latest build
|
||||
npx shadcn@latest build registry.json --output public/r
|
||||
```
|
||||
|
||||
Use CLI commands to inspect the result:
|
||||
|
||||
```bash
|
||||
npx shadcn@latest list @acme
|
||||
npx shadcn@latest search @acme -q "login"
|
||||
npx shadcn@latest view @acme/login-form
|
||||
npx shadcn@latest add @acme/login-form --dry-run
|
||||
npx shadcn@latest registry validate ./registry.json
|
||||
```
|
||||
|
||||
Use GitHub addresses directly for public GitHub registries:
|
||||
|
||||
```bash
|
||||
npx shadcn@latest list owner/repo
|
||||
npx shadcn@latest search owner/repo -q "login"
|
||||
npx shadcn@latest view owner/repo/item
|
||||
npx shadcn@latest add owner/repo/item --dry-run
|
||||
npx shadcn@latest registry validate owner/repo
|
||||
```
|
||||
|
||||
When working on registry implementation in the shadcn/ui codebase:
|
||||
|
||||
- Keep address parsing pure and testable.
|
||||
- Do not add side effects to validators.
|
||||
- Preserve existing behavior for official shadcn, namespace, URL, and file
|
||||
schemes.
|
||||
- Add tests for address parsing, source loading, dependency resolution, list,
|
||||
search, view, and add paths.
|
||||
- Prefer small source-reader abstractions over a plugin system until there are
|
||||
multiple real providers.
|
||||
@@ -0,0 +1,306 @@
|
||||
# Base vs Radix
|
||||
|
||||
API differences between `base` and `radix`. Check the `base` field from `npx shadcn@latest info`.
|
||||
|
||||
## Contents
|
||||
|
||||
- Composition: asChild vs render
|
||||
- Button / trigger as non-button element
|
||||
- Select (items prop, placeholder, positioning, multiple, object values)
|
||||
- ToggleGroup (type vs multiple)
|
||||
- Slider (scalar vs array)
|
||||
- Accordion (type and defaultValue)
|
||||
|
||||
---
|
||||
|
||||
## Composition: asChild (radix) vs render (base)
|
||||
|
||||
Radix uses `asChild` to replace the default element. Base uses `render`. Don't wrap triggers in extra elements.
|
||||
|
||||
**Incorrect:**
|
||||
|
||||
```tsx
|
||||
<DialogTrigger>
|
||||
<div>
|
||||
<Button>Open</Button>
|
||||
</div>
|
||||
</DialogTrigger>
|
||||
```
|
||||
|
||||
**Correct (radix):**
|
||||
|
||||
```tsx
|
||||
<DialogTrigger asChild>
|
||||
<Button>Open</Button>
|
||||
</DialogTrigger>
|
||||
```
|
||||
|
||||
**Correct (base):**
|
||||
|
||||
```tsx
|
||||
<DialogTrigger render={<Button />}>Open</DialogTrigger>
|
||||
```
|
||||
|
||||
This applies to all trigger and close components: `DialogTrigger`, `SheetTrigger`, `AlertDialogTrigger`, `DropdownMenuTrigger`, `PopoverTrigger`, `TooltipTrigger`, `CollapsibleTrigger`, `DialogClose`, `SheetClose`, `NavigationMenuLink`, `BreadcrumbLink`, `SidebarMenuButton`, `Badge`, `Item`.
|
||||
|
||||
---
|
||||
|
||||
## Button / trigger as non-button element (base only)
|
||||
|
||||
When `render` changes an element to a non-button (`<a>`, `<span>`), add `nativeButton={false}`.
|
||||
|
||||
**Incorrect (base):** missing `nativeButton={false}`.
|
||||
|
||||
```tsx
|
||||
<Button render={<a href="/docs" />}>Read the docs</Button>
|
||||
```
|
||||
|
||||
**Correct (base):**
|
||||
|
||||
```tsx
|
||||
<Button render={<a href="/docs" />} nativeButton={false}>
|
||||
Read the docs
|
||||
</Button>
|
||||
```
|
||||
|
||||
**Correct (radix):**
|
||||
|
||||
```tsx
|
||||
<Button asChild>
|
||||
<a href="/docs">Read the docs</a>
|
||||
</Button>
|
||||
```
|
||||
|
||||
Same for triggers whose `render` is not a `Button`:
|
||||
|
||||
```tsx
|
||||
// base.
|
||||
<PopoverTrigger render={<InputGroupAddon />} nativeButton={false}>
|
||||
Pick date
|
||||
</PopoverTrigger>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Select
|
||||
|
||||
**items prop (base only).** Base requires an `items` prop on the root. Radix uses inline JSX only.
|
||||
|
||||
**Incorrect (base):**
|
||||
|
||||
```tsx
|
||||
<Select>
|
||||
<SelectTrigger><SelectValue placeholder="Select a fruit" /></SelectTrigger>
|
||||
</Select>
|
||||
```
|
||||
|
||||
**Correct (base):**
|
||||
|
||||
```tsx
|
||||
const items = [
|
||||
{ label: "Select a fruit", value: null },
|
||||
{ label: "Apple", value: "apple" },
|
||||
{ label: "Banana", value: "banana" },
|
||||
]
|
||||
|
||||
<Select items={items}>
|
||||
<SelectTrigger>
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectGroup>
|
||||
{items.map((item) => (
|
||||
<SelectItem key={item.value} value={item.value}>{item.label}</SelectItem>
|
||||
))}
|
||||
</SelectGroup>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
```
|
||||
|
||||
**Correct (radix):**
|
||||
|
||||
```tsx
|
||||
<Select>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Select a fruit" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectGroup>
|
||||
<SelectItem value="apple">Apple</SelectItem>
|
||||
<SelectItem value="banana">Banana</SelectItem>
|
||||
</SelectGroup>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
```
|
||||
|
||||
**Placeholder.** Base uses a `{ value: null }` item in the items array. Radix uses `<SelectValue placeholder="...">`.
|
||||
|
||||
**Content positioning.** Base uses `alignItemWithTrigger`. Radix uses `position`.
|
||||
|
||||
```tsx
|
||||
// base.
|
||||
<SelectContent alignItemWithTrigger={false} side="bottom">
|
||||
|
||||
// radix.
|
||||
<SelectContent position="popper">
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Select — multiple selection and object values (base only)
|
||||
|
||||
Base supports `multiple`, render-function children on `SelectValue`, and object values with `itemToStringValue`. Radix is single-select with string values only.
|
||||
|
||||
**Correct (base — multiple selection):**
|
||||
|
||||
```tsx
|
||||
<Select items={items} multiple defaultValue={[]}>
|
||||
<SelectTrigger>
|
||||
<SelectValue>
|
||||
{(value: string[]) => value.length === 0 ? "Select fruits" : `${value.length} selected`}
|
||||
</SelectValue>
|
||||
</SelectTrigger>
|
||||
...
|
||||
</Select>
|
||||
```
|
||||
|
||||
**Correct (base — object values):**
|
||||
|
||||
```tsx
|
||||
<Select defaultValue={plans[0]} itemToStringValue={(plan) => plan.name}>
|
||||
<SelectTrigger>
|
||||
<SelectValue>{(value) => value.name}</SelectValue>
|
||||
</SelectTrigger>
|
||||
...
|
||||
</Select>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ToggleGroup
|
||||
|
||||
Base uses a `multiple` boolean prop. Radix uses `type="single"` or `type="multiple"`.
|
||||
|
||||
**Incorrect (base):**
|
||||
|
||||
```tsx
|
||||
<ToggleGroup type="single" defaultValue="daily">
|
||||
<ToggleGroupItem value="daily">Daily</ToggleGroupItem>
|
||||
</ToggleGroup>
|
||||
```
|
||||
|
||||
**Correct (base):**
|
||||
|
||||
```tsx
|
||||
// Single (no prop needed), defaultValue is always an array.
|
||||
<ToggleGroup defaultValue={["daily"]} spacing={2}>
|
||||
<ToggleGroupItem value="daily">Daily</ToggleGroupItem>
|
||||
<ToggleGroupItem value="weekly">Weekly</ToggleGroupItem>
|
||||
</ToggleGroup>
|
||||
|
||||
// Multi-selection.
|
||||
<ToggleGroup multiple>
|
||||
<ToggleGroupItem value="bold">Bold</ToggleGroupItem>
|
||||
<ToggleGroupItem value="italic">Italic</ToggleGroupItem>
|
||||
</ToggleGroup>
|
||||
```
|
||||
|
||||
**Correct (radix):**
|
||||
|
||||
```tsx
|
||||
// Single, defaultValue is a string.
|
||||
<ToggleGroup type="single" defaultValue="daily" spacing={2}>
|
||||
<ToggleGroupItem value="daily">Daily</ToggleGroupItem>
|
||||
<ToggleGroupItem value="weekly">Weekly</ToggleGroupItem>
|
||||
</ToggleGroup>
|
||||
|
||||
// Multi-selection.
|
||||
<ToggleGroup type="multiple">
|
||||
<ToggleGroupItem value="bold">Bold</ToggleGroupItem>
|
||||
<ToggleGroupItem value="italic">Italic</ToggleGroupItem>
|
||||
</ToggleGroup>
|
||||
```
|
||||
|
||||
**Controlled single value:**
|
||||
|
||||
```tsx
|
||||
// base — wrap/unwrap arrays.
|
||||
const [value, setValue] = React.useState("normal")
|
||||
<ToggleGroup value={[value]} onValueChange={(v) => setValue(v[0])}>
|
||||
|
||||
// radix — plain string.
|
||||
const [value, setValue] = React.useState("normal")
|
||||
<ToggleGroup type="single" value={value} onValueChange={setValue}>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Slider
|
||||
|
||||
Base accepts a plain number for a single thumb. Radix always requires an array.
|
||||
|
||||
**Incorrect (base):**
|
||||
|
||||
```tsx
|
||||
<Slider defaultValue={[50]} max={100} step={1} />
|
||||
```
|
||||
|
||||
**Correct (base):**
|
||||
|
||||
```tsx
|
||||
<Slider defaultValue={50} max={100} step={1} />
|
||||
```
|
||||
|
||||
**Correct (radix):**
|
||||
|
||||
```tsx
|
||||
<Slider defaultValue={[50]} max={100} step={1} />
|
||||
```
|
||||
|
||||
Both use arrays for range sliders. Controlled `onValueChange` in base may need a cast:
|
||||
|
||||
```tsx
|
||||
// base.
|
||||
const [value, setValue] = React.useState([0.3, 0.7])
|
||||
<Slider value={value} onValueChange={(v) => setValue(v as number[])} />
|
||||
|
||||
// radix.
|
||||
const [value, setValue] = React.useState([0.3, 0.7])
|
||||
<Slider value={value} onValueChange={setValue} />
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Accordion
|
||||
|
||||
Radix requires `type="single"` or `type="multiple"` and supports `collapsible`. `defaultValue` is a string. Base uses no `type` prop, uses `multiple` boolean, and `defaultValue` is always an array.
|
||||
|
||||
**Incorrect (base):**
|
||||
|
||||
```tsx
|
||||
<Accordion type="single" collapsible defaultValue="item-1">
|
||||
<AccordionItem value="item-1">...</AccordionItem>
|
||||
</Accordion>
|
||||
```
|
||||
|
||||
**Correct (base):**
|
||||
|
||||
```tsx
|
||||
<Accordion defaultValue={["item-1"]}>
|
||||
<AccordionItem value="item-1">...</AccordionItem>
|
||||
</Accordion>
|
||||
|
||||
// Multi-select.
|
||||
<Accordion multiple defaultValue={["item-1", "item-2"]}>
|
||||
<AccordionItem value="item-1">...</AccordionItem>
|
||||
<AccordionItem value="item-2">...</AccordionItem>
|
||||
</Accordion>
|
||||
```
|
||||
|
||||
**Correct (radix):**
|
||||
|
||||
```tsx
|
||||
<Accordion type="single" collapsible defaultValue="item-1">
|
||||
<AccordionItem value="item-1">...</AccordionItem>
|
||||
</Accordion>
|
||||
```
|
||||
@@ -0,0 +1,224 @@
|
||||
# Chat & Messaging
|
||||
|
||||
Components for conversation and chat UI. Compose these instead of hand-rolling
|
||||
bubbles, scroll containers, dividers, or attachment cards.
|
||||
|
||||
Install: `npx shadcn@latest add message-scroller message bubble attachment marker`
|
||||
|
||||
The same component names and props ship for both `base` and `radix`; only
|
||||
composition differs (`render` vs `asChild`). See [base-vs-radix.md](./base-vs-radix.md).
|
||||
|
||||
## Contents
|
||||
|
||||
- Scrollable threads use MessageScroller
|
||||
- Message rows use Message
|
||||
- Message surfaces use Bubble
|
||||
- Attachments use Attachment
|
||||
- System notes and dividers use Marker
|
||||
- Streaming, anchoring, and jump-to-latest are built in
|
||||
- Escape hatch: the scroller hooks
|
||||
|
||||
---
|
||||
|
||||
## Scrollable threads use MessageScroller
|
||||
|
||||
A conversation that scrolls, follows new messages, restores position, or jumps
|
||||
to a message uses `MessageScroller`. Don't build a raw overflow container with
|
||||
manual scroll wiring, and don't reach for `ScrollArea`.
|
||||
|
||||
The parts nest in a fixed order. Every direct child of the content is wrapped in
|
||||
a `MessageScrollerItem` so the scroller can measure, anchor, preserve position,
|
||||
track visibility, and jump to it. `MessageScrollerButton` sits inside
|
||||
`MessageScroller`, after the viewport.
|
||||
|
||||
**Incorrect:**
|
||||
|
||||
```tsx
|
||||
// Hand-rolled scroll container with manual stick-to-bottom logic.
|
||||
<div ref={scrollRef} onScroll={handleScroll} className="flex-1 overflow-y-auto">
|
||||
<div className="flex flex-col gap-6 p-4">
|
||||
{messages.map((m) => (
|
||||
<ChatMessage key={m.id} message={m} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
**Correct:**
|
||||
|
||||
```tsx
|
||||
<MessageScrollerProvider autoScroll>
|
||||
<MessageScroller>
|
||||
<MessageScrollerViewport>
|
||||
<MessageScrollerContent>
|
||||
{messages.map((message) => (
|
||||
<MessageScrollerItem
|
||||
key={message.id}
|
||||
messageId={message.id}
|
||||
scrollAnchor={message.role === "user"}
|
||||
>
|
||||
<Message align={message.role === "user" ? "end" : "start"}>
|
||||
{/* ...message content... */}
|
||||
</Message>
|
||||
</MessageScrollerItem>
|
||||
))}
|
||||
</MessageScrollerContent>
|
||||
</MessageScrollerViewport>
|
||||
<MessageScrollerButton />
|
||||
</MessageScroller>
|
||||
</MessageScrollerProvider>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Message rows use Message
|
||||
|
||||
`Message` lays out a single row: avatar, header, content, footer, with
|
||||
alignment. Group consecutive rows from one sender with `MessageGroup`. Don't
|
||||
rebuild the row from flex divs.
|
||||
|
||||
`align="end"` is the current user's side; `align="start"` is everyone else.
|
||||
|
||||
```tsx
|
||||
<Message align="start">
|
||||
<MessageAvatar>
|
||||
<Avatar>
|
||||
<AvatarImage src={sender.avatar} alt={sender.name} />
|
||||
<AvatarFallback>{initials}</AvatarFallback>
|
||||
</Avatar>
|
||||
</MessageAvatar>
|
||||
<MessageContent>
|
||||
<MessageHeader>{sender.name}</MessageHeader>
|
||||
<Bubble>
|
||||
<BubbleContent>{text}</BubbleContent>
|
||||
</Bubble>
|
||||
<MessageFooter>{time}</MessageFooter>
|
||||
</MessageContent>
|
||||
</Message>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Message surfaces use Bubble
|
||||
|
||||
The colored message surface is `Bubble` + `BubbleContent`, never a styled `div`
|
||||
with `bg-muted` / `bg-primary` and hand-managed corners.
|
||||
|
||||
- `variant`: `default`, `secondary`, `muted`, `tinted`, `outline`, `ghost`, `destructive`.
|
||||
- `align`: `start` or `end` (matches the `Message` side).
|
||||
|
||||
`BubbleReactions` renders the reaction cluster. `side` (`top` | `bottom`) and
|
||||
`align` (`start` | `end`) position it against the bubble. Don't lay reactions out
|
||||
with absolutely-positioned `Badge`s.
|
||||
|
||||
**Incorrect:**
|
||||
|
||||
```tsx
|
||||
<div className="w-fit rounded-2xl bg-primary px-3 py-2 text-primary-foreground">
|
||||
{text}
|
||||
</div>
|
||||
```
|
||||
|
||||
**Correct:**
|
||||
|
||||
```tsx
|
||||
<Bubble variant="default" align="end">
|
||||
<BubbleContent>{text}</BubbleContent>
|
||||
<BubbleReactions side="bottom" align="end">
|
||||
<Badge variant="secondary">👍 2</Badge>
|
||||
</BubbleReactions>
|
||||
</Bubble>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Attachments use Attachment
|
||||
|
||||
File and image attachments use `Attachment`, not `Item` or a custom card. It
|
||||
carries upload state, so wire `state` to the real status rather than rendering a
|
||||
separate spinner.
|
||||
|
||||
- `state`: `idle`, `uploading`, `processing`, `error`, `done`. `uploading` and
|
||||
`processing` apply the `shimmer` animation to the title automatically.
|
||||
- `size`: `default`, `sm`, `xs`. `orientation`: `horizontal`, `vertical`.
|
||||
- Use `AttachmentGroup` to lay out several attachments in a scrolling row.
|
||||
|
||||
```tsx
|
||||
<Attachment state="done">
|
||||
<AttachmentMedia variant="icon">
|
||||
<FileTextIcon />
|
||||
</AttachmentMedia>
|
||||
<AttachmentContent>
|
||||
<AttachmentTitle>homepage-feedback.pdf</AttachmentTitle>
|
||||
<AttachmentDescription>PDF · 2.4 MB</AttachmentDescription>
|
||||
</AttachmentContent>
|
||||
<AttachmentActions>
|
||||
<AttachmentAction>
|
||||
<DownloadIcon />
|
||||
</AttachmentAction>
|
||||
</AttachmentActions>
|
||||
</Attachment>
|
||||
```
|
||||
|
||||
For an image, use `<AttachmentMedia variant="image">` with an `img` child.
|
||||
|
||||
---
|
||||
|
||||
## System notes and dividers use Marker
|
||||
|
||||
Status lines ("Sarah joined the conversation"), date dividers ("Today"), and
|
||||
labeled separators are `Marker`, not a `Separator` plus a centered span.
|
||||
|
||||
- `variant`: `default` (plain row), `separator` (centered label with rules on
|
||||
each side), `border` (bottom-bordered row).
|
||||
- `MarkerIcon` holds a leading icon; `MarkerContent` holds the label.
|
||||
|
||||
**Incorrect:**
|
||||
|
||||
```tsx
|
||||
<div className="flex items-center gap-3 py-2">
|
||||
<Separator className="flex-1" />
|
||||
<span className="text-xs text-muted-foreground">Today</span>
|
||||
<Separator className="flex-1" />
|
||||
</div>
|
||||
```
|
||||
|
||||
**Correct:**
|
||||
|
||||
```tsx
|
||||
<Marker variant="separator">
|
||||
<MarkerContent>Today</MarkerContent>
|
||||
</Marker>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Streaming, anchoring, and jump-to-latest are built in
|
||||
|
||||
`MessageScroller` handles the behavior that chat UIs usually reinvent. Don't
|
||||
write a `useStickToBottom` hook, a `ResizeObserver`, or manual `scrollTop` math.
|
||||
|
||||
- **Follow the live edge while streaming.** `MessageScrollerProvider` with
|
||||
`autoScroll` keeps the view pinned to new content and yields the moment the
|
||||
user scrolls up. Streaming token updates that grow the last message are
|
||||
followed automatically.
|
||||
- **Anchor a turn.** `scrollAnchor` on a `MessageScrollerItem` marks the row to
|
||||
hold in view (typically the user's message that started the turn).
|
||||
- **Jump to latest.** `MessageScrollerButton` appears when the user scrolls away
|
||||
and scrolls back on click. `direction="end"` (default) or `direction="start"`.
|
||||
It is a self-managing control, so don't gate it behind your own scroll-position
|
||||
state.
|
||||
|
||||
For a "thinking…" indicator while the model generates, apply the `shimmer`
|
||||
utility to text. Don't author a custom keyframe animation. See
|
||||
[styling.md](./styling.md).
|
||||
|
||||
---
|
||||
|
||||
## Escape hatch: the scroller hooks
|
||||
|
||||
For behavior the parts don't expose, read state from the hooks rather than
|
||||
re-implementing the scroller: `useMessageScroller`,
|
||||
`useMessageScrollerVisibility`, and `useMessageScrollerScrollable`. They come
|
||||
from the auto-installed `@shadcn/react` dependency, so there's nothing extra to
|
||||
install. Reach for them only when composition can't express what you need.
|
||||
@@ -0,0 +1,213 @@
|
||||
# Component Composition
|
||||
|
||||
## Contents
|
||||
|
||||
- Items always inside their Group component
|
||||
- Callouts use Alert
|
||||
- Empty states use Empty component
|
||||
- Toast notifications follow the project base
|
||||
- Choosing between overlay components
|
||||
- Dialog, Sheet, and Drawer always need a Title
|
||||
- Card structure
|
||||
- Button has no isPending or isLoading prop
|
||||
- TabsTrigger must be inside TabsList
|
||||
- Avatar always needs AvatarFallback
|
||||
- Use Separator instead of raw hr or border divs
|
||||
- Use Skeleton for loading placeholders
|
||||
- Use Badge instead of custom styled spans
|
||||
|
||||
---
|
||||
|
||||
## Items always inside their Group component
|
||||
|
||||
Never render items directly inside the content container.
|
||||
|
||||
**Incorrect:**
|
||||
|
||||
```tsx
|
||||
<SelectContent>
|
||||
<SelectItem value="apple">Apple</SelectItem>
|
||||
<SelectItem value="banana">Banana</SelectItem>
|
||||
</SelectContent>
|
||||
```
|
||||
|
||||
**Correct:**
|
||||
|
||||
```tsx
|
||||
<SelectContent>
|
||||
<SelectGroup>
|
||||
<SelectItem value="apple">Apple</SelectItem>
|
||||
<SelectItem value="banana">Banana</SelectItem>
|
||||
</SelectGroup>
|
||||
</SelectContent>
|
||||
```
|
||||
|
||||
This applies to all group-based components:
|
||||
|
||||
| Item | Group |
|
||||
|------|-------|
|
||||
| `SelectItem`, `SelectLabel` | `SelectGroup` |
|
||||
| `DropdownMenuItem`, `DropdownMenuLabel`, `DropdownMenuSub` | `DropdownMenuGroup` |
|
||||
| `MenubarItem` | `MenubarGroup` |
|
||||
| `ContextMenuItem` | `ContextMenuGroup` |
|
||||
| `CommandItem` | `CommandGroup` |
|
||||
| `MessageScrollerItem` | `MessageScrollerContent` |
|
||||
| `Message` (consecutive, same sender) | `MessageGroup` |
|
||||
| `Bubble` (stacked) | `BubbleGroup` |
|
||||
| `Attachment` (in a row) | `AttachmentGroup` |
|
||||
|
||||
Chat components nest in a fixed order (`MessageScrollerProvider` → `MessageScroller` → `MessageScrollerViewport` → `MessageScrollerContent` → `MessageScrollerItem`). See [chat.md](./chat.md).
|
||||
|
||||
---
|
||||
|
||||
## Callouts use Alert
|
||||
|
||||
```tsx
|
||||
<Alert>
|
||||
<AlertTitle>Warning</AlertTitle>
|
||||
<AlertDescription>Something needs attention.</AlertDescription>
|
||||
</Alert>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Empty states use Empty component
|
||||
|
||||
```tsx
|
||||
<Empty>
|
||||
<EmptyHeader>
|
||||
<EmptyMedia variant="icon"><FolderIcon /></EmptyMedia>
|
||||
<EmptyTitle>No projects yet</EmptyTitle>
|
||||
<EmptyDescription>Get started by creating a new project.</EmptyDescription>
|
||||
</EmptyHeader>
|
||||
<EmptyContent>
|
||||
<Button>Create Project</Button>
|
||||
</EmptyContent>
|
||||
</Empty>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Toast notifications follow the project base
|
||||
|
||||
For Base UI projects, use the `toast` component:
|
||||
|
||||
```tsx
|
||||
import { toast } from "@/components/ui/toast"
|
||||
|
||||
toast.add({
|
||||
title: "Changes saved.",
|
||||
})
|
||||
```
|
||||
|
||||
For Radix and React Aria projects, use Sonner:
|
||||
|
||||
```tsx
|
||||
import { toast } from "sonner"
|
||||
|
||||
toast.success("Changes saved.")
|
||||
toast.error("Something went wrong.")
|
||||
toast("File deleted.", {
|
||||
action: { label: "Undo", onClick: () => undoDelete() },
|
||||
})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Choosing between overlay components
|
||||
|
||||
| Use case | Component |
|
||||
|----------|-----------|
|
||||
| Focused task that requires input | `Dialog` |
|
||||
| Destructive action confirmation | `AlertDialog` |
|
||||
| Side panel with details or filters | `Sheet` |
|
||||
| Mobile-first bottom panel | `Drawer` |
|
||||
| Quick info on hover | `HoverCard` |
|
||||
| Small contextual content on click | `Popover` |
|
||||
|
||||
---
|
||||
|
||||
## Dialog, Sheet, and Drawer always need a Title
|
||||
|
||||
`DialogTitle`, `SheetTitle`, `DrawerTitle` are required for accessibility. Use `className="sr-only"` if visually hidden.
|
||||
|
||||
```tsx
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Edit Profile</DialogTitle>
|
||||
<DialogDescription>Update your profile.</DialogDescription>
|
||||
</DialogHeader>
|
||||
...
|
||||
</DialogContent>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Card structure
|
||||
|
||||
Use full composition — don't dump everything into `CardContent`:
|
||||
|
||||
```tsx
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Team Members</CardTitle>
|
||||
<CardDescription>Manage your team.</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>...</CardContent>
|
||||
<CardFooter>
|
||||
<Button>Invite</Button>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Button has no isPending or isLoading prop
|
||||
|
||||
Compose with `Spinner` + `data-icon` + `disabled`:
|
||||
|
||||
```tsx
|
||||
<Button disabled>
|
||||
<Spinner data-icon="inline-start" />
|
||||
Saving...
|
||||
</Button>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## TabsTrigger must be inside TabsList
|
||||
|
||||
Never render `TabsTrigger` directly inside `Tabs` — always wrap in `TabsList`:
|
||||
|
||||
```tsx
|
||||
<Tabs defaultValue="account">
|
||||
<TabsList>
|
||||
<TabsTrigger value="account">Account</TabsTrigger>
|
||||
<TabsTrigger value="password">Password</TabsTrigger>
|
||||
</TabsList>
|
||||
<TabsContent value="account">...</TabsContent>
|
||||
</Tabs>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Avatar always needs AvatarFallback
|
||||
|
||||
Always include `AvatarFallback` for when the image fails to load:
|
||||
|
||||
```tsx
|
||||
<Avatar>
|
||||
<AvatarImage src="/avatar.png" alt="User" />
|
||||
<AvatarFallback>JD</AvatarFallback>
|
||||
</Avatar>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Use existing components instead of custom markup
|
||||
|
||||
| Instead of | Use |
|
||||
|---|---|
|
||||
| `<hr>` or `<div className="border-t">` | `<Separator />` |
|
||||
| `<div className="animate-pulse">` with styled divs | `<Skeleton className="h-4 w-3/4" />` |
|
||||
| `<span className="rounded-full bg-green-100 ...">` | `<Badge variant="secondary">` |
|
||||
@@ -0,0 +1,192 @@
|
||||
# Forms & Inputs
|
||||
|
||||
## Contents
|
||||
|
||||
- Forms use FieldGroup + Field
|
||||
- InputGroup requires InputGroupInput/InputGroupTextarea
|
||||
- Buttons inside inputs use InputGroup + InputGroupAddon
|
||||
- Option sets (2–7 choices) use ToggleGroup
|
||||
- FieldSet + FieldLegend for grouping related fields
|
||||
- Field validation and disabled states
|
||||
|
||||
---
|
||||
|
||||
## Forms use FieldGroup + Field
|
||||
|
||||
Always use `FieldGroup` + `Field` — never raw `div` with `space-y-*`:
|
||||
|
||||
```tsx
|
||||
<FieldGroup>
|
||||
<Field>
|
||||
<FieldLabel htmlFor="email">Email</FieldLabel>
|
||||
<Input id="email" type="email" />
|
||||
</Field>
|
||||
<Field>
|
||||
<FieldLabel htmlFor="password">Password</FieldLabel>
|
||||
<Input id="password" type="password" />
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
```
|
||||
|
||||
Use `Field orientation="horizontal"` for settings pages. Use `FieldLabel className="sr-only"` for visually hidden labels.
|
||||
|
||||
**Choosing form controls:**
|
||||
|
||||
- Simple text input → `Input`
|
||||
- Dropdown with predefined options → `Select`
|
||||
- Searchable dropdown → `Combobox`
|
||||
- Native HTML select (no JS) → `native-select`
|
||||
- Boolean toggle → `Switch` (for settings) or `Checkbox` (for forms)
|
||||
- Single choice from few options → `RadioGroup`
|
||||
- Toggle between 2–5 options → `ToggleGroup` + `ToggleGroupItem`
|
||||
- OTP/verification code → `InputOTP`
|
||||
- Multi-line text → `Textarea`
|
||||
|
||||
---
|
||||
|
||||
## InputGroup requires InputGroupInput/InputGroupTextarea
|
||||
|
||||
Never use raw `Input` or `Textarea` inside an `InputGroup`.
|
||||
|
||||
**Incorrect:**
|
||||
|
||||
```tsx
|
||||
<InputGroup>
|
||||
<Input placeholder="Search..." />
|
||||
</InputGroup>
|
||||
```
|
||||
|
||||
**Correct:**
|
||||
|
||||
```tsx
|
||||
import { InputGroup, InputGroupInput } from "@/components/ui/input-group"
|
||||
|
||||
<InputGroup>
|
||||
<InputGroupInput placeholder="Search..." />
|
||||
</InputGroup>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Buttons inside inputs use InputGroup + InputGroupAddon
|
||||
|
||||
Never place a `Button` directly inside or adjacent to an `Input` with custom positioning.
|
||||
|
||||
**Incorrect:**
|
||||
|
||||
```tsx
|
||||
<div className="relative">
|
||||
<Input placeholder="Search..." className="pr-10" />
|
||||
<Button className="absolute right-0 top-0" size="icon">
|
||||
<SearchIcon />
|
||||
</Button>
|
||||
</div>
|
||||
```
|
||||
|
||||
**Correct:**
|
||||
|
||||
```tsx
|
||||
import { InputGroup, InputGroupInput, InputGroupAddon } from "@/components/ui/input-group"
|
||||
|
||||
<InputGroup>
|
||||
<InputGroupInput placeholder="Search..." />
|
||||
<InputGroupAddon>
|
||||
<Button size="icon">
|
||||
<SearchIcon data-icon="inline-start" />
|
||||
</Button>
|
||||
</InputGroupAddon>
|
||||
</InputGroup>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Option sets (2–7 choices) use ToggleGroup
|
||||
|
||||
Don't manually loop `Button` components with active state.
|
||||
|
||||
**Incorrect:**
|
||||
|
||||
```tsx
|
||||
const [selected, setSelected] = useState("daily")
|
||||
|
||||
<div className="flex gap-2">
|
||||
{["daily", "weekly", "monthly"].map((option) => (
|
||||
<Button
|
||||
key={option}
|
||||
variant={selected === option ? "default" : "outline"}
|
||||
onClick={() => setSelected(option)}
|
||||
>
|
||||
{option}
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
```
|
||||
|
||||
**Correct:**
|
||||
|
||||
```tsx
|
||||
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"
|
||||
|
||||
<ToggleGroup spacing={2}>
|
||||
<ToggleGroupItem value="daily">Daily</ToggleGroupItem>
|
||||
<ToggleGroupItem value="weekly">Weekly</ToggleGroupItem>
|
||||
<ToggleGroupItem value="monthly">Monthly</ToggleGroupItem>
|
||||
</ToggleGroup>
|
||||
```
|
||||
|
||||
Combine with `Field` for labelled toggle groups:
|
||||
|
||||
```tsx
|
||||
<Field orientation="horizontal">
|
||||
<FieldTitle id="theme-label">Theme</FieldTitle>
|
||||
<ToggleGroup aria-labelledby="theme-label" spacing={2}>
|
||||
<ToggleGroupItem value="light">Light</ToggleGroupItem>
|
||||
<ToggleGroupItem value="dark">Dark</ToggleGroupItem>
|
||||
<ToggleGroupItem value="system">System</ToggleGroupItem>
|
||||
</ToggleGroup>
|
||||
</Field>
|
||||
```
|
||||
|
||||
> **Note:** `defaultValue` and `type`/`multiple` props differ between base and radix. See [base-vs-radix.md](./base-vs-radix.md#togglegroup).
|
||||
|
||||
---
|
||||
|
||||
## FieldSet + FieldLegend for grouping related fields
|
||||
|
||||
Use `FieldSet` + `FieldLegend` for related checkboxes, radios, or switches — not `div` with a heading:
|
||||
|
||||
```tsx
|
||||
<FieldSet>
|
||||
<FieldLegend variant="label">Preferences</FieldLegend>
|
||||
<FieldDescription>Select all that apply.</FieldDescription>
|
||||
<FieldGroup className="gap-3">
|
||||
<Field orientation="horizontal">
|
||||
<Checkbox id="dark" />
|
||||
<FieldLabel htmlFor="dark" className="font-normal">Dark mode</FieldLabel>
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
</FieldSet>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Field validation and disabled states
|
||||
|
||||
Both attributes are needed — `data-invalid`/`data-disabled` styles the field (label, description), while `aria-invalid`/`disabled` styles the control.
|
||||
|
||||
```tsx
|
||||
// Invalid.
|
||||
<Field data-invalid>
|
||||
<FieldLabel htmlFor="email">Email</FieldLabel>
|
||||
<Input id="email" aria-invalid />
|
||||
<FieldDescription>Invalid email address.</FieldDescription>
|
||||
</Field>
|
||||
|
||||
// Disabled.
|
||||
<Field data-disabled>
|
||||
<FieldLabel htmlFor="email">Email</FieldLabel>
|
||||
<Input id="email" disabled />
|
||||
</Field>
|
||||
```
|
||||
|
||||
Works for all controls: `Input`, `Textarea`, `Select`, `Checkbox`, `RadioGroupItem`, `Switch`, `Slider`, `NativeSelect`, `InputOTP`.
|
||||
@@ -0,0 +1,101 @@
|
||||
# Icons
|
||||
|
||||
**Always use the project's configured `iconLibrary` for imports.** Check the `iconLibrary` field from project context: `lucide` → `lucide-react`, `tabler` → `@tabler/icons-react`, etc. Never assume `lucide-react`.
|
||||
|
||||
---
|
||||
|
||||
## Icons in Button use data-icon attribute
|
||||
|
||||
Add `data-icon="inline-start"` (prefix) or `data-icon="inline-end"` (suffix) to the icon. No sizing classes on the icon.
|
||||
|
||||
**Incorrect:**
|
||||
|
||||
```tsx
|
||||
<Button>
|
||||
<SearchIcon className="mr-2 size-4" />
|
||||
Search
|
||||
</Button>
|
||||
```
|
||||
|
||||
**Correct:**
|
||||
|
||||
```tsx
|
||||
<Button>
|
||||
<SearchIcon data-icon="inline-start"/>
|
||||
Search
|
||||
</Button>
|
||||
|
||||
<Button>
|
||||
Next
|
||||
<ArrowRightIcon data-icon="inline-end"/>
|
||||
</Button>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## No sizing classes on icons inside components
|
||||
|
||||
Components handle icon sizing via CSS. Don't add `size-4`, `w-4 h-4`, or other sizing classes to icons inside `Button`, `DropdownMenuItem`, `Alert`, `Sidebar*`, or other shadcn components. Unless the user explicitly asks for custom icon sizes.
|
||||
|
||||
**Incorrect:**
|
||||
|
||||
```tsx
|
||||
<Button>
|
||||
<SearchIcon className="size-4" data-icon="inline-start" />
|
||||
Search
|
||||
</Button>
|
||||
|
||||
<DropdownMenuItem>
|
||||
<SettingsIcon className="mr-2 size-4" />
|
||||
Settings
|
||||
</DropdownMenuItem>
|
||||
```
|
||||
|
||||
**Correct:**
|
||||
|
||||
```tsx
|
||||
<Button>
|
||||
<SearchIcon data-icon="inline-start" />
|
||||
Search
|
||||
</Button>
|
||||
|
||||
<DropdownMenuItem>
|
||||
<SettingsIcon />
|
||||
Settings
|
||||
</DropdownMenuItem>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Pass icons as component objects, not string keys
|
||||
|
||||
Use `icon={CheckIcon}`, not a string key to a lookup map.
|
||||
|
||||
**Incorrect:**
|
||||
|
||||
```tsx
|
||||
const iconMap = {
|
||||
check: CheckIcon,
|
||||
alert: AlertIcon,
|
||||
}
|
||||
|
||||
function StatusBadge({ icon }: { icon: string }) {
|
||||
const Icon = iconMap[icon]
|
||||
return <Icon />
|
||||
}
|
||||
|
||||
<StatusBadge icon="check" />
|
||||
```
|
||||
|
||||
**Correct:**
|
||||
|
||||
```tsx
|
||||
// Import from the project's configured iconLibrary (e.g. lucide-react, @tabler/icons-react).
|
||||
import { CheckIcon } from "lucide-react"
|
||||
|
||||
function StatusBadge({ icon: Icon }: { icon: React.ComponentType }) {
|
||||
return <Icon />
|
||||
}
|
||||
|
||||
<StatusBadge icon={CheckIcon} />
|
||||
```
|
||||
@@ -0,0 +1,185 @@
|
||||
# Styling & Customization
|
||||
|
||||
See [customization.md](../customization.md) for theming, CSS variables, and adding custom colors.
|
||||
|
||||
## Contents
|
||||
|
||||
- Semantic colors
|
||||
- Built-in variants first
|
||||
- className for layout only
|
||||
- No space-x-* / space-y-*
|
||||
- Prefer size-* over w-* h-* when equal
|
||||
- Prefer truncate shorthand
|
||||
- No manual dark: color overrides
|
||||
- Use cn() for conditional classes
|
||||
- No manual z-index on overlay components
|
||||
- Use shimmer / scroll-fade utilities, not custom animations
|
||||
|
||||
---
|
||||
|
||||
## Semantic colors
|
||||
|
||||
**Incorrect:**
|
||||
|
||||
```tsx
|
||||
<div className="bg-blue-500 text-white">
|
||||
<p className="text-gray-600">Secondary text</p>
|
||||
</div>
|
||||
```
|
||||
|
||||
**Correct:**
|
||||
|
||||
```tsx
|
||||
<div className="bg-primary text-primary-foreground">
|
||||
<p className="text-muted-foreground">Secondary text</p>
|
||||
</div>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## No raw color values for status/state indicators
|
||||
|
||||
For positive, negative, or status indicators, use Badge variants, semantic tokens like `text-destructive`, or define custom CSS variables — don't reach for raw Tailwind colors.
|
||||
|
||||
**Incorrect:**
|
||||
|
||||
```tsx
|
||||
<span className="text-emerald-600">+20.1%</span>
|
||||
<span className="text-green-500">Active</span>
|
||||
<span className="text-red-600">-3.2%</span>
|
||||
```
|
||||
|
||||
**Correct:**
|
||||
|
||||
```tsx
|
||||
<Badge variant="secondary">+20.1%</Badge>
|
||||
<Badge>Active</Badge>
|
||||
<span className="text-destructive">-3.2%</span>
|
||||
```
|
||||
|
||||
If you need a success/positive color that doesn't exist as a semantic token, use a Badge variant or ask the user about adding a custom CSS variable to the theme (see [customization.md](../customization.md)).
|
||||
|
||||
---
|
||||
|
||||
## Built-in variants first
|
||||
|
||||
**Incorrect:**
|
||||
|
||||
```tsx
|
||||
<Button className="border border-input bg-transparent hover:bg-accent">
|
||||
Click me
|
||||
</Button>
|
||||
```
|
||||
|
||||
**Correct:**
|
||||
|
||||
```tsx
|
||||
<Button variant="outline">Click me</Button>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## className for layout only
|
||||
|
||||
Use `className` for layout (e.g. `max-w-md`, `mx-auto`, `mt-4`), **not** for overriding component colors or typography. To change colors, use semantic tokens, built-in variants, or CSS variables.
|
||||
|
||||
**Incorrect:**
|
||||
|
||||
```tsx
|
||||
<Card className="bg-blue-100 text-blue-900 font-bold">
|
||||
<CardContent>Dashboard</CardContent>
|
||||
</Card>
|
||||
```
|
||||
|
||||
**Correct:**
|
||||
|
||||
```tsx
|
||||
<Card className="max-w-md mx-auto">
|
||||
<CardContent>Dashboard</CardContent>
|
||||
</Card>
|
||||
```
|
||||
|
||||
To customize a component's appearance, prefer these approaches in order:
|
||||
1. **Built-in variants** — `variant="outline"`, `variant="destructive"`, etc.
|
||||
2. **Semantic color tokens** — `bg-primary`, `text-muted-foreground`.
|
||||
3. **CSS variables** — define custom colors in the global CSS file (see [customization.md](../customization.md)).
|
||||
|
||||
---
|
||||
|
||||
## No space-x-* / space-y-*
|
||||
|
||||
Use `gap-*` instead. `space-y-4` → `flex flex-col gap-4`. `space-x-2` → `flex gap-2`.
|
||||
|
||||
```tsx
|
||||
<div className="flex flex-col gap-4">
|
||||
<Input />
|
||||
<Input />
|
||||
<Button>Submit</Button>
|
||||
</div>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Prefer size-* over w-* h-* when equal
|
||||
|
||||
`size-10` not `w-10 h-10`. Applies to icons, avatars, skeletons, etc.
|
||||
|
||||
---
|
||||
|
||||
## Prefer truncate shorthand
|
||||
|
||||
`truncate` not `overflow-hidden text-ellipsis whitespace-nowrap`.
|
||||
|
||||
---
|
||||
|
||||
## No manual dark: color overrides
|
||||
|
||||
Use semantic tokens — they handle light/dark via CSS variables. `bg-background text-foreground` not `bg-white dark:bg-gray-950`.
|
||||
|
||||
---
|
||||
|
||||
## Use cn() for conditional classes
|
||||
|
||||
Use the `cn()` utility from the project for conditional or merged class names. Don't write manual ternaries in className strings.
|
||||
|
||||
**Incorrect:**
|
||||
|
||||
```tsx
|
||||
<div className={`flex items-center ${isActive ? "bg-primary text-primary-foreground" : "bg-muted"}`}>
|
||||
```
|
||||
|
||||
**Correct:**
|
||||
|
||||
```tsx
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
<div className={cn("flex items-center", isActive ? "bg-primary text-primary-foreground" : "bg-muted")}>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## No manual z-index on overlay components
|
||||
|
||||
`Dialog`, `Sheet`, `Drawer`, `AlertDialog`, `DropdownMenu`, `Popover`, `Tooltip`, `HoverCard` handle their own stacking. Never add `z-50` or `z-[999]`.
|
||||
|
||||
---
|
||||
|
||||
## Use shimmer / scroll-fade utilities, not custom animations
|
||||
|
||||
For a live "thinking…" or loading-text shimmer, apply the `shimmer` utility. Don't author a custom `@keyframes` or a `bg-clip-text` gradient sweep.
|
||||
|
||||
For scroll-aware edge fading on a scroll container, use `scroll-fade` (and the axis variants `scroll-fade-x` / `scroll-fade-b`). Don't hand-roll mask gradients. The chat components already apply these internally: `Attachment` shimmers its title during upload, and `MessageScrollerViewport` fades its edges.
|
||||
|
||||
**Incorrect:**
|
||||
|
||||
```tsx
|
||||
<span className="animate-pulse bg-gradient-to-r from-muted-foreground/40 via-foreground/70 to-muted-foreground/40 bg-clip-text text-transparent [animation:shimmer_1.6s_infinite]">
|
||||
Thinking…
|
||||
</span>
|
||||
```
|
||||
|
||||
**Correct:**
|
||||
|
||||
```tsx
|
||||
<span className="shimmer">Thinking…</span>
|
||||
```
|
||||
+1
-1
@@ -24,7 +24,7 @@
|
||||
<classpathentry kind="lib" path="lib/jctools-core-4.0.6.jar"/>
|
||||
<classpathentry kind="lib" path="lib/jctools-core-4.0.6-javadoc.jar"/>
|
||||
<classpathentry kind="lib" path="lib/jctools-core-4.0.6-sources.jar"/>
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/jdk-26.0.1">
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-25">
|
||||
<attributes>
|
||||
<attribute name="module" value="true"/>
|
||||
</attributes>
|
||||
|
||||
+13
@@ -3,3 +3,16 @@
|
||||
/klalbs4.json
|
||||
/klalbs.json
|
||||
/klalbs2.json
|
||||
|
||||
# Dashboard / Frontend
|
||||
dashboard/node_modules/
|
||||
dashboard/dist/
|
||||
dashboard/.pnpm-store/
|
||||
.pnpm-debug.log*
|
||||
dashboard/.env.local
|
||||
dashboard/.env.*.local
|
||||
|
||||
/klalb-config.json
|
||||
/klalbconfig-old.json
|
||||
|
||||
/.slim/
|
||||
@@ -0,0 +1,3 @@
|
||||
[submodule "dashboard"]
|
||||
path = dashboard
|
||||
url = https://git.code.cq.cn/SerinaNya/KLALB-dashboard.git
|
||||
@@ -14,4 +14,15 @@
|
||||
<natures>
|
||||
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||
</natures>
|
||||
<filteredResources>
|
||||
<filter>
|
||||
<id>1787317094049</id>
|
||||
<name></name>
|
||||
<type>30</type>
|
||||
<matcher>
|
||||
<id>org.eclipse.core.resources.regexFilterMatcher</id>
|
||||
<arguments>node_modules|\.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__</arguments>
|
||||
</matcher>
|
||||
</filter>
|
||||
</filteredResources>
|
||||
</projectDescription>
|
||||
|
||||
Vendored
+27
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "java",
|
||||
"name": "KLALBMain (VS Code 自动构建)",
|
||||
"request": "launch",
|
||||
"mainClass": "org.kne.cloud.network.klalb.KLALBMain",
|
||||
"projectName": "KLALB",
|
||||
"cwd": "${workspaceFolder}",
|
||||
"vmArgs": "--enable-native-access=ALL-UNNAMED --add-opens=java.base/jdk.internal.misc=ALL-UNNAMED"
|
||||
},
|
||||
{
|
||||
"type": "java",
|
||||
"name": "KLALBMain (直接用 bin 目录)",
|
||||
"request": "launch",
|
||||
"mainClass": "org.kne.cloud.network.klalb.KLALBMain",
|
||||
"cwd": "${workspaceFolder}",
|
||||
"classPaths": [
|
||||
"${workspaceFolder}/bin",
|
||||
"${workspaceFolder}/src",
|
||||
"${workspaceFolder}/lib/*"
|
||||
],
|
||||
"vmArgs": "--enable-native-access=ALL-UNNAMED --add-opens=java.base/jdk.internal.misc=ALL-UNNAMED"
|
||||
}
|
||||
]
|
||||
}
|
||||
Vendored
+9
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"java.configuration.runtimes": [
|
||||
{
|
||||
"name": "JavaSE-25",
|
||||
"path": "C:\\Program Files\\Zulu\\zulu-25",
|
||||
"default": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
# KLALB Repository Guide
|
||||
|
||||
KLALB is a Java SRv6/load-balancing system. Start at `org.kne.cloud.network.klalb.KLALBMain`; runtime configuration is root-relative `klalb-config.json`.
|
||||
|
||||
## Build And Run
|
||||
|
||||
- This is an Eclipse Java project, not Maven or Gradle: `src/` contains sources, `lib/` vendored dependencies, and `bin/` compiled output. `.classpath` targets Java 25.
|
||||
- Source files contain Chinese text, so compile with UTF-8 from the repository root:
|
||||
|
||||
```powershell
|
||||
& "C:\Program Files\Zulu\zulu-25\bin\javac.exe" -encoding UTF-8 -cp "lib/*" -d bin (Get-ChildItem -Recurse src -Filter *.java | ForEach-Object FullName)
|
||||
```
|
||||
|
||||
- Run from the repository root. `src` must stay on the classpath because manual compilation does not copy resource bundles:
|
||||
|
||||
```powershell
|
||||
& "C:\Program Files\Zulu\zulu-25\bin\java.exe" --enable-native-access=ALL-UNNAMED "--add-opens=java.base/jdk.internal.misc=ALL-UNNAMED" -cp "bin;src;lib/*" org.kne.cloud.network.klalb.KLALBMain
|
||||
```
|
||||
|
||||
- Native libraries and `klalb-config.json` are resolved from the current directory; restart the JVM after recompiling. TUN creation requires elevation, so use `"enableTUN": false` for non-admin checks.
|
||||
|
||||
## Dashboard
|
||||
|
||||
- `dashboard/` is a Git submodule. Commit dashboard changes in that repository, then update the parent submodule pointer.
|
||||
- Run frontend commands from `dashboard/`: `pnpm install --frozen-lockfile`, `pnpm lint`, `pnpm typecheck`, and `pnpm build`. The build is `tsc -b && vite build` and produces `dashboard/dist`, which the Java server hosts.
|
||||
- Vite proxies `/api` to `http://127.0.0.1:4665`; use `pnpm dev` only with the Java API running there.
|
||||
|
||||
## Verification
|
||||
|
||||
- No repository test runner or CI workflow is configured. For Java changes, compile and launch the app; for dashboard changes, run `pnpm typecheck` and `pnpm build`.
|
||||
|
||||
## Important Boundaries
|
||||
|
||||
- `KLALBConfigItem` is a polymorphic JSON array keyed by case-sensitive `Type`. New types need serializer and deserializer support; preserve unknown items' raw JSON.
|
||||
- `/api/config` is field-by-field parsing, not whole-object Gson mapping. Keep legacy key aliases in sync with new fields.
|
||||
- Vendored Gson is `2.1`: responses that are `JsonElement` instances must be serialized with `JsonElement.toString()`, not reflective `gson.toJson(Object)`.
|
||||
- UI strings use `UIEnv.getRsb()`; add keys to both `src/klalb_zh_CN.properties` and `src/klalb_en_US.properties`.
|
||||
- `KLALBController.PublishedNodeInfo` is the thread-safe node-info source. Publish profile, effective external endpoints, and extra routes through the controller so snapshots and update flags remain coherent. Peer queries are separate profile, endpoint, and extra-route requests; use `NodeInfoQueryCoordinator` when a consumer needs an aggregate detail result.
|
||||
- `RouterInfo` retains an empty legacy UTF slot. `RouterInfoPacket` appends optional update flags behind a marker; treat binary codec changes as mesh-compatibility work and preserve old-reader behavior.
|
||||
Submodule
+1
Submodule dashboard added at 8c13af680a
@@ -1,123 +0,0 @@
|
||||
[
|
||||
{
|
||||
"language": "ZH_CN",
|
||||
"nogui": false,
|
||||
"VirtualAddress": "2486:1:0:0:0:0:0:8888",
|
||||
"VirtualASN": 2142606939373348329,
|
||||
"DNS": [
|
||||
"2486:1:0:0:0:0:0:8888"
|
||||
],
|
||||
"TCPListen": "0.0.0.0:4565",
|
||||
"UDPListen": "{UDP}0.0.0.0:4572",
|
||||
"VirtualSocketName": "KLALB_Stream",
|
||||
"LineTable": [
|
||||
"07f4acdef99b.ofalias.net:4565"
|
||||
],
|
||||
"ConnectLineTable": [
|
||||
"home-ipv6.sliveridc1.cn:4565",
|
||||
"home-ltipv6.sliveridc1.cn:4565",
|
||||
"cn-gd-gz.sliveridc1.cn:4565"
|
||||
],
|
||||
"ntpServerTable": [
|
||||
"{UDP}ntp1.aliyun.com:123",
|
||||
"{UDP}ntp2.aliyun.com:123",
|
||||
"{UDP}ntp3.aliyun.com:123",
|
||||
"{UDP}ntp4.aliyun.com:123",
|
||||
"{UDP}ntp5.aliyun.com:123",
|
||||
"{UDP}ntp6.aliyun.com:123",
|
||||
"{UDP}ntp7.aliyun.com:123",
|
||||
"{UDP}ntp1.tencent.com:123",
|
||||
"{UDP}ntp2.tencent.com:123",
|
||||
"{UDP}ntp3.tencent.com:123",
|
||||
"{UDP}ntp4.tencent.com:123",
|
||||
"{UDP}ntp5.tencent.com:123",
|
||||
"{UDP}time.google.com:123",
|
||||
"{UDP}time.apple.com:123",
|
||||
"{UDP}pool.ntp.org:123",
|
||||
"{UDP}ntp.ntsc.ac.cn:123",
|
||||
"{UDP}us.ntp.org.cn:123"
|
||||
],
|
||||
"denyLineTableQuery": true,
|
||||
"denyLineTableBroadcast": true,
|
||||
"congestionAlgorithm": "BBR",
|
||||
"burstLimit": 1.5,
|
||||
"delayUpperBound": 1.3,
|
||||
"delayLowerBound": 1.3,
|
||||
"nagleDelayTime": 1000000,
|
||||
"linkNagleDelayTime": 0,
|
||||
"linkConnectionsCount": 1,
|
||||
"TUNName": "KLALB_SRv6",
|
||||
"NetworkInterfaceExcepts": [],
|
||||
"Type": "KLALBController"
|
||||
},
|
||||
{
|
||||
"Listen": "0.0.0.0:4572",
|
||||
"Bridge": {
|
||||
"KLALB": "SocketBridge",
|
||||
"RDP": "SocketBridge",
|
||||
"HTTP": "SocketBridge",
|
||||
"HTTPS": "SocketBridge",
|
||||
"DEFAULT": "MinecraftSocketBridge23332"
|
||||
},
|
||||
"Connect": {
|
||||
"KLALB": "127.0.0.1:4565",
|
||||
"RDP": "127.0.0.1:3389",
|
||||
"HTTP": "127.0.0.1:5212",
|
||||
"HTTPS": "192.168.1.235:443",
|
||||
"DEFAULT": "127.0.0.1:36555"
|
||||
},
|
||||
"Type": "SocketBridge"
|
||||
},
|
||||
{
|
||||
"Listen": "{KLALB_Stream}[::0]:23332",
|
||||
"Bridge": {
|
||||
"DEFAULT": "MinecraftSocketBridge23332"
|
||||
},
|
||||
"Connect": {
|
||||
"DEFAULT": "127.0.0.1:36555"
|
||||
},
|
||||
"Type": "SocketBridge"
|
||||
},
|
||||
{
|
||||
"Listen": "{KLALB_Stream}[::0]:23333",
|
||||
"Bridge": {
|
||||
"DEFAULT": "SocketBridge"
|
||||
},
|
||||
"Connect": {
|
||||
"DEFAULT": "127.0.0.1:5212"
|
||||
},
|
||||
"Type": "SocketBridge"
|
||||
},
|
||||
{
|
||||
"Listen": "{KLALB_Stream}[::0]:25565",
|
||||
"Bridge": {
|
||||
"DEFAULT": "SocketBridge"
|
||||
},
|
||||
"Connect": {
|
||||
"DEFAULT": "127.0.0.1:25566"
|
||||
},
|
||||
"Type": "SocketBridge"
|
||||
},
|
||||
{
|
||||
"Listen": "0.0.0.0:25561",
|
||||
"Bridge": {
|
||||
"KLALB": "SocketBridge",
|
||||
"DEFAULT": "MinecraftSocketBridge23330"
|
||||
},
|
||||
"Connect": {
|
||||
"KLALB": "127.0.0.1:4565",
|
||||
"DEFAULT": "127.0.0.1:25562"
|
||||
},
|
||||
"Type": "SocketBridge"
|
||||
},
|
||||
{
|
||||
"Listen": "{KLALB_Stream}[::0]:23330",
|
||||
"Bridge": {
|
||||
"DEFAULT": "MinecraftSocketBridge23330"
|
||||
},
|
||||
"Connect": {
|
||||
"DEFAULT": "127.0.0.1:25562"
|
||||
},
|
||||
"Type": "SocketBridge"
|
||||
}
|
||||
]
|
||||
@@ -1,3 +0,0 @@
|
||||
virtualip=cefe:49b8:f837:4b00:b9cd:dbdd:c299:349
|
||||
bind=0.0.0.0:4569
|
||||
local=127.0.0.1:5212
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"version": 1,
|
||||
"skills": {
|
||||
"shadcn": {
|
||||
"source": "shadcn/ui",
|
||||
"sourceType": "github",
|
||||
"skillPath": "skills/shadcn/SKILL.md",
|
||||
"computedHash": "c1a68ee06a668aced9ab2b5fbdea5f989864123794eb2e056b339a072dbb7f10"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,11 @@ networkgraph=Network graph
|
||||
language=Language
|
||||
basicsettings=Basic settings
|
||||
ipv6addr=IPv6 address
|
||||
devicename=Device name
|
||||
devicedescription=Device description
|
||||
dnsserver=DNS server
|
||||
extraroutes=Extra routes
|
||||
noextraroutes=No extra routes
|
||||
asnumber=AS number
|
||||
tcplistening=TCP listening
|
||||
udplistening=UDP listening
|
||||
@@ -34,6 +38,7 @@ saveconfigsuccess=Save config success
|
||||
warning=Warning
|
||||
invaildipv6addr=IPv6 address:Invaild Input
|
||||
invailddnsserver=DNS server:Invaild Input
|
||||
invaildextraroutes=Extra routes:Invaild Input
|
||||
invaildasnumber=AS number:Invaild Input
|
||||
invaildtcplisten=TCP listen:Invaild Input
|
||||
invaildudplisten=UDP listen:Invaild Input
|
||||
@@ -105,3 +110,11 @@ uploadbasedelay=Upload Base
|
||||
burstlimit=Burst limit
|
||||
performancesettings=Performance settings
|
||||
performancestrategy=Performance strategy
|
||||
singlecore=Single-Core - Cache Affinity First (Best energy/performance ratio, for low-power & cloud)
|
||||
multifill=Multi-Core - Fill Cores Sequentially (Recommended for general-purpose physical servers)
|
||||
multiscatter=Multi-Core - Spread Load Evenly (Optimized for multi-socket NUMA architectures)
|
||||
webapisettings=Web API settings
|
||||
enablewebapi=Enable Web API
|
||||
weblistenaddr=Web API listen address
|
||||
invaildweblistenaddr=Invalid Web API listen address
|
||||
enabletun=Enable TUN adapter
|
||||
|
||||
@@ -3,7 +3,11 @@ networkgraph=网络拓扑图
|
||||
language=语言
|
||||
basicsettings=基本设置
|
||||
ipv6addr=IPv6地址
|
||||
devicename=设备名称
|
||||
devicedescription=设备描述
|
||||
dnsserver=DNS服务器
|
||||
extraroutes=额外路由
|
||||
noextraroutes=暂无额外路由
|
||||
asnumber=AS号码
|
||||
tcplistening=TCP监听端口
|
||||
udplistening=UDP监听端口
|
||||
@@ -34,6 +38,7 @@ saveconfigsuccess=保存配置成功
|
||||
warning=警告
|
||||
invaildipv6addr=IPv6地址:非法输入
|
||||
invailddnsserver=DNS服务器:非法输入
|
||||
invaildextraroutes=额外路由:非法输入
|
||||
invaildasnumber=AS号码:非法输入
|
||||
invaildtcplisten=TCP监听端口:非法输入
|
||||
invaildudplisten=UDP监听端口:非法输入
|
||||
@@ -105,3 +110,11 @@ uploadbasedelay=上传延迟基线
|
||||
burstlimit=突发限制
|
||||
performancesettings=性能设置
|
||||
performancestrategy=性能策略
|
||||
singlecore=单核-缓存命中率优先(高能耗比,适合低功耗设备、云机)
|
||||
multifill=多核-负载按顺序填充(适合大多数物理服务器、电脑)
|
||||
multiscatter=多核-负载均匀打散分配(适合特殊的多路NUMA服务器)
|
||||
webapisettings=Web API 设置
|
||||
enablewebapi=启用 Web API
|
||||
weblistenaddr=Web API 监听地址:端口
|
||||
invaildweblistenaddr=无效的 Web API 监听地址:端口
|
||||
enabletun=启用TUN虚拟网卡
|
||||
|
||||
@@ -3,14 +3,9 @@ package org.kne.cloud.network;
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.net.DatagramSocket;
|
||||
import java.net.InetAddress;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketException;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import javax.net.ServerSocketFactory;
|
||||
|
||||
public class DatagramSocketListener implements Closeable,AutoCloseable{
|
||||
private DatagramServerSocket serverSocket;
|
||||
public DatagramServerSocket getDatagramServerSocket() {
|
||||
@@ -36,10 +31,10 @@ public class DatagramSocketListener implements Closeable,AutoCloseable{
|
||||
}
|
||||
}
|
||||
};
|
||||
private MultipurposeSocketAddress multipurposeSocketAddress;
|
||||
private MultiProtocolSocketAddress multiProtocolSocketAddress;
|
||||
|
||||
public DatagramSocketListener(MultipurposeSocketAddress multipurposeSocketAddress) throws IOException {
|
||||
this.multipurposeSocketAddress=multipurposeSocketAddress;
|
||||
public DatagramSocketListener(MultiProtocolSocketAddress multiProtocolSocketAddress) throws IOException {
|
||||
this.multiProtocolSocketAddress = multiProtocolSocketAddress;
|
||||
open();
|
||||
}
|
||||
public DatagramSocketListener(DatagramServerSocket tserverSocket) throws IOException {
|
||||
@@ -49,7 +44,7 @@ public class DatagramSocketListener implements Closeable,AutoCloseable{
|
||||
|
||||
private void open() throws IOException {
|
||||
if(serverSocket==null)
|
||||
serverSocket=multipurposeSocketAddress.listenDatagramServerSocket();
|
||||
serverSocket= multiProtocolSocketAddress.listenDatagramServerSocket();
|
||||
//new Thread(r).start();
|
||||
ThreadTool.makePThreadIfSupport("端口监听线程",r).start();
|
||||
}
|
||||
@@ -63,8 +58,8 @@ public class DatagramSocketListener implements Closeable,AutoCloseable{
|
||||
|
||||
|
||||
|
||||
public MultipurposeSocketAddress getMultipurposeSocketAddress() {
|
||||
return multipurposeSocketAddress;
|
||||
public MultiProtocolSocketAddress getMultipurposeSocketAddress() {
|
||||
return multiProtocolSocketAddress;
|
||||
}
|
||||
|
||||
public Consumer<DatagramSocket> getCon() {
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
package org.kne.cloud.network;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
|
||||
public class HostPortMap extends LinkedHashMap<String, MultipurposeSocketAddress> {
|
||||
public class HostPortMap extends LinkedHashMap<String, MultiProtocolSocketAddress> {
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -11,9 +10,9 @@ public class HostPortMap extends LinkedHashMap<String, MultipurposeSocketAddress
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
|
||||
public MultipurposeSocketAddress putN$H(String str) {
|
||||
public MultiProtocolSocketAddress putN$H(String str) {
|
||||
String[] strx=str.split("\\$");
|
||||
return super.put(strx[0], new MultipurposeSocketAddress( strx[1]));
|
||||
return super.put(strx[0], new MultiProtocolSocketAddress( strx[1]));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -7,16 +7,8 @@ import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
import java.net.DatagramPacket;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.MulticastSocket;
|
||||
import java.net.NetworkInterface;
|
||||
import java.net.NoRouteToHostException;
|
||||
import java.net.SocketException;
|
||||
import java.net.UnknownHostException;
|
||||
import java.net.*;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
@@ -29,12 +21,11 @@ public class IPMulticastDiscovery implements Closeable, AutoCloseable {
|
||||
private NetworkInterface ninterface;
|
||||
private volatile boolean closed = false;
|
||||
private InetSocketAddress group;
|
||||
private InetSocketAddress bind;
|
||||
private int type;
|
||||
private List<MultipurposeSocketAddress> msas;
|
||||
private List<MultiProtocolSocketAddress> msas;
|
||||
private UUID selfUUID;
|
||||
|
||||
private volatile Consumer<MultipurposeSocketAddress> con;
|
||||
private volatile Consumer<MultiProtocolSocketAddress> con;
|
||||
private long timeInterval;
|
||||
|
||||
public long getTimeInterval() {
|
||||
@@ -45,7 +36,7 @@ public class IPMulticastDiscovery implements Closeable, AutoCloseable {
|
||||
this.timeInterval = timeInterval;
|
||||
}
|
||||
|
||||
public NetworkInterface getNinterface() {
|
||||
public NetworkInterface getInterface() {
|
||||
return ninterface;
|
||||
}
|
||||
|
||||
@@ -54,22 +45,24 @@ public class IPMulticastDiscovery implements Closeable, AutoCloseable {
|
||||
}
|
||||
|
||||
public InetSocketAddress getBind() {
|
||||
return bind;
|
||||
return (InetSocketAddress) soc.getLocalSocketAddress();
|
||||
}
|
||||
|
||||
public Consumer<MultipurposeSocketAddress> getCon() {
|
||||
public Consumer<MultiProtocolSocketAddress> getCon() {
|
||||
return con;
|
||||
}
|
||||
|
||||
public void setCon(Consumer<MultipurposeSocketAddress> con) {
|
||||
public void setCon(Consumer<MultiProtocolSocketAddress> con) {
|
||||
this.con = con;
|
||||
}
|
||||
|
||||
public IPMulticastDiscovery(InetSocketAddress bind, InetSocketAddress group, NetworkInterface ninterface,
|
||||
List<MultipurposeSocketAddress> msas,UUID node,long timeInterval) throws IOException {
|
||||
soc = new MulticastSocket(bind);
|
||||
List<MultiProtocolSocketAddress> msas, UUID node, long timeInterval) throws IOException {
|
||||
soc = new MulticastSocket(null);
|
||||
soc.setReuseAddress(true);
|
||||
soc.bind(bind);
|
||||
soc.setNetworkInterface(ninterface);
|
||||
soc.joinGroup(group, ninterface);
|
||||
this.bind = bind;
|
||||
this.group = group;
|
||||
this.ninterface = ninterface;
|
||||
this.msas = msas;
|
||||
@@ -82,46 +75,22 @@ public class IPMulticastDiscovery implements Closeable, AutoCloseable {
|
||||
ThreadTool.makeVDaemonThreadIfSupport("线路通知线程", () -> {
|
||||
try {
|
||||
while (!closed) {
|
||||
HashSet<MultipurposeSocketAddress> s = new HashSet<MultipurposeSocketAddress>();
|
||||
/*MultipurposeSocketAddress mpsa = new MultipurposeSocketAddress(
|
||||
multipurposeSocketAddress.getType(), "::0", multipurposeSocketAddress.getPort());*/
|
||||
if(msas!=null) {
|
||||
synchronized (msas) {
|
||||
for (Iterator<MultipurposeSocketAddress> iterator = msas.iterator(); iterator.hasNext();) {
|
||||
MultipurposeSocketAddress multipurposeSocketAddress = (MultipurposeSocketAddress) iterator
|
||||
for (Iterator<MultiProtocolSocketAddress> iterator = msas.iterator(); iterator.hasNext(); ) {
|
||||
MultiProtocolSocketAddress multiProtocolSocketAddress = (MultiProtocolSocketAddress) iterator
|
||||
.next();
|
||||
|
||||
MultipurposeSocketAddress mpsa = new MultipurposeSocketAddress(
|
||||
multipurposeSocketAddress.getType(), "::0", multipurposeSocketAddress.getPort());
|
||||
try {
|
||||
if (s.add(mpsa)) {
|
||||
boolean bf = true;
|
||||
Enumeration<InetAddress> ei = ninterface.getInetAddresses();
|
||||
while (ei.hasMoreElements()) {
|
||||
InetAddress inetAddress = (InetAddress) ei.nextElement();
|
||||
if (inetAddress.equals(multipurposeSocketAddress.getInetAddress())) {
|
||||
bf = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (bf) {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
DataOutputStream dops = new DataOutputStream(baos);
|
||||
dops.writeInt(multipurposeSocketAddress.getPort());
|
||||
dops.writeUTF(multipurposeSocketAddress.getType());
|
||||
dops.writeLong(selfUUID.getMostSignificantBits());
|
||||
dops.writeLong(selfUUID.getLeastSignificantBits());
|
||||
dops.close();
|
||||
byte[] b = baos.toByteArray();
|
||||
DatagramPacket dp = new DatagramPacket(b, b.length, group);
|
||||
soc.send(dp);
|
||||
if(debug)
|
||||
System.out.println("发送广播:"+multipurposeSocketAddress+" "+group+" "+selfUUID);
|
||||
}
|
||||
}
|
||||
} catch (NoRouteToHostException|UnknownHostException e) {
|
||||
send(multiProtocolSocketAddress);
|
||||
} catch (NoRouteToHostException | UnknownHostException e) {
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Thread.sleep(timeInterval);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
@@ -143,27 +112,8 @@ public class IPMulticastDiscovery implements Closeable, AutoCloseable {
|
||||
try {
|
||||
loop:while (!closed) {
|
||||
try {
|
||||
byte[] b = new byte[65535];
|
||||
DatagramPacket dp = new DatagramPacket(b, b.length);
|
||||
soc.receive(dp);
|
||||
ByteArrayInputStream bis = new ByteArrayInputStream(b, 0, dp.getLength());
|
||||
DataInputStream dis = new DataInputStream(bis);
|
||||
int port = dis.readInt();
|
||||
String type = dis.readUTF();
|
||||
long h=dis.readLong();
|
||||
long l=dis.readLong();
|
||||
UUID uid=new UUID(h, l);
|
||||
dis.close();
|
||||
MultipurposeSocketAddress mpsa = new MultipurposeSocketAddress(type, dp.getAddress().getHostAddress(),
|
||||
port);
|
||||
if(debug) {
|
||||
if(uid.equals(selfUUID)) {
|
||||
System.out.println("丢弃广播:"+mpsa+" "+dp.getSocketAddress()+" "+uid);
|
||||
continue;
|
||||
}else {
|
||||
System.out.println("接收广播:"+mpsa+" "+dp.getSocketAddress()+" "+uid);
|
||||
}
|
||||
}
|
||||
MultiProtocolSocketAddress mpsa = receive();
|
||||
if (mpsa == null) continue;
|
||||
InetAddress mpsai=mpsa.getInetAddress();
|
||||
Enumeration<InetAddress> ei = ninterface.getInetAddresses();
|
||||
while (ei.hasMoreElements()) {
|
||||
@@ -186,7 +136,7 @@ public class IPMulticastDiscovery implements Closeable, AutoCloseable {
|
||||
if(!isClosed())
|
||||
e.printStackTrace();
|
||||
}catch (IOException e) {
|
||||
System.out.println(bind + " " + group + " " + ninterface);
|
||||
System.out.println(getBind() + " " + group + " " + ninterface);
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
try {
|
||||
@@ -196,7 +146,48 @@ public class IPMulticastDiscovery implements Closeable, AutoCloseable {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
});
|
||||
}).start();
|
||||
}
|
||||
|
||||
private MultiProtocolSocketAddress receive() throws IOException {
|
||||
byte[] b = new byte[65535];
|
||||
DatagramPacket dp = new DatagramPacket(b, b.length);
|
||||
soc.receive(dp);
|
||||
ByteArrayInputStream bis = new ByteArrayInputStream(b, 0, dp.getLength());
|
||||
DataInputStream dis = new DataInputStream(bis);
|
||||
String val = dis.readUTF();
|
||||
long h=dis.readLong();
|
||||
long l=dis.readLong();
|
||||
UUID uid=new UUID(h, l);
|
||||
dis.close();
|
||||
MultiProtocolSocketAddress mpsa = new MultiProtocolSocketAddress(val);
|
||||
if(mpsa.getInetAddress().isAnyLocalAddress()){
|
||||
mpsa=new MultiProtocolSocketAddress(mpsa.getProtocol(),dp.getAddress().getHostAddress(),mpsa.getPort());
|
||||
}
|
||||
if(debug) {
|
||||
if(uid.equals(selfUUID)) {
|
||||
System.out.println(ninterface.getDisplayName()+" 丢弃广播:"+mpsa+" "+dp.getSocketAddress()+" "+uid);
|
||||
return null;
|
||||
}else {
|
||||
System.out.println(ninterface.getDisplayName()+ " 接收广播:"+mpsa+" "+dp.getSocketAddress()+" "+uid);
|
||||
}
|
||||
}
|
||||
return mpsa;
|
||||
}
|
||||
|
||||
private void send(MultiProtocolSocketAddress multiProtocolSocketAddress) throws IOException {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
DataOutputStream dops = new DataOutputStream(baos);
|
||||
dops.writeUTF(multiProtocolSocketAddress.toString());
|
||||
dops.writeLong(selfUUID.getMostSignificantBits());
|
||||
dops.writeLong(selfUUID.getLeastSignificantBits());
|
||||
dops.close();
|
||||
byte[] b = baos.toByteArray();
|
||||
DatagramPacket dp = new DatagramPacket(b, b.length, group);
|
||||
soc.send(dp);
|
||||
if(debug)
|
||||
System.out.println(ninterface.getDisplayName()+" 发送广播:"+ multiProtocolSocketAddress +" "+group+" "+selfUUID);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+101
-95
@@ -3,14 +3,7 @@ package org.kne.cloud.network;
|
||||
import java.io.IOException;
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.Type;
|
||||
import java.net.DatagramSocket;
|
||||
import java.net.Inet6Address;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.net.*;
|
||||
import java.nio.channels.ServerSocketChannel;
|
||||
import java.nio.channels.SocketChannel;
|
||||
import java.util.*;
|
||||
@@ -18,7 +11,6 @@ import java.util.*;
|
||||
import javax.net.ServerSocketFactory;
|
||||
import javax.net.SocketFactory;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonDeserializationContext;
|
||||
import com.google.gson.JsonDeserializer;
|
||||
@@ -27,17 +19,18 @@ import com.google.gson.JsonParseException;
|
||||
import com.google.gson.JsonPrimitive;
|
||||
import com.google.gson.JsonSerializationContext;
|
||||
import com.google.gson.JsonSerializer;
|
||||
import org.kne.cloud.network.ntp.NTPSocketType;
|
||||
|
||||
public class MultipurposeSocketAddress implements Serializable{
|
||||
public class MultiProtocolSocketAddress implements Serializable{
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private static final Map<String,SocketType> socketTypeRegister=new HashMap<>();
|
||||
|
||||
|
||||
static {
|
||||
socketTypeRegister.put("TCP", new SocketType(new DefaultSocketFactory(), new DefaultServerSocketFactory(),new DefaultSocketChannelFactory(),new DefaultServerSocketChannelFactory()));
|
||||
socketTypeRegister.put("UDP", new SocketType(new DefaultDatagramSocketFactory(),new DefaultDatagramServerSocketFactory()));
|
||||
socketTypeRegister.put("tcp", TCPSocketType.getInstance());
|
||||
socketTypeRegister.put("udp", UDPSocketType.getInstance());
|
||||
socketTypeRegister.put("ntp", NTPSocketType.getInstance());
|
||||
}
|
||||
|
||||
|
||||
@@ -46,46 +39,59 @@ public class MultipurposeSocketAddress implements Serializable{
|
||||
return socketTypeRegister;
|
||||
}
|
||||
private static final long serialVersionUID = 1L;
|
||||
private String type;
|
||||
private String protocol;
|
||||
private String host;
|
||||
private ProtocolStack ps=new ProtocolStack();
|
||||
|
||||
|
||||
private int port;
|
||||
|
||||
public MultipurposeSocketAddress(String hostport) {
|
||||
this(hostport,"TCP");
|
||||
public MultiProtocolSocketAddress(String hostport) {
|
||||
this(hostport,"tcp");
|
||||
}
|
||||
public MultipurposeSocketAddress(String hostport,String defaulttype) {
|
||||
hostport=hostport.trim();
|
||||
if (hostport.startsWith("{")) {
|
||||
int i=hostport.indexOf("}");
|
||||
if(i==-1) {
|
||||
throw new IllegalArgumentException("missing }");
|
||||
public MultiProtocolSocketAddress(String hostport, String defaultprotocol) {
|
||||
try {
|
||||
if (!hostport.contains("://")) {
|
||||
hostport = defaultprotocol.toLowerCase() + "://" + hostport;
|
||||
}
|
||||
type=hostport.substring(1, i);
|
||||
hostport=hostport.substring(i+1);
|
||||
}else {
|
||||
type=defaulttype;
|
||||
URI uri = new URI(hostport);
|
||||
|
||||
this.protocol = uri.getScheme().toLowerCase();
|
||||
this.host = uri.getHost();
|
||||
if (this.host == null) {
|
||||
throw new IllegalArgumentException("无效的主机地址: " + hostport);
|
||||
}
|
||||
int index =hostport.lastIndexOf(":");
|
||||
host=hostport.substring(0,index);
|
||||
host=host.replace("[", "");
|
||||
host=host.replace("]", "");
|
||||
String tps=hostport.substring(index+1);
|
||||
int v=tps.indexOf("(");
|
||||
if(v!=-1) {
|
||||
ps.add(new Protocol(tps.substring(v+1,tps.lastIndexOf(")") )));
|
||||
tps=tps.substring(0, v);
|
||||
this.port = resolvePort(uri, this.protocol);
|
||||
|
||||
// 可选:处理查询参数
|
||||
String query = uri.getQuery();
|
||||
if (query != null && !query.isEmpty()) {
|
||||
// 解析查询参数作为扩展信息
|
||||
}
|
||||
port=Integer.parseInt(tps);
|
||||
if(port<0) {
|
||||
throw new IllegalArgumentException("port < 0");
|
||||
} catch (URISyntaxException e) {
|
||||
throw new IllegalArgumentException("无效的地址格式: " + hostport, e);
|
||||
}
|
||||
}
|
||||
|
||||
private int resolvePort(URI uri, String protocol) {
|
||||
int port = uri.getPort();
|
||||
if (port != -1) {
|
||||
return port;
|
||||
}
|
||||
SocketType socketType = socketTypeRegister.get(protocol);
|
||||
if (socketType != null) {
|
||||
int defaultPort = socketType.getDefaultPort();
|
||||
if (defaultPort != -1) {
|
||||
return defaultPort;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException(
|
||||
"未指定端口号,且协议 " + protocol + " 没有默认端口: " + uri
|
||||
);
|
||||
}
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(host, port, ps, type);
|
||||
return Objects.hash(host, port, ps, protocol);
|
||||
}
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
@@ -95,9 +101,9 @@ public class MultipurposeSocketAddress implements Serializable{
|
||||
return false;
|
||||
if (getClass() != obj.getClass())
|
||||
return false;
|
||||
MultipurposeSocketAddress other = (MultipurposeSocketAddress) obj;
|
||||
MultiProtocolSocketAddress other = (MultiProtocolSocketAddress) obj;
|
||||
return Objects.equals(host, other.host) && port == other.port && Objects.equals(ps, other.ps)
|
||||
&& Objects.equals(type, other.type);
|
||||
&& Objects.equals(protocol, other.protocol);
|
||||
}
|
||||
|
||||
public boolean equals2(Object obj) {
|
||||
@@ -107,7 +113,7 @@ public class MultipurposeSocketAddress implements Serializable{
|
||||
return false;
|
||||
if (getClass() != obj.getClass())
|
||||
return false;
|
||||
MultipurposeSocketAddress other = (MultipurposeSocketAddress) obj;
|
||||
MultiProtocolSocketAddress other = (MultiProtocolSocketAddress) obj;
|
||||
InetAddress addra=null;
|
||||
try {
|
||||
addra = InetAddress.getByName(host);
|
||||
@@ -121,25 +127,25 @@ public class MultipurposeSocketAddress implements Serializable{
|
||||
boolean hostequals=Objects.equals(addra, addrb);
|
||||
|
||||
return hostequals && port == other.port && Objects.equals(ps, other.ps)
|
||||
&& Objects.equals(type, other.type);
|
||||
&& Objects.equals(protocol, other.protocol);
|
||||
}
|
||||
|
||||
public MultipurposeSocketAddress(String host2, int port2) {
|
||||
this("TCP", host2, port2);
|
||||
public MultiProtocolSocketAddress(String host2, int port2) {
|
||||
this("tcp", host2, port2);
|
||||
}
|
||||
|
||||
public MultipurposeSocketAddress(String type,String host2, int port2) {
|
||||
public MultiProtocolSocketAddress(String protocol, String host2, int port2) {
|
||||
host=host2;
|
||||
port=port2;
|
||||
this.type=type;
|
||||
this.protocol = protocol;
|
||||
}
|
||||
public MultipurposeSocketAddress(InetSocketAddress remoteSocketAddress) {
|
||||
this("TCP",remoteSocketAddress);
|
||||
public MultiProtocolSocketAddress(InetSocketAddress remoteSocketAddress) {
|
||||
this("tcp",remoteSocketAddress);
|
||||
}
|
||||
public MultipurposeSocketAddress(String type,InetSocketAddress remoteSocketAddress) {
|
||||
public MultiProtocolSocketAddress(String protocol, InetSocketAddress remoteSocketAddress) {
|
||||
host=remoteSocketAddress.getAddress().getHostAddress();
|
||||
port=remoteSocketAddress.getPort();
|
||||
this.type=type;
|
||||
this.protocol = protocol;
|
||||
}
|
||||
|
||||
|
||||
@@ -149,8 +155,8 @@ public class MultipurposeSocketAddress implements Serializable{
|
||||
public int getPort() {
|
||||
return port;
|
||||
}
|
||||
public String getType() {
|
||||
return type;
|
||||
public String getProtocol() {
|
||||
return protocol;
|
||||
}
|
||||
|
||||
public InetSocketAddress getSocketAddress() {
|
||||
@@ -159,27 +165,25 @@ public class MultipurposeSocketAddress implements Serializable{
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb=new StringBuilder();
|
||||
if(!"TCP".equals(type)) {
|
||||
sb.append('{').append(type).append('}');
|
||||
}
|
||||
if(host.contains(":")) {
|
||||
sb.append('[').append( host).append( "]:").append( port);
|
||||
}else {
|
||||
sb.append( host).append( ':').append( port);
|
||||
}
|
||||
if(!ps.isEmpty()) {
|
||||
sb.append('(').append(ps.pop().getName()).append(')');
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
try {
|
||||
// 获取协议的默认端口
|
||||
SocketType socketType = socketTypeRegister.get(protocol);
|
||||
int defaultPort = (socketType != null) ? socketType.getDefaultPort() : -1;
|
||||
|
||||
// 如果当前端口等于默认端口,则隐去端口号
|
||||
int portToUse = (defaultPort != -1 && this.port == defaultPort) ? -1 : this.port;
|
||||
|
||||
URI uri = new URI(protocol, null, host, portToUse, null, null, null);
|
||||
return uri.toString();
|
||||
} catch (URISyntaxException e) {
|
||||
return protocol + "://" + host + ":" + port;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
public Socket connectSocket(InetAddress bindip,int bindport,int timeout) throws UnknownHostException, IOException {
|
||||
SocketFactory sf=socketTypeRegister.get(type).getSocketFactory();
|
||||
SocketFactory sf=socketTypeRegister.get(protocol).getSocketFactory();
|
||||
if(sf==null) {
|
||||
throw new UnsupportedOperationException("Socket Unsupported");
|
||||
}
|
||||
@@ -194,7 +198,7 @@ public class MultipurposeSocketAddress implements Serializable{
|
||||
return s;
|
||||
}
|
||||
public Socket connectSocket(InetAddress bindip,int bindport) throws UnknownHostException, IOException {
|
||||
SocketFactory sf=socketTypeRegister.get(type).getSocketFactory();
|
||||
SocketFactory sf=socketTypeRegister.get(protocol).getSocketFactory();
|
||||
if(sf==null) {
|
||||
throw new UnsupportedOperationException("Socket Unsupported");
|
||||
}
|
||||
@@ -209,7 +213,7 @@ public class MultipurposeSocketAddress implements Serializable{
|
||||
return s;
|
||||
}
|
||||
public Socket connectSocket() throws UnknownHostException, IOException {
|
||||
SocketFactory sf=socketTypeRegister.get(type).getSocketFactory();
|
||||
SocketFactory sf=socketTypeRegister.get(protocol).getSocketFactory();
|
||||
if(sf==null) {
|
||||
throw new UnsupportedOperationException("Socket Unsupported");
|
||||
}
|
||||
@@ -223,7 +227,7 @@ public class MultipurposeSocketAddress implements Serializable{
|
||||
return s;
|
||||
}
|
||||
public Socket connectSocket(int timeout) throws UnknownHostException, IOException {
|
||||
SocketFactory sf=socketTypeRegister.get(type).getSocketFactory();
|
||||
SocketFactory sf=socketTypeRegister.get(protocol).getSocketFactory();
|
||||
if(sf==null) {
|
||||
throw new UnsupportedOperationException("Socket Unsupported");
|
||||
}
|
||||
@@ -244,7 +248,7 @@ public class MultipurposeSocketAddress implements Serializable{
|
||||
|
||||
|
||||
public SocketChannel connectSocketChannel(InetAddress bindip,int bindport,int timeout) throws UnknownHostException, IOException {
|
||||
SocketChannelFactory sf=socketTypeRegister.get(type).getSocketChannelFactory();
|
||||
SocketChannelFactory sf=socketTypeRegister.get(protocol).getSocketChannelFactory();
|
||||
if(sf==null) {
|
||||
throw new UnsupportedOperationException("Socket Unsupported");
|
||||
}
|
||||
@@ -265,7 +269,7 @@ public class MultipurposeSocketAddress implements Serializable{
|
||||
return s;
|
||||
}
|
||||
public SocketChannel connectSocketChannel(InetAddress bindip,int bindport) throws UnknownHostException, IOException {
|
||||
SocketChannelFactory sf=socketTypeRegister.get(type).getSocketChannelFactory();
|
||||
SocketChannelFactory sf=socketTypeRegister.get(protocol).getSocketChannelFactory();
|
||||
if(sf==null) {
|
||||
throw new UnsupportedOperationException("Socket Unsupported");
|
||||
}
|
||||
@@ -280,7 +284,7 @@ public class MultipurposeSocketAddress implements Serializable{
|
||||
return s;
|
||||
}
|
||||
public SocketChannel connectSocketChannel() throws UnknownHostException, IOException {
|
||||
SocketChannelFactory sf=socketTypeRegister.get(type).getSocketChannelFactory();
|
||||
SocketChannelFactory sf=socketTypeRegister.get(protocol).getSocketChannelFactory();
|
||||
if(sf==null) {
|
||||
throw new UnsupportedOperationException("Socket Unsupported");
|
||||
}
|
||||
@@ -294,7 +298,7 @@ public class MultipurposeSocketAddress implements Serializable{
|
||||
return s;
|
||||
}
|
||||
public SocketChannel connectSocketChannel(int timeout) throws UnknownHostException, IOException {
|
||||
SocketChannelFactory sf=socketTypeRegister.get(type).getSocketChannelFactory();
|
||||
SocketChannelFactory sf=socketTypeRegister.get(protocol).getSocketChannelFactory();
|
||||
if(sf==null) {
|
||||
throw new UnsupportedOperationException("Socket Unsupported");
|
||||
}
|
||||
@@ -319,7 +323,7 @@ public class MultipurposeSocketAddress implements Serializable{
|
||||
|
||||
|
||||
public ServerSocket listenServerSocket() throws UnknownHostException, IOException {
|
||||
ServerSocketFactory srf=socketTypeRegister.get(type).getServerSocketFactory();
|
||||
ServerSocketFactory srf=socketTypeRegister.get(protocol).getServerSocketFactory();
|
||||
if(srf==null) {
|
||||
throw new UnsupportedOperationException("ServerSocket Unsupported");
|
||||
}
|
||||
@@ -327,7 +331,7 @@ public class MultipurposeSocketAddress implements Serializable{
|
||||
return sk;
|
||||
}
|
||||
public ServerSocket listenServerSocket(int backlog) throws UnknownHostException, IOException {
|
||||
ServerSocketFactory srf=socketTypeRegister.get(type).getServerSocketFactory();
|
||||
ServerSocketFactory srf=socketTypeRegister.get(protocol).getServerSocketFactory();
|
||||
if(srf==null) {
|
||||
throw new UnsupportedOperationException("ServerSocket Unsupported");
|
||||
}
|
||||
@@ -337,7 +341,7 @@ public class MultipurposeSocketAddress implements Serializable{
|
||||
|
||||
|
||||
public ServerSocketChannel listenServerSocketChannel() throws UnknownHostException, IOException {
|
||||
ServerSocketChannelFactory srf=socketTypeRegister.get(type).getServerSocketChannelFactory();
|
||||
ServerSocketChannelFactory srf=socketTypeRegister.get(protocol).getServerSocketChannelFactory();
|
||||
if(srf==null) {
|
||||
throw new UnsupportedOperationException("ServerSocket Unsupported");
|
||||
}
|
||||
@@ -345,7 +349,7 @@ public class MultipurposeSocketAddress implements Serializable{
|
||||
return sk;
|
||||
}
|
||||
public ServerSocketChannel listenServerSocketChannel(int backlog) throws UnknownHostException, IOException {
|
||||
ServerSocketChannelFactory srf=socketTypeRegister.get(type).getServerSocketChannelFactory();
|
||||
ServerSocketChannelFactory srf=socketTypeRegister.get(protocol).getServerSocketChannelFactory();
|
||||
if(srf==null) {
|
||||
throw new UnsupportedOperationException("ServerSocket Unsupported");
|
||||
}
|
||||
@@ -355,13 +359,13 @@ public class MultipurposeSocketAddress implements Serializable{
|
||||
|
||||
|
||||
public boolean isStream() {
|
||||
return checkIsStream(type);
|
||||
return checkIsStream(protocol);
|
||||
}
|
||||
public static boolean checkIsStream(String type2) {
|
||||
return socketTypeRegister.get(type2).isStream();
|
||||
}
|
||||
public DatagramSocket connectDatagramSocket(InetAddress bindip,int bindport) throws IOException {
|
||||
DatagramSocketFactory dgs=socketTypeRegister.get(type).getDatagramSocketFactory();
|
||||
DatagramSocketFactory dgs=socketTypeRegister.get(protocol).getDatagramSocketFactory();
|
||||
if(dgs==null) {
|
||||
throw new UnsupportedOperationException("DatagramSocket Unsupported");
|
||||
}
|
||||
@@ -376,7 +380,7 @@ public class MultipurposeSocketAddress implements Serializable{
|
||||
return dgd;
|
||||
}
|
||||
public DatagramSocket connectDatagramSocket() throws IOException {
|
||||
DatagramSocketFactory dgs=socketTypeRegister.get(type).getDatagramSocketFactory();
|
||||
DatagramSocketFactory dgs=socketTypeRegister.get(protocol).getDatagramSocketFactory();
|
||||
if(dgs==null) {
|
||||
throw new UnsupportedOperationException("DatagramSocket Unsupported");
|
||||
}
|
||||
@@ -390,7 +394,7 @@ public class MultipurposeSocketAddress implements Serializable{
|
||||
return dgd;
|
||||
}
|
||||
public DatagramSocket listenDatagramSocket() throws UnknownHostException, IOException {
|
||||
DatagramSocketFactory dgs=socketTypeRegister.get(type).getDatagramSocketFactory();
|
||||
DatagramSocketFactory dgs=socketTypeRegister.get(protocol).getDatagramSocketFactory();
|
||||
if(dgs==null) {
|
||||
throw new UnsupportedOperationException("DatagramSocket Unsupported");
|
||||
}
|
||||
@@ -405,7 +409,7 @@ public class MultipurposeSocketAddress implements Serializable{
|
||||
}
|
||||
|
||||
public DatagramServerSocket listenDatagramServerSocket() throws UnknownHostException, IOException {
|
||||
DatagramServerSocketFactory srf=socketTypeRegister.get(type).getDatagramServerSocketFactory();
|
||||
DatagramServerSocketFactory srf=socketTypeRegister.get(protocol).getDatagramServerSocketFactory();
|
||||
if(srf==null) {
|
||||
throw new UnsupportedOperationException("DatagramServerSocket Unsupported");
|
||||
}
|
||||
@@ -413,7 +417,7 @@ public class MultipurposeSocketAddress implements Serializable{
|
||||
return sk;
|
||||
}
|
||||
public DatagramServerSocket listenDatagramServerSocket(int backlog) throws UnknownHostException, IOException {
|
||||
DatagramServerSocketFactory srf=socketTypeRegister.get(type).getDatagramServerSocketFactory();
|
||||
DatagramServerSocketFactory srf=socketTypeRegister.get(protocol).getDatagramServerSocketFactory();
|
||||
if(srf==null) {
|
||||
throw new UnsupportedOperationException("DatagramServerSocket Unsupported");
|
||||
}
|
||||
@@ -427,7 +431,7 @@ public class MultipurposeSocketAddress implements Serializable{
|
||||
return InetAddress.getAllByName(host);
|
||||
}
|
||||
public boolean supportNIO() {
|
||||
SocketType stp= socketTypeRegister.get(getType());
|
||||
SocketType stp= socketTypeRegister.get(getProtocol());
|
||||
if(stp==null)
|
||||
return false;
|
||||
if(stp.getSocketChannelFactory()==null) {
|
||||
@@ -437,35 +441,37 @@ public class MultipurposeSocketAddress implements Serializable{
|
||||
}
|
||||
}
|
||||
|
||||
public static JsonDeserializer<MultipurposeSocketAddress>getDefaultJsonDeserializer(){
|
||||
return new JsonDeserializer<MultipurposeSocketAddress>() {
|
||||
public static JsonDeserializer<MultiProtocolSocketAddress>getDefaultJsonDeserializer(){
|
||||
return new JsonDeserializer<MultiProtocolSocketAddress>() {
|
||||
|
||||
@Override
|
||||
public MultipurposeSocketAddress deserialize(JsonElement arg0, Type arg1, JsonDeserializationContext arg2)
|
||||
public MultiProtocolSocketAddress deserialize(JsonElement arg0, Type arg1, JsonDeserializationContext arg2)
|
||||
throws JsonParseException {
|
||||
if(arg0.isJsonPrimitive()) {
|
||||
JsonPrimitive jp=(JsonPrimitive) arg0;
|
||||
if(jp.isString()) {
|
||||
return new MultipurposeSocketAddress(jp.getAsString());
|
||||
MultiProtocolSocketAddress msa=new MultiProtocolSocketAddress(jp.getAsString());
|
||||
// System.out.println(msa);
|
||||
return msa;
|
||||
}
|
||||
}
|
||||
throw new JsonParseException("not a string:"+arg0);
|
||||
}
|
||||
};
|
||||
}
|
||||
public static JsonSerializer<MultipurposeSocketAddress>getDefaultJsonSerializer(){
|
||||
return new JsonSerializer<MultipurposeSocketAddress>() {
|
||||
public static JsonSerializer<MultiProtocolSocketAddress>getDefaultJsonSerializer(){
|
||||
return new JsonSerializer<MultiProtocolSocketAddress>() {
|
||||
|
||||
@Override
|
||||
public JsonElement serialize(MultipurposeSocketAddress arg0, Type arg1, JsonSerializationContext arg2) {
|
||||
public JsonElement serialize(MultiProtocolSocketAddress arg0, Type arg1, JsonSerializationContext arg2) {
|
||||
return new JsonPrimitive(arg0.toString());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static void registerToGsonBuilder(GsonBuilder gsonBuilder) {
|
||||
gsonBuilder.registerTypeAdapter(MultipurposeSocketAddress.class, getDefaultJsonDeserializer());
|
||||
gsonBuilder.registerTypeAdapter(MultipurposeSocketAddress.class, getDefaultJsonSerializer());
|
||||
gsonBuilder.registerTypeAdapter(MultiProtocolSocketAddress.class, getDefaultJsonDeserializer());
|
||||
gsonBuilder.registerTypeAdapter(MultiProtocolSocketAddress.class, getDefaultJsonSerializer());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
package org.kne.cloud.network;
|
||||
|
||||
public interface NetworkService {
|
||||
public void listen(MultipurposeSocketAddress msa);
|
||||
public void unlisten(MultipurposeSocketAddress msc);
|
||||
public void listen(MultiProtocolSocketAddress msa);
|
||||
public void unlisten(MultiProtocolSocketAddress msc);
|
||||
|
||||
public void connect(MultipurposeSocketAddress msa);
|
||||
public void unconnect(MultipurposeSocketAddress msc);
|
||||
public void connect(MultiProtocolSocketAddress msa);
|
||||
public void unconnect(MultiProtocolSocketAddress msc);
|
||||
}
|
||||
|
||||
@@ -1,13 +1,7 @@
|
||||
package org.kne.cloud.network;
|
||||
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.net.*;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.*;
|
||||
import java.nio.channels.spi.AbstractInterruptibleChannel;
|
||||
import java.util.*;
|
||||
|
||||
public class PortRelay {
|
||||
private int port;
|
||||
@@ -16,8 +10,8 @@ public class PortRelay {
|
||||
public PortRelay(int port, HostPortMap services) throws IOException {
|
||||
this.services = services;
|
||||
this.port = port;
|
||||
MultipurposeSocketAddress.getSocketTypeRegister().put("ProtocolDetectorServerSocket",new SocketType(null, new ProtocolDetectorServerSocketFactory()) );
|
||||
ssc=new SocketListener(new MultipurposeSocketAddress("ProtocolDetectorServerSocket", "0.0.0.0", port));
|
||||
MultiProtocolSocketAddress.getSocketTypeRegister().put("ProtocolDetectorServerSocket",new SocketType(null, new ProtocolDetectorServerSocketFactory()) );
|
||||
ssc=new SocketListener(new MultiProtocolSocketAddress("ProtocolDetectorServerSocket", "0.0.0.0", port));
|
||||
}
|
||||
public void start() throws IOException {
|
||||
ssc.setCon((s)->{
|
||||
@@ -30,7 +24,7 @@ public class PortRelay {
|
||||
}else {
|
||||
nx=pds.getProtocolStack().pop().getName();
|
||||
}
|
||||
MultipurposeSocketAddress hp=services.get(nx);
|
||||
MultiProtocolSocketAddress hp=services.get(nx);
|
||||
InetSocketAddress sa=(InetSocketAddress) s.getRemoteSocketAddress();
|
||||
System.out.println(sa.getAddress().getHostAddress() + ":" + port
|
||||
+ "-" + "(" + nx + ")->" + hp.getHost() + ":" + hp.getPort());
|
||||
|
||||
@@ -1,23 +1,19 @@
|
||||
package org.kne.cloud.network;
|
||||
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class ServiceElement {
|
||||
public String name;
|
||||
public String protocol;
|
||||
public MultipurposeSocketAddress ipport;
|
||||
public MultiProtocolSocketAddress ipport;
|
||||
public ServiceElement(String ini) throws UnknownHostException {
|
||||
String[]t=ini.split("\\$");
|
||||
if(t.length==1) {
|
||||
protocol="";
|
||||
ipport=new MultipurposeSocketAddress(t[0]);
|
||||
ipport=new MultiProtocolSocketAddress(t[0]);
|
||||
}else {
|
||||
protocol=t[0];
|
||||
ipport=new MultipurposeSocketAddress(t[1]);
|
||||
ipport=new MultiProtocolSocketAddress(t[1]);
|
||||
}
|
||||
}
|
||||
public ServiceElement() {
|
||||
|
||||
@@ -1,21 +1,20 @@
|
||||
package org.kne.cloud.network;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class ServiceToSocketProxy extends Proxy{
|
||||
public NetworkService getSrc() {
|
||||
return src;
|
||||
}
|
||||
public MultipurposeSocketAddress getDes() {
|
||||
public MultiProtocolSocketAddress getDes() {
|
||||
return des;
|
||||
}
|
||||
public ServiceToSocketProxy(String l, String r) {
|
||||
src=getRegister().get(l.substring(1, l.length()-1));
|
||||
des=new MultipurposeSocketAddress(r);
|
||||
des=new MultiProtocolSocketAddress(r);
|
||||
}
|
||||
private NetworkService src;
|
||||
private MultipurposeSocketAddress des;
|
||||
private MultiProtocolSocketAddress des;
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
// TODO 自动生成的方法存根
|
||||
|
||||
@@ -50,10 +50,10 @@ public class SocketChannelListener implements Closeable,AutoCloseable{
|
||||
}
|
||||
}
|
||||
};
|
||||
private MultipurposeSocketAddress multipurposeSocketAddress;
|
||||
private MultiProtocolSocketAddress multiProtocolSocketAddress;
|
||||
|
||||
public SocketChannelListener(MultipurposeSocketAddress multipurposeSocketAddress) throws IOException {
|
||||
this.multipurposeSocketAddress=multipurposeSocketAddress;
|
||||
public SocketChannelListener(MultiProtocolSocketAddress multiProtocolSocketAddress) throws IOException {
|
||||
this.multiProtocolSocketAddress = multiProtocolSocketAddress;
|
||||
open();
|
||||
}
|
||||
public SocketChannelListener(ServerSocketChannel tserverSocket) throws IOException {
|
||||
@@ -62,9 +62,9 @@ public class SocketChannelListener implements Closeable,AutoCloseable{
|
||||
}
|
||||
protected void open() throws UnknownHostException, IOException {
|
||||
if(serverSocketChannel==null)
|
||||
serverSocketChannel=multipurposeSocketAddress.listenServerSocketChannel();
|
||||
serverSocketChannel= multiProtocolSocketAddress.listenServerSocketChannel();
|
||||
if(serverSocketChannel==null) {
|
||||
throw new IOException(multipurposeSocketAddress.getType()+" unsupport SocketChannel");
|
||||
throw new IOException(multiProtocolSocketAddress.getProtocol()+" unsupport SocketChannel");
|
||||
}
|
||||
ThreadTool.makeVThread("端口监听线程", r).start();
|
||||
}
|
||||
@@ -82,8 +82,8 @@ public class SocketChannelListener implements Closeable,AutoCloseable{
|
||||
|
||||
|
||||
|
||||
public MultipurposeSocketAddress getMultipurposeSocketAddress() {
|
||||
return multipurposeSocketAddress;
|
||||
public MultiProtocolSocketAddress getMultipurposeSocketAddress() {
|
||||
return multiProtocolSocketAddress;
|
||||
}
|
||||
|
||||
public Consumer<SocketChannel> getCon() {
|
||||
|
||||
@@ -2,15 +2,12 @@ package org.kne.cloud.network;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketException;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import javax.net.ServerSocketFactory;
|
||||
|
||||
public class SocketListener implements Closeable,AutoCloseable{
|
||||
protected ServerSocket serverSocket;
|
||||
public ServerSocket getServerSocket() {
|
||||
@@ -53,10 +50,10 @@ public class SocketListener implements Closeable,AutoCloseable{
|
||||
}
|
||||
}
|
||||
};
|
||||
private MultipurposeSocketAddress multipurposeSocketAddress;
|
||||
private MultiProtocolSocketAddress multiProtocolSocketAddress;
|
||||
|
||||
public SocketListener(MultipurposeSocketAddress multipurposeSocketAddress) throws IOException {
|
||||
this.multipurposeSocketAddress=multipurposeSocketAddress;
|
||||
public SocketListener(MultiProtocolSocketAddress multiProtocolSocketAddress) throws IOException {
|
||||
this.multiProtocolSocketAddress = multiProtocolSocketAddress;
|
||||
open();
|
||||
}
|
||||
public SocketListener(ServerSocket tserverSocket) throws IOException {
|
||||
@@ -65,7 +62,7 @@ public class SocketListener implements Closeable,AutoCloseable{
|
||||
}
|
||||
protected void open() throws UnknownHostException, IOException {
|
||||
if(serverSocket==null)
|
||||
serverSocket=multipurposeSocketAddress.listenServerSocket();
|
||||
serverSocket= multiProtocolSocketAddress.listenServerSocket();
|
||||
ThreadTool.makeVThread("端口监听线程", r).start();
|
||||
}
|
||||
public void close() {
|
||||
@@ -82,8 +79,8 @@ public class SocketListener implements Closeable,AutoCloseable{
|
||||
|
||||
|
||||
|
||||
public MultipurposeSocketAddress getMultipurposeSocketAddress() {
|
||||
return multipurposeSocketAddress;
|
||||
public MultiProtocolSocketAddress getMultipurposeSocketAddress() {
|
||||
return multiProtocolSocketAddress;
|
||||
}
|
||||
|
||||
public Consumer<Socket> getCon() {
|
||||
|
||||
@@ -1,21 +1,19 @@
|
||||
package org.kne.cloud.network;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.Socket;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class SocketToServiceProxy extends Proxy {
|
||||
public SocketToServiceProxy(String l, String r) {
|
||||
src=new MultipurposeSocketAddress(l);
|
||||
src=new MultiProtocolSocketAddress(l);
|
||||
des=getRegister().get(r.substring(1, r.length()-1)) ;
|
||||
}
|
||||
public MultipurposeSocketAddress getSrc() {
|
||||
public MultiProtocolSocketAddress getSrc() {
|
||||
return src;
|
||||
}
|
||||
public NetworkService getDes() {
|
||||
return des;
|
||||
}
|
||||
private MultipurposeSocketAddress src;
|
||||
private MultiProtocolSocketAddress src;
|
||||
private NetworkService des;
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
|
||||
@@ -4,10 +4,7 @@ import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketOption;
|
||||
import java.net.SocketOptions;
|
||||
import java.net.StandardSocketOptions;
|
||||
import java.net.UnknownHostException;
|
||||
import java.nio.channels.SocketChannel;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -15,8 +12,8 @@ public class SocketToSocketProxy extends Proxy {
|
||||
private SocketListener sl;
|
||||
private SocketChannelListener scl;
|
||||
private DatagramSocketListener dsl;
|
||||
private MultipurposeSocketAddress listen, cbind, defaultConnect;
|
||||
private Map<String, MultipurposeSocketAddress> detectedConnect;
|
||||
private MultiProtocolSocketAddress listen, cbind, defaultConnect;
|
||||
private Map<String, MultiProtocolSocketAddress> detectedConnect;
|
||||
private SocketBridgeFactory defaultFactory;
|
||||
private SocketChannelBridgeFactory defaultChannelFactory;
|
||||
private Map<String, SocketBridgeFactory> detectedFactory;
|
||||
@@ -30,28 +27,28 @@ public class SocketToSocketProxy extends Proxy {
|
||||
this.defaultSoTimeout = defaultSoTimeout;
|
||||
}
|
||||
|
||||
public SocketToSocketProxy(MultipurposeSocketAddress listen, MultipurposeSocketAddress connect) throws IOException {
|
||||
this(listen, new MultipurposeSocketAddress(new InetSocketAddress(0)), connect);
|
||||
public SocketToSocketProxy(MultiProtocolSocketAddress listen, MultiProtocolSocketAddress connect) throws IOException {
|
||||
this(listen, new MultiProtocolSocketAddress(new InetSocketAddress(0)), connect);
|
||||
}
|
||||
|
||||
public SocketToSocketProxy(String l, String r) throws IOException {
|
||||
this(new MultipurposeSocketAddress(l), new MultipurposeSocketAddress(r));
|
||||
this(new MultiProtocolSocketAddress(l), new MultiProtocolSocketAddress(r));
|
||||
}
|
||||
|
||||
public SocketToSocketProxy(MultipurposeSocketAddress listen, MultipurposeSocketAddress cbind,
|
||||
MultipurposeSocketAddress defaultConnect, Map<String, MultipurposeSocketAddress> detectedConnect)
|
||||
public SocketToSocketProxy(MultiProtocolSocketAddress listen, MultiProtocolSocketAddress cbind,
|
||||
MultiProtocolSocketAddress defaultConnect, Map<String, MultiProtocolSocketAddress> detectedConnect)
|
||||
throws IOException {
|
||||
this(listen,cbind,defaultConnect,detectedConnect,new DefaultSocketBridgeFactory(),null);
|
||||
}
|
||||
|
||||
public SocketToSocketProxy(MultipurposeSocketAddress listen, MultipurposeSocketAddress cbind,
|
||||
MultipurposeSocketAddress defaultConnect, Map<String, MultipurposeSocketAddress> detectedConnect,SocketBridgeFactory defaultFactory,Map<String, SocketBridgeFactory> detectedFactory)
|
||||
public SocketToSocketProxy(MultiProtocolSocketAddress listen, MultiProtocolSocketAddress cbind,
|
||||
MultiProtocolSocketAddress defaultConnect, Map<String, MultiProtocolSocketAddress> detectedConnect, SocketBridgeFactory defaultFactory, Map<String, SocketBridgeFactory> detectedFactory)
|
||||
throws IOException {
|
||||
this(listen, cbind, defaultConnect, detectedConnect, defaultFactory, detectedFactory, new DefaultSocketChannelBridgeFactory());
|
||||
}
|
||||
|
||||
public SocketToSocketProxy(MultipurposeSocketAddress listen, MultipurposeSocketAddress cbind,
|
||||
MultipurposeSocketAddress defaultConnect, Map<String, MultipurposeSocketAddress> detectedConnect,SocketBridgeFactory defaultFactory,Map<String, SocketBridgeFactory> detectedFactory,SocketChannelBridgeFactory defaultChannelFactory)
|
||||
public SocketToSocketProxy(MultiProtocolSocketAddress listen, MultiProtocolSocketAddress cbind,
|
||||
MultiProtocolSocketAddress defaultConnect, Map<String, MultiProtocolSocketAddress> detectedConnect, SocketBridgeFactory defaultFactory, Map<String, SocketBridgeFactory> detectedFactory, SocketChannelBridgeFactory defaultChannelFactory)
|
||||
throws IOException {
|
||||
this.listen = listen;
|
||||
this.cbind = cbind;
|
||||
@@ -66,8 +63,8 @@ public class SocketToSocketProxy extends Proxy {
|
||||
open();
|
||||
}
|
||||
|
||||
public SocketToSocketProxy(MultipurposeSocketAddress listen, MultipurposeSocketAddress cbind,
|
||||
MultipurposeSocketAddress defaultConnect) throws IOException {
|
||||
public SocketToSocketProxy(MultiProtocolSocketAddress listen, MultiProtocolSocketAddress cbind,
|
||||
MultiProtocolSocketAddress defaultConnect) throws IOException {
|
||||
this(listen, cbind, defaultConnect,null);
|
||||
}
|
||||
|
||||
@@ -112,7 +109,7 @@ public class SocketToSocketProxy extends Proxy {
|
||||
ProtocolStack ps = ((ProtocolDetectorSocket) sox).getProtocolStack();
|
||||
if (!ps.isEmpty()) {
|
||||
String pname=ps.pop().getName();
|
||||
MultipurposeSocketAddress pmsa = detectedConnect.get(pname);
|
||||
MultiProtocolSocketAddress pmsa = detectedConnect.get(pname);
|
||||
if(pmsa!=null) {
|
||||
sk = pmsa.connectSocket(InetAddress.getByName(cbind.getHost()), cbind.getPort());
|
||||
}else {
|
||||
@@ -178,19 +175,19 @@ public class SocketToSocketProxy extends Proxy {
|
||||
sb.run();
|
||||
}
|
||||
|
||||
public MultipurposeSocketAddress getListen() {
|
||||
public MultiProtocolSocketAddress getListen() {
|
||||
return listen;
|
||||
}
|
||||
|
||||
public MultipurposeSocketAddress getCbind() {
|
||||
public MultiProtocolSocketAddress getCbind() {
|
||||
return cbind;
|
||||
}
|
||||
|
||||
public MultipurposeSocketAddress getDefaultConnect() {
|
||||
public MultiProtocolSocketAddress getDefaultConnect() {
|
||||
return defaultConnect;
|
||||
}
|
||||
|
||||
public Map<String, MultipurposeSocketAddress> getDetectedConnect() {
|
||||
public Map<String, MultiProtocolSocketAddress> getDetectedConnect() {
|
||||
return detectedConnect;
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ public class SocketType {
|
||||
private DatagramSocketFactory datagramSocketFactory;
|
||||
private DatagramServerSocketFactory datagramServerSocketFactory;
|
||||
private boolean stream;
|
||||
private int defaultPort=-1;
|
||||
|
||||
public SocketFactory Channel() {
|
||||
return socketFactory;
|
||||
@@ -74,5 +75,7 @@ public class SocketType {
|
||||
return stream;
|
||||
}
|
||||
|
||||
|
||||
public int getDefaultPort(){
|
||||
return defaultPort;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
package org.kne.cloud.network;
|
||||
|
||||
public class TCPSocketType extends SocketType {
|
||||
private static final TCPSocketType INSTANCE = new TCPSocketType();
|
||||
|
||||
private TCPSocketType() {
|
||||
super(
|
||||
new DefaultSocketFactory(),
|
||||
new DefaultServerSocketFactory(),
|
||||
new DefaultSocketChannelFactory(),
|
||||
new DefaultServerSocketChannelFactory()
|
||||
);
|
||||
}
|
||||
|
||||
public static TCPSocketType getInstance() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isStream() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package org.kne.cloud.network;
|
||||
|
||||
public class UDPSocketType extends SocketType {
|
||||
private static final UDPSocketType INSTANCE = new UDPSocketType();
|
||||
|
||||
protected UDPSocketType() {
|
||||
super(
|
||||
new DefaultDatagramSocketFactory(),
|
||||
new DefaultDatagramServerSocketFactory()
|
||||
);
|
||||
}
|
||||
|
||||
public static UDPSocketType getInstance() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isStream() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -4,16 +4,13 @@ import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketAddress;
|
||||
import java.net.SocketImpl;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
|
||||
import org.ini4j.Profile.Section;
|
||||
import org.kne.cloud.network.MultipurposeSocketAddress;
|
||||
import org.kne.cloud.network.MultiProtocolSocketAddress;
|
||||
|
||||
public class FrpcServerSocket extends ServerSocket {
|
||||
private FrpcProcess frpc;
|
||||
@@ -76,10 +73,10 @@ public class FrpcServerSocket extends ServerSocket {
|
||||
return frpcini.getRemotePort(tunnel);
|
||||
}
|
||||
|
||||
public MultipurposeSocketAddress getTunnelSocketAddress() {
|
||||
public MultiProtocolSocketAddress getTunnelSocketAddress() {
|
||||
if (!isBound())
|
||||
return null;
|
||||
return new MultipurposeSocketAddress(frpcini.getType(tunnel).toUpperCase(),getTunnelHost(), getTunnelPort());
|
||||
return new MultiProtocolSocketAddress(frpcini.getType(tunnel).toUpperCase(),getTunnelHost(), getTunnelPort());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -116,9 +116,7 @@ public class DatagramKLALBPacketLink implements KLALBPacketLink {
|
||||
}*/
|
||||
package org.kne.cloud.network.klalb;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.net.DatagramPacket;
|
||||
@@ -128,15 +126,14 @@ import java.net.SocketException;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.kne.cloud.network.DatagramServerSocket;
|
||||
import org.kne.cloud.network.MultipurposeSocketAddress;
|
||||
import org.kne.cloud.network.MultiProtocolSocketAddress;
|
||||
import org.kne.cloud.network.NetworkPacket;
|
||||
import org.kne.cloud.network.SpeedLimiter;
|
||||
|
||||
public class DatagramKLALBPacketLink extends AbstractKLALBPacketLink implements KLALBPacketLink {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new MultipurposeSocketAddress("UDP",(InetSocketAddress)ds.getLocalSocketAddress())+"←"+new MultipurposeSocketAddress("UDP",(InetSocketAddress)ds.getRemoteSocketAddress());
|
||||
return new MultiProtocolSocketAddress("UDP",(InetSocketAddress)ds.getLocalSocketAddress())+"←"+new MultiProtocolSocketAddress("UDP",(InetSocketAddress)ds.getRemoteSocketAddress());
|
||||
}
|
||||
|
||||
private DatagramSocket ds;
|
||||
|
||||
@@ -1,39 +1,31 @@
|
||||
package org.kne.cloud.network.klalb;
|
||||
|
||||
import java.io.FileReader;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Type;
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import javax.annotation.processing.Filer;
|
||||
import javax.lang.model.element.Element;
|
||||
import javax.tools.FileObject;
|
||||
import javax.tools.JavaFileObject;
|
||||
import javax.tools.JavaFileManager.Location;
|
||||
|
||||
import org.kne.cloud.network.MultipurposeSocketAddress;
|
||||
import org.kne.cloud.network.MultiProtocolSocketAddress;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonDeserializationContext;
|
||||
import com.google.gson.JsonDeserializer;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonIOException;
|
||||
import com.google.gson.JsonParseException;
|
||||
import com.google.gson.JsonPrimitive;
|
||||
import com.google.gson.JsonSerializationContext;
|
||||
import com.google.gson.JsonSerializer;
|
||||
import com.google.gson.JsonSyntaxException;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
|
||||
public class KLALBConfig extends ArrayList<KLALBConfigItem>{
|
||||
|
||||
public KLALBConfig(){
|
||||
}
|
||||
public static KLALBConfig getDefault(){
|
||||
KLALBConfig config=new KLALBConfig();
|
||||
config.add(new KLALBControllerConfigItem());
|
||||
return config;
|
||||
}
|
||||
public static void main(String[] args) throws UnknownHostException, JsonSyntaxException, JsonIOException, FileNotFoundException {
|
||||
GsonBuilder gb=new GsonBuilder().setPrettyPrinting();
|
||||
MultipurposeSocketAddress.registerToGsonBuilder(gb);
|
||||
MultiProtocolSocketAddress.registerToGsonBuilder(gb);
|
||||
KLALBConfigItem.registerToGsonBuilder(gb);
|
||||
Gson gson=gb.create();
|
||||
KLALBConfig kc=gson.fromJson(new FileReader(new File("klalb-config.json")),KLALBConfig.class);
|
||||
|
||||
@@ -1,15 +1,8 @@
|
||||
package org.kne.cloud.network.klalb;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.kne.cloud.network.MultipurposeSocketAddress;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonDeserializationContext;
|
||||
import com.google.gson.JsonDeserializer;
|
||||
@@ -69,6 +62,7 @@ public class KLALBConfigItem {
|
||||
String s=jobj.get("Type").getAsString();
|
||||
switch(s) {
|
||||
case "KLALBController":
|
||||
normalizeLegacyControllerKeys(jobj);
|
||||
return arg2.deserialize(arg0, new TypeToken<KLALBControllerConfigItem>() {}.getType());
|
||||
case "SocketBridge":
|
||||
return arg2.deserialize(arg0, new TypeToken<SocketBridgeConfigItem>() {}.getType());
|
||||
@@ -78,6 +72,39 @@ public class KLALBConfigItem {
|
||||
}
|
||||
throw new JsonParseException("not a object:"+arg0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将历史命名错误的配置键归一化为当前键名(仅在当前键名不存在时生效):
|
||||
* openConnections/lineTable → externalEndpoints,
|
||||
* denyConnectionQuery/denyLineTableQuery → denyExternalEndpointQuery,
|
||||
* denyConnectionBroadcast/denyLineTableBroadcast → denyExternalEndpointBroadcast。
|
||||
*/
|
||||
private void normalizeLegacyControllerKeys(JsonObject jobj) {
|
||||
renameLegacyKey(jobj,"externalEndpoints","openConnections","OpenConnections","lineTable","LineTable");
|
||||
renameLegacyKey(jobj,"denyExternalEndpointQuery","denyConnectionQuery","denyLineTableQuery");
|
||||
renameLegacyKey(jobj,"denyExternalEndpointBroadcast","denyConnectionBroadcast","denyLineTableBroadcast");
|
||||
if (!jobj.has("webListen") && jobj.has("webPort") && !jobj.get("webPort").isJsonNull()) {
|
||||
int port = jobj.get("webPort").getAsInt();
|
||||
jobj.add("webListen", new JsonPrimitive("http://0.0.0.0:" + port));
|
||||
}
|
||||
jobj.remove("webPort");
|
||||
}
|
||||
|
||||
private void renameLegacyKey(JsonObject jobj, String newKey, String... legacyKeys) {
|
||||
if (jobj.has(newKey)) {
|
||||
for (String legacyKey : legacyKeys) {
|
||||
jobj.remove(legacyKey);
|
||||
}
|
||||
return;
|
||||
}
|
||||
for (String legacyKey : legacyKeys) {
|
||||
if (jobj.has(legacyKey)) {
|
||||
jobj.add(newKey, jobj.get(legacyKey));
|
||||
jobj.remove(legacyKey);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
public static JsonSerializer<KLALBConfigItem>getDefaultJsonSerializer(){
|
||||
|
||||
@@ -11,11 +11,13 @@ import java.net.SocketException;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
@@ -33,7 +35,7 @@ import java.util.function.Consumer;
|
||||
|
||||
import org.kne.cloud.clock.HighAccuracyClock;
|
||||
import org.kne.cloud.network.IPMulticastDiscovery;
|
||||
import org.kne.cloud.network.MultipurposeSocketAddress;
|
||||
import org.kne.cloud.network.MultiProtocolSocketAddress;
|
||||
import org.kne.cloud.network.PortPair;
|
||||
import org.kne.cloud.network.SocketType;
|
||||
import org.kne.cloud.network.ThreadTool;
|
||||
@@ -54,6 +56,7 @@ import org.kne.cloud.network.ntp.NTPv4Protocol.NTPPeer;
|
||||
import org.kne.cloud.network.srv6.KLALBRoutingProtocol;
|
||||
import org.kne.cloud.network.srv6.KLALBRoutingProtocolAPIClient;
|
||||
import org.kne.cloud.network.srv6.KLALBRoutingProtocolAPIServer;
|
||||
import org.kne.cloud.network.srv6.NodeInfoQueryStatus;
|
||||
import org.kne.cloud.network.srv6.SRv6Router;
|
||||
import org.kne.cloud.network.srv6.SRv6RouterListener;
|
||||
import org.kne.cloud.network.tcp.UDPPacket;
|
||||
@@ -67,7 +70,7 @@ public class KLALBController {
|
||||
public HighAccuracyClock getClock() {
|
||||
return clock;
|
||||
}
|
||||
private final long TIME_WINDOW=5000000000L;
|
||||
private final long TIME_WINDOW=2000000000L;
|
||||
private SpeedAndTrafficMonitorDataImpl linkMonitor = new SpeedAndTrafficMonitorDataImpl(
|
||||
new HashMapTimestampMonitor<UUID>(HighAccuracyClock.SYSTEM_CLOCK, "up", 100,TIME_WINDOW ),
|
||||
new HashMapTimestampMonitor<UUID>(HighAccuracyClock.SYSTEM_CLOCK, "down", 100, TIME_WINDOW));
|
||||
@@ -76,8 +79,56 @@ public class KLALBController {
|
||||
new HashMapTimestampMonitor<UUID>(HighAccuracyClock.SYSTEM_CLOCK, "up", 100, TIME_WINDOW),
|
||||
new HashMapTimestampMonitor<UUID>(HighAccuracyClock.SYSTEM_CLOCK, "down", 100, TIME_WINDOW));
|
||||
|
||||
private List<MultipurposeSocketAddress> selflineTable = new ArrayList<>();
|
||||
private List<MultiProtocolSocketAddress> externalEndpoints = new ArrayList<>();
|
||||
private List<MultiProtocolSocketAddress> configuredExternalEndpoints = new ArrayList<>();
|
||||
private List<MultiProtocolSocketAddress> discoveredExternalEndpoints = new ArrayList<>();
|
||||
|
||||
public static final class PublishedNodeInfo {
|
||||
private final String deviceName;
|
||||
private final String deviceDescription;
|
||||
private final List<MultiProtocolSocketAddress> externalEndpoints;
|
||||
private final List<String> extraRoutes;
|
||||
private final long fullRevision;
|
||||
|
||||
private PublishedNodeInfo(String deviceName, String deviceDescription,
|
||||
List<MultiProtocolSocketAddress> externalEndpoints, List<String> extraRoutes, long fullRevision) {
|
||||
this.deviceName = deviceName;
|
||||
this.deviceDescription = deviceDescription;
|
||||
this.externalEndpoints = Collections.unmodifiableList(
|
||||
new ArrayList<MultiProtocolSocketAddress>(externalEndpoints));
|
||||
this.extraRoutes = Collections.unmodifiableList(new ArrayList<String>(extraRoutes));
|
||||
this.fullRevision = fullRevision;
|
||||
}
|
||||
|
||||
public String getDeviceName() {
|
||||
return deviceName;
|
||||
}
|
||||
|
||||
public String getDeviceDescription() {
|
||||
return deviceDescription;
|
||||
}
|
||||
|
||||
public List<MultiProtocolSocketAddress> getExternalEndpoints() {
|
||||
return externalEndpoints;
|
||||
}
|
||||
|
||||
public List<String> getExtraRoutes() {
|
||||
return extraRoutes;
|
||||
}
|
||||
|
||||
public long getFullRevision() {
|
||||
return fullRevision;
|
||||
}
|
||||
}
|
||||
|
||||
private volatile PublishedNodeInfo publishedNodeInfo = new PublishedNodeInfo(null, null,
|
||||
Collections.<MultiProtocolSocketAddress>emptyList(), Collections.<String>emptyList(), 0L);
|
||||
|
||||
private List<MultiProtocolSocketAddress> listensSocketAddress = new CopyOnWriteArrayList<>();
|
||||
|
||||
public List<MultiProtocolSocketAddress> getListenSocketAddress() {
|
||||
return listensSocketAddress;
|
||||
}
|
||||
private static final boolean debug=false;
|
||||
private static final boolean showpacket = false;
|
||||
|
||||
@@ -86,10 +137,12 @@ public class KLALBController {
|
||||
|
||||
private static List<IPMulticastDiscovery> ipmd = new ArrayList<>();
|
||||
|
||||
private List<NetworkInterface> networkInterfaceExcept = new ArrayList<>();
|
||||
|
||||
private NetworkInterfaceManager networkInterfaceManager=new NetworkInterfaceManager();
|
||||
public NetworkInterfaceManager getNetworkInterfaceManager() {
|
||||
return networkInterfaceManager;
|
||||
}
|
||||
public List<NetworkInterface> getNetworkInterfaceExcept() {
|
||||
return networkInterfaceExcept;
|
||||
return networkInterfaceManager.getNetworkInterfaceExcept();
|
||||
}
|
||||
|
||||
private List<InetAddress> dnsAddresses = new ArrayList<>();
|
||||
@@ -111,56 +164,28 @@ public class KLALBController {
|
||||
|
||||
private Timer twk = new Timer("网卡检测扫描计时器", true);
|
||||
|
||||
private List<InetAddress> getAllNetworkInterfaceAddress() throws SocketException {
|
||||
List<InetAddress> addresses = new ArrayList<InetAddress>();
|
||||
Enumeration<NetworkInterface> eu = NetworkInterface.getNetworkInterfaces();
|
||||
while (eu.hasMoreElements()) {
|
||||
NetworkInterface networkInterface = (NetworkInterface) eu.nextElement();
|
||||
|
||||
if (networkInterface.getDisplayName()
|
||||
.startsWith(CONST.KLALB_DECENTRALIZED_S_RV6_NETWORK)) {
|
||||
continue;
|
||||
}
|
||||
if (networkInterfaceExcept.contains(networkInterface)) {
|
||||
continue;
|
||||
}
|
||||
if (networkInterface.isUp()) {
|
||||
// System.out.println(networkInterface+" "+networkInterface.isUp());
|
||||
Enumeration<InetAddress> ei = networkInterface.getInetAddresses();
|
||||
while (ei.hasMoreElements()) {
|
||||
InetAddress inetAddress = (InetAddress) ei.nextElement();
|
||||
if (checkIsSelfLocator(inetAddress)) {
|
||||
continue;
|
||||
}
|
||||
addresses.add(inetAddress);
|
||||
}
|
||||
}
|
||||
}
|
||||
return addresses;
|
||||
}
|
||||
|
||||
private TimerTask tsk1 = new TimerTask() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
List<InetAddress> localaddress = getAllNetworkInterfaceAddress();
|
||||
List<InetAddress> localaddress = networkInterfaceManager.getAllNetworkInterfaceAddress();
|
||||
List<MultiProtocolSocketAddress> currentDiscoveredExternalEndpoints = new ArrayList<>();
|
||||
|
||||
for (InetAddress inetAddress : localaddress) {
|
||||
if (!inetAddress.isLoopbackAddress())
|
||||
for (Iterator<MultipurposeSocketAddress> iterator = listens.iterator(); iterator.hasNext();) {
|
||||
MultipurposeSocketAddress tcpl = (MultipurposeSocketAddress) iterator.next();
|
||||
for (Iterator<MultiProtocolSocketAddress> iterator = listensSocketAddress.iterator(); iterator.hasNext();) {
|
||||
MultiProtocolSocketAddress tcpl = (MultiProtocolSocketAddress) iterator.next();
|
||||
|
||||
try {
|
||||
if (tcpl.getInetAddress().isAnyLocalAddress()
|
||||
|| tcpl.getInetAddress().equals(inetAddress)) {
|
||||
MultipurposeSocketAddress bind = new MultipurposeSocketAddress(tcpl.getType(),
|
||||
MultiProtocolSocketAddress bind = new MultiProtocolSocketAddress(tcpl.getProtocol(),
|
||||
inetAddress.getHostAddress(), tcpl.getPort());
|
||||
// System.out.println(bind);
|
||||
synchronized (selflineTable) {
|
||||
if (!selflineTable.contains(bind)) {
|
||||
selflineTable.add(bind);
|
||||
}
|
||||
if (!currentDiscoveredExternalEndpoints.contains(bind)) {
|
||||
currentDiscoveredExternalEndpoints.add(bind);
|
||||
}
|
||||
}
|
||||
} catch (UnknownHostException e) {
|
||||
@@ -170,14 +195,19 @@ public class KLALBController {
|
||||
|
||||
}
|
||||
}
|
||||
synchronized (externalEndpoints) {
|
||||
if (!discoveredExternalEndpoints.equals(currentDiscoveredExternalEndpoints)) {
|
||||
discoveredExternalEndpoints = currentDiscoveredExternalEndpoints;
|
||||
publishDiscoveredExternalEndpointLocked();
|
||||
}
|
||||
}
|
||||
|
||||
lineslock.writeLock().lock();
|
||||
try {
|
||||
|
||||
|
||||
Set<MultipurposeSocketAddress> st = new HashSet<>();
|
||||
for (Iterator<IPv6NetworkLink> iterator = srv6Router.getLinkTabel().iterator(); iterator.hasNext();) {
|
||||
IPv6NetworkLink link = iterator.next();
|
||||
Set<MultiProtocolSocketAddress> st = new HashSet<>();
|
||||
for (IPv6NetworkLink link : srv6Router.getLinkTabel()) {
|
||||
if (link instanceof KLALBRemoteLink) {
|
||||
KLALBRemoteLink multipurposeSocketAddress = (KLALBRemoteLink) link;
|
||||
if (multipurposeSocketAddress.getSocketAddress() != null)
|
||||
@@ -185,24 +215,24 @@ public class KLALBController {
|
||||
}
|
||||
}
|
||||
|
||||
for (Iterator<MultipurposeSocketAddress> iterator = st.iterator(); iterator.hasNext();) {
|
||||
MultipurposeSocketAddress target = (MultipurposeSocketAddress) iterator.next();
|
||||
for (MultiProtocolSocketAddress target : st) {
|
||||
addRemoteLines(target);
|
||||
}
|
||||
for (Iterator<IPv6NetworkLink> iterator = srv6Router.getLinkTabel().iterator(); iterator.hasNext();) {
|
||||
IPv6NetworkLink link = iterator.next();
|
||||
|
||||
/*
|
||||
for (IPv6NetworkLink link : srv6Router.getLinkTabel()) {
|
||||
if (link instanceof KLALBRemoteLink) {
|
||||
KLALBRemoteLink reml = (KLALBRemoteLink) link;
|
||||
if (reml.getSocketAddress() != null) {
|
||||
if (reml.getBindAddress() != null || reml.getRemoteVaddr() != null)
|
||||
try {
|
||||
MultipurposeSocketAddress iadr=reml.getBindAddress();
|
||||
if(iadr!=null) {
|
||||
MultipurposeSocketAddress iadr = reml.getBindAddress();
|
||||
if (iadr != null) {
|
||||
if (!localaddress.contains(iadr.getInetAddress())) {
|
||||
reml.close();
|
||||
srv6Router.getLinkTabel().remove(reml);
|
||||
}
|
||||
}else {
|
||||
} else {
|
||||
|
||||
reml.close();
|
||||
}
|
||||
@@ -214,7 +244,7 @@ public class KLALBController {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
*/
|
||||
} finally {
|
||||
lineslock.writeLock().unlock();
|
||||
}
|
||||
@@ -228,47 +258,31 @@ public class KLALBController {
|
||||
}
|
||||
|
||||
private void updateBroadcastNetworkInterface() throws SocketException {
|
||||
for (Iterator<IPMulticastDiscovery> iterator = ipmd.iterator(); iterator.hasNext();) {
|
||||
for (Iterator<IPMulticastDiscovery> iterator = ipmd.iterator(); iterator.hasNext(); ) {
|
||||
IPMulticastDiscovery ipMulticastDiscovery = (IPMulticastDiscovery) iterator.next();
|
||||
if (ipMulticastDiscovery.isClosed() || (!ipMulticastDiscovery.getNinterface().isUp())
|
||||
|| (configItem != null && configItem.isDenyLineTableBroadcast())) {
|
||||
if (ipMulticastDiscovery.isClosed() || (!ipMulticastDiscovery.getInterface().isUp())
|
||||
|| (configItem != null && configItem.isDenyExternalEndpointBroadcast())) {
|
||||
iterator.remove();
|
||||
try {
|
||||
ipMulticastDiscovery.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
if(debug)
|
||||
System.out.println("关闭网卡地址广播:" + ipMulticastDiscovery.getNinterface());
|
||||
if (debug)
|
||||
System.out.println("关闭网卡地址广播:" + ipMulticastDiscovery.getInterface());
|
||||
}
|
||||
}
|
||||
|
||||
if (configItem != null && configItem.isDenyLineTableBroadcast()) {
|
||||
if (configItem != null && configItem.isDenyExternalEndpointBroadcast()) {
|
||||
|
||||
} else {
|
||||
Enumeration<NetworkInterface> eu2 = NetworkInterface.getNetworkInterfaces();
|
||||
while (eu2.hasMoreElements()) {
|
||||
NetworkInterface networkInterface = (NetworkInterface) eu2.nextElement();
|
||||
if (networkInterface.getDisplayName()
|
||||
.startsWith(CONST.KLALB_DECENTRALIZED_S_RV6_NETWORK)) {
|
||||
continue;
|
||||
}
|
||||
List<NetworkInterface> interfaceList = networkInterfaceManager.getAllAvaliableNetworkInterface();
|
||||
for(NetworkInterface networkInterface:interfaceList){
|
||||
List<InetAddress> addressList= networkInterfaceManager.getNetworkInterfaceAddress(networkInterface);
|
||||
loop:for (InetAddress bidr:addressList) {
|
||||
|
||||
if (networkInterfaceExcept.contains(networkInterface)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (networkInterface.isUp()) {
|
||||
|
||||
Enumeration<InetAddress> ei = networkInterface.getInetAddresses();
|
||||
loop: while (ei.hasMoreElements()) {
|
||||
InetAddress bidr = (InetAddress) ei.nextElement();
|
||||
if (checkIsSelfLocator(bidr)) {
|
||||
continue;
|
||||
}
|
||||
for (Iterator<IPMulticastDiscovery> iterator = ipmd.iterator(); iterator.hasNext();) {
|
||||
IPMulticastDiscovery ipMulticastDiscovery = (IPMulticastDiscovery) iterator.next();
|
||||
if (networkInterface.equals(ipMulticastDiscovery.getNinterface())
|
||||
for (IPMulticastDiscovery ipMulticastDiscovery : ipmd) {
|
||||
if (networkInterface.equals(ipMulticastDiscovery.getInterface())
|
||||
&& bidr.equals(ipMulticastDiscovery.getBind().getAddress())) {
|
||||
continue loop;
|
||||
}
|
||||
@@ -278,13 +292,13 @@ public class KLALBController {
|
||||
// InetAddress bidr=InetAddress.getByName("::0");
|
||||
|
||||
if (bidr instanceof Inet6Address) {
|
||||
IPMulticastDiscovery ipd = new IPMulticastDiscovery(
|
||||
new InetSocketAddress(bidr, DISCOVERY_PORT),
|
||||
IPMulticastDiscovery ipd = new IPMulticastDiscovery(new InetSocketAddress(bidr,DISCOVERY_PORT),
|
||||
new InetSocketAddress(InetAddress.getByName("ff02::2486"),
|
||||
DISCOVERY_PORT),
|
||||
networkInterface, selflineTable, nodeuuid,10000L);
|
||||
networkInterface, listensSocketAddress, nodeuuid, 10000L);
|
||||
ipd.setCon((mpa) -> {
|
||||
// System.out.println("添加本地IPv6链路:"+mpa);
|
||||
if (debug)
|
||||
System.out.println("添加本地IPv6链路:" + mpa);
|
||||
try {
|
||||
if (!checkIsSelf(mpa))
|
||||
addRemoteLines(mpa);
|
||||
@@ -295,13 +309,13 @@ public class KLALBController {
|
||||
ipmd.add(ipd);
|
||||
} else if (bidr instanceof Inet4Address) {
|
||||
// bidr=InetAddress.getByName("0.0.0.0");
|
||||
IPMulticastDiscovery ipd2 = new IPMulticastDiscovery(
|
||||
new InetSocketAddress(bidr, DISCOVERY_PORT),
|
||||
IPMulticastDiscovery ipd2 = new IPMulticastDiscovery(new InetSocketAddress(bidr,DISCOVERY_PORT),
|
||||
new InetSocketAddress(InetAddress.getByName("224.0.0.86"),
|
||||
DISCOVERY_PORT),
|
||||
networkInterface, selflineTable,nodeuuid, 10000L);
|
||||
networkInterface, listensSocketAddress, nodeuuid, 10000L);
|
||||
ipd2.setCon((mpa) -> {
|
||||
// System.out.println("添加本地IPv4链路:"+mpa);
|
||||
if (debug)
|
||||
System.out.println("添加本地IPv4链路:" + mpa);
|
||||
try {
|
||||
if (!checkIsSelf(mpa))
|
||||
addRemoteLines(mpa);
|
||||
@@ -311,7 +325,7 @@ public class KLALBController {
|
||||
ipd2.start();
|
||||
ipmd.add(ipd2);
|
||||
}
|
||||
if(debug)
|
||||
if (debug)
|
||||
System.out.println("开启网卡地址广播:" + networkInterface);
|
||||
} catch (BindException e) {
|
||||
// e.printStackTrace();
|
||||
@@ -321,19 +335,22 @@ public class KLALBController {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// }
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
private boolean checkIsSelf(MultipurposeSocketAddress inetAddress) throws UnknownHostException {
|
||||
private boolean checkIsSelf(MultiProtocolSocketAddress inetAddress) throws UnknownHostException {
|
||||
|
||||
return inetAddress.getInetAddress().isAnyLocalAddress() || inetAddress.getInetAddress().isLoopbackAddress()
|
||||
|| selflineTable.contains(inetAddress);
|
||||
|| publishedNodeInfo.getExternalEndpoints().contains(inetAddress);
|
||||
}
|
||||
|
||||
private boolean checkIsSelfLocator(InetAddress inetAddress) {
|
||||
@@ -345,16 +362,16 @@ public class KLALBController {
|
||||
@Override
|
||||
public void run() {
|
||||
|
||||
List<MultipurposeSocketAddress> nt = ntptable;
|
||||
List<MultiProtocolSocketAddress> nt = ntptable;
|
||||
// System.out.println("srv6 ip"+nb);
|
||||
if (nvc2 != null) {
|
||||
Set<NTPPeer> sp = nvc2.getPeers();
|
||||
for (MultipurposeSocketAddress server : nt) {
|
||||
for (MultiProtocolSocketAddress server : nt) {
|
||||
sp.add(new NTPPeer(server, NTPv4Packet.NTP_CLIENT));
|
||||
|
||||
}
|
||||
sp.removeIf((p) -> {
|
||||
for (MultipurposeSocketAddress server : nt) {
|
||||
for (MultiProtocolSocketAddress server : nt) {
|
||||
if (server.equals(p.getAddress())) {
|
||||
return false;
|
||||
}
|
||||
@@ -365,10 +382,7 @@ public class KLALBController {
|
||||
}
|
||||
};
|
||||
|
||||
private TimerTask tsk3 = new TimerTask() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
public void tryConnectMore(){
|
||||
if (srv6Router != null && routingProtocol != null) {
|
||||
Set<IPv6AddressGroup> nb = srv6Router.getLocators();
|
||||
for (Iterator<IPv6AddressGroup> iterator = nb.iterator(); iterator.hasNext();) {
|
||||
@@ -376,9 +390,11 @@ public class KLALBController {
|
||||
try {
|
||||
InetSocketAddress iaddr = new InetSocketAddress(neighbor.getAddress().toInet6Address(),
|
||||
KLALBRoutingProtocol.DEFAULT_PORT);
|
||||
apiClient.requestOpenLines(iaddr, (v) -> {
|
||||
apiClient.requestExternalEndpoints(iaddr, (v) -> {
|
||||
if (v == null || v.getStatus() != NodeInfoQueryStatus.OK) return;
|
||||
ThreadTool.makeVDaemonThreadIfSupport("线路添加任务", () -> {
|
||||
for (MultipurposeSocketAddress msa : v) {
|
||||
if (v.getExternalEndpoints() == null) return;
|
||||
for (MultiProtocolSocketAddress msa : v.getExternalEndpoints()) {
|
||||
// System.out.print(msa);
|
||||
addRemoteLines(msa);
|
||||
}
|
||||
@@ -391,7 +407,7 @@ public class KLALBController {
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
public SpeedAndTrafficMonitorDataImpl getLinkMonitor() {
|
||||
return linkMonitor;
|
||||
@@ -403,6 +419,8 @@ public class KLALBController {
|
||||
|
||||
private SocketType streamSocketType = new KLALBStreamSocketType();
|
||||
|
||||
|
||||
|
||||
public class KLALBStreamSocketType extends SocketType {
|
||||
|
||||
public KLALBStreamSocketType() {
|
||||
@@ -436,8 +454,103 @@ public class KLALBController {
|
||||
}
|
||||
|
||||
|
||||
public List<MultipurposeSocketAddress> getSelflineTable() {
|
||||
return selflineTable;
|
||||
public List<MultiProtocolSocketAddress> getExternalEndpoints() {
|
||||
return publishedNodeInfo.getExternalEndpoints();
|
||||
}
|
||||
|
||||
public List<MultiProtocolSocketAddress> getExternalEndpointsSnapshot() {
|
||||
return new ArrayList<MultiProtocolSocketAddress>(publishedNodeInfo.getExternalEndpoints());
|
||||
}
|
||||
|
||||
public PublishedNodeInfo getPublishedNodeInfo() {
|
||||
return publishedNodeInfo;
|
||||
}
|
||||
|
||||
private List<MultiProtocolSocketAddress> createEffectiveExternalEndpointsLocked() {
|
||||
List<MultiProtocolSocketAddress> effective = new ArrayList<MultiProtocolSocketAddress>();
|
||||
for (MultiProtocolSocketAddress endpoint : configuredExternalEndpoints) {
|
||||
if (!effective.contains(endpoint)) {
|
||||
effective.add(endpoint);
|
||||
}
|
||||
}
|
||||
for (MultiProtocolSocketAddress endpoint : discoveredExternalEndpoints) {
|
||||
if (!effective.contains(endpoint)) {
|
||||
effective.add(endpoint);
|
||||
}
|
||||
}
|
||||
return effective;
|
||||
}
|
||||
|
||||
private void publishNodeInfoLocked(String deviceName, String deviceDescription,
|
||||
List<MultiProtocolSocketAddress> configEndpoints, List<String> extraRoutes) {
|
||||
configuredExternalEndpoints = configEndpoints == null
|
||||
? new ArrayList<MultiProtocolSocketAddress>()
|
||||
: new ArrayList<MultiProtocolSocketAddress>(configEndpoints);
|
||||
List<String> publishedExtraRoutes = extraRoutes == null
|
||||
? new ArrayList<String>() : new ArrayList<String>(extraRoutes);
|
||||
List<MultiProtocolSocketAddress> effective = createEffectiveExternalEndpointsLocked();
|
||||
PublishedNodeInfo previous = publishedNodeInfo;
|
||||
boolean nameChanged = !Objects.equals(previous.getDeviceName(), deviceName);
|
||||
boolean fullChanged = !Objects.equals(previous.getDeviceDescription(), deviceDescription)
|
||||
|| !previous.getExternalEndpoints().equals(effective)
|
||||
|| !previous.getExtraRoutes().equals(publishedExtraRoutes);
|
||||
|
||||
externalEndpoints.clear();
|
||||
externalEndpoints.addAll(effective);
|
||||
if (srv6Router != null) {
|
||||
srv6Router.setDeviceName(deviceName);
|
||||
}
|
||||
long fullRevision = previous.getFullRevision() + (fullChanged ? 1L : 0L);
|
||||
publishedNodeInfo = new PublishedNodeInfo(deviceName, deviceDescription, effective, publishedExtraRoutes,
|
||||
fullRevision);
|
||||
if (routingProtocol != null) {
|
||||
if (nameChanged) {
|
||||
routingProtocol.announceNodeInfoUpdate(
|
||||
org.kne.cloud.network.srv6.RouterInfoPacket.NODE_INFO_TINY_UPDATE_REQUIRED);
|
||||
}
|
||||
if (fullChanged) {
|
||||
routingProtocol.announceNodeInfoUpdate(
|
||||
org.kne.cloud.network.srv6.RouterInfoPacket.NODE_INFO_FULL_UPDATE_REQUIRED);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void publishNodeInfo(String deviceName, String deviceDescription,
|
||||
List<MultiProtocolSocketAddress> configEndpoints, List<String> extraRoutes) {
|
||||
synchronized (externalEndpoints) {
|
||||
if (configItem != null) {
|
||||
configItem.setDeviceName(deviceName);
|
||||
configItem.setDeviceDescription(deviceDescription);
|
||||
configItem.setExternalEndpoints(new ArrayList<MultiProtocolSocketAddress>(configEndpoints == null
|
||||
? Collections.<MultiProtocolSocketAddress>emptyList() : configEndpoints));
|
||||
configItem.setExtraRoutes(new ArrayList<String>(extraRoutes == null
|
||||
? Collections.<String>emptyList() : extraRoutes));
|
||||
}
|
||||
publishNodeInfoLocked(deviceName, deviceDescription, configEndpoints, extraRoutes);
|
||||
}
|
||||
}
|
||||
|
||||
private void publishDiscoveredExternalEndpointLocked() {
|
||||
List<MultiProtocolSocketAddress> effective = createEffectiveExternalEndpointsLocked();
|
||||
if (new HashSet<MultiProtocolSocketAddress>(effective)
|
||||
.equals(new HashSet<MultiProtocolSocketAddress>(externalEndpoints))) {
|
||||
return;
|
||||
}
|
||||
externalEndpoints.clear();
|
||||
externalEndpoints.addAll(effective);
|
||||
PublishedNodeInfo previous = publishedNodeInfo;
|
||||
publishedNodeInfo = new PublishedNodeInfo(previous.getDeviceName(), previous.getDeviceDescription(), effective,
|
||||
previous.getExtraRoutes(), previous.getFullRevision() + 1L);
|
||||
if (routingProtocol != null) {
|
||||
routingProtocol.announceNodeInfoUpdate(
|
||||
org.kne.cloud.network.srv6.RouterInfoPacket.NODE_INFO_FULL_UPDATE_REQUIRED);
|
||||
}
|
||||
}
|
||||
|
||||
public void replaceExternalEndpoints(List<MultiProtocolSocketAddress> endpoints) {
|
||||
PublishedNodeInfo published = publishedNodeInfo;
|
||||
publishNodeInfo(published.getDeviceName(), published.getDeviceDescription(), endpoints,
|
||||
published.getExtraRoutes());
|
||||
}
|
||||
|
||||
|
||||
@@ -472,13 +585,13 @@ public class KLALBController {
|
||||
|
||||
private KLALBControllerConfigItem configItem;
|
||||
|
||||
public void addRemoteLines(List<MultipurposeSocketAddress> select) {
|
||||
for (MultipurposeSocketAddress target : select) {
|
||||
public void addRemoteLines(List<MultiProtocolSocketAddress> select) {
|
||||
for (MultiProtocolSocketAddress target : select) {
|
||||
addRemoteLines(target);
|
||||
}
|
||||
}
|
||||
|
||||
public List<KLALBRemoteLink> addRemoteLines(MultipurposeSocketAddress target) {
|
||||
public List<KLALBRemoteLink> addRemoteLines(MultiProtocolSocketAddress target) {
|
||||
lineslock.writeLock().lock();
|
||||
try {
|
||||
List<KLALBRemoteLink> added = new ArrayList<>();
|
||||
@@ -492,7 +605,7 @@ public class KLALBController {
|
||||
while (ei.hasMoreElements()) {
|
||||
InetAddress inetAddress = (InetAddress) ei.nextElement();
|
||||
try {
|
||||
MultipurposeSocketAddress bind = new MultipurposeSocketAddress(
|
||||
MultiProtocolSocketAddress bind = new MultiProtocolSocketAddress(
|
||||
inetAddress.getHostAddress(), 0);
|
||||
try {
|
||||
if (target.getInetAddress().isLoopbackAddress()
|
||||
@@ -553,7 +666,7 @@ public class KLALBController {
|
||||
}
|
||||
}
|
||||
|
||||
public List<KLALBRemoteLink> removeRemoteLines(MultipurposeSocketAddress mpsa) {
|
||||
public List<KLALBRemoteLink> removeRemoteLines(MultiProtocolSocketAddress mpsa) {
|
||||
|
||||
List<KLALBRemoteLink> rmved = new ArrayList<>();
|
||||
for (Iterator<IPv6NetworkLink> iterator = srv6Router.getLinkTabel().iterator(); iterator.hasNext();) {
|
||||
@@ -569,7 +682,7 @@ public class KLALBController {
|
||||
return rmved;
|
||||
}
|
||||
|
||||
public Inet6Address getRemoteVaddrBySocketAddress(MultipurposeSocketAddress target) throws SocketTimeoutException {
|
||||
public Inet6Address getRemoteVaddrBySocketAddress(MultiProtocolSocketAddress target) throws SocketTimeoutException {
|
||||
KLALBRemoteLink kr = null;
|
||||
for (Iterator<IPv6NetworkLink> iterator = srv6Router.getLinkTabel().iterator(); iterator.hasNext();) {
|
||||
IPv6NetworkLink link = iterator.next();
|
||||
@@ -593,7 +706,7 @@ public class KLALBController {
|
||||
return kr.getRemoteVaddr().getAddress().toInet6Address();
|
||||
}
|
||||
|
||||
private boolean checkContainsTargetAndBind(MultipurposeSocketAddress target, MultipurposeSocketAddress bind) {
|
||||
private boolean checkContainsTargetAndBind(MultiProtocolSocketAddress target, MultiProtocolSocketAddress bind) {
|
||||
boolean b = false;
|
||||
for (Iterator<IPv6NetworkLink> iterator = srv6Router.getLinkTabel().iterator(); iterator.hasNext();) {
|
||||
IPv6NetworkLink link = iterator.next();
|
||||
@@ -609,7 +722,7 @@ public class KLALBController {
|
||||
return b;
|
||||
}
|
||||
|
||||
private boolean checkContainsTarget(MultipurposeSocketAddress target) {
|
||||
private boolean checkContainsTarget(MultiProtocolSocketAddress target) {
|
||||
boolean b = false;
|
||||
for (Iterator<IPv6NetworkLink> iterator = srv6Router.getLinkTabel().iterator(); iterator.hasNext();) {
|
||||
IPv6NetworkLink link = iterator.next();
|
||||
@@ -628,19 +741,18 @@ public class KLALBController {
|
||||
lineslock.writeLock().lock();
|
||||
try {
|
||||
krs.startIO();
|
||||
String selflineTable = generateSelfLineTable();
|
||||
if (selflineTable != null && !selflineTable.equals(""))
|
||||
krs.sendPacket(new ADDLINESPacket(selflineTable));
|
||||
String externalEndpointsText = generateExternalEndpointsString();
|
||||
if (externalEndpointsText != null && !externalEndpointsText.equals(""))
|
||||
krs.sendPacket(new ADDLINESPacket(externalEndpointsText));
|
||||
srv6Router.getLinkTabel().add(krs);
|
||||
} finally {
|
||||
lineslock.writeLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
private String generateSelfLineTable() {
|
||||
private String generateExternalEndpointsString() {
|
||||
StringBuilder sbd = new StringBuilder();
|
||||
for (Iterator<MultipurposeSocketAddress> iterator = selflineTable.iterator(); iterator.hasNext();) {
|
||||
MultipurposeSocketAddress klalbRemoteLine = (MultipurposeSocketAddress) iterator.next();
|
||||
for (MultiProtocolSocketAddress klalbRemoteLine : publishedNodeInfo.getExternalEndpoints()) {
|
||||
sbd.append(klalbRemoteLine.toString());
|
||||
sbd.append('\n');
|
||||
}
|
||||
@@ -668,7 +780,12 @@ public class KLALBController {
|
||||
}
|
||||
|
||||
private void loadSRv6ProtocolStack(IPv6AddressGroup selfx, boolean enableVirtualAdapter) {
|
||||
networkInterfaceManager.getInetAddressesExcept().add(selfx.getAddress().toInet6Address());
|
||||
srv6Router = new SRv6Router(selfx, clock);
|
||||
if(configItem!=null) {
|
||||
srv6Router.setPerformanceStrategy(PerformanceStrategy.fromDescription(configItem.getPerformanceStrategy()));
|
||||
srv6Router.setDeviceName(configItem.getDeviceName());
|
||||
}
|
||||
srv6Router.runKLALBRouteProtocol();
|
||||
routingProtocol = srv6Router.getKlalbRouteProtol();
|
||||
routingProtocol.addReceiver((addr, packet) -> {
|
||||
@@ -677,11 +794,13 @@ public class KLALBController {
|
||||
rawPortBinder= new PortBinder(this.getSelf().getAddress());
|
||||
System.out.println(" Loaded: SRv6 Stack");
|
||||
|
||||
String name=(configItem!=null)?configItem.getTUNName():CONST.KLALB_S_RV6;
|
||||
if (enableVirtualAdapter&&(name!=null)) {
|
||||
String name = (configItem != null && configItem.getTUNName() != null) ? configItem.getTUNName() : CONST.KLALB_S_RV6;
|
||||
boolean isEnabled = configItem == null || configItem.isEnableTUN();
|
||||
boolean enableTUN = enableVirtualAdapter && isEnabled && (name != null) && !name.trim().isEmpty() && !name.trim().equalsIgnoreCase("null");
|
||||
if (enableTUN) {
|
||||
Thread t=new Thread(()->{
|
||||
try {
|
||||
IPv6TUNLoopbackNetworkLink tunlink = new IPv6TUNLoopbackNetworkLink(name,
|
||||
IPv6TUNLoopbackNetworkLink tunlink = new IPv6TUNLoopbackNetworkLink(name.trim(),
|
||||
new IPv6AddressGroup(srv6Router.getLocator().getAddress(), 32), SRv6Router.MTU, dnsAddresses);
|
||||
tunlink.setMonitor(datatMonitor);
|
||||
// srv6Router.getLinkTabel().add(tunlink);
|
||||
@@ -695,7 +814,7 @@ public class KLALBController {
|
||||
});
|
||||
t.start();
|
||||
}else {
|
||||
System.out.println(" Tun Adapter Disbaled");
|
||||
System.out.println(" Tun Adapter Disabled");
|
||||
|
||||
}
|
||||
udpr=new UDPProtocolRegister(this);
|
||||
@@ -715,7 +834,7 @@ public class KLALBController {
|
||||
// System.out.println(context);
|
||||
try {
|
||||
NTPv4Protocol nvc = new NTPv4Protocol(context,
|
||||
new MultipurposeSocketAddress("{" + iproxyname + "_Datagram}0.0.0.0:123"));
|
||||
new MultiProtocolSocketAddress(iproxyname + "_Datagram","0.0.0.0",123));
|
||||
srv6Router.addSRv6RouterListener(new SRv6RouterListener() {
|
||||
|
||||
@Override
|
||||
@@ -726,7 +845,7 @@ public class KLALBController {
|
||||
for (Neighbor neighbor : nb) {
|
||||
if (neighbor.getLocator() != null)
|
||||
sp.add(new NTPPeer(
|
||||
new MultipurposeSocketAddress(iproxyname + "_Datagram",
|
||||
new MultiProtocolSocketAddress(iproxyname + "_Datagram",
|
||||
neighbor.getLocator().getAddress().toString(), 123),
|
||||
NTPv4Packet.NTP_SYMMETRIC_ACTIVE));
|
||||
|
||||
@@ -747,7 +866,7 @@ public class KLALBController {
|
||||
});
|
||||
}
|
||||
});
|
||||
nvc2 = new NTPv4Protocol(context, new MultipurposeSocketAddress("{UDP}0.0.0.0:0"));// 106.55.184.199
|
||||
nvc2 = new NTPv4Protocol(context, new MultiProtocolSocketAddress("udp","0.0.0.0",0));
|
||||
|
||||
System.out.println(" Loaded: NTP Module");
|
||||
} catch (UnknownHostException e) {
|
||||
@@ -764,7 +883,6 @@ public class KLALBController {
|
||||
this.apiClient = new KLALBRoutingProtocolAPIClient(routingProtocol);
|
||||
twk.schedule(tsk1, 5000, 5000);
|
||||
twk.schedule(tsk2, 5000, 5000);
|
||||
twk.schedule(tsk3, 5000, 5000);
|
||||
}
|
||||
|
||||
public KLALBController() {
|
||||
@@ -794,6 +912,9 @@ public class KLALBController {
|
||||
}
|
||||
}).start();
|
||||
loadSRv6ProtocolStack(selfg, enableVirtualAdapter);
|
||||
if (configItem == null) {
|
||||
publishNodeInfo(srv6Router.getDeviceName(), null, null, null);
|
||||
}
|
||||
loadController();
|
||||
}
|
||||
|
||||
@@ -830,18 +951,17 @@ public class KLALBController {
|
||||
getIpv6Router().setASN(vasn);
|
||||
}
|
||||
|
||||
List<MultipurposeSocketAddress> linele = configItem.getLineTable();
|
||||
if (linele != null) {
|
||||
getSelflineTable().addAll(linele);
|
||||
}
|
||||
List<MultipurposeSocketAddress> linetoc = configItem.getConnectLineTable();
|
||||
List<MultiProtocolSocketAddress> linele = configItem.getExternalEndpoints();
|
||||
publishNodeInfo(configItem.getDeviceName(), configItem.getDeviceDescription(), linele,
|
||||
configItem.getExtraRoutes());
|
||||
List<MultiProtocolSocketAddress> linetoc = configItem.getAutoConnections();
|
||||
if (linetoc != null) {
|
||||
linetoc.forEach((aline) -> {
|
||||
addRemoteLines(aline);
|
||||
});
|
||||
}
|
||||
|
||||
List<MultipurposeSocketAddress> ntps = configItem.getNtpServerTable();
|
||||
List<MultiProtocolSocketAddress> ntps = configItem.getNtpServers();
|
||||
if (ntps != null) {
|
||||
getNTPTable().addAll(ntps);
|
||||
}
|
||||
@@ -1004,24 +1124,19 @@ public class KLALBController {
|
||||
|
||||
|
||||
|
||||
private List<MultipurposeSocketAddress> listens = new CopyOnWriteArrayList<>();
|
||||
|
||||
|
||||
public void registerToProxyTypeAs(String proxyname) {
|
||||
MultipurposeSocketAddress.getSocketTypeRegister().put(proxyname + "_Stream", streamSocketType);
|
||||
MultipurposeSocketAddress.getSocketTypeRegister().put(proxyname + "_Datagram", datagramSocketType);
|
||||
MultiProtocolSocketAddress.getSocketTypeRegister().put(proxyname + "_Stream", streamSocketType);
|
||||
MultiProtocolSocketAddress.getSocketTypeRegister().put(proxyname + "_Datagram", datagramSocketType);
|
||||
// ProxyProfileEntry.getRegister().put(proxyname, this);
|
||||
}
|
||||
|
||||
public List<MultipurposeSocketAddress> getListenSocketAddress() {
|
||||
return listens;
|
||||
}
|
||||
|
||||
private List<MultipurposeSocketAddress> ntptable = new CopyOnWriteArrayList();
|
||||
private List<MultiProtocolSocketAddress> ntptable = new CopyOnWriteArrayList();
|
||||
|
||||
private KLALBRoutingProtocol routingProtocol;
|
||||
|
||||
public List<MultipurposeSocketAddress> getNTPTable() {
|
||||
public List<MultiProtocolSocketAddress> getNTPTable() {
|
||||
return ntptable;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,31 +3,87 @@ package org.kne.cloud.network.klalb;
|
||||
import java.net.InetAddress;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.kne.cloud.network.MultipurposeSocketAddress;
|
||||
import org.kne.cloud.network.MultiProtocolSocketAddress;
|
||||
|
||||
public class KLALBControllerConfigItem extends KLALBConfigItem {
|
||||
private String language;
|
||||
private boolean nogui;
|
||||
private String VirtualAddress;
|
||||
private String VirtualAddress=KLALBUtils.randomKLALBIPv6Address().toString();
|
||||
private Long VirtualASN;
|
||||
private List<InetAddress> DNS;
|
||||
private MultipurposeSocketAddress TCPListen;
|
||||
private MultipurposeSocketAddress UDPListen;
|
||||
private MultiProtocolSocketAddress TCPListen=new MultiProtocolSocketAddress("0.0.0.0",4565);
|
||||
private MultiProtocolSocketAddress UDPListen=new MultiProtocolSocketAddress("udp","0.0.0.0",4565);
|
||||
private String VirtualSocketName;
|
||||
private List<MultipurposeSocketAddress>LineTable=new ArrayList<>();
|
||||
private List<MultipurposeSocketAddress>ConnectLineTable=new ArrayList<>();
|
||||
private List<MultipurposeSocketAddress>ntpServerTable=new ArrayList<>();
|
||||
private boolean denyLineTableQuery=false;
|
||||
private boolean denyLineTableBroadcast=false;
|
||||
private List<MultiProtocolSocketAddress> externalEndpoints = new ArrayList<>();
|
||||
private List<MultiProtocolSocketAddress> autoConnections = new ArrayList<>();
|
||||
private List<MultiProtocolSocketAddress> ntpServers = new ArrayList<>();
|
||||
private List<String> ExtraRoutes = new ArrayList<>();
|
||||
private boolean denyExternalEndpointQuery = false;
|
||||
private boolean denyExternalEndpointBroadcast = false;
|
||||
private String congestionAlgorithm="BBR";
|
||||
private double burstLimit=1.20;
|
||||
private double burstLimit=1.50;
|
||||
private double delayUpperBound=1.20;
|
||||
private double delayLowerBound=1.15;
|
||||
private long nagleDelayTime=1000000L;
|
||||
private long linkNagleDelayTime=1000000L;
|
||||
private int linkConnectionsCount=1;
|
||||
private boolean enableTUN = true;
|
||||
private String TUNName=CONST.KLALB_S_RV6;
|
||||
private String performanceStrategy="multifill";
|
||||
private String DeviceName;
|
||||
private String DeviceDescription;
|
||||
private boolean webUI = false;
|
||||
private MultiProtocolSocketAddress webListen = new MultiProtocolSocketAddress("http", "0.0.0.0", 4665);
|
||||
|
||||
public boolean isEnableTUN() {
|
||||
return enableTUN;
|
||||
}
|
||||
|
||||
public void setEnableTUN(boolean enableTUN) {
|
||||
this.enableTUN = enableTUN;
|
||||
}
|
||||
|
||||
public boolean isWebUI() {
|
||||
return webUI;
|
||||
}
|
||||
|
||||
public void setWebUI(boolean webUI) {
|
||||
this.webUI = webUI;
|
||||
}
|
||||
|
||||
public MultiProtocolSocketAddress getWebListen() {
|
||||
return webListen;
|
||||
}
|
||||
|
||||
public void setWebListen(MultiProtocolSocketAddress webListen) {
|
||||
this.webListen = webListen;
|
||||
}
|
||||
|
||||
public String getPerformanceStrategy() {
|
||||
return performanceStrategy;
|
||||
}
|
||||
|
||||
public void setPerformanceStrategy(String performanceStrategy) {
|
||||
this.performanceStrategy = performanceStrategy;
|
||||
}
|
||||
|
||||
public String getDeviceName() {
|
||||
return DeviceName;
|
||||
}
|
||||
|
||||
public void setDeviceName(String deviceName) {
|
||||
DeviceName = deviceName;
|
||||
}
|
||||
|
||||
public String getDeviceDescription() {
|
||||
return DeviceDescription;
|
||||
}
|
||||
|
||||
public void setDeviceDescription(String deviceDescription) {
|
||||
DeviceDescription = deviceDescription;
|
||||
}
|
||||
|
||||
public void setNetworkInterfaceExcepts(List<String> networkInterfaceExcepts) {
|
||||
NetworkInterfaceExcepts = networkInterfaceExcepts;
|
||||
@@ -52,8 +108,8 @@ public class KLALBControllerConfigItem extends KLALBConfigItem {
|
||||
|
||||
|
||||
|
||||
public KLALBControllerConfigItem(String type) {
|
||||
super(type);
|
||||
public KLALBControllerConfigItem() {
|
||||
super("KLALBController");
|
||||
}
|
||||
|
||||
|
||||
@@ -124,63 +180,76 @@ public class KLALBControllerConfigItem extends KLALBConfigItem {
|
||||
|
||||
|
||||
|
||||
public MultipurposeSocketAddress getTCPListen() {
|
||||
public MultiProtocolSocketAddress getTCPListen() {
|
||||
return TCPListen;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public void setTCPListen(MultipurposeSocketAddress tCPListen) {
|
||||
public void setTCPListen(MultiProtocolSocketAddress tCPListen) {
|
||||
TCPListen = tCPListen;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public MultipurposeSocketAddress getUDPListen() {
|
||||
public MultiProtocolSocketAddress getUDPListen() {
|
||||
return UDPListen;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public void setUDPListen(MultipurposeSocketAddress uDPListen) {
|
||||
public void setUDPListen(MultiProtocolSocketAddress uDPListen) {
|
||||
UDPListen = uDPListen;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public List<MultipurposeSocketAddress> getLineTable() {
|
||||
return LineTable;
|
||||
public List<MultiProtocolSocketAddress> getExternalEndpoints() {
|
||||
return externalEndpoints;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public void setLineTable(List<MultipurposeSocketAddress> lineTable) {
|
||||
LineTable = lineTable;
|
||||
public void setExternalEndpoints(List<MultiProtocolSocketAddress> externalEndpoints) {
|
||||
this.externalEndpoints = externalEndpoints;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public List<MultipurposeSocketAddress> getConnectLineTable() {
|
||||
return ConnectLineTable;
|
||||
public List<MultiProtocolSocketAddress> getAutoConnections() {
|
||||
return autoConnections;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public void setConnectLineTable(List<MultipurposeSocketAddress> connectLineTable) {
|
||||
ConnectLineTable = connectLineTable;
|
||||
public void setAutoConnections(List<MultiProtocolSocketAddress> autoConnections) {
|
||||
this.autoConnections = autoConnections;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public List<MultipurposeSocketAddress> getNtpServerTable() {
|
||||
return ntpServerTable;
|
||||
public List<MultiProtocolSocketAddress> getConnectLineTable() {
|
||||
return autoConnections;
|
||||
}
|
||||
|
||||
public void setConnectLineTable(List<MultiProtocolSocketAddress> connectLineTable) {
|
||||
this.autoConnections = connectLineTable;
|
||||
}
|
||||
|
||||
public List<MultiProtocolSocketAddress> getNtpServers() {
|
||||
return ntpServers;
|
||||
}
|
||||
|
||||
public void setNtpServers(List<MultiProtocolSocketAddress> ntpServers) {
|
||||
this.ntpServers = ntpServers;
|
||||
}
|
||||
|
||||
public void setNtpServerTable(List<MultipurposeSocketAddress> ntpServerTable) {
|
||||
this.ntpServerTable = ntpServerTable;
|
||||
public List<MultiProtocolSocketAddress> getNtpServerTable() {
|
||||
return ntpServers;
|
||||
}
|
||||
|
||||
public void setNtpServerTable(List<MultiProtocolSocketAddress> ntpServerTable) {
|
||||
this.ntpServers = ntpServerTable;
|
||||
}
|
||||
|
||||
public List<String> getExtraRoutes() {
|
||||
return ExtraRoutes;
|
||||
}
|
||||
|
||||
public void setExtraRoutes(List<String> extraRoutes) {
|
||||
ExtraRoutes = extraRoutes;
|
||||
}
|
||||
|
||||
|
||||
@@ -243,23 +312,20 @@ public class KLALBControllerConfigItem extends KLALBConfigItem {
|
||||
|
||||
|
||||
|
||||
public boolean isDenyLineTableQuery() {
|
||||
return denyLineTableQuery;
|
||||
public boolean isDenyExternalEndpointQuery() {
|
||||
return denyExternalEndpointQuery;
|
||||
}
|
||||
|
||||
|
||||
public void setDenyLineTableQuery(boolean denyLineTableQuery) {
|
||||
this.denyLineTableQuery = denyLineTableQuery;
|
||||
public void setDenyExternalEndpointQuery(boolean denyExternalEndpointQuery) {
|
||||
this.denyExternalEndpointQuery = denyExternalEndpointQuery;
|
||||
}
|
||||
|
||||
|
||||
public boolean isDenyLineTableBroadcast() {
|
||||
return denyLineTableBroadcast;
|
||||
public boolean isDenyExternalEndpointBroadcast() {
|
||||
return denyExternalEndpointBroadcast;
|
||||
}
|
||||
|
||||
|
||||
public void setDenyLineTableBroadcast(boolean denyLineTableBroadcast) {
|
||||
this.denyLineTableBroadcast = denyLineTableBroadcast;
|
||||
public void setDenyExternalEndpointBroadcast(boolean denyExternalEndpointBroadcast) {
|
||||
this.denyExternalEndpointBroadcast = denyExternalEndpointBroadcast;
|
||||
}
|
||||
|
||||
|
||||
@@ -306,146 +372,49 @@ public class KLALBControllerConfigItem extends KLALBConfigItem {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "KLALBControllerConfigItem [language=" + language + ", nogui=" + nogui + ", VirtualAddress="
|
||||
+ VirtualAddress + ", VirtualASN=" + VirtualASN + ", DNS=" + DNS + ", TCPListen=" + TCPListen
|
||||
+ ", UDPListen=" + UDPListen + ", VirtualSocketName=" + VirtualSocketName + ", LineTable=" + LineTable
|
||||
+ ", ConnectLineTable=" + ConnectLineTable + ", ntpServerTable=" + ntpServerTable
|
||||
+ ", denyLineTableQuery=" + denyLineTableQuery + ", denyLineTableBroadcast=" + denyLineTableBroadcast
|
||||
+ ", congestionAlgorithm=" + congestionAlgorithm + ", delayUpperBound=" + delayUpperBound
|
||||
+ ", delayLowerBound=" + delayLowerBound + ", nagleDelayTime=" + nagleDelayTime
|
||||
+ ", linkNagleDelayTime=" + linkNagleDelayTime + ", linkConnectionsCount=" + linkConnectionsCount
|
||||
+ ", TUNName=" + TUNName + ", NetworkInterfaceExcepts=" + NetworkInterfaceExcepts + "]";
|
||||
return "KLALBControllerConfigItem{" +
|
||||
"language='" + language + '\'' +
|
||||
", nogui=" + nogui +
|
||||
", VirtualAddress='" + VirtualAddress + '\'' +
|
||||
", VirtualASN=" + VirtualASN +
|
||||
", DNS=" + DNS +
|
||||
", TCPListen=" + TCPListen +
|
||||
", UDPListen=" + UDPListen +
|
||||
", VirtualSocketName='" + VirtualSocketName + '\'' +
|
||||
", externalEndpoints=" + externalEndpoints +
|
||||
", autoConnections=" + autoConnections +
|
||||
", ntpServers=" + ntpServers +
|
||||
", ExtraRoutes=" + ExtraRoutes +
|
||||
", denyExternalEndpointQuery=" + denyExternalEndpointQuery +
|
||||
", denyExternalEndpointBroadcast=" + denyExternalEndpointBroadcast +
|
||||
", congestionAlgorithm='" + congestionAlgorithm + '\'' +
|
||||
", burstLimit=" + burstLimit +
|
||||
", delayUpperBound=" + delayUpperBound +
|
||||
", delayLowerBound=" + delayLowerBound +
|
||||
", nagleDelayTime=" + nagleDelayTime +
|
||||
", linkNagleDelayTime=" + linkNagleDelayTime +
|
||||
", linkConnectionsCount=" + linkConnectionsCount +
|
||||
", enableTUN=" + enableTUN +
|
||||
", TUNName='" + TUNName + '\'' +
|
||||
", performanceStrategy='" + performanceStrategy + '\'' +
|
||||
", DeviceName='" + DeviceName + '\'' +
|
||||
", DeviceDescription='" + DeviceDescription + '\'' +
|
||||
", NetworkInterfaceExcepts=" + NetworkInterfaceExcepts +
|
||||
", webUI=" + webUI +
|
||||
", webListen=" + webListen +
|
||||
'}';
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
if (!super.equals(o)) return false;
|
||||
KLALBControllerConfigItem that = (KLALBControllerConfigItem) o;
|
||||
return nogui == that.nogui && enableTUN == that.enableTUN && webUI == that.webUI && denyExternalEndpointQuery == that.denyExternalEndpointQuery && denyExternalEndpointBroadcast == that.denyExternalEndpointBroadcast && Double.compare(burstLimit, that.burstLimit) == 0 && Double.compare(delayUpperBound, that.delayUpperBound) == 0 && Double.compare(delayLowerBound, that.delayLowerBound) == 0 && nagleDelayTime == that.nagleDelayTime && linkNagleDelayTime == that.linkNagleDelayTime && linkConnectionsCount == that.linkConnectionsCount && Objects.equals(webListen, that.webListen) && Objects.equals(language, that.language) && Objects.equals(VirtualAddress, that.VirtualAddress) && Objects.equals(VirtualASN, that.VirtualASN) && Objects.equals(DNS, that.DNS) && Objects.equals(TCPListen, that.TCPListen) && Objects.equals(UDPListen, that.UDPListen) && Objects.equals(VirtualSocketName, that.VirtualSocketName) && Objects.equals(externalEndpoints, that.externalEndpoints) && Objects.equals(autoConnections, that.autoConnections) && Objects.equals(ntpServers, that.ntpServers) && Objects.equals(ExtraRoutes, that.ExtraRoutes) && Objects.equals(congestionAlgorithm, that.congestionAlgorithm) && Objects.equals(TUNName, that.TUNName) && Objects.equals(performanceStrategy, that.performanceStrategy) && Objects.equals(DeviceName, that.DeviceName) && Objects.equals(DeviceDescription, that.DeviceDescription) && Objects.equals(NetworkInterfaceExcepts, that.NetworkInterfaceExcepts);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = super.hashCode();
|
||||
result = prime * result + ((ConnectLineTable == null) ? 0 : ConnectLineTable.hashCode());
|
||||
result = prime * result + ((DNS == null) ? 0 : DNS.hashCode());
|
||||
result = prime * result + ((LineTable == null) ? 0 : LineTable.hashCode());
|
||||
result = prime * result + ((NetworkInterfaceExcepts == null) ? 0 : NetworkInterfaceExcepts.hashCode());
|
||||
result = prime * result + ((TCPListen == null) ? 0 : TCPListen.hashCode());
|
||||
result = prime * result + ((TUNName == null) ? 0 : TUNName.hashCode());
|
||||
result = prime * result + ((UDPListen == null) ? 0 : UDPListen.hashCode());
|
||||
result = prime * result + ((VirtualASN == null) ? 0 : VirtualASN.hashCode());
|
||||
result = prime * result + ((VirtualAddress == null) ? 0 : VirtualAddress.hashCode());
|
||||
result = prime * result + ((VirtualSocketName == null) ? 0 : VirtualSocketName.hashCode());
|
||||
result = prime * result + ((congestionAlgorithm == null) ? 0 : congestionAlgorithm.hashCode());
|
||||
long temp;
|
||||
temp = Double.doubleToLongBits(delayLowerBound);
|
||||
result = prime * result + (int) (temp ^ (temp >>> 32));
|
||||
temp = Double.doubleToLongBits(delayUpperBound);
|
||||
result = prime * result + (int) (temp ^ (temp >>> 32));
|
||||
result = prime * result + (denyLineTableBroadcast ? 1231 : 1237);
|
||||
result = prime * result + (denyLineTableQuery ? 1231 : 1237);
|
||||
result = prime * result + ((language == null) ? 0 : language.hashCode());
|
||||
result = prime * result + linkConnectionsCount;
|
||||
result = prime * result + (int) (linkNagleDelayTime ^ (linkNagleDelayTime >>> 32));
|
||||
result = prime * result + (int) (nagleDelayTime ^ (nagleDelayTime >>> 32));
|
||||
result = prime * result + (nogui ? 1231 : 1237);
|
||||
result = prime * result + ((ntpServerTable == null) ? 0 : ntpServerTable.hashCode());
|
||||
return result;
|
||||
return Objects.hash(super.hashCode(), language, nogui, VirtualAddress, VirtualASN, DNS, TCPListen, UDPListen, VirtualSocketName, externalEndpoints, autoConnections, ntpServers, ExtraRoutes, denyExternalEndpointQuery, denyExternalEndpointBroadcast, congestionAlgorithm, burstLimit, delayUpperBound, delayLowerBound, nagleDelayTime, linkNagleDelayTime, linkConnectionsCount, enableTUN, TUNName, performanceStrategy, DeviceName, DeviceDescription, NetworkInterfaceExcepts, webUI, webListen);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (!super.equals(obj))
|
||||
return false;
|
||||
if (getClass() != obj.getClass())
|
||||
return false;
|
||||
KLALBControllerConfigItem other = (KLALBControllerConfigItem) obj;
|
||||
if (ConnectLineTable == null) {
|
||||
if (other.ConnectLineTable != null)
|
||||
return false;
|
||||
} else if (!ConnectLineTable.equals(other.ConnectLineTable))
|
||||
return false;
|
||||
if (DNS == null) {
|
||||
if (other.DNS != null)
|
||||
return false;
|
||||
} else if (!DNS.equals(other.DNS))
|
||||
return false;
|
||||
if (LineTable == null) {
|
||||
if (other.LineTable != null)
|
||||
return false;
|
||||
} else if (!LineTable.equals(other.LineTable))
|
||||
return false;
|
||||
if (NetworkInterfaceExcepts == null) {
|
||||
if (other.NetworkInterfaceExcepts != null)
|
||||
return false;
|
||||
} else if (!NetworkInterfaceExcepts.equals(other.NetworkInterfaceExcepts))
|
||||
return false;
|
||||
if (TCPListen == null) {
|
||||
if (other.TCPListen != null)
|
||||
return false;
|
||||
} else if (!TCPListen.equals(other.TCPListen))
|
||||
return false;
|
||||
if (TUNName == null) {
|
||||
if (other.TUNName != null)
|
||||
return false;
|
||||
} else if (!TUNName.equals(other.TUNName))
|
||||
return false;
|
||||
if (UDPListen == null) {
|
||||
if (other.UDPListen != null)
|
||||
return false;
|
||||
} else if (!UDPListen.equals(other.UDPListen))
|
||||
return false;
|
||||
if (VirtualASN == null) {
|
||||
if (other.VirtualASN != null)
|
||||
return false;
|
||||
} else if (!VirtualASN.equals(other.VirtualASN))
|
||||
return false;
|
||||
if (VirtualAddress == null) {
|
||||
if (other.VirtualAddress != null)
|
||||
return false;
|
||||
} else if (!VirtualAddress.equals(other.VirtualAddress))
|
||||
return false;
|
||||
if (VirtualSocketName == null) {
|
||||
if (other.VirtualSocketName != null)
|
||||
return false;
|
||||
} else if (!VirtualSocketName.equals(other.VirtualSocketName))
|
||||
return false;
|
||||
if (congestionAlgorithm == null) {
|
||||
if (other.congestionAlgorithm != null)
|
||||
return false;
|
||||
} else if (!congestionAlgorithm.equals(other.congestionAlgorithm))
|
||||
return false;
|
||||
if (Double.doubleToLongBits(delayLowerBound) != Double.doubleToLongBits(other.delayLowerBound))
|
||||
return false;
|
||||
if (Double.doubleToLongBits(delayUpperBound) != Double.doubleToLongBits(other.delayUpperBound))
|
||||
return false;
|
||||
if (denyLineTableBroadcast != other.denyLineTableBroadcast)
|
||||
return false;
|
||||
if (denyLineTableQuery != other.denyLineTableQuery)
|
||||
return false;
|
||||
if (language == null) {
|
||||
if (other.language != null)
|
||||
return false;
|
||||
} else if (!language.equals(other.language))
|
||||
return false;
|
||||
if (linkConnectionsCount != other.linkConnectionsCount)
|
||||
return false;
|
||||
if (linkNagleDelayTime != other.linkNagleDelayTime)
|
||||
return false;
|
||||
if (nagleDelayTime != other.nagleDelayTime)
|
||||
return false;
|
||||
if (nogui != other.nogui)
|
||||
return false;
|
||||
if (ntpServerTable == null) {
|
||||
if (other.ntpServerTable != null)
|
||||
return false;
|
||||
} else if (!ntpServerTable.equals(other.ntpServerTable))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -2,7 +2,9 @@ package org.kne.cloud.network.klalb;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.NetworkInterface;
|
||||
import java.nio.channels.ServerSocketChannel;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
@@ -11,7 +13,7 @@ import java.util.List;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Scanner;
|
||||
|
||||
import org.kne.cloud.network.MultipurposeSocketAddress;
|
||||
import org.kne.cloud.network.MultiProtocolSocketAddress;
|
||||
import org.kne.cloud.network.SocketChannelListener;
|
||||
import org.kne.cloud.network.ipv6.IPv6NetworkLink;
|
||||
import org.kne.cloud.network.ipv6.RouteItem;
|
||||
@@ -50,13 +52,15 @@ public class KLALBMain {
|
||||
}catch(Throwable e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
try {
|
||||
if(kpcje.getControllerConfig() != null && kpcje.getControllerConfig().isWebUI()) {
|
||||
kpcje.enableWebServer();
|
||||
}
|
||||
} catch(Throwable e) {
|
||||
System.err.println("Failed to start web server: " + e.getMessage());
|
||||
}
|
||||
dtb.putTime("UI");
|
||||
//dtb.print();
|
||||
/*MultipurposeSocketAddress mpa=new MultipurposeSocketAddress("127.9.9.9", 49573);
|
||||
kpcje.enableRemoteManagement(mpa);
|
||||
System.out.println("远程管理端口已在"+mpa+"端口上开启");*/
|
||||
/*if(true)
|
||||
return;*/
|
||||
ServerSocketChannel kpsvr=KLALBVirtualServerSocketChannel.open(kpcje.getKlalbController());
|
||||
kpsvr.bind(new InetSocketAddress("::0", 4564));
|
||||
SocketChannelListener stlr=new SocketChannelListener(kpsvr);
|
||||
@@ -82,11 +86,13 @@ public class KLALBMain {
|
||||
case "help":
|
||||
System.out.println(" help / ?: see help");
|
||||
System.out.println(" monitor: show monitor GUI");
|
||||
System.out.println(" web [start|stop|status <port>]: manage web dashboard");
|
||||
System.out.println(" links-state: query link states");
|
||||
System.out.println(" links-add <addr:port>: add link");
|
||||
System.out.println(" links-remove <addr:port>: remove link");
|
||||
System.out.println(" links-reconnect: reconnect all link");
|
||||
System.out.println(" kltp-state: query kltp states");
|
||||
System.out.println(" interfaces: show all network interfaces");
|
||||
System.out.println(" route: display internal route table");
|
||||
System.out.println(" kperf <addr:port>: performance benchmark");
|
||||
System.out.println(" exit: Exit");
|
||||
@@ -99,6 +105,42 @@ public class KLALBMain {
|
||||
e.printStackTrace();
|
||||
}
|
||||
break;
|
||||
case "web":
|
||||
if(sc.length >= 2) {
|
||||
String action = sc[1].toLowerCase();
|
||||
if("start".equals(action)) {
|
||||
int p = 4665;
|
||||
if(sc.length >= 3) {
|
||||
try { p = Integer.parseInt(sc[2]); } catch (NumberFormatException ignored) {}
|
||||
} else if(kpcje.getControllerConfig() != null && kpcje.getControllerConfig().getWebListen() != null) {
|
||||
p = kpcje.getControllerConfig().getWebListen().getPort();
|
||||
}
|
||||
try {
|
||||
kpcje.enableWebServer(p);
|
||||
System.out.println("Web dashboard started on http://localhost:" + p);
|
||||
} catch(Exception e) {
|
||||
System.out.println("Failed to start web server: " + e.getMessage());
|
||||
}
|
||||
} else if("stop".equals(action)) {
|
||||
kpcje.disableWebServer();
|
||||
System.out.println("Web dashboard stopped.");
|
||||
} else if("status".equals(action)) {
|
||||
if(kpcje.isWebServerEnabled()) {
|
||||
System.out.println("Web dashboard is running on port " + kpcje.getWebServer().getPort());
|
||||
} else {
|
||||
System.out.println("Web dashboard is stopped.");
|
||||
}
|
||||
} else {
|
||||
System.out.println("Usage: web [start|stop|status <port>]");
|
||||
}
|
||||
} else {
|
||||
if(kpcje.isWebServerEnabled()) {
|
||||
System.out.println("Web dashboard is running on port " + kpcje.getWebServer().getPort());
|
||||
} else {
|
||||
System.out.println("Web dashboard is not running. Use 'web start [port]' to start.");
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "links-state":
|
||||
System.out.println("links state:");
|
||||
//System.out.println("状态\t上传流量\t下载流量\t上传速度\t下载速度\t上传延迟\t下载延迟\t上传抖动\t下载抖动");
|
||||
@@ -115,7 +157,7 @@ public class KLALBMain {
|
||||
break;
|
||||
case "links-add":
|
||||
if(sc.length>=2) {
|
||||
MultipurposeSocketAddress mpsa=new MultipurposeSocketAddress(sc[1]);
|
||||
MultiProtocolSocketAddress mpsa=new MultiProtocolSocketAddress(sc[1]);
|
||||
List<KLALBRemoteLink>addl=kpcje.getKlalbController().addRemoteLines(mpsa);
|
||||
if(addl.isEmpty()) {
|
||||
//System.out.println("添加失败,线路已存在!");
|
||||
@@ -135,7 +177,7 @@ public class KLALBMain {
|
||||
break;
|
||||
case "links-remove":
|
||||
if(sc.length>=2) {
|
||||
MultipurposeSocketAddress mpsa=new MultipurposeSocketAddress(sc[1]);
|
||||
MultiProtocolSocketAddress mpsa=new MultiProtocolSocketAddress(sc[1]);
|
||||
List<KLALBRemoteLink>rmvl=kpcje.getKlalbController().removeRemoteLines(mpsa);
|
||||
if(rmvl.isEmpty()) {
|
||||
System.out.println("No matched link has been found.");
|
||||
@@ -161,6 +203,18 @@ public class KLALBMain {
|
||||
case "kltp-state":
|
||||
System.out.println(kpcje.getKlalbController().getKLTPregister().toString());
|
||||
break;
|
||||
case "interfaces":
|
||||
NetworkInterfaceManager networkInterfaceManager= kpcje.getKlalbController().getNetworkInterfaceManager();
|
||||
List<NetworkInterface> interfaceList=networkInterfaceManager.getAllAvaliableNetworkInterface();
|
||||
System.out.println("Network interface detected:");
|
||||
for(NetworkInterface networkInterface:interfaceList){
|
||||
System.out.println(networkInterface.getName()+"("+networkInterface.getDisplayName()+")");
|
||||
List<InetAddress> lst= networkInterfaceManager.getNetworkInterfaceAddress(networkInterface);
|
||||
for(InetAddress address :lst){
|
||||
System.out.println(" "+address.getHostAddress());
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "route":
|
||||
List<RouteItem> lri=new ArrayList<>( kpcje.getKlalbController().getIpv6Router().getCurrentRouteTabel());
|
||||
Collections.sort(lri);
|
||||
@@ -174,7 +228,7 @@ public class KLALBMain {
|
||||
break;
|
||||
case "kperf":
|
||||
if(sc.length>=2) {
|
||||
MultipurposeSocketAddress mpsa=new MultipurposeSocketAddress(sc[1]);
|
||||
MultiProtocolSocketAddress mpsa=new MultiProtocolSocketAddress(sc[1]);
|
||||
Kperf kp=new Kperf(mpsa);
|
||||
kp.startPerfing();
|
||||
}else {
|
||||
|
||||
@@ -2,12 +2,6 @@ package org.kne.cloud.network.klalb;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.SocketException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.concurrent.atomic.LongAdder;
|
||||
|
||||
import org.kne.cloud.network.MultipurposeSocketAddress;
|
||||
|
||||
public interface KLALBPacketLink {
|
||||
public void writeKLALBPacket(KLALBPacket kp) throws IOException;
|
||||
|
||||
@@ -7,7 +7,7 @@ import java.net.UnknownHostException;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.kne.cloud.network.KLALBDetectorItem;
|
||||
import org.kne.cloud.network.MultipurposeSocketAddress;
|
||||
import org.kne.cloud.network.MultiProtocolSocketAddress;
|
||||
import org.kne.cloud.network.ProtocolDetector;
|
||||
import org.kne.cloud.network.ProtocolDetectorServerSocket;
|
||||
import org.kne.cloud.network.ProtocolDetectorSocket;
|
||||
@@ -30,8 +30,8 @@ public class KLALBProtocolDetectSocketListener extends SocketListener {
|
||||
super.open();
|
||||
}
|
||||
|
||||
public KLALBProtocolDetectSocketListener(MultipurposeSocketAddress multipurposeSocketAddress,KLALBController klalbc) throws IOException {
|
||||
super(multipurposeSocketAddress);
|
||||
public KLALBProtocolDetectSocketListener(MultiProtocolSocketAddress multiProtocolSocketAddress, KLALBController klalbc) throws IOException {
|
||||
super(multiProtocolSocketAddress);
|
||||
this.klalbController=klalbc;
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,8 @@ import java.io.FileReader;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
import java.lang.reflect.Type;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.HashSet;
|
||||
|
||||
@@ -12,27 +14,54 @@ import org.kne.cloud.network.*;
|
||||
import org.kne.cloud.network.klalb.ui.KLALBStateGUI3;
|
||||
import org.kne.cloud.network.klalb.ui.Language;
|
||||
import org.kne.cloud.network.klalb.ui.UIEnv;
|
||||
import org.kne.cloud.network.klalb.web.KLALBWebServer;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonDeserializationContext;
|
||||
import com.google.gson.JsonDeserializer;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonParseException;
|
||||
import com.google.gson.JsonPrimitive;
|
||||
import com.google.gson.JsonSerializationContext;
|
||||
import com.google.gson.JsonSerializer;
|
||||
import com.google.gson.JsonParser;
|
||||
|
||||
public class KLALBProxySystem {
|
||||
private Set<Proxy> proxys=new HashSet<>();
|
||||
private KLALBController klalbController;
|
||||
private KLALBRemoteManagement krm;
|
||||
private KLALBWebServer webServer;
|
||||
private KLALBConfig config;
|
||||
private Gson gson;
|
||||
private File jsonFile;
|
||||
{
|
||||
GsonBuilder gb=new GsonBuilder().setPrettyPrinting();
|
||||
MultipurposeSocketAddress.registerToGsonBuilder(gb);
|
||||
MultiProtocolSocketAddress.registerToGsonBuilder(gb);
|
||||
KLALBConfigItem.registerToGsonBuilder(gb);
|
||||
gb.registerTypeAdapter(InetAddress.class, new JsonSerializer<InetAddress>() {
|
||||
@Override
|
||||
public JsonElement serialize(InetAddress src, Type typeOfSrc, JsonSerializationContext context) {
|
||||
return new JsonPrimitive(src.getHostAddress());
|
||||
}
|
||||
});
|
||||
gb.registerTypeAdapter(InetAddress.class, new JsonDeserializer<InetAddress>() {
|
||||
@Override
|
||||
public InetAddress deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
|
||||
try {
|
||||
return InetAddress.getByName(json.getAsString());
|
||||
} catch (Exception e) {
|
||||
throw new JsonParseException(e);
|
||||
}
|
||||
}
|
||||
});
|
||||
gson=gb.create();
|
||||
}
|
||||
public Gson getGson() {
|
||||
return gson;
|
||||
}
|
||||
|
||||
public Set<Proxy> getProxys() {
|
||||
return proxys;
|
||||
}
|
||||
@@ -55,43 +84,55 @@ public class KLALBProxySystem {
|
||||
public KLALBProxySystem() {
|
||||
}
|
||||
|
||||
public void enableRemoteManagement() throws IOException {
|
||||
if(krm==null) {
|
||||
krm=new KLALBRemoteManagement(this);
|
||||
}else {
|
||||
throw new IllegalStateException("Remote Management already enabled!");
|
||||
public void enableWebServer() throws IOException {
|
||||
KLALBControllerConfigItem cci = getControllerConfig();
|
||||
MultiProtocolSocketAddress listen = cci == null || cci.getWebListen() == null
|
||||
? new MultiProtocolSocketAddress("http", "0.0.0.0", 4665) : cci.getWebListen();
|
||||
enableWebServer(listen);
|
||||
}
|
||||
|
||||
public void enableWebServer(int port) throws IOException {
|
||||
enableWebServer(new MultiProtocolSocketAddress("http", "0.0.0.0", port));
|
||||
}
|
||||
|
||||
public void enableWebServer(MultiProtocolSocketAddress listen) throws IOException {
|
||||
if (webServer == null) {
|
||||
webServer = new KLALBWebServer(this, listen);
|
||||
webServer.start();
|
||||
} else {
|
||||
throw new IllegalStateException("Web server already enabled!");
|
||||
}
|
||||
}
|
||||
|
||||
public void enableRemoteManagement(MultipurposeSocketAddress bind) throws IOException {
|
||||
if(krm==null) {
|
||||
krm=new KLALBRemoteManagement(this,bind);
|
||||
}else {
|
||||
throw new IllegalStateException("Remote Management already enabled!");
|
||||
}
|
||||
public boolean isWebServerEnabled() {
|
||||
return webServer != null && webServer.isRunning();
|
||||
}
|
||||
|
||||
public boolean isRemoteManagementEnabled() {
|
||||
return krm!=null;
|
||||
public KLALBWebServer getWebServer() {
|
||||
return webServer;
|
||||
}
|
||||
|
||||
public void disableRemoteManagement() {
|
||||
if(krm!=null) {
|
||||
krm.close();
|
||||
krm=null;
|
||||
public void disableWebServer() {
|
||||
if (webServer != null) {
|
||||
webServer.stop();
|
||||
webServer = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void loadConfigJson(File jsonFile) throws IOException {
|
||||
this.jsonFile=jsonFile;
|
||||
if(jsonFile.exists()) {
|
||||
FileReader fr = null;
|
||||
try {
|
||||
fr=new FileReader(jsonFile);
|
||||
fr = new FileReader(jsonFile);
|
||||
loadConfigJson(fr);
|
||||
}finally {
|
||||
if(fr!=null)
|
||||
} finally {
|
||||
if (fr != null)
|
||||
fr.close();
|
||||
}
|
||||
}else{
|
||||
loadConfig(KLALBConfig.getDefault());
|
||||
}
|
||||
|
||||
}
|
||||
public void loadConfigJson(String json) {
|
||||
@@ -128,9 +169,9 @@ public class KLALBProxySystem {
|
||||
|
||||
String vsne=kcci.getVirtualSocketName();
|
||||
//if(vsne!=null) {
|
||||
MultipurposeSocketAddress.getSocketTypeRegister().put(vsne, klalbController.getStreamSocketType());
|
||||
MultiProtocolSocketAddress.getSocketTypeRegister().put(vsne, klalbController.getStreamSocketType());
|
||||
//}
|
||||
MultipurposeSocketAddress tcple=kcci.getTCPListen();
|
||||
MultiProtocolSocketAddress tcple=kcci.getTCPListen();
|
||||
if(tcple!=null) {
|
||||
|
||||
SocketChannelListener tcpl = null;
|
||||
@@ -148,13 +189,13 @@ public class KLALBProxySystem {
|
||||
}
|
||||
|
||||
});
|
||||
MultipurposeSocketAddress mpsa2=new MultipurposeSocketAddress(tcple.getType(), tcple.getHost(),((InetSocketAddress)tcpl.getServerSocketChannel().getLocalAddress()).getPort());
|
||||
MultiProtocolSocketAddress mpsa2=new MultiProtocolSocketAddress(tcple.getProtocol(), tcple.getHost(),((InetSocketAddress)tcpl.getServerSocketChannel().getLocalAddress()).getPort());
|
||||
klalbController.getListenSocketAddress().add(mpsa2);
|
||||
} catch (IOException e1) {
|
||||
e1.printStackTrace();
|
||||
}
|
||||
}
|
||||
MultipurposeSocketAddress udple=kcci.getUDPListen();
|
||||
MultiProtocolSocketAddress udple=kcci.getUDPListen();
|
||||
if(udple!=null) {
|
||||
DatagramSocketListener udpl = null;
|
||||
try {
|
||||
@@ -171,7 +212,7 @@ public class KLALBProxySystem {
|
||||
}
|
||||
|
||||
});
|
||||
MultipurposeSocketAddress mpsau2=new MultipurposeSocketAddress(udple.getType(), udple.getHost(),((InetSocketAddress)(udpl.getDatagramServerSocket().getLocalSocketAddress())).getPort());
|
||||
MultiProtocolSocketAddress mpsau2=new MultiProtocolSocketAddress(udple.getProtocol(), udple.getHost(),((InetSocketAddress)(udpl.getDatagramServerSocket().getLocalSocketAddress())).getPort());
|
||||
//klalbController.getListenSocketAddress().add(mpsau2);
|
||||
} catch (IOException e1) {
|
||||
e1.printStackTrace();
|
||||
@@ -191,30 +232,24 @@ public class KLALBProxySystem {
|
||||
}
|
||||
|
||||
|
||||
private KLALBStateGUI3 kgui;
|
||||
public KLALBStateGUI3 getKLALBGUI() {
|
||||
if(kgui==null) {
|
||||
kgui=new KLALBStateGUI3(klalbController);
|
||||
kgui.loadConfig(config);
|
||||
kgui.setSaveComsumer((cfg)->{
|
||||
String json=gson.toJson(cfg);
|
||||
if(jsonFile!=null) {
|
||||
FileWriter fw = null;
|
||||
try {
|
||||
fw=new FileWriter(jsonFile);
|
||||
public void saveConfigToFile() {
|
||||
if (jsonFile != null && config != null) {
|
||||
String json = gson.toJson(config);
|
||||
try (FileWriter fw = new FileWriter(jsonFile)) {
|
||||
fw.write(json);
|
||||
}catch(IOException e) {
|
||||
e.printStackTrace();
|
||||
}finally {
|
||||
if(fw!=null)
|
||||
try {
|
||||
fw.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private KLALBStateGUI3 kgui;
|
||||
public KLALBStateGUI3 getKLALBGUI() {
|
||||
if(kgui==null) {
|
||||
kgui=new KLALBStateGUI3(klalbController);
|
||||
kgui.loadConfig(config);
|
||||
kgui.setSaveComsumer((cfg)->{
|
||||
saveConfigToFile();
|
||||
});
|
||||
}
|
||||
return kgui;
|
||||
|
||||
@@ -2,6 +2,9 @@ package org.kne.cloud.network.klalb;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.StreamCorruptedException;
|
||||
import java.net.BindException;
|
||||
import java.net.NoRouteToHostException;
|
||||
import java.net.SocketException;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.nio.channels.UnresolvedAddressException;
|
||||
import java.security.SecureRandom;
|
||||
@@ -21,7 +24,7 @@ import org.kne.cloud.clock.HighAccuracyClock;
|
||||
import org.kne.cloud.clock.NTPTimestamps;
|
||||
import org.kne.cloud.clock.ReliabilityBackoffTimeClock;
|
||||
import org.kne.cloud.clock.WatchDogTimer;
|
||||
import org.kne.cloud.network.MultipurposeSocketAddress;
|
||||
import org.kne.cloud.network.MultiProtocolSocketAddress;
|
||||
import org.kne.cloud.network.ThreadTool;
|
||||
import org.kne.cloud.network.congestion.BBRCongestionAlgorithm;
|
||||
import org.kne.cloud.network.congestion.CongestionAlgorithm;
|
||||
@@ -100,8 +103,8 @@ public class KLALBRemoteLink extends AbstractControlledIPv6NetworkLink implement
|
||||
private LinkStatus status=new LinkStatus();
|
||||
private SpeedAndTrafficAndDelayMonitorDataImpl monitor;
|
||||
private volatile KLALBPacketLink kplink;
|
||||
private MultipurposeSocketAddress bindAddress;
|
||||
private MultipurposeSocketAddress socketAddress;
|
||||
private MultiProtocolSocketAddress bindAddress;
|
||||
private MultiProtocolSocketAddress socketAddress;
|
||||
|
||||
|
||||
public void setKplink(KLALBPacketLink kplink) {
|
||||
@@ -112,11 +115,11 @@ public class KLALBRemoteLink extends AbstractControlledIPv6NetworkLink implement
|
||||
return kplink;
|
||||
}
|
||||
|
||||
public MultipurposeSocketAddress getSocketAddress() {
|
||||
public MultiProtocolSocketAddress getSocketAddress() {
|
||||
return socketAddress;
|
||||
}
|
||||
|
||||
public MultipurposeSocketAddress getBindAddress() {
|
||||
public MultiProtocolSocketAddress getBindAddress() {
|
||||
return bindAddress;
|
||||
}
|
||||
|
||||
@@ -134,7 +137,7 @@ public class KLALBRemoteLink extends AbstractControlledIPv6NetworkLink implement
|
||||
return peerAddress;
|
||||
}
|
||||
|
||||
public KLALBRemoteLink(KLALBController controller, MultipurposeSocketAddress mpa, IPv6AddressGroup address) {
|
||||
public KLALBRemoteLink(KLALBController controller, MultiProtocolSocketAddress mpa, IPv6AddressGroup address) {
|
||||
this(controller,mpa, null,address);
|
||||
}
|
||||
|
||||
@@ -147,7 +150,7 @@ public class KLALBRemoteLink extends AbstractControlledIPv6NetworkLink implement
|
||||
name=kpl.toString();
|
||||
}
|
||||
|
||||
private KLALBRemoteLink(KLALBController controller,MultipurposeSocketAddress mpa, MultipurposeSocketAddress bindaddr, IPv6AddressGroup address) {
|
||||
private KLALBRemoteLink(KLALBController controller, MultiProtocolSocketAddress mpa, MultiProtocolSocketAddress bindaddr, IPv6AddressGroup address) {
|
||||
this.klalbController=controller;
|
||||
sysclk = klalbController.getClock();
|
||||
this.socketAddress = mpa;
|
||||
@@ -162,11 +165,11 @@ public class KLALBRemoteLink extends AbstractControlledIPv6NetworkLink implement
|
||||
}
|
||||
|
||||
|
||||
public KLALBRemoteLink(KLALBController controller,MultipurposeSocketAddress mpa, MultipurposeSocketAddress bind) {
|
||||
public KLALBRemoteLink(KLALBController controller, MultiProtocolSocketAddress mpa, MultiProtocolSocketAddress bind) {
|
||||
this(controller,mpa, bind, null);
|
||||
}
|
||||
|
||||
public KLALBRemoteLink(KLALBController controller,MultipurposeSocketAddress mpa) {
|
||||
public KLALBRemoteLink(KLALBController controller, MultiProtocolSocketAddress mpa) {
|
||||
this(controller,mpa, (IPv6AddressGroup) null);
|
||||
}
|
||||
|
||||
@@ -283,7 +286,7 @@ public class KLALBRemoteLink extends AbstractControlledIPv6NetworkLink implement
|
||||
sendLock.unlock();
|
||||
}
|
||||
}
|
||||
tlock.parkNanos(1000000L);
|
||||
tlock.parkNanos(10000000L);
|
||||
|
||||
}
|
||||
} catch (StreamCorruptedException sce) {
|
||||
@@ -464,7 +467,9 @@ public class KLALBRemoteLink extends AbstractControlledIPv6NetworkLink implement
|
||||
break;
|
||||
case KLALBPacket.VADDR:
|
||||
IPv6AddressGroup vdr = ((VADDRPacket) kpp).getVaddr();
|
||||
|
||||
if(vdr.getAddress().equals(klalbController.getSelf().getAddress())){
|
||||
close();
|
||||
}else {
|
||||
remoteVaddr = vdr;
|
||||
sendPacket(new VADDRACKPacket());
|
||||
if (fst.compareAndSet(true, false)) {
|
||||
@@ -473,7 +478,7 @@ public class KLALBRemoteLink extends AbstractControlledIPv6NetworkLink implement
|
||||
KLALBRemoteLink.this.onOnlineStateUpdate();
|
||||
}
|
||||
KLALBRemoteLink.this.onLocatorUpdate();
|
||||
|
||||
}
|
||||
break;
|
||||
case KLALBPacket.VADDRACK:
|
||||
break;
|
||||
@@ -567,7 +572,9 @@ public class KLALBRemoteLink extends AbstractControlledIPv6NetworkLink implement
|
||||
public void run() {
|
||||
while (!closed) {
|
||||
status.updateReliability();
|
||||
if(status.getState()!=LinkStatus.DOWN) {
|
||||
monitor.update();
|
||||
}
|
||||
try {
|
||||
Thread.sleep(1);
|
||||
} catch (InterruptedException e) {
|
||||
@@ -672,7 +679,23 @@ public class KLALBRemoteLink extends AbstractControlledIPv6NetworkLink implement
|
||||
}
|
||||
} catch (StreamCorruptedException sce) {
|
||||
sce.printStackTrace();
|
||||
} catch (IOException | UnresolvedAddressException e) {
|
||||
}catch(BindException e) {
|
||||
close();
|
||||
if (debug)
|
||||
e.printStackTrace();
|
||||
}catch(NoRouteToHostException e){
|
||||
close();
|
||||
if (debug)
|
||||
e.printStackTrace();
|
||||
} catch(SocketException e) {
|
||||
String massage = e.getMessage();
|
||||
if (massage.toLowerCase().contains("unreachable")) {
|
||||
close();
|
||||
} else {
|
||||
if (debug)
|
||||
e.printStackTrace();
|
||||
}
|
||||
}catch (IOException | UnresolvedAddressException e) {
|
||||
if (debug)
|
||||
e.printStackTrace();
|
||||
} catch (Exception e) {
|
||||
@@ -927,23 +950,25 @@ public class KLALBRemoteLink extends AbstractControlledIPv6NetworkLink implement
|
||||
public List<RouteItem> getRouteItems() {
|
||||
List<RouteItem> rlist = new ArrayList<>();
|
||||
|
||||
if(isUp()) {
|
||||
IPv6AddressGroup adg = addressGroup;
|
||||
/*if (adg != null)
|
||||
rlist.add(new RouteItem(new IPv6AddressGroup(adg.getAddress(), 128), adg.getAddress(), this, "Direct", 0, 0,
|
||||
null, "D", true));*/
|
||||
|
||||
for (Iterator<Neighbor> iteratorx = getNeighborsInfo().iterator(); iteratorx.hasNext();) {
|
||||
for (Iterator<Neighbor> iteratorx = getNeighborsInfo().iterator(); iteratorx.hasNext(); ) {
|
||||
Neighbor addresses = (Neighbor) iteratorx.next();
|
||||
RouteItem ri = new RouteItem(new IPv6AddressGroup(addresses.getAddress().getAddress(), 128),
|
||||
addresses.getAddress().getAddress(), this, "Direct", 0, 128,CostSupplierFactory.expectedDelaySupplier((DelayMonitorData) addresses.getMonitor(),status,algorithm::getRTO), "D", false);
|
||||
addresses.getAddress().getAddress(), this, "Direct", 0, 128, CostSupplierFactory.expectedDelaySupplier((DelayMonitorData) addresses.getMonitor(), status, algorithm::getRTO), "D", false);
|
||||
rlist.add(ri);
|
||||
|
||||
if (addresses.getLocator() != null) {
|
||||
RouteItem ris = new RouteItem(addresses.getLocator(), addresses.getLocator().getAddress(), this,
|
||||
"KLALB SRv6", 13, 128,CostSupplierFactory.expectedDelaySupplier((DelayMonitorData) addresses.getMonitor(),status,algorithm::getRTO), "D", false);
|
||||
"KLALB SRv6", 13, 128, CostSupplierFactory.expectedDelaySupplier((DelayMonitorData) addresses.getMonitor(), status, algorithm::getRTO), "D", false);
|
||||
rlist.add(ris);
|
||||
}
|
||||
}
|
||||
}
|
||||
return rlist;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,174 +0,0 @@
|
||||
package org.kne.cloud.network.klalb;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import org.kne.cloud.network.MultipurposeSocketAddress;
|
||||
import org.kne.cloud.network.SocketListener;
|
||||
import org.kne.cloud.network.ipv6.IPv6NetworkLink;
|
||||
import org.kne.cloud.network.monitor.LinkStatus;
|
||||
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
import com.google.gson.JsonPrimitive;
|
||||
|
||||
public class KLALBRemoteManagement {
|
||||
SocketListener slr;
|
||||
private KLALBProxySystem klalbProxySystem;
|
||||
public KLALBRemoteManagement(KLALBProxySystem klalbProxySystem) throws IOException {
|
||||
this(klalbProxySystem,new MultipurposeSocketAddress("127.9.9.9", 49573));
|
||||
}
|
||||
public KLALBRemoteManagement(KLALBProxySystem klalbProxySystem,MultipurposeSocketAddress listen) throws IOException {
|
||||
this.klalbProxySystem=klalbProxySystem;
|
||||
slr=new SocketListener(listen);
|
||||
slr.setCon((srcv)->{
|
||||
try {
|
||||
srcv.setSoTimeout(10000);
|
||||
byte[]input= srcv.getInputStream().readAllBytes();
|
||||
srcv.shutdownInput();
|
||||
String req=new String(input,Charset.forName("UTF-8"));
|
||||
System.out.println("远程管理请求:"+req);
|
||||
String rsp=processSignal(req);
|
||||
System.out.println("远程管理响应:"+rsp);
|
||||
srcv.getOutputStream().write(rsp.getBytes(Charset.forName("UTF-8")));
|
||||
srcv.shutdownOutput();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}finally {
|
||||
try {
|
||||
srcv.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
public KLALBProxySystem getKlalbProxySystem() {
|
||||
return klalbProxySystem;
|
||||
}
|
||||
private String processSignal(String req) {
|
||||
JsonObject jreq= (JsonObject) new JsonParser().parse(req);
|
||||
JsonObject jrsp=new JsonObject();
|
||||
|
||||
|
||||
String reqt=jreq.getAsJsonPrimitive("REQ").getAsString();
|
||||
jrsp.addProperty("RSP", reqt);
|
||||
switch (reqt) {
|
||||
case "GETLINES":
|
||||
JsonArray lines=new JsonArray();
|
||||
List<IPv6NetworkLink>lineslist= klalbProxySystem.getKlalbController().getLines();
|
||||
synchronized (lineslist) {
|
||||
for (Iterator<IPv6NetworkLink> iterator = lineslist.iterator(); iterator.hasNext();) {
|
||||
IPv6NetworkLink link=iterator.next();
|
||||
if(!(link instanceof KLALBRemoteLink)) {
|
||||
continue;
|
||||
}
|
||||
KLALBRemoteLink klalbRemoteLine = (KLALBRemoteLink) link;
|
||||
if(klalbRemoteLine.getSocketAddress()==null)
|
||||
continue;
|
||||
JsonObject jklbrl=new JsonObject();
|
||||
jklbrl.addProperty("ipport", klalbRemoteLine.getSocketAddress().toString());
|
||||
jklbrl.addProperty("state",LinkStatus.stateToString( klalbRemoteLine.getState()));
|
||||
|
||||
jklbrl.addProperty("Vaddr", klalbRemoteLine.getRemoteVaddr().getAddress().toString());
|
||||
|
||||
jklbrl.addProperty("uploadspeed", klalbRemoteLine.getMonitor().getOutSpeed());
|
||||
jklbrl.addProperty("downloadspeed", klalbRemoteLine.getMonitor().getInSpeed());
|
||||
|
||||
jklbrl.addProperty("uploadtraffic", klalbRemoteLine.getMonitor().getOutTraffic());
|
||||
jklbrl.addProperty("downloadtraffic", klalbRemoteLine.getMonitor().getInTraffic());
|
||||
|
||||
jklbrl.addProperty("uploaddelay",klalbRemoteLine.getMonitor().getOutDelay() );
|
||||
jklbrl.addProperty("downloaddelay", klalbRemoteLine.getMonitor().getInDelay());
|
||||
|
||||
jklbrl.addProperty("uploaddelaymin",klalbRemoteLine.getMonitor().getOutDelayMin() );
|
||||
jklbrl.addProperty("downloaddelaymin", klalbRemoteLine.getMonitor().getInDelayMin());
|
||||
lines.add(jklbrl);
|
||||
}
|
||||
}
|
||||
jrsp.add("table", lines);
|
||||
break;
|
||||
case "ADDLINE":
|
||||
String mip=jreq.getAsJsonPrimitive("ipport").getAsString();
|
||||
try {
|
||||
|
||||
jrsp.addProperty ("Vaddr",klalbProxySystem.getKlalbController().getRemoteVaddrBySocketAddress(new MultipurposeSocketAddress(mip)).getHostAddress());
|
||||
} catch (SocketTimeoutException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
break;
|
||||
case "GETSELFLINES":
|
||||
JsonArray lines2=new JsonArray();
|
||||
List<MultipurposeSocketAddress>selflineslist=klalbProxySystem.getKlalbController().getSelflineTable();
|
||||
synchronized (selflineslist) {
|
||||
for (Iterator<MultipurposeSocketAddress> iterator = selflineslist.iterator(); iterator.hasNext();) {
|
||||
MultipurposeSocketAddress multipurposeSocketAddress = (MultipurposeSocketAddress) iterator.next();
|
||||
lines2.add(new JsonPrimitive(multipurposeSocketAddress.toString()));
|
||||
}
|
||||
}
|
||||
jrsp.add("table", lines2);
|
||||
|
||||
break;
|
||||
case "ADDSELFLINE":
|
||||
String mips=jreq.getAsJsonPrimitive("ipport").getAsString();
|
||||
List<MultipurposeSocketAddress>selflineslist2=klalbProxySystem.getKlalbController().getSelflineTable();
|
||||
synchronized (selflineslist2) {
|
||||
selflineslist2.add(new MultipurposeSocketAddress(mips));
|
||||
}
|
||||
break;
|
||||
case "REMOVESELFLINE":
|
||||
String mipsr=jreq.getAsJsonPrimitive("ipport").getAsString();
|
||||
List<MultipurposeSocketAddress>selflineslist21=klalbProxySystem.getKlalbController().getSelflineTable();
|
||||
synchronized (selflineslist21) {
|
||||
selflineslist21.add(new MultipurposeSocketAddress(mipsr));
|
||||
}
|
||||
break;
|
||||
case "GETLINKMONITOR":
|
||||
jrsp.addProperty("uploadspeed", klalbProxySystem.getKlalbController().getLinkMonitor().getOutSpeed());
|
||||
jrsp.addProperty("downloadspeed", klalbProxySystem.getKlalbController().getLinkMonitor().getInSpeed());
|
||||
|
||||
jrsp.addProperty("uploadtraffic", klalbProxySystem.getKlalbController().getLinkMonitor().getOutTraffic());
|
||||
jrsp.addProperty("downloadtraffic", klalbProxySystem.getKlalbController().getLinkMonitor().getInTraffic());
|
||||
|
||||
break;
|
||||
case "OPENMONITORUI":
|
||||
klalbProxySystem.getKLALBGUI().setVisible(true);
|
||||
break;
|
||||
/*case "GETSOCKETBRIDGE":
|
||||
JsonArray bridges=new JsonArray();
|
||||
Set<Proxy> pxy=klalbProxySystem.getProxys();
|
||||
synchronized (pxy) {
|
||||
for (Iterator<Proxy> iterator = pxy.iterator(); iterator.hasNext();) {
|
||||
Proxy proxy = (Proxy) iterator.next();
|
||||
bridges.add(klalbProxySystem.createJsonObjectByProxy(proxy));
|
||||
}
|
||||
}
|
||||
jrsp.add("table", bridges);
|
||||
break;
|
||||
case "ADDSOCKETBRIDGE":
|
||||
JsonObject jpxy= jreq.getAsJsonObject("socketbridge");
|
||||
Set<Proxy> pxy2=klalbProxySystem.getProxys();
|
||||
synchronized (pxy2) {
|
||||
try {
|
||||
pxy2.add(klalbProxySystem.createProxyByJson(jpxy));
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
break;*/
|
||||
default:
|
||||
System.out.println("未知请求类型:"+reqt);
|
||||
break;
|
||||
}
|
||||
|
||||
return jrsp.toString();
|
||||
}
|
||||
public void close() {
|
||||
slr.close();
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,7 @@ import java.util.regex.Pattern;
|
||||
|
||||
import org.jctools.counters.CountersFactory;
|
||||
import org.jctools.counters.FixedSizeStripedLongCounter;
|
||||
import org.kne.cloud.network.MultipurposeSocketAddress;
|
||||
import org.kne.cloud.network.MultiProtocolSocketAddress;
|
||||
import org.kne.cloud.network.ipv6.IPv6Address;
|
||||
|
||||
public class KLALBUtils {
|
||||
@@ -51,6 +51,27 @@ public class KLALBUtils {
|
||||
v[3] = 0x01;
|
||||
return IPv6Address.valueOf(v);
|
||||
}
|
||||
|
||||
public static String parseCidr(String cidr) {
|
||||
int idx = cidr.indexOf('/');
|
||||
if (idx <= 0 || idx == cidr.length() - 1)
|
||||
throw new IllegalArgumentException("not a cidr: " + cidr);
|
||||
int prefix;
|
||||
try {
|
||||
prefix = Integer.parseInt(cidr.substring(idx + 1));
|
||||
} catch (NumberFormatException e) {
|
||||
throw new IllegalArgumentException("bad prefix: " + cidr);
|
||||
}
|
||||
try {
|
||||
InetAddress addr = InetAddress.getByName(cidr.substring(0, idx));
|
||||
int max = (addr instanceof Inet6Address) ? 128 : 32;
|
||||
if (prefix < 0 || prefix > max)
|
||||
throw new IllegalArgumentException("prefix out of range: " + cidr);
|
||||
return addr.getHostAddress() + "/" + prefix;
|
||||
} catch (UnknownHostException e) {
|
||||
throw new IllegalArgumentException("bad address: " + cidr);
|
||||
}
|
||||
}
|
||||
public static void main(String[] args) {
|
||||
System.out.println(uuidToIP(new UUID(-1,-1)));
|
||||
}
|
||||
@@ -208,7 +229,7 @@ public class KLALBUtils {
|
||||
return col;
|
||||
}
|
||||
|
||||
public static KLALBPacketLink createKLALBPacketLink(MultipurposeSocketAddress bindAddress, MultipurposeSocketAddress targetAddress,boolean buffered)
|
||||
public static KLALBPacketLink createKLALBPacketLink(MultiProtocolSocketAddress bindAddress, MultiProtocolSocketAddress targetAddress, boolean buffered)
|
||||
throws IOException {
|
||||
if (targetAddress.isStream()) {
|
||||
if (targetAddress.supportNIO()) {
|
||||
@@ -235,8 +256,8 @@ public class KLALBUtils {
|
||||
}
|
||||
}
|
||||
}
|
||||
public static KLALBPacketLink createKLALBPacketLink(MultipurposeSocketAddress bindAddress,
|
||||
MultipurposeSocketAddress targetAddress,boolean buffered, int timeout) throws UnknownHostException, IOException {
|
||||
public static KLALBPacketLink createKLALBPacketLink(MultiProtocolSocketAddress bindAddress,
|
||||
MultiProtocolSocketAddress targetAddress, boolean buffered, int timeout) throws UnknownHostException, IOException {
|
||||
if (targetAddress.isStream()) {
|
||||
if (targetAddress.supportNIO()) {
|
||||
if (bindAddress != null) {
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
package org.kne.cloud.network.klalb;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.NetworkInterface;
|
||||
import java.net.SocketException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Enumeration;
|
||||
import java.util.List;
|
||||
|
||||
public class NetworkInterfaceManager {
|
||||
private List<NetworkInterface> networkInterfaceExcept = new ArrayList<>();
|
||||
private List<InetAddress> inetAddressesExcept = new ArrayList<>();
|
||||
|
||||
public List<NetworkInterface> getNetworkInterfaceExcept() {
|
||||
return networkInterfaceExcept;
|
||||
}
|
||||
|
||||
|
||||
public List<InetAddress> getInetAddressesExcept() {
|
||||
return inetAddressesExcept;
|
||||
}
|
||||
|
||||
public List<NetworkInterface> getAllAvaliableNetworkInterface() throws SocketException {
|
||||
List<NetworkInterface> interfaces = new ArrayList<NetworkInterface>();
|
||||
Enumeration<NetworkInterface> eu = NetworkInterface.getNetworkInterfaces();
|
||||
while (eu.hasMoreElements()) {
|
||||
NetworkInterface networkInterface = (NetworkInterface) eu.nextElement();
|
||||
|
||||
if (networkInterface.getDisplayName()
|
||||
.startsWith(CONST.KLALB_DECENTRALIZED_S_RV6_NETWORK)) {
|
||||
continue;
|
||||
}
|
||||
if (networkInterfaceExcept.contains(networkInterface)) {
|
||||
continue;
|
||||
}
|
||||
if (networkInterface.isUp()) {
|
||||
interfaces.add(networkInterface);
|
||||
}
|
||||
}
|
||||
return interfaces;
|
||||
}
|
||||
|
||||
public List<InetAddress> getAllNetworkInterfaceAddress() throws SocketException {
|
||||
List<NetworkInterface> interfaces=getAllAvaliableNetworkInterface();
|
||||
List<InetAddress> addresses = new ArrayList<InetAddress>();
|
||||
for (NetworkInterface nif:interfaces){
|
||||
addresses.addAll( getNetworkInterfaceAddress(nif));
|
||||
}
|
||||
return addresses;
|
||||
}
|
||||
|
||||
public List<InetAddress> getNetworkInterfaceAddress(NetworkInterface networkInterface) throws SocketException {
|
||||
List<InetAddress> addresses = new ArrayList<InetAddress>();
|
||||
// System.out.println(networkInterface+" "+networkInterface.isUp());
|
||||
Enumeration<InetAddress> ei = networkInterface.getInetAddresses();
|
||||
while (ei.hasMoreElements()) {
|
||||
InetAddress inetAddress = (InetAddress) ei.nextElement();
|
||||
|
||||
if (inetAddress.isLoopbackAddress()){
|
||||
continue;
|
||||
}
|
||||
if (inetAddressesExcept.contains( inetAddress)) {
|
||||
continue;
|
||||
}
|
||||
addresses.add(inetAddress);
|
||||
}
|
||||
|
||||
return addresses;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,211 @@
|
||||
package org.kne.cloud.network.klalb;
|
||||
|
||||
import java.net.SocketAddress;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import org.kne.cloud.network.MultiProtocolSocketAddress;
|
||||
import org.kne.cloud.network.srv6.ExternalEndpointsResult;
|
||||
import org.kne.cloud.network.srv6.ExtraRoutesResult;
|
||||
import org.kne.cloud.network.srv6.KLALBRoutingProtocolAPIClient;
|
||||
import org.kne.cloud.network.srv6.NodeInfoQueryStatus;
|
||||
import org.kne.cloud.network.srv6.NodeProfile;
|
||||
|
||||
/** Coordinates the three independent sections of a remote node-info query. */
|
||||
public final class NodeInfoQueryCoordinator {
|
||||
private static final long DEADLINE_MILLIS = 3000L;
|
||||
private static final ExecutorService REQUEST_EXECUTOR = Executors.newVirtualThreadPerTaskExecutor();
|
||||
private static final ScheduledExecutorService DEADLINE_EXECUTOR = Executors
|
||||
.newSingleThreadScheduledExecutor(r -> {
|
||||
Thread thread = new Thread(r, "KLALB-node-info-deadline");
|
||||
thread.setDaemon(true);
|
||||
return thread;
|
||||
});
|
||||
|
||||
private final KLALBRoutingProtocolAPIClient client;
|
||||
|
||||
public NodeInfoQueryCoordinator(KLALBRoutingProtocolAPIClient client) {
|
||||
this.client = Objects.requireNonNull(client, "client");
|
||||
}
|
||||
|
||||
/** Starts all section requests and completes at the first all-sections response or the shared deadline. */
|
||||
public CompletableFuture<Result> query(SocketAddress address) {
|
||||
Objects.requireNonNull(address, "address");
|
||||
Pending pending = new Pending();
|
||||
pending.result.whenComplete((ignored, error) -> {
|
||||
if (pending.result.isCancelled()) pending.cancel();
|
||||
});
|
||||
pending.deadline = DEADLINE_EXECUTOR.schedule(pending::timeout, DEADLINE_MILLIS, TimeUnit.MILLISECONDS);
|
||||
|
||||
issue(pending, () -> client.requestNodeProfile(address, pending::profile), pending::profileFailed);
|
||||
issue(pending, () -> client.requestExternalEndpoints(address, pending::externalEndpoints),
|
||||
pending::externalEndpointsFailed);
|
||||
issue(pending, () -> client.requestExtraRoutes(address, pending::extraRoutes), pending::extraRoutesFailed);
|
||||
return pending.result;
|
||||
}
|
||||
|
||||
private void issue(Pending pending, ThrowingRequest request, Runnable failure) {
|
||||
REQUEST_EXECUTOR.execute(() -> pending.issue(request, failure));
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
private interface ThrowingRequest {
|
||||
void run() throws Exception;
|
||||
}
|
||||
|
||||
public static final class Section<T> {
|
||||
private final boolean received;
|
||||
private final Optional<T> value;
|
||||
private final Optional<NodeInfoQueryStatus> status;
|
||||
|
||||
private Section(boolean received, T value, NodeInfoQueryStatus status) {
|
||||
this.received = received;
|
||||
this.value = Optional.ofNullable(value);
|
||||
this.status = Optional.ofNullable(status);
|
||||
}
|
||||
|
||||
public boolean isReceived() {
|
||||
return received;
|
||||
}
|
||||
|
||||
public Optional<T> getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public Optional<NodeInfoQueryStatus> getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
private static <T> Section<T> missing() {
|
||||
return new Section<T>(false, null, null);
|
||||
}
|
||||
|
||||
private static <T> Section<T> received(T value, NodeInfoQueryStatus status) {
|
||||
return new Section<T>(true, value, status);
|
||||
}
|
||||
}
|
||||
|
||||
public static final class Result {
|
||||
private final Section<NodeProfile> profile;
|
||||
private final Section<List<MultiProtocolSocketAddress>> externalEndpoints;
|
||||
private final Section<List<String>> extraRoutes;
|
||||
|
||||
private Result(Section<NodeProfile> profile,
|
||||
Section<List<MultiProtocolSocketAddress>> externalEndpoints,
|
||||
Section<List<String>> extraRoutes) {
|
||||
this.profile = profile;
|
||||
this.externalEndpoints = externalEndpoints;
|
||||
this.extraRoutes = extraRoutes;
|
||||
}
|
||||
|
||||
public Section<NodeProfile> getProfile() {
|
||||
return profile;
|
||||
}
|
||||
|
||||
public Section<List<MultiProtocolSocketAddress>> getExternalEndpoints() {
|
||||
return externalEndpoints;
|
||||
}
|
||||
|
||||
public Section<List<String>> getExtraRoutes() {
|
||||
return extraRoutes;
|
||||
}
|
||||
}
|
||||
|
||||
private final class Pending {
|
||||
private final CompletableFuture<Result> result = new CompletableFuture<>();
|
||||
private final AtomicBoolean finished = new AtomicBoolean();
|
||||
private int remaining = 3;
|
||||
private Section<NodeProfile> profile = Section.missing();
|
||||
private Section<List<MultiProtocolSocketAddress>> externalEndpoints = Section.missing();
|
||||
private Section<List<String>> extraRoutes = Section.missing();
|
||||
private java.util.concurrent.ScheduledFuture<?> deadline;
|
||||
|
||||
private synchronized void profile(NodeProfile value) {
|
||||
if (profile.isReceived()) return;
|
||||
profile = Section.received(value, NodeInfoQueryStatus.OK);
|
||||
completeSection();
|
||||
}
|
||||
|
||||
private synchronized void profileFailed() {
|
||||
if (profile.isReceived()) return;
|
||||
profile = Section.missing();
|
||||
completeSection();
|
||||
}
|
||||
|
||||
private synchronized void externalEndpoints(ExternalEndpointsResult value) {
|
||||
if (externalEndpoints.isReceived()) return;
|
||||
if (value == null) {
|
||||
externalEndpoints = Section.missing();
|
||||
} else {
|
||||
List<MultiProtocolSocketAddress> endpoints = value.getExternalEndpoints();
|
||||
externalEndpoints = Section.received(endpoints == null
|
||||
? Collections.<MultiProtocolSocketAddress>emptyList()
|
||||
: Collections.unmodifiableList(new ArrayList<MultiProtocolSocketAddress>(endpoints)),
|
||||
value.getStatus());
|
||||
}
|
||||
completeSection();
|
||||
}
|
||||
|
||||
private synchronized void externalEndpointsFailed() {
|
||||
if (externalEndpoints.isReceived()) return;
|
||||
externalEndpoints = Section.missing();
|
||||
completeSection();
|
||||
}
|
||||
|
||||
private synchronized void extraRoutes(ExtraRoutesResult value) {
|
||||
if (extraRoutes.isReceived()) return;
|
||||
if (value == null) {
|
||||
extraRoutes = Section.missing();
|
||||
} else {
|
||||
List<String> routes = value.getExtraRoutes();
|
||||
extraRoutes = Section.received(routes == null
|
||||
? Collections.<String>emptyList()
|
||||
: Collections.unmodifiableList(new ArrayList<String>(routes)), value.getStatus());
|
||||
}
|
||||
completeSection();
|
||||
}
|
||||
|
||||
private synchronized void extraRoutesFailed() {
|
||||
if (extraRoutes.isReceived()) return;
|
||||
extraRoutes = Section.missing();
|
||||
completeSection();
|
||||
}
|
||||
|
||||
private synchronized void issue(ThrowingRequest request, Runnable failure) {
|
||||
if (finished.get() || result.isDone() || result.isCancelled()) return;
|
||||
try {
|
||||
request.run();
|
||||
} catch (Exception e) {
|
||||
failure.run();
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void cancel() {
|
||||
finish();
|
||||
}
|
||||
|
||||
private void completeSection() {
|
||||
remaining--;
|
||||
if (remaining == 0) finish();
|
||||
}
|
||||
|
||||
private synchronized void timeout() {
|
||||
finish();
|
||||
}
|
||||
|
||||
private void finish() {
|
||||
if (!finished.compareAndSet(false, true)) return;
|
||||
if (deadline != null) deadline.cancel(false);
|
||||
result.complete(new Result(profile, externalEndpoints, extraRoutes));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package org.kne.cloud.network.klalb;
|
||||
|
||||
import org.kne.cloud.network.klalb.ui.UIEnv;
|
||||
|
||||
public enum PerformanceStrategy {
|
||||
SINGLE_CORE("singlecore"),
|
||||
MULTI_FILL("multifill"),
|
||||
MULTI_SCATTER("multiscatter");
|
||||
|
||||
private String description;
|
||||
|
||||
private PerformanceStrategy(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return description;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据 description 查找对应的枚举对象。
|
||||
*
|
||||
* @param description 描述文本(资源包key)
|
||||
* @return 对应的枚举对象
|
||||
*/
|
||||
public static PerformanceStrategy fromDescription(String description) {
|
||||
for (PerformanceStrategy strategy : values()) {
|
||||
if (strategy.description.equals(description)) {
|
||||
return strategy;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -1,40 +1,23 @@
|
||||
package org.kne.cloud.network.klalb;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Type;
|
||||
import java.nio.channels.MulticastChannel;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import org.kne.cloud.network.DefaultMinecraftSocketBridgeFactory;
|
||||
import org.kne.cloud.network.DefaultSocketBridgeFactory;
|
||||
import org.kne.cloud.network.HostPortMap;
|
||||
import org.kne.cloud.network.MultipurposeSocketAddress;
|
||||
import org.kne.cloud.network.MultiProtocolSocketAddress;
|
||||
import org.kne.cloud.network.Proxy;
|
||||
import org.kne.cloud.network.SocketBridgeFactory;
|
||||
import org.kne.cloud.network.SocketToSocketProxy;
|
||||
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonDeserializationContext;
|
||||
import com.google.gson.JsonDeserializer;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParseException;
|
||||
import com.google.gson.JsonPrimitive;
|
||||
import com.google.gson.JsonSerializationContext;
|
||||
import com.google.gson.JsonSerializer;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
|
||||
public class SocketBridgeConfigItem extends KLALBConfigItem {
|
||||
private MultipurposeSocketAddress Listen;
|
||||
private MultiProtocolSocketAddress Listen;
|
||||
|
||||
private LinkedHashMap<String, String>Bridge=new LinkedHashMap<>();
|
||||
private HostPortMap Connect=new HostPortMap();
|
||||
|
||||
public SocketBridgeConfigItem( MultipurposeSocketAddress listen,
|
||||
public SocketBridgeConfigItem( MultiProtocolSocketAddress listen,
|
||||
LinkedHashMap<String, String> bridge, HostPortMap connect) {
|
||||
super("SocketBridge");
|
||||
Listen = listen;
|
||||
@@ -85,12 +68,12 @@ public class SocketBridgeConfigItem extends KLALBConfigItem {
|
||||
public Proxy createProxy(KLALBController controller) throws IOException {
|
||||
HostPortMap mapp=new HostPortMap();
|
||||
LinkedHashMap<String, SocketBridgeFactory>mapb=new LinkedHashMap<>();
|
||||
MultipurposeSocketAddress l=Listen;
|
||||
MultiProtocolSocketAddress l=Listen;
|
||||
SocketBridgeFactory bdg=getDefaultBridgeFactory(Bridge,mapb,controller);
|
||||
MultipurposeSocketAddress r=getDefaultConnect(Connect,mapp);
|
||||
return new SocketToSocketProxy(l ,new MultipurposeSocketAddress("0.0.0.0:0"), r,mapp,bdg,mapb);
|
||||
MultiProtocolSocketAddress r=getDefaultConnect(Connect,mapp);
|
||||
return new SocketToSocketProxy(l ,new MultiProtocolSocketAddress("0.0.0.0:0"), r,mapp,bdg,mapb);
|
||||
}
|
||||
private MultipurposeSocketAddress getDefaultConnect(HostPortMap connect2, LinkedHashMap<String, MultipurposeSocketAddress> mapp) {
|
||||
private MultiProtocolSocketAddress getDefaultConnect(HostPortMap connect2, LinkedHashMap<String, MultiProtocolSocketAddress> mapp) {
|
||||
connect2.entrySet().forEach((en)->{
|
||||
mapp.put(en.getKey(), en.getValue());
|
||||
});
|
||||
|
||||
@@ -116,29 +116,20 @@ public class DatagramKLALBPacketLink implements KLALBPacketLink {
|
||||
}*/
|
||||
package org.kne.cloud.network.klalb;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.net.DatagramPacket;
|
||||
import java.net.DatagramSocket;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.SocketException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.Channels;
|
||||
import java.nio.channels.ReadableByteChannel;
|
||||
import java.nio.channels.WritableByteChannel;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
import org.kne.cloud.network.DatagramServerSocket;
|
||||
import org.kne.cloud.network.DatagramServerSocket.SubDatagramSocket;
|
||||
import org.kne.cloud.network.MultipurposeSocketAddress;
|
||||
import org.kne.cloud.network.MultiProtocolSocketAddress;
|
||||
import org.kne.cloud.network.NetworkPacket;
|
||||
import org.kne.cloud.network.PacketRebuilder;
|
||||
import org.kne.cloud.network.PacketSpliter;
|
||||
import org.kne.cloud.network.SpeedLimiter;
|
||||
import org.kne.debug.TimeDebugger;
|
||||
|
||||
public class SplitedDatagramKLALBPacketLink extends AbstractKLALBPacketLink implements KLALBPacketLink {
|
||||
private PacketSpliter pslr;
|
||||
@@ -146,7 +137,7 @@ public class SplitedDatagramKLALBPacketLink extends AbstractKLALBPacketLink impl
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new MultipurposeSocketAddress("UDP",(InetSocketAddress)ds.getLocalSocketAddress())+"←"+new MultipurposeSocketAddress("UDP",(InetSocketAddress)ds.getRemoteSocketAddress());
|
||||
return new MultiProtocolSocketAddress("UDP",(InetSocketAddress)ds.getLocalSocketAddress())+"←"+new MultiProtocolSocketAddress("UDP",(InetSocketAddress)ds.getRemoteSocketAddress());
|
||||
}
|
||||
|
||||
private DatagramSocket ds;
|
||||
|
||||
@@ -13,7 +13,7 @@ import java.nio.channels.SocketChannel;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
import org.kne.cloud.network.BufferedChannel;
|
||||
import org.kne.cloud.network.MultipurposeSocketAddress;
|
||||
import org.kne.cloud.network.MultiProtocolSocketAddress;
|
||||
import org.kne.cloud.network.NetworkPacket;
|
||||
import org.kne.cloud.network.ThreadTool;
|
||||
import org.kne.io.KNEChannels;
|
||||
@@ -45,7 +45,7 @@ public class StreamChannelKLALBPacketLink extends AbstractKLALBPacketLink implem
|
||||
@Override
|
||||
public String toString() {
|
||||
try {
|
||||
return new MultipurposeSocketAddress("TCP",(InetSocketAddress)connectSocket.getLocalAddress())+"←"+new MultipurposeSocketAddress("TCP",(InetSocketAddress)connectSocket.getRemoteAddress());
|
||||
return new MultiProtocolSocketAddress((InetSocketAddress)connectSocket.getLocalAddress())+"←"+new MultiProtocolSocketAddress((InetSocketAddress)connectSocket.getRemoteAddress());
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return "?←?";
|
||||
|
||||
@@ -1,21 +1,18 @@
|
||||
package org.kne.cloud.network.klalb;
|
||||
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketException;
|
||||
import java.nio.Buffer;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.kne.cloud.network.MultipurposeSocketAddress;
|
||||
import org.kne.cloud.network.MultiProtocolSocketAddress;
|
||||
|
||||
public class StreamKLALBPacketLink extends AbstractKLALBPacketLink implements KLALBPacketLink {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new MultipurposeSocketAddress("TCP",(InetSocketAddress)connectSocket.getLocalSocketAddress())+"←"+new MultipurposeSocketAddress("TCP",(InetSocketAddress)connectSocket.getRemoteSocketAddress());
|
||||
return new MultiProtocolSocketAddress((InetSocketAddress)connectSocket.getLocalSocketAddress())+"←"+new MultiProtocolSocketAddress((InetSocketAddress)connectSocket.getRemoteSocketAddress());
|
||||
}
|
||||
|
||||
private Socket connectSocket;
|
||||
|
||||
@@ -346,7 +346,16 @@ public class GraphPanel extends JPanel {
|
||||
g.setFont(UIEnv.getFont().deriveFont(10.0f));
|
||||
|
||||
//g.drawString(text, (int)(x+nodesize/2), (int)(y-nodesize/3));
|
||||
g.drawString(text, (int)(x-image.getWidth(null)/2-10), (int)(y+image.getHeight(null)/2+10));
|
||||
int imgw=(image!=null)?image.getWidth(null):(int)nodesize;
|
||||
int imgh=(image!=null)?image.getHeight(null):(int)nodesize;
|
||||
java.awt.FontMetrics fm=g.getFontMetrics();
|
||||
int lineHeight=fm.getHeight()+2;
|
||||
int textx=(int)(x-imgw/2-10);
|
||||
int texty=(int)(y+imgh/2+10)+fm.getAscent();
|
||||
String[] lines=text.split("\n");
|
||||
for (int i = 0; i < lines.length; i++) {
|
||||
g.drawString(lines[i], textx, texty+i*lineHeight);
|
||||
}
|
||||
}
|
||||
|
||||
public Vector2 getPositionVec2() {
|
||||
|
||||
@@ -7,6 +7,7 @@ import java.io.IOException;
|
||||
import java.net.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
import java.util.function.Consumer;
|
||||
@@ -20,7 +21,7 @@ import org.jfree.chart.plot.dial.*;
|
||||
import org.jfree.data.general.DefaultValueDataset;
|
||||
import org.kne.cloud.clock.HighAccuracyClock;
|
||||
import org.kne.cloud.klalb.uitool.*;
|
||||
import org.kne.cloud.network.MultipurposeSocketAddress;
|
||||
import org.kne.cloud.network.MultiProtocolSocketAddress;
|
||||
import org.kne.cloud.network.ipv6.IPv6Address;
|
||||
import org.kne.cloud.network.ipv6.IPv6NetworkLink;
|
||||
import org.kne.cloud.network.klalb.*;
|
||||
@@ -75,12 +76,18 @@ public class KLALBStateGUI3 extends XFrame {
|
||||
private JTextField addressField; // 地址显示框
|
||||
private JTextField addressFieldSet; // 地址设置框
|
||||
private JTextField asnFieldSet; // ASN设置框
|
||||
private JCheckBox enableTUN; // 虚拟网卡启用禁用
|
||||
private JTextField tunDeviceName; // 虚拟网卡名称设置框
|
||||
private JTextField webListenSet; // Web API 监听地址设置框
|
||||
private NetworkGraphPanel graph; // 网络图面板
|
||||
private JTextField textFieldLocate; // 定位地址输入框
|
||||
private JTextField asnField2; // ASN显示框
|
||||
private JTextArea dnsAreaSet; // DNS服务器设置区域
|
||||
private JComboBox comboPerformance; // 性能策略选择框
|
||||
private JTextArea extraRoutesSet; // 额外路由设置区域
|
||||
private JTextField deviceNameSet; // 设备名称设置框
|
||||
private JTextArea deviceDescriptionSet; // 设备描述设置区域
|
||||
private KLALBController kcontroller; // 关联的控制器
|
||||
private JComboBox<PerformanceStrategyItem> comboPerformance; // 性能策略选择框
|
||||
private JComboBox comboCongestion; // 拥塞控制算法选择框
|
||||
private JTextField tcpListeningSet; // TCP监听设置
|
||||
private JTextField udpListeningSet; // UDP监听设置
|
||||
@@ -92,6 +99,7 @@ public class KLALBStateGUI3 extends XFrame {
|
||||
private JTextArea ntpServerSet; // NTP服务器设置区域
|
||||
private JCheckBox denyBroadcast;
|
||||
private JCheckBox denyQuery;
|
||||
private JCheckBox webApiEnabled; // 启用 Web API 开关
|
||||
private ClosableTabbedPane tabbedPane; // 可关闭的标签页面板
|
||||
private JCheckBox nogui;
|
||||
|
||||
@@ -138,6 +146,7 @@ public class KLALBStateGUI3 extends XFrame {
|
||||
* 初始化UI界面
|
||||
*/
|
||||
private void initUI(KLALBController kc, String title) {
|
||||
kcontroller = kc;
|
||||
// 设置窗口基本属性
|
||||
setIconImage(UIEnv.getIcon());
|
||||
setTitleColor(UIEnv.getDefaultTitleColor());
|
||||
@@ -597,7 +606,11 @@ public class KLALBStateGUI3 extends XFrame {
|
||||
JButton btnNewButton = new JButton(UIEnv.getRsb().getString("addline"));
|
||||
btnNewButton.addActionListener(e -> {
|
||||
try {
|
||||
kc.addRemoteLines(new MultipurposeSocketAddress(textField.getText()));
|
||||
MultiProtocolSocketAddress mpsa= new MultiProtocolSocketAddress(textField.getText());
|
||||
Thread t=new Thread(()->{
|
||||
kc.addRemoteLines(mpsa);
|
||||
});
|
||||
t.start();
|
||||
} catch (RuntimeException ex) {
|
||||
JOptionPane.showMessageDialog(KLALBStateGUI3.this, "Input format error", "Error",
|
||||
JOptionPane.ERROR_MESSAGE);
|
||||
@@ -781,18 +794,42 @@ public class KLALBStateGUI3 extends XFrame {
|
||||
addressFieldSet = addr6.getTextField();
|
||||
settings.getView().add(addr6);
|
||||
|
||||
// 设备名称设置
|
||||
TextSettingItem deviceName = new TextSettingItem(UIEnv.getRsb().getString("devicename"),
|
||||
CONST.itemwidth, CONST.settingheight);
|
||||
deviceNameSet = deviceName.getTextField();
|
||||
settings.getView().add(deviceName);
|
||||
|
||||
// 设备描述设置
|
||||
TextAreaSettingItem deviceDescription = new TextAreaSettingItem(UIEnv.getRsb().getString("devicedescription"),
|
||||
CONST.itemwidth, CONST.settingheight * 5);
|
||||
deviceDescriptionSet = deviceDescription.getTextArea();
|
||||
settings.getView().add(deviceDescription);
|
||||
|
||||
// DNS服务器设置
|
||||
TextAreaSettingItem dns = new TextAreaSettingItem(UIEnv.getRsb().getString("dnsserver"),
|
||||
CONST.itemwidth, CONST.settingheight * 5);
|
||||
dnsAreaSet = dns.getTextArea();
|
||||
settings.getView().add(dns);
|
||||
|
||||
// 额外路由设置
|
||||
TextAreaSettingItem extraRoutes = new TextAreaSettingItem(UIEnv.getRsb().getString("extraroutes"),
|
||||
CONST.itemwidth, CONST.settingheight * 5);
|
||||
extraRoutesSet = extraRoutes.getTextArea();
|
||||
settings.getView().add(extraRoutes);
|
||||
|
||||
// ASN设置
|
||||
TextSettingItem asn = new TextSettingItem(UIEnv.getRsb().getString("asnumber"),
|
||||
CONST.itemwidth, CONST.settingheight);
|
||||
asnFieldSet = asn.getTextField();
|
||||
settings.getView().add(asn);
|
||||
|
||||
//虚拟网卡
|
||||
CheckBoxSettingItem enableTUNs = new CheckBoxSettingItem(UIEnv.getRsb().getString("enabletun"),
|
||||
CONST.itemwidth, CONST.settingheight);
|
||||
enableTUN=enableTUNs.getCheckBox();
|
||||
settings.getView().add(enableTUNs);
|
||||
|
||||
TextSettingItem tunName = new TextSettingItem(UIEnv.getRsb().getString("tunname"),
|
||||
CONST.itemwidth, CONST.settingheight);
|
||||
tunDeviceName = tunName.getTextField();
|
||||
@@ -878,6 +915,21 @@ public class KLALBStateGUI3 extends XFrame {
|
||||
denyBroadcast=denyBroadcastc.getCheckBox();
|
||||
settings.getView().add(denyBroadcastc);
|
||||
|
||||
// Web服务设置标题
|
||||
SettingItem wsi = new SettingItem(UIEnv.getRsb().getString("webapisettings"),
|
||||
UIEnv.getFont().deriveFont(20.0f).deriveFont(Font.BOLD), CONST.itemwidth, CONST.settingheight);
|
||||
settings.getView().add(wsi);
|
||||
|
||||
CheckBoxSettingItem webApic = new CheckBoxSettingItem(UIEnv.getRsb().getString("enablewebapi"),
|
||||
CONST.itemwidth, CONST.settingheight);
|
||||
webApiEnabled=webApic.getCheckBox();
|
||||
settings.getView().add(webApic);
|
||||
|
||||
TextSettingItem webListen = new TextSettingItem(UIEnv.getRsb().getString("weblistenaddr"),
|
||||
CONST.itemwidth, CONST.settingheight);
|
||||
webListenSet = webListen.getTextField();
|
||||
settings.getView().add(webListen);
|
||||
|
||||
//性能设置标题
|
||||
|
||||
SettingItem pshi = new SettingItem(UIEnv.getRsb().getString("performancesettings"),
|
||||
@@ -888,8 +940,10 @@ public class KLALBStateGUI3 extends XFrame {
|
||||
performanceStrategy = new ComboSettingItem(UIEnv.getRsb().getString("performancestrategy"),
|
||||
CONST.itemwidth, CONST.settingheight);
|
||||
comboPerformance = performanceStrategy.getComboBox();
|
||||
comboPerformance.addItem("BBR");
|
||||
comboPerformance.addItem("Vegas2");
|
||||
comboPerformance.addItem(new PerformanceStrategyItem(PerformanceStrategy.SINGLE_CORE));
|
||||
comboPerformance.addItem(new PerformanceStrategyItem(PerformanceStrategy.MULTI_FILL));
|
||||
comboPerformance.addItem(new PerformanceStrategyItem(PerformanceStrategy.MULTI_SCATTER));
|
||||
comboPerformance.setSelectedIndex(1);
|
||||
settings.getView().add(performanceStrategy);
|
||||
|
||||
|
||||
@@ -1004,18 +1058,45 @@ public class KLALBStateGUI3 extends XFrame {
|
||||
private void saveConfig() {
|
||||
if (config == null) {
|
||||
config = new KLALBConfig();
|
||||
config.add(new KLALBControllerConfigItem("KLALBController"));
|
||||
config.add(new KLALBControllerConfigItem());
|
||||
}
|
||||
|
||||
for (KLALBConfigItem item : config) {
|
||||
if (item instanceof KLALBControllerConfigItem) {
|
||||
KLALBControllerConfigItem kck = (KLALBControllerConfigItem) item;
|
||||
String oldDeviceName = kck.getDeviceName();
|
||||
String oldDeviceDescription = kck.getDeviceDescription();
|
||||
List<MultiProtocolSocketAddress> oldExternalEndpoints = kck.getExternalEndpoints() == null
|
||||
? null
|
||||
: new ArrayList<MultiProtocolSocketAddress>(kck.getExternalEndpoints());
|
||||
List<String> oldExtraRoutes = kck.getExtraRoutes() == null
|
||||
? null : new ArrayList<String>(kck.getExtraRoutes());
|
||||
String newDeviceName;
|
||||
String newDeviceDescription;
|
||||
List<String> newExtraRoutes;
|
||||
List<MultiProtocolSocketAddress> newExternalEndpoints;
|
||||
|
||||
// 保存语言设置
|
||||
kck.setLanguage(((Language) comboLang.getSelectedItem()).name());
|
||||
|
||||
kck.setNogui(nogui.isSelected());
|
||||
|
||||
// 保存设备名称
|
||||
String dnametext = deviceNameSet.getText().trim();
|
||||
if (dnametext.equals("")) {
|
||||
newDeviceName = null;
|
||||
} else {
|
||||
newDeviceName = dnametext;
|
||||
}
|
||||
// 保存设备描述
|
||||
String ddesctext = deviceDescriptionSet.getText().trim();
|
||||
if (ddesctext.equals("")) {
|
||||
newDeviceDescription = null;
|
||||
} else {
|
||||
newDeviceDescription = ddesctext;
|
||||
}
|
||||
|
||||
|
||||
// 保存IPv6地址
|
||||
String ttext = addressFieldSet.getText().trim();
|
||||
if (ttext.equals("")) {
|
||||
@@ -1052,6 +1133,24 @@ public class KLALBStateGUI3 extends XFrame {
|
||||
}
|
||||
kck.setDNS(iaddr);
|
||||
|
||||
// 保存额外路由列表
|
||||
String[] spltEr = extraRoutesSet.getText().split("\n");
|
||||
List<String> eroutes = new ArrayList<>();
|
||||
for (String str : spltEr) {
|
||||
str = str.trim();
|
||||
if (!str.isEmpty()) {
|
||||
try {
|
||||
eroutes.add(KLALBUtils.parseCidr(str));
|
||||
} catch (RuntimeException e) {
|
||||
e.printStackTrace();
|
||||
JOptionPane.showMessageDialog(this, UIEnv.getRsb().getString("invaildextraroutes"),
|
||||
UIEnv.getRsb().getString("warning"), JOptionPane.WARNING_MESSAGE);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
newExtraRoutes = eroutes;
|
||||
|
||||
// 保存ASN
|
||||
String asntext = asnFieldSet.getText();
|
||||
if (asntext.equals("")) {
|
||||
@@ -1067,20 +1166,38 @@ public class KLALBStateGUI3 extends XFrame {
|
||||
}
|
||||
}
|
||||
|
||||
String tunNameText=tunDeviceName.getText();
|
||||
if(tunNameText.equals("")) {
|
||||
kck.setEnableTUN( enableTUN.isSelected());
|
||||
|
||||
String tunNameText=tunDeviceName.getText().trim();
|
||||
if(tunNameText.equals("") || tunNameText.equalsIgnoreCase("null")) {
|
||||
kck.setTUNName(null);
|
||||
}else {
|
||||
kck.setTUNName(tunNameText);
|
||||
}
|
||||
|
||||
// 保存Web API设置
|
||||
kck.setWebUI(webApiEnabled.isSelected());
|
||||
String webListenText = webListenSet.getText().trim();
|
||||
if (webListenText.equals("")) {
|
||||
kck.setWebListen(new MultiProtocolSocketAddress("http", "0.0.0.0", 4665));
|
||||
} else {
|
||||
try {
|
||||
kck.setWebListen(new MultiProtocolSocketAddress(webListenText));
|
||||
} catch (RuntimeException e) {
|
||||
e.printStackTrace();
|
||||
JOptionPane.showMessageDialog(this, UIEnv.getRsb().getString("invaildweblistenaddr"),
|
||||
UIEnv.getRsb().getString("warning"), JOptionPane.WARNING_MESSAGE);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 保存TCP监听设置
|
||||
String tcptext = tcpListeningSet.getText();
|
||||
if (tcptext.equals("")) {
|
||||
kck.setTCPListen(null);
|
||||
} else {
|
||||
try {
|
||||
kck.setTCPListen(new MultipurposeSocketAddress(tcptext));
|
||||
kck.setTCPListen(new MultiProtocolSocketAddress(tcptext));
|
||||
} catch (RuntimeException e) {
|
||||
e.printStackTrace();
|
||||
JOptionPane.showMessageDialog(this, UIEnv.getRsb().getString("invaildtcplisten"),
|
||||
@@ -1095,7 +1212,7 @@ public class KLALBStateGUI3 extends XFrame {
|
||||
kck.setUDPListen(null);
|
||||
} else {
|
||||
try {
|
||||
kck.setUDPListen(new MultipurposeSocketAddress(udptext));
|
||||
kck.setUDPListen(new MultiProtocolSocketAddress(udptext,"udp"));
|
||||
} catch (RuntimeException e) {
|
||||
e.printStackTrace();
|
||||
JOptionPane.showMessageDialog(this, UIEnv.getRsb().getString("invaildudplisten"),
|
||||
@@ -1106,13 +1223,13 @@ public class KLALBStateGUI3 extends XFrame {
|
||||
|
||||
// 保存开放线路表
|
||||
String[] splt1 = openLineTabelSet.getText().split("\n");
|
||||
List<MultipurposeSocketAddress> iaddr1 = new ArrayList<>();
|
||||
List<MultiProtocolSocketAddress> iaddr1 = new ArrayList<>();
|
||||
boolean show1 = true;
|
||||
for (String str : splt1) {
|
||||
try {
|
||||
str = str.trim();
|
||||
if (!str.isEmpty())
|
||||
iaddr1.add(new MultipurposeSocketAddress(str));
|
||||
iaddr1.add(new MultiProtocolSocketAddress(str));
|
||||
} catch (RuntimeException e) {
|
||||
e.printStackTrace();
|
||||
if (show1) {
|
||||
@@ -1123,17 +1240,17 @@ public class KLALBStateGUI3 extends XFrame {
|
||||
}
|
||||
}
|
||||
}
|
||||
kck.setLineTable(iaddr1);
|
||||
newExternalEndpoints = iaddr1;
|
||||
|
||||
// 保存自动连接线路表
|
||||
String[] splt11 = connectLineTabelSet.getText().split("\n");
|
||||
List<MultipurposeSocketAddress> iaddr11 = new ArrayList<>();
|
||||
List<MultiProtocolSocketAddress> iaddr11 = new ArrayList<>();
|
||||
boolean show11 = true;
|
||||
for (String str : splt11) {
|
||||
try {
|
||||
str = str.trim();
|
||||
if (!str.isEmpty())
|
||||
iaddr11.add(new MultipurposeSocketAddress(str));
|
||||
iaddr11.add(new MultiProtocolSocketAddress(str));
|
||||
} catch (RuntimeException e) {
|
||||
e.printStackTrace();
|
||||
if (show11) {
|
||||
@@ -1148,13 +1265,13 @@ public class KLALBStateGUI3 extends XFrame {
|
||||
|
||||
// 保存NTP服务器表
|
||||
String[] splt111 = ntpServerSet.getText().split("\n");
|
||||
List<MultipurposeSocketAddress> iaddr111 = new ArrayList<>();
|
||||
List<MultiProtocolSocketAddress> iaddr111 = new ArrayList<>();
|
||||
boolean show111 = true;
|
||||
for (String str : splt111) {
|
||||
try {
|
||||
str = str.trim();
|
||||
if (!str.isEmpty())
|
||||
iaddr111.add(new MultipurposeSocketAddress(str));
|
||||
iaddr111.add(new MultiProtocolSocketAddress(str,"ntp"));
|
||||
} catch (RuntimeException e) {
|
||||
e.printStackTrace();
|
||||
if (show111) {
|
||||
@@ -1176,9 +1293,14 @@ public class KLALBStateGUI3 extends XFrame {
|
||||
|
||||
kck.setLinkConnectionsCount(linkConnectionsCount.getSlider().getValue());
|
||||
|
||||
kck.setDenyLineTableQuery(denyQuery.isSelected());
|
||||
kck.setDenyExternalEndpointQuery(denyQuery.isSelected());
|
||||
|
||||
kck.setDenyLineTableBroadcast(denyBroadcast.isSelected());
|
||||
kck.setDenyExternalEndpointBroadcast(denyBroadcast.isSelected());
|
||||
|
||||
PerformanceStrategyItem psi=((PerformanceStrategyItem)comboPerformance.getSelectedItem());
|
||||
if(psi!=null) {
|
||||
kck.setPerformanceStrategy(psi.getStrategy().toString());
|
||||
}
|
||||
|
||||
kck.setCongestionAlgorithm((String) congestions.getComboBox().getSelectedItem());
|
||||
|
||||
@@ -1191,6 +1313,22 @@ public class KLALBStateGUI3 extends XFrame {
|
||||
kck.setNagleDelayTime(nagleDelayTime.getSlider().getValue()*100000L);
|
||||
|
||||
kck.setLinkNagleDelayTime(linkNagleDelayTime.getSlider().getValue()*100000L);
|
||||
|
||||
boolean deviceNameChanged = !Objects.equals(oldDeviceName, newDeviceName);
|
||||
boolean deviceDescriptionChanged = !Objects.equals(oldDeviceDescription, newDeviceDescription);
|
||||
boolean externalEndpointsChanged = !Objects.equals(oldExternalEndpoints, newExternalEndpoints);
|
||||
boolean extraRoutesChanged = !Objects.equals(oldExtraRoutes, newExtraRoutes);
|
||||
if (deviceNameChanged || deviceDescriptionChanged || externalEndpointsChanged || extraRoutesChanged) {
|
||||
if (kcontroller != null) {
|
||||
kcontroller.publishNodeInfo(newDeviceName, newDeviceDescription, newExternalEndpoints,
|
||||
newExtraRoutes);
|
||||
} else {
|
||||
kck.setDeviceName(newDeviceName);
|
||||
kck.setDeviceDescription(newDeviceDescription);
|
||||
kck.setExternalEndpoints(newExternalEndpoints);
|
||||
kck.setExtraRoutes(newExtraRoutes);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1213,6 +1351,7 @@ public class KLALBStateGUI3 extends XFrame {
|
||||
tsk = new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (isVisible()) {
|
||||
Object[] kll = kc.getLines().toArray();
|
||||
|
||||
// 遍历所有线路,更新或添加TPanel2
|
||||
@@ -1242,7 +1381,7 @@ public class KLALBStateGUI3 extends XFrame {
|
||||
if (!found) {
|
||||
TPanel2 tp = new TPanel2(ent, kc);
|
||||
tp.setVisible(showoffline.isSelected() || (
|
||||
ent.getState()!= LinkStatus.DOWN));
|
||||
ent.getState() != LinkStatus.DOWN));
|
||||
ysp.getView().add(tp);
|
||||
}
|
||||
}
|
||||
@@ -1266,6 +1405,7 @@ public class KLALBStateGUI3 extends XFrame {
|
||||
|
||||
ysp.updateScrool();
|
||||
}
|
||||
}
|
||||
};
|
||||
t.scheduleAtFixedRate(tsk, 1000, 1000);
|
||||
|
||||
@@ -1290,6 +1430,7 @@ public class KLALBStateGUI3 extends XFrame {
|
||||
tsk5 = new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
if (isVisible()) {
|
||||
graph.loadNodes();
|
||||
for (int i = 0; i < 100; i++) {
|
||||
@@ -1298,9 +1439,10 @@ public class KLALBStateGUI3 extends XFrame {
|
||||
graph.repaint();
|
||||
graph.revalidate();
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
t.scheduleAtFixedRate(tsk5, 1000, 1000);
|
||||
t.scheduleAtFixedRate(tsk5, 0, 1000);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1440,6 +1582,14 @@ public class KLALBStateGUI3 extends XFrame {
|
||||
|
||||
nogui.setSelected( kck.isNogui());
|
||||
|
||||
// 加载设备名称
|
||||
String dname = kck.getDeviceName();
|
||||
deviceNameSet.setText(dname != null ? dname : "");
|
||||
|
||||
// 加载设备描述
|
||||
String ddesc = kck.getDeviceDescription();
|
||||
deviceDescriptionSet.setText(ddesc != null ? ddesc : "");
|
||||
|
||||
// 加载IPv6地址
|
||||
String vaddr = kck.getVirtualAddress();
|
||||
addressFieldSet.setText(vaddr != null ? vaddr : "");
|
||||
@@ -1448,31 +1598,45 @@ public class KLALBStateGUI3 extends XFrame {
|
||||
List<InetAddress> dns = kck.getDNS();
|
||||
dnsAreaSet.setText(listToStr(dns));
|
||||
|
||||
// 加载额外路由
|
||||
List<String> ers = kck.getExtraRoutes();
|
||||
extraRoutesSet.setText(listToStr2(ers));
|
||||
|
||||
// 加载ASN
|
||||
Long vasn = kck.getVirtualASN();
|
||||
asnFieldSet.setText(vasn != null ? vasn.toString() : "");
|
||||
|
||||
enableTUN.setSelected(kck.isEnableTUN());
|
||||
|
||||
|
||||
String tunname=kck.getTUNName();
|
||||
tunDeviceName.setText(tunname!=null?tunname:"");
|
||||
|
||||
|
||||
|
||||
// 加载Web API设置
|
||||
webApiEnabled.setSelected(kck.isWebUI());
|
||||
webListenSet.setText(kck.getWebListen() != null ? kck.getWebListen().toString()
|
||||
: "http://0.0.0.0:4665");
|
||||
|
||||
// 加载TCP监听
|
||||
MultipurposeSocketAddress mpat = kck.getTCPListen();
|
||||
MultiProtocolSocketAddress mpat = kck.getTCPListen();
|
||||
tcpListeningSet.setText(mpat != null ? mpat.toString() : "");
|
||||
|
||||
// 加载UDP监听
|
||||
MultipurposeSocketAddress mpau = kck.getUDPListen();
|
||||
MultiProtocolSocketAddress mpau = kck.getUDPListen();
|
||||
udpListeningSet.setText(mpau != null ? mpau.toString() : "");
|
||||
|
||||
// 加载开放线路表
|
||||
List<MultipurposeSocketAddress> linet = kck.getLineTable();
|
||||
List<MultiProtocolSocketAddress> linet = kck.getExternalEndpoints();
|
||||
openLineTabelSet.setText(listToStr2(linet));
|
||||
|
||||
// 加载自动连接线路表
|
||||
List<MultipurposeSocketAddress> clinet = kck.getConnectLineTable();
|
||||
List<MultiProtocolSocketAddress> clinet = kck.getConnectLineTable();
|
||||
connectLineTabelSet.setText(listToStr2(clinet));
|
||||
|
||||
// 加载NTP服务器表
|
||||
List<MultipurposeSocketAddress> ntps = kck.getNtpServerTable();
|
||||
List<MultiProtocolSocketAddress> ntps = kck.getNtpServerTable();
|
||||
ntpServerSet.setText(listToStr2(ntps));
|
||||
|
||||
// 加载网络接口排除列表
|
||||
@@ -1493,9 +1657,20 @@ public class KLALBStateGUI3 extends XFrame {
|
||||
}
|
||||
linkConnectionsCount.getSlider().setValue(conc);
|
||||
|
||||
denyQuery.setSelected( kck.isDenyLineTableQuery());
|
||||
denyQuery.setSelected( kck.isDenyExternalEndpointQuery());
|
||||
|
||||
denyBroadcast.setSelected( kck.isDenyLineTableBroadcast());
|
||||
denyBroadcast.setSelected( kck.isDenyExternalEndpointBroadcast());
|
||||
|
||||
String stategy= kck.getPerformanceStrategy();
|
||||
PerformanceStrategy pfs=PerformanceStrategy.fromDescription(stategy);
|
||||
if(pfs!=null) {
|
||||
for (int i=0;i<comboPerformance.getItemCount();i++){
|
||||
if(((PerformanceStrategyItem)comboPerformance.getModel().getElementAt(i)).getStrategy().equals(pfs)) {
|
||||
comboPerformance.setSelectedIndex(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String con=kck.getCongestionAlgorithm();
|
||||
if(con==null) {
|
||||
@@ -1613,6 +1788,15 @@ public class KLALBStateGUI3 extends XFrame {
|
||||
* 关闭窗口并清理资源
|
||||
*/
|
||||
public void close() {
|
||||
for (int i = 0; i < tabbedPane.getTabCount(); i++) {
|
||||
Component component = tabbedPane.getComponentAt(i);
|
||||
if (component instanceof NodeInformationPanel) {
|
||||
((NodeInformationPanel) component).close();
|
||||
}
|
||||
}
|
||||
if (graph != null) {
|
||||
graph.close();
|
||||
}
|
||||
setVisible(false);
|
||||
if (tsk != null) {
|
||||
tsk.cancel();
|
||||
|
||||
@@ -8,7 +8,7 @@ import javax.swing.border.LineBorder;
|
||||
|
||||
import org.jfree.chart.plot.dial.DialTextAnnotation;
|
||||
import org.jfree.data.general.DefaultValueDataset;
|
||||
import org.kne.cloud.network.MultipurposeSocketAddress;
|
||||
import org.kne.cloud.network.MultiProtocolSocketAddress;
|
||||
import org.kne.cloud.network.klalb.CONST;
|
||||
import org.kne.cloud.network.klalb.KLALBUtils;
|
||||
import org.kne.cloud.network.monitor.SpeedAndTrafficAndDelayMonitorDataImpl;
|
||||
@@ -143,9 +143,9 @@ public class KperfGUI extends XFrame{
|
||||
if(kperf==null) {
|
||||
try {
|
||||
String tar=targetAddressField.getText();
|
||||
MultipurposeSocketAddress msa=new MultipurposeSocketAddress(tar);
|
||||
MultiProtocolSocketAddress msa=new MultiProtocolSocketAddress(tar);
|
||||
String sour=sourceAddressField.getText();
|
||||
MultipurposeSocketAddress msas=new MultipurposeSocketAddress(sour);
|
||||
MultiProtocolSocketAddress msas=new MultiProtocolSocketAddress(sour);
|
||||
kperf=new Kperf(msa,msas);
|
||||
kperf.setReports(reports);
|
||||
kperf.startPerfing();
|
||||
@@ -650,10 +650,10 @@ public class KperfGUI extends XFrame{
|
||||
startButton.setText(UIEnv.getRsb().getString("stoptest"));
|
||||
}
|
||||
}
|
||||
public void setTarget(MultipurposeSocketAddress target) {
|
||||
public void setTarget(MultiProtocolSocketAddress target) {
|
||||
targetAddressField.setText(target.toString());
|
||||
}
|
||||
public void setTarget(MultipurposeSocketAddress target,MultipurposeSocketAddress source) {
|
||||
public void setTarget(MultiProtocolSocketAddress target, MultiProtocolSocketAddress source) {
|
||||
targetAddressField.setText(target.toString());
|
||||
sourceAddressField.setText(source.toString());
|
||||
}
|
||||
|
||||
@@ -1,58 +1,65 @@
|
||||
package org.kne.cloud.network.klalb.ui;
|
||||
|
||||
import java.awt.BasicStroke;
|
||||
import java.awt.Color;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.Font;
|
||||
import java.awt.FontMetrics;
|
||||
import java.awt.Graphics;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.Image;
|
||||
import java.awt.Rectangle;
|
||||
import java.awt.Shape;
|
||||
import java.awt.Toolkit;
|
||||
import java.awt.datatransfer.StringSelection;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.awt.event.MouseListener;
|
||||
import java.awt.geom.Dimension2D;
|
||||
import java.awt.geom.GeneralPath;
|
||||
import java.awt.geom.Point2D;
|
||||
import java.awt.image.ImageObserver;
|
||||
import java.net.Inet6Address;
|
||||
import java.net.InetAddress;
|
||||
import java.text.AttributedCharacterIterator;
|
||||
import java.util.ArrayList;
|
||||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Random;
|
||||
import java.util.Set;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JMenuItem;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JPopupMenu;
|
||||
import javax.swing.border.LineBorder;
|
||||
import javax.swing.SwingUtilities;
|
||||
|
||||
import org.kne.cloud.network.MultipurposeSocketAddress;
|
||||
import org.kne.cloud.network.MultiProtocolSocketAddress;
|
||||
import org.kne.cloud.network.ipv6.IPv6Address;
|
||||
import org.kne.cloud.network.klalb.KLALBController;
|
||||
import org.kne.cloud.network.klalb.KLALBUtils;
|
||||
import org.kne.cloud.network.srv6.KLALBRoutingProtocol;
|
||||
import org.kne.cloud.network.srv6.KLALBRoutingProtocolAPIClient;
|
||||
import org.kne.cloud.network.srv6.KLALBRoutingProtocol.LinkDirection;
|
||||
import org.kne.cloud.network.srv6.NodeProfile;
|
||||
import org.kne.cloud.network.srv6.RouterInfoPacket;
|
||||
|
||||
public class NetworkGraphPanel extends GraphPanel {
|
||||
private KLALBController controller;
|
||||
private KLALBStateGUI3 klbgui;
|
||||
private final KLALBRoutingProtocolAPIClient nodeInfoClient;
|
||||
private final KLALBRoutingProtocol nodeInfoRoutingProtocol;
|
||||
private final BiConsumer<IPv6Address, Integer> nodeInfoUpdateListener;
|
||||
private final Object nodeInfoLock = new Object();
|
||||
private final Map<IPv6Address, String> nodeNameCache = new HashMap<IPv6Address, String>();
|
||||
private final Map<IPv6Address, Long> nodeNameCacheTimes = new HashMap<IPv6Address, Long>();
|
||||
private final Map<IPv6Address, Long> nodeInfoRequestTimes = new HashMap<IPv6Address, Long>();
|
||||
private final Map<IPv6Address, Long> nodeInfoRequestGenerations = new HashMap<IPv6Address, Long>();
|
||||
private static final long NODE_INFO_REQUEST_TIMEOUT = 3000L;
|
||||
private static final long NODE_INFO_CACHE_TTL = 60000L;
|
||||
private long nextNodeInfoRequestGeneration;
|
||||
private volatile boolean nodeInfoClosed;
|
||||
|
||||
public NetworkGraphPanel(KLALBController controller,KLALBStateGUI3 klbgui) {
|
||||
super();
|
||||
this.controller = controller;
|
||||
this.klbgui=klbgui;
|
||||
this.nodeInfoRoutingProtocol = controller.getIpv6Router().getKlalbRouteProtol();
|
||||
this.nodeInfoClient = new KLALBRoutingProtocolAPIClient(nodeInfoRoutingProtocol);
|
||||
this.nodeInfoUpdateListener = (address, flags) -> {
|
||||
if ((flags & RouterInfoPacket.NODE_INFO_TINY_UPDATE_REQUIRED) != 0) {
|
||||
SwingUtilities.invokeLater(() -> invalidateTinyNodeInfo(address));
|
||||
}
|
||||
};
|
||||
nodeInfoRoutingProtocol.addNodeInfoUpdateListener(nodeInfoUpdateListener);
|
||||
cacheLocalNodeName();
|
||||
}
|
||||
|
||||
public NetworkGraphPanel(KLALBController kc) {
|
||||
@@ -70,7 +77,7 @@ public class NetworkGraphPanel extends GraphPanel {
|
||||
}
|
||||
|
||||
public InetGraphNode(IPv6Address address, Color color, double x, double y, boolean ismarked) {
|
||||
super(getText(address), color, x, y, ismarked);
|
||||
super(getNodeText(address), color, x, y, ismarked);
|
||||
this.address=address;
|
||||
initign();
|
||||
}
|
||||
@@ -105,8 +112,8 @@ public class NetworkGraphPanel extends GraphPanel {
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
KperfGUI kpfg=new KperfGUI();
|
||||
kpfg.setVisible(true);
|
||||
MultipurposeSocketAddress source=new MultipurposeSocketAddress("KLALB_Stream",controller.getIpv6Router().getLocator().getAddress().toString(),0);
|
||||
MultipurposeSocketAddress target=new MultipurposeSocketAddress("KLALB_Stream", address.toString(), 4564);
|
||||
MultiProtocolSocketAddress source=new MultiProtocolSocketAddress("KLALB_Stream",controller.getIpv6Router().getLocator().getAddress().toString(),0);
|
||||
MultiProtocolSocketAddress target=new MultiProtocolSocketAddress("KLALB_Stream", address.toString(), 4564);
|
||||
kpfg.setTarget(target,source);
|
||||
}
|
||||
});
|
||||
@@ -160,18 +167,162 @@ public class NetworkGraphPanel extends GraphPanel {
|
||||
return address2.toCompressedString();
|
||||
}
|
||||
|
||||
public void updateLabel() {
|
||||
setText(getNodeText(address));
|
||||
}
|
||||
|
||||
public IPv6Address getAddress() {
|
||||
return address;
|
||||
}
|
||||
|
||||
public void setAddress(IPv6Address address) {
|
||||
this.address = address;
|
||||
super.setText(getText(address));
|
||||
super.setText(getNodeText(address));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private double nsPerPixel=1000L;
|
||||
/**
|
||||
* 节点标签:显示 Tiny API 查询的设备名称(若有)+IP地址
|
||||
*/
|
||||
private String getNodeText(IPv6Address address) {
|
||||
StringBuilder sb=new StringBuilder();
|
||||
String dname;
|
||||
synchronized (nodeInfoLock) {
|
||||
dname = nodeNameCache.get(address);
|
||||
}
|
||||
if(dname!=null&&!dname.isEmpty())
|
||||
sb.append(dname).append('\n');
|
||||
sb.append(InetGraphNode.getText(address));
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private void cacheLocalNodeName() {
|
||||
IPv6Address localAddress = controller.getIpv6Router().getLocator().getAddress();
|
||||
synchronized (nodeInfoLock) {
|
||||
nodeNameCache.put(localAddress, controller.getIpv6Router().getDeviceName());
|
||||
nodeNameCacheTimes.put(localAddress, System.currentTimeMillis());
|
||||
}
|
||||
}
|
||||
|
||||
private void requestNodeProfile(final IPv6Address address) {
|
||||
final long requestTime = System.currentTimeMillis();
|
||||
final long requestGeneration;
|
||||
synchronized (nodeInfoLock) {
|
||||
if (nodeInfoClosed)
|
||||
return;
|
||||
Long cachedAt = nodeNameCacheTimes.get(address);
|
||||
if (nodeNameCache.containsKey(address) && cachedAt != null
|
||||
&& requestTime - cachedAt < NODE_INFO_CACHE_TTL)
|
||||
return;
|
||||
nodeNameCache.remove(address);
|
||||
nodeNameCacheTimes.remove(address);
|
||||
Long previousRequestTime = nodeInfoRequestTimes.get(address);
|
||||
if (previousRequestTime != null && requestTime - previousRequestTime < NODE_INFO_REQUEST_TIMEOUT)
|
||||
return;
|
||||
nodeInfoRequestTimes.put(address, requestTime);
|
||||
requestGeneration = ++nextNodeInfoRequestGeneration;
|
||||
nodeInfoRequestGenerations.put(address, requestGeneration);
|
||||
}
|
||||
try {
|
||||
nodeInfoClient.requestNodeProfile(
|
||||
new InetSocketAddress(address.toInet6Address(), KLALBRoutingProtocol.DEFAULT_PORT),
|
||||
(profile) -> SwingUtilities.invokeLater(() -> handleNodeProfile(address, requestGeneration, profile)));
|
||||
} catch (IOException e) {
|
||||
synchronized (nodeInfoLock) {
|
||||
if (nodeInfoRequestGenerations.get(address) != null
|
||||
&& nodeInfoRequestGenerations.get(address).longValue() == requestGeneration) {
|
||||
nodeInfoRequestTimes.remove(address);
|
||||
nodeInfoRequestGenerations.remove(address);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void handleNodeProfile(IPv6Address address, long requestGeneration, NodeProfile profile) {
|
||||
if (nodeInfoClosed)
|
||||
return;
|
||||
boolean currentRequest;
|
||||
boolean nodeExists = getNodes().containsKey(address);
|
||||
String deviceName = profile == null ? "" : profile.getDeviceName();
|
||||
if (deviceName == null || deviceName.isEmpty())
|
||||
deviceName = "";
|
||||
synchronized (nodeInfoLock) {
|
||||
if (nodeInfoClosed)
|
||||
return;
|
||||
Long activeRequestGeneration = nodeInfoRequestGenerations.get(address);
|
||||
currentRequest = activeRequestGeneration != null
|
||||
&& activeRequestGeneration.longValue() == requestGeneration;
|
||||
if (currentRequest) {
|
||||
nodeInfoRequestTimes.remove(address);
|
||||
nodeInfoRequestGenerations.remove(address);
|
||||
if (nodeExists)
|
||||
nodeNameCache.put(address, deviceName);
|
||||
else
|
||||
nodeNameCache.remove(address);
|
||||
if (nodeExists)
|
||||
nodeNameCacheTimes.put(address, System.currentTimeMillis());
|
||||
else
|
||||
nodeNameCacheTimes.remove(address);
|
||||
}
|
||||
}
|
||||
if (currentRequest && nodeExists) {
|
||||
((InetGraphNode) getNodes().get(address)).updateLabel();
|
||||
repaint();
|
||||
}
|
||||
}
|
||||
|
||||
private void removeNodeInfoState(IPv6Address address) {
|
||||
synchronized (nodeInfoLock) {
|
||||
nodeNameCache.remove(address);
|
||||
nodeNameCacheTimes.remove(address);
|
||||
nodeInfoRequestTimes.remove(address);
|
||||
nodeInfoRequestGenerations.remove(address);
|
||||
}
|
||||
}
|
||||
|
||||
private void invalidateTinyNodeInfo(IPv6Address address) {
|
||||
synchronized (nodeInfoLock) {
|
||||
if (nodeInfoClosed)
|
||||
return;
|
||||
nodeNameCache.remove(address);
|
||||
nodeNameCacheTimes.remove(address);
|
||||
nodeInfoRequestTimes.remove(address);
|
||||
nodeInfoRequestGenerations.remove(address);
|
||||
}
|
||||
InetGraphNode node = (InetGraphNode) getNodes().get(address);
|
||||
if (node != null) {
|
||||
node.updateLabel();
|
||||
}
|
||||
repaint();
|
||||
requestNodeProfile(address);
|
||||
}
|
||||
|
||||
public void close() {
|
||||
synchronized (nodeInfoLock) {
|
||||
if (nodeInfoClosed)
|
||||
return;
|
||||
nodeInfoClosed = true;
|
||||
nodeNameCache.clear();
|
||||
nodeNameCacheTimes.clear();
|
||||
nodeInfoRequestTimes.clear();
|
||||
nodeInfoRequestGenerations.clear();
|
||||
}
|
||||
nodeInfoRoutingProtocol.removeNodeInfoUpdateListener(nodeInfoUpdateListener);
|
||||
nodeInfoClient.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeNotify() {
|
||||
super.removeNotify();
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
if (!isDisplayable() && getParent() == null) {
|
||||
close();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private double nsPerPixel=5000L;
|
||||
private class InetGraphEdgeGroup extends GraphEdgeGroup{
|
||||
public InetGraphEdgeGroup(GraphNode nodeA, GraphNode nodeB) {
|
||||
super(nodeA, nodeB);
|
||||
@@ -188,6 +339,8 @@ public class NetworkGraphPanel extends GraphPanel {
|
||||
}
|
||||
protected void loadNodes() {
|
||||
Map<IPv6Address, Long> addr= controller.getIpv6Router().getKlalbRouteProtol().getAddresses();
|
||||
IPv6Address localAddress = controller.getIpv6Router().getLocator().getAddress();
|
||||
cacheLocalNodeName();
|
||||
Set<IPv6Address> ks=addr.keySet();
|
||||
for (Iterator<IPv6Address> iterator = ks.iterator(); iterator.hasNext();) {
|
||||
IPv6Address inet6Address = (IPv6Address) iterator.next();
|
||||
@@ -195,12 +348,17 @@ public class NetworkGraphPanel extends GraphPanel {
|
||||
Vector2 v2pos=super.getRandomPos();
|
||||
getNodes().put(inet6Address,new InetGraphNode(inet6Address,Color.BLACK,v2pos.x,v2pos.y,inet6Address.equals(controller.getIpv6Router().getLocator().getAddress())));
|
||||
}
|
||||
if(!inet6Address.equals(localAddress))
|
||||
requestNodeProfile(inet6Address);
|
||||
}
|
||||
Set<IPv6Address> kns=getNodes().keySet();
|
||||
for (Iterator<IPv6Address> iterator = kns.iterator(); iterator.hasNext();) {
|
||||
IPv6Address inet6Address = (IPv6Address) iterator.next();
|
||||
if(!addr.containsKey(inet6Address)) {
|
||||
iterator.remove();
|
||||
removeNodeInfoState(inet6Address);
|
||||
}else {
|
||||
((InetGraphNode)getNodes().get(inet6Address)).updateLabel();
|
||||
}
|
||||
}
|
||||
//System.out.println("------------------------------------");
|
||||
|
||||
@@ -4,14 +4,13 @@ import java.awt.BorderLayout;
|
||||
import java.awt.Image;
|
||||
import java.awt.Toolkit;
|
||||
import java.awt.datatransfer.StringSelection;
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import javax.swing.AbstractAction;
|
||||
import javax.swing.Icon;
|
||||
import javax.swing.ImageIcon;
|
||||
import javax.swing.BorderFactory;
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JPopupMenu;
|
||||
@@ -22,28 +21,36 @@ import javax.swing.event.ListSelectionEvent;
|
||||
import javax.swing.event.ListSelectionListener;
|
||||
|
||||
import org.kne.cloud.klalb.uitool.XDefaultListModel;
|
||||
import org.kne.cloud.network.MultipurposeSocketAddress;
|
||||
import org.kne.cloud.network.MultiProtocolSocketAddress;
|
||||
import org.kne.cloud.network.ipv6.IPv6Address;
|
||||
import org.kne.cloud.network.klalb.KLALBController;
|
||||
import org.kne.cloud.network.klalb.NodeInfoQueryCoordinator;
|
||||
import org.kne.cloud.network.srv6.KLALBRoutingProtocol;
|
||||
import org.kne.cloud.network.srv6.KLALBRoutingProtocolAPIClient;
|
||||
import org.kne.cloud.network.srv6.NodeInfoQueryStatus;
|
||||
import org.kne.cloud.network.srv6.NodeProfile;
|
||||
import javax.swing.JTabbedPane;
|
||||
import javax.swing.JList;
|
||||
import javax.swing.JMenuItem;
|
||||
import javax.swing.JOptionPane;
|
||||
|
||||
import java.awt.event.ActionListener;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.awt.event.MouseListener;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
public class NodeInformationPanel extends JPanel {
|
||||
private IPv6Address address;
|
||||
private KLALBRoutingProtocolAPIClient client;
|
||||
private NodeInfoQueryCoordinator queryCoordinator;
|
||||
private KLALBController controller;
|
||||
private Image image;
|
||||
|
||||
private XDefaultListModel<MultipurposeSocketAddress> listModel=new XDefaultListModel<>();
|
||||
private XDefaultListModel<MultiProtocolSocketAddress> listModel=new XDefaultListModel<>();
|
||||
private XDefaultListModel<String> extraRoutesModel=new XDefaultListModel<>();
|
||||
private volatile boolean active=true;
|
||||
private final AtomicLong fullInfoGeneration=new AtomicLong();
|
||||
private final java.util.function.BiConsumer<IPv6Address,Integer> nodeInfoUpdateListener;
|
||||
public KLALBController getController() {
|
||||
return controller;
|
||||
}
|
||||
@@ -69,11 +76,32 @@ public class NodeInformationPanel extends JPanel {
|
||||
panel.setLayout(new BorderLayout(0, 0));
|
||||
tabbedPane.addTab(UIEnv.getRsb().getString("overview"), null, panel, null);
|
||||
|
||||
JTextArea overviewArea = new JTextArea();
|
||||
overviewArea.setEditable(false);
|
||||
overviewArea.setLineWrap(true);
|
||||
overviewArea.setWrapStyleWord(true);
|
||||
overviewArea.setFont(UIEnv.getFont().deriveFont(14.0f));
|
||||
overviewArea.setBorder(BorderFactory.createEmptyBorder(8, 8, 8, 8));
|
||||
overviewArea.setText(buildOverviewText(null, null));
|
||||
panel.add(new JScrollPane(overviewArea), BorderLayout.CENTER);
|
||||
|
||||
JPanel extraRoutesPanel = new JPanel(new BorderLayout());
|
||||
JList<String> extraRoutesList = new JList<String>(extraRoutesModel);
|
||||
extraRoutesList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
|
||||
extraRoutesList.setFont(UIEnv.getFont().deriveFont(14.0f));
|
||||
extraRoutesPanel.add(new JScrollPane(extraRoutesList), BorderLayout.CENTER);
|
||||
javax.swing.JLabel extraRoutesEmptyLabel=new javax.swing.JLabel(UIEnv.getRsb().getString("noextraroutes"));
|
||||
extraRoutesEmptyLabel.setText("...");
|
||||
extraRoutesEmptyLabel.setBorder(BorderFactory.createEmptyBorder(8, 8, 8, 8));
|
||||
extraRoutesEmptyLabel.setHorizontalAlignment(javax.swing.SwingConstants.CENTER);
|
||||
extraRoutesPanel.add(extraRoutesEmptyLabel, BorderLayout.SOUTH);
|
||||
tabbedPane.addTab(UIEnv.getRsb().getString("extraroutes"), null, extraRoutesPanel, null);
|
||||
|
||||
JPanel panel_1 = new JPanel();
|
||||
panel_1.setLayout(new BorderLayout(0, 0));
|
||||
panel_1.add(scrollPane);
|
||||
|
||||
JList<MultipurposeSocketAddress> list = new JList<MultipurposeSocketAddress>(listModel);
|
||||
JList<MultiProtocolSocketAddress> list = new JList<MultiProtocolSocketAddress>(listModel);
|
||||
list.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
|
||||
scrollPane.setViewportView(list);
|
||||
tabbedPane.addTab(UIEnv.getRsb().getString("openlinetable"), null, panel_1, null);
|
||||
@@ -81,7 +109,7 @@ public class NodeInformationPanel extends JPanel {
|
||||
JButton btnNewButton = new JButton(UIEnv.getRsb().getString("addline"));
|
||||
btnNewButton.addActionListener(new ActionListener() {
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
List<MultipurposeSocketAddress> select=list.getSelectedValuesList();
|
||||
List<MultiProtocolSocketAddress> select=list.getSelectedValuesList();
|
||||
controller.addRemoteLines(select);
|
||||
}
|
||||
});
|
||||
@@ -93,7 +121,7 @@ public class NodeInformationPanel extends JPanel {
|
||||
|
||||
@Override
|
||||
public void valueChanged(ListSelectionEvent e) {
|
||||
List<MultipurposeSocketAddress> select=list.getSelectedValuesList();
|
||||
List<MultiProtocolSocketAddress> select=list.getSelectedValuesList();
|
||||
btnNewButton.setEnabled( !select.isEmpty()) ;
|
||||
copy.setEnabled(!select.isEmpty());
|
||||
}
|
||||
@@ -103,9 +131,9 @@ public class NodeInformationPanel extends JPanel {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
StringBuilder sb=new StringBuilder();
|
||||
List<MultipurposeSocketAddress> select=list.getSelectedValuesList();
|
||||
for (MultipurposeSocketAddress multipurposeSocketAddress : select) {
|
||||
sb.append(multipurposeSocketAddress);
|
||||
List<MultiProtocolSocketAddress> select=list.getSelectedValuesList();
|
||||
for (MultiProtocolSocketAddress multiProtocolSocketAddress : select) {
|
||||
sb.append(multiProtocolSocketAddress);
|
||||
sb.append('\n');
|
||||
}
|
||||
Toolkit.getDefaultToolkit().getSystemClipboard().setContents(new StringSelection(sb.toString()), null);
|
||||
@@ -148,18 +176,122 @@ public class NodeInformationPanel extends JPanel {
|
||||
|
||||
panel_1.add(btnNewButton, BorderLayout.SOUTH);
|
||||
|
||||
client=new KLALBRoutingProtocolAPIClient(controller.getIpv6Router().getKlalbRouteProtol());
|
||||
KLALBRoutingProtocol routingProtocol=controller.getIpv6Router().getKlalbRouteProtol();
|
||||
client=new KLALBRoutingProtocolAPIClient(routingProtocol);
|
||||
queryCoordinator=new NodeInfoQueryCoordinator(client);
|
||||
nodeInfoUpdateListener=(updatedAddress, flags) -> {
|
||||
if(active && address.equals(updatedAddress)
|
||||
&& (flags & org.kne.cloud.network.srv6.RouterInfoPacket.NODE_INFO_FULL_UPDATE_REQUIRED) != 0) {
|
||||
requestFullInfo(overviewArea, extraRoutesEmptyLabel);
|
||||
}
|
||||
};
|
||||
routingProtocol.addNodeInfoUpdateListener(nodeInfoUpdateListener);
|
||||
requestFullInfo(overviewArea, extraRoutesEmptyLabel);
|
||||
}
|
||||
|
||||
private void requestFullInfo(JTextArea overviewArea, javax.swing.JLabel extraRoutesEmptyLabel) {
|
||||
final long generation=fullInfoGeneration.incrementAndGet();
|
||||
if (controller.getIpv6Router().getLocator().getAddress().equals(address)) {
|
||||
KLALBController.PublishedNodeInfo published=controller.getPublishedNodeInfo();
|
||||
applyNodeInfo(overviewArea, extraRoutesEmptyLabel, published.getDeviceName(),
|
||||
published.getDeviceDescription(), published.getExternalEndpoints(), published.getExtraRoutes());
|
||||
return;
|
||||
}
|
||||
try {
|
||||
client.requestOpenLines(new InetSocketAddress( address.toInet6Address(), KLALBRoutingProtocol.DEFAULT_PORT), (result)->{
|
||||
listModel.clear();
|
||||
for (MultipurposeSocketAddress multipurposeSocketAddress : result) {
|
||||
listModel.addElement(multipurposeSocketAddress);
|
||||
queryCoordinator.query(new InetSocketAddress(address.toInet6Address(), KLALBRoutingProtocol.DEFAULT_PORT)).whenComplete((info, error)->{
|
||||
if(!active || generation!=fullInfoGeneration.get()) return;
|
||||
javax.swing.SwingUtilities.invokeLater(() -> {
|
||||
if(!active || generation!=fullInfoGeneration.get()) return;
|
||||
if (error != null || info == null) {
|
||||
return;
|
||||
}
|
||||
applyRemoteNodeInfo(overviewArea, extraRoutesEmptyLabel, info);
|
||||
});
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
});
|
||||
} catch (RuntimeException e) {
|
||||
if(active) e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private void applyNodeInfo(JTextArea overviewArea, javax.swing.JLabel extraRoutesEmptyLabel,
|
||||
String deviceName, String deviceDescription, List<MultiProtocolSocketAddress> endpoints,
|
||||
List<String> routes) {
|
||||
applyProfile(overviewArea, deviceName, deviceDescription);
|
||||
applyEndpoints(endpoints == null ? Collections.<MultiProtocolSocketAddress>emptyList() : endpoints);
|
||||
applyRoutes(extraRoutesEmptyLabel, routes == null ? Collections.<String>emptyList() : routes);
|
||||
}
|
||||
|
||||
private void applyRemoteNodeInfo(JTextArea overviewArea, javax.swing.JLabel extraRoutesEmptyLabel,
|
||||
NodeInfoQueryCoordinator.Result info) {
|
||||
if (info.getProfile().isReceived()) {
|
||||
NodeProfile profile=info.getProfile().getValue().orElse(null);
|
||||
applyProfile(overviewArea, profile == null ? null : profile.getDeviceName(),
|
||||
profile == null ? null : profile.getDeviceDescription());
|
||||
}
|
||||
|
||||
if (info.getExternalEndpoints().isReceived()) {
|
||||
List<MultiProtocolSocketAddress> endpoints=info.getExternalEndpoints().getStatus()
|
||||
.filter(NodeInfoQueryStatus.OK::equals).isPresent()
|
||||
? info.getExternalEndpoints().getValue().orElse(Collections.<MultiProtocolSocketAddress>emptyList())
|
||||
: Collections.<MultiProtocolSocketAddress>emptyList();
|
||||
applyEndpoints(endpoints);
|
||||
}
|
||||
|
||||
if (info.getExtraRoutes().isReceived()) {
|
||||
List<String> routes=info.getExtraRoutes().getStatus().filter(NodeInfoQueryStatus.OK::equals).isPresent()
|
||||
? info.getExtraRoutes().getValue().orElse(Collections.<String>emptyList())
|
||||
: Collections.<String>emptyList();
|
||||
applyRoutes(extraRoutesEmptyLabel, routes);
|
||||
}
|
||||
}
|
||||
|
||||
private void applyProfile(JTextArea overviewArea, String deviceName, String deviceDescription) {
|
||||
overviewArea.setText(buildOverviewText(deviceName,
|
||||
deviceDescription == null || deviceDescription.isEmpty() ? null : deviceDescription));
|
||||
}
|
||||
|
||||
private void applyEndpoints(List<MultiProtocolSocketAddress> endpoints) {
|
||||
listModel.clear();
|
||||
if (endpoints != null) for (MultiProtocolSocketAddress item : endpoints) listModel.addElement(item);
|
||||
}
|
||||
|
||||
private void applyRoutes(javax.swing.JLabel extraRoutesEmptyLabel, List<String> routes) {
|
||||
extraRoutesModel.clear();
|
||||
if (routes != null) for (String route : routes) extraRoutesModel.addElement(route);
|
||||
extraRoutesEmptyLabel.setText(UIEnv.getRsb().getString("noextraroutes"));
|
||||
extraRoutesEmptyLabel.setVisible(extraRoutesModel.isEmpty());
|
||||
}
|
||||
|
||||
public synchronized void close() {
|
||||
if (!active)
|
||||
return;
|
||||
active=false;
|
||||
fullInfoGeneration.incrementAndGet();
|
||||
KLALBRoutingProtocol routingProtocol=controller.getIpv6Router().getKlalbRouteProtol();
|
||||
routingProtocol.removeNodeInfoUpdateListener(nodeInfoUpdateListener);
|
||||
if(client!=null) {
|
||||
client.close();
|
||||
client=null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeNotify() {
|
||||
close();
|
||||
super.removeNotify();
|
||||
}
|
||||
|
||||
private String buildOverviewText(String dname,String ddesc) {
|
||||
StringBuilder sb=new StringBuilder();
|
||||
sb.append(UIEnv.getRsb().getString("ipv6addr")).append(": ").append(address.toCompressedString()).append('\n');
|
||||
if(dname!=null&&!dname.isEmpty()) {
|
||||
sb.append('\n').append(UIEnv.getRsb().getString("devicename")).append(": ").append(dname).append('\n');
|
||||
}
|
||||
if(ddesc!=null&&!ddesc.isEmpty()) {
|
||||
sb.append('\n').append(UIEnv.getRsb().getString("devicedescription")).append(":\n").append(ddesc).append('\n');
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
public Icon getIcon() {
|
||||
return new ImageIcon(image);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
package org.kne.cloud.network.klalb.ui;
|
||||
|
||||
import org.kne.cloud.network.klalb.PerformanceStrategy;
|
||||
|
||||
public class PerformanceStrategyItem {
|
||||
private PerformanceStrategy strategy;
|
||||
|
||||
public PerformanceStrategyItem(PerformanceStrategy strategy) {
|
||||
this.strategy = strategy;
|
||||
}
|
||||
|
||||
public PerformanceStrategy getStrategy() {
|
||||
return strategy;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return UIEnv.getRsb().getString(strategy.toString());
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,7 @@ import java.awt.Dimension;
|
||||
import javax.swing.SwingConstants;
|
||||
import javax.swing.border.LineBorder;
|
||||
|
||||
import org.kne.cloud.network.MultipurposeSocketAddress;
|
||||
import org.kne.cloud.network.MultiProtocolSocketAddress;
|
||||
import org.kne.cloud.network.ipv6.IPv6AddressGroup;
|
||||
import org.kne.cloud.network.klalb.CONST;
|
||||
import org.kne.cloud.network.klalb.KLALBController;
|
||||
@@ -219,7 +219,7 @@ public class TPanel2 extends JPanel {
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
MultipurposeSocketAddress mtar=tunnel.getSocketAddress();
|
||||
MultiProtocolSocketAddress mtar=tunnel.getSocketAddress();
|
||||
if(mtar!=null) {
|
||||
kc.removeRemoteLines(mtar);
|
||||
}else {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -258,7 +258,7 @@ public class KLTPInputStream extends InputStream implements KLTPPacketConsumer,
|
||||
case KLTPPacket.KLTP_TYPE_DATA:
|
||||
//ackSequenceBatcher.putMessage(kseq);
|
||||
recvMap.put(kltp.getSequence(), kltp);
|
||||
controller.getIpv6Router().runPacketSendTask(()->{
|
||||
controller.getIpv6Router().enqueuePacketSendTask(()->{
|
||||
KLTPPacket pack=new KLTPPacket(streamUUID,KLTPPacket.KLTP_TYPE_ACK,kltp.getSequence(),0);
|
||||
pack.setCE(u.isCE());
|
||||
return controller.createPacketToAddress(remoteaddr,0,pack);
|
||||
@@ -267,7 +267,7 @@ public class KLTPInputStream extends InputStream implements KLTPPacketConsumer,
|
||||
case KLTPPacket.KLTP_TYPE_DATAFIN:
|
||||
//ackSequenceBatcher.putMessage(kseq2);
|
||||
recvMap.put(kltp.getSequence(), kltp);
|
||||
controller.getIpv6Router().runPacketSendTask(()->{
|
||||
controller.getIpv6Router().enqueuePacketSendTask(()->{
|
||||
KLTPPacket pack=new KLTPPacket(streamUUID,KLTPPacket.KLTP_TYPE_ACK,kltp.getSequence(),0);
|
||||
pack.setCE(u.isCE());
|
||||
return controller.createPacketToAddress(remoteaddr,0,pack);
|
||||
|
||||
@@ -1,23 +1,22 @@
|
||||
package org.kne.cloud.network.minecraft;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
import org.kne.cloud.network.MultipurposeSocketAddress;
|
||||
import org.kne.cloud.network.MultiProtocolSocketAddress;
|
||||
import org.kne.cloud.network.scanner.InetAddressRange;
|
||||
import org.kne.cloud.network.scanner.PortRange;
|
||||
import org.kne.cloud.network.scanner.ScanRange;
|
||||
import org.kne.cloud.network.scanner.TCPNetworkScanner;
|
||||
|
||||
public class MinecraftScanner extends TCPNetworkScanner{
|
||||
private BiConsumer<MultipurposeSocketAddress, MinecraftServerPinger>minecraftConsumer;
|
||||
private BiConsumer<MultiProtocolSocketAddress, MinecraftServerPinger>minecraftConsumer;
|
||||
|
||||
public BiConsumer<MultipurposeSocketAddress, MinecraftServerPinger> getMinecraftConsumer() {
|
||||
public BiConsumer<MultiProtocolSocketAddress, MinecraftServerPinger> getMinecraftConsumer() {
|
||||
return minecraftConsumer;
|
||||
}
|
||||
|
||||
public void setMinecraftConsumer(BiConsumer<MultipurposeSocketAddress, MinecraftServerPinger> minecraftConsumer) {
|
||||
public void setMinecraftConsumer(BiConsumer<MultiProtocolSocketAddress, MinecraftServerPinger> minecraftConsumer) {
|
||||
this.minecraftConsumer = minecraftConsumer;
|
||||
}
|
||||
{
|
||||
|
||||
@@ -6,9 +6,9 @@ import javax.naming.directory.Attributes;
|
||||
import javax.naming.directory.DirContext;
|
||||
import javax.naming.directory.InitialDirContext;
|
||||
|
||||
import org.kne.cloud.network.MultipurposeSocketAddress;
|
||||
import org.kne.cloud.network.MultiProtocolSocketAddress;
|
||||
|
||||
public class MinecraftServerAddress extends MultipurposeSocketAddress
|
||||
public class MinecraftServerAddress extends MultiProtocolSocketAddress
|
||||
{
|
||||
|
||||
private String originAddr;
|
||||
|
||||
@@ -3,14 +3,8 @@ package org.kne.cloud.network.minecraft;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.EOFException;
|
||||
import java.io.Externalizable;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.ObjectInput;
|
||||
import java.io.ObjectOutput;
|
||||
import java.io.OutputStream;
|
||||
import java.io.Serializable;
|
||||
import java.io.StringReader;
|
||||
import java.net.Inet6Address;
|
||||
import java.net.InetAddress;
|
||||
import java.net.Socket;
|
||||
@@ -19,13 +13,11 @@ import java.util.Base64;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
|
||||
import org.kne.cloud.network.MultipurposeSocketAddress;
|
||||
import org.kne.cloud.network.MultiProtocolSocketAddress;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
import com.google.gson.stream.JsonReader;
|
||||
import com.google.gson.stream.JsonToken;
|
||||
|
||||
public class MinecraftServerPinger implements Serializable{
|
||||
/**
|
||||
@@ -34,7 +26,7 @@ public class MinecraftServerPinger implements Serializable{
|
||||
private static final long serialVersionUID = 1L;
|
||||
private transient Socket socket;
|
||||
private transient MinecraftProtocolContext mpc=new MinecraftProtocolContext();
|
||||
private MultipurposeSocketAddress targetaddr;
|
||||
private MultiProtocolSocketAddress targetaddr;
|
||||
|
||||
|
||||
|
||||
@@ -53,11 +45,11 @@ public class MinecraftServerPinger implements Serializable{
|
||||
private String icon;
|
||||
private transient BufferedImage image;
|
||||
|
||||
public MinecraftServerPinger(MultipurposeSocketAddress target) throws UnknownHostException, IOException {
|
||||
public MinecraftServerPinger(MultiProtocolSocketAddress target) throws UnknownHostException, IOException {
|
||||
this(target,target.connectSocket());
|
||||
}
|
||||
|
||||
public MinecraftServerPinger(MultipurposeSocketAddress target, Socket Asocket) throws UnknownHostException, IOException{
|
||||
public MinecraftServerPinger(MultiProtocolSocketAddress target, Socket Asocket) throws UnknownHostException, IOException{
|
||||
targetaddr=target;
|
||||
socket=Asocket;
|
||||
socket.setTcpNoDelay(true);
|
||||
@@ -302,7 +294,7 @@ public class MinecraftServerPinger implements Serializable{
|
||||
return json;
|
||||
}
|
||||
|
||||
public MultipurposeSocketAddress getTargetaddr() {
|
||||
public MultiProtocolSocketAddress getTargetaddr() {
|
||||
return targetaddr;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ package org.kne.cloud.network.minecraft;
|
||||
|
||||
import java.net.UnknownHostException;
|
||||
|
||||
import org.kne.cloud.network.MultipurposeSocketAddress;
|
||||
import org.kne.cloud.network.MultiProtocolSocketAddress;
|
||||
import org.kne.cloud.network.scanner.InetAddressRange;
|
||||
import org.kne.cloud.network.scanner.PortRange;
|
||||
import org.kne.cloud.network.scanner.ScanRange;
|
||||
@@ -13,7 +13,7 @@ public class MinecraftServerScanRange extends ScanRange {
|
||||
super(addressRange, portRange);
|
||||
}
|
||||
|
||||
public MinecraftServerScanRange(MultipurposeSocketAddress begin, MultipurposeSocketAddress end)
|
||||
public MinecraftServerScanRange(MultiProtocolSocketAddress begin, MultiProtocolSocketAddress end)
|
||||
throws UnknownHostException {
|
||||
super(begin, end);
|
||||
}
|
||||
@@ -25,8 +25,8 @@ public class MinecraftServerScanRange extends ScanRange {
|
||||
@Override
|
||||
protected void setMsaString(String range) throws UnknownHostException {
|
||||
String[]sp=range.split("~");
|
||||
MultipurposeSocketAddress begin=MinecraftServerAddress.findAddress (sp[0]);
|
||||
MultipurposeSocketAddress end;
|
||||
MultiProtocolSocketAddress begin=MinecraftServerAddress.findAddress (sp[0]);
|
||||
MultiProtocolSocketAddress end;
|
||||
if(sp.length>1) {
|
||||
end=MinecraftServerAddress.findAddress (sp[1]);
|
||||
}else {
|
||||
|
||||
@@ -4,6 +4,7 @@ import java.util.UUID;
|
||||
|
||||
import org.kne.cloud.clock.HighAccuracyClock;
|
||||
import org.kne.cloud.network.klalb.KLALBUtils;
|
||||
import org.kne.concurrent.LazyEvaluator;
|
||||
|
||||
public class SpeedAndTrafficMonitorDataImpl extends MonitorDataImpl implements SpeedAndTrafficMonitorData {
|
||||
|
||||
@@ -16,11 +17,12 @@ public class SpeedAndTrafficMonitorDataImpl extends MonitorDataImpl implements S
|
||||
|
||||
|
||||
|
||||
private final long TIME_WINDOW=2000000000L;
|
||||
public SpeedAndTrafficMonitorDataImpl(HighAccuracyClock clock) {
|
||||
super();
|
||||
this.clock=clock;
|
||||
uploadBandwidth=new ArrayListTimestampMonitor<UUID>(clock,"UploadMonitor", 100, 5000000000L);
|
||||
downloadBandwidth=new ArrayListTimestampMonitor<UUID>(clock,"DownloadMonitor", 100, 5000000000L);
|
||||
uploadBandwidth=new ArrayListTimestampMonitor<UUID>(clock,"UploadMonitor", 100, TIME_WINDOW);
|
||||
downloadBandwidth=new ArrayListTimestampMonitor<UUID>(clock,"DownloadMonitor", 100, TIME_WINDOW);
|
||||
|
||||
}
|
||||
|
||||
@@ -86,12 +88,13 @@ public class SpeedAndTrafficMonitorDataImpl extends MonitorDataImpl implements S
|
||||
|
||||
|
||||
|
||||
|
||||
private LazyEvaluator<Long> outSpeedEvaluator=new LazyEvaluator<Long>(()->{return uploadBandwidth.calculateBandwidth(timewindow);});
|
||||
public long getOutSpeed() {
|
||||
return uploadBandwidth.calculateBandwidth(timewindow);
|
||||
return outSpeedEvaluator.get();
|
||||
}
|
||||
private LazyEvaluator<Long> inSpeedEvaluator=new LazyEvaluator<Long>(()->{return downloadBandwidth.calculateBandwidth(timewindow);});
|
||||
public long getInSpeed() {
|
||||
return downloadBandwidth.calculateBandwidth(timewindow);
|
||||
return inSpeedEvaluator.get();
|
||||
}
|
||||
|
||||
|
||||
@@ -117,13 +120,15 @@ public class SpeedAndTrafficMonitorDataImpl extends MonitorDataImpl implements S
|
||||
|
||||
|
||||
|
||||
private LazyEvaluator<Long> outPPSEvaluator=new LazyEvaluator<Long>(()->{return uploadBandwidth.calculatePacketRate(timewindow);});
|
||||
public long getOutPPS() {
|
||||
return uploadBandwidth.calculatePacketRate(timewindow);
|
||||
return outPPSEvaluator.get();
|
||||
}
|
||||
|
||||
private LazyEvaluator<Long> inPPSEvaluator=new LazyEvaluator<Long>(()->{return downloadBandwidth.calculatePacketRate(timewindow);});
|
||||
|
||||
public long getInPPS() {
|
||||
return downloadBandwidth.calculatePacketRate(timewindow);
|
||||
return inPPSEvaluator.get();
|
||||
}
|
||||
private long inSpeed;
|
||||
private long outSpeed;
|
||||
@@ -159,9 +164,18 @@ public class SpeedAndTrafficMonitorDataImpl extends MonitorDataImpl implements S
|
||||
public void update() {
|
||||
upSampler.update();
|
||||
downSampler.update();
|
||||
uploadBandwidth.recordPacket(KLALBUtils.createGlobalUUID(),(int)upSampler.getPacketCountSinceLastSnapshot() ,(int)upSampler.getByteCountSinceLastSnapshot());
|
||||
downloadBandwidth.recordPacket(KLALBUtils.createGlobalUUID(),(int)downSampler.getPacketCountSinceLastSnapshot() ,(int) downSampler.getByteCountSinceLastSnapshot());
|
||||
|
||||
long uppacketcount=upSampler.getPacketCountSinceLastSnapshot();
|
||||
if(uppacketcount>0) {
|
||||
uploadBandwidth.recordPacket(KLALBUtils.createGlobalUUID(), (int) uppacketcount, (int) upSampler.getByteCountSinceLastSnapshot());
|
||||
outSpeedEvaluator.markDirty();
|
||||
outPPSEvaluator.markDirty();
|
||||
}
|
||||
long downpacketcount=downSampler.getPacketCountSinceLastSnapshot();
|
||||
if(downpacketcount>0) {
|
||||
downloadBandwidth.recordPacket(KLALBUtils.createGlobalUUID(), (int) downpacketcount, (int) downSampler.getByteCountSinceLastSnapshot());
|
||||
inSpeedEvaluator.markDirty();
|
||||
inPPSEvaluator.markDirty();
|
||||
}
|
||||
long time=System.nanoTime();
|
||||
if(time-stime>timewindowmax) {
|
||||
stime=time;
|
||||
|
||||
@@ -18,7 +18,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import org.kne.cloud.clock.HighAccuracyClock;
|
||||
import org.kne.cloud.clock.NTPTimestamps;
|
||||
import org.kne.cloud.network.MultipurposeSocketAddress;
|
||||
import org.kne.cloud.network.MultiProtocolSocketAddress;
|
||||
import org.kne.cloud.network.ntp.NTPv4Protocol.NTPPeer;
|
||||
import org.kne.math.Long128;
|
||||
|
||||
@@ -110,13 +110,13 @@ public class NTPContext implements Closeable, AutoCloseable {
|
||||
return clock;
|
||||
}
|
||||
|
||||
private ConcurrentHashMap<MultipurposeSocketAddress, List<NTPv4Packet>> recvmap = new ConcurrentHashMap<MultipurposeSocketAddress, List<NTPv4Packet>>();
|
||||
private ConcurrentHashMap<MultiProtocolSocketAddress, List<NTPv4Packet>> recvmap = new ConcurrentHashMap<MultiProtocolSocketAddress, List<NTPv4Packet>>();
|
||||
|
||||
protected void clearPackets() {
|
||||
recvmap.clear();
|
||||
}
|
||||
|
||||
protected void putPacket(NTPv4Packet nv4, MultipurposeSocketAddress inetSocketAddress) {
|
||||
protected void putPacket(NTPv4Packet nv4, MultiProtocolSocketAddress inetSocketAddress) {
|
||||
checkIP();
|
||||
List<NTPv4Packet> newv = new Vector<NTPv4Packet>();
|
||||
List<NTPv4Packet> oldv = recvmap.putIfAbsent(inetSocketAddress, newv);
|
||||
@@ -133,10 +133,10 @@ public class NTPContext implements Closeable, AutoCloseable {
|
||||
}
|
||||
|
||||
private void checkIP() {
|
||||
Set<Entry<MultipurposeSocketAddress, List<NTPv4Packet>>> ens = recvmap.entrySet();
|
||||
for (Iterator<Entry<MultipurposeSocketAddress, List<NTPv4Packet>>> iterator = ens.iterator(); iterator
|
||||
Set<Entry<MultiProtocolSocketAddress, List<NTPv4Packet>>> ens = recvmap.entrySet();
|
||||
for (Iterator<Entry<MultiProtocolSocketAddress, List<NTPv4Packet>>> iterator = ens.iterator(); iterator
|
||||
.hasNext();) {
|
||||
Entry<MultipurposeSocketAddress, List<NTPv4Packet>> entry = (Entry<MultipurposeSocketAddress, List<NTPv4Packet>>) iterator
|
||||
Entry<MultiProtocolSocketAddress, List<NTPv4Packet>> entry = (Entry<MultiProtocolSocketAddress, List<NTPv4Packet>>) iterator
|
||||
.next();
|
||||
AtomicBoolean ab = new AtomicBoolean(false);
|
||||
ios.forEach((x) -> {
|
||||
@@ -153,7 +153,7 @@ public class NTPContext implements Closeable, AutoCloseable {
|
||||
}
|
||||
|
||||
private class PeerInfo implements Comparable<PeerInfo> {
|
||||
private MultipurposeSocketAddress address;
|
||||
private MultiProtocolSocketAddress address;
|
||||
private int leapIndicator = 3;
|
||||
private int stratum = 16;
|
||||
private int referenceIdentifier;
|
||||
@@ -265,10 +265,10 @@ public class NTPContext implements Closeable, AutoCloseable {
|
||||
|
||||
private List<PeerInfo> mergeResponses() {
|
||||
List<PeerInfo> peerInfo = new ArrayList<PeerInfo>();
|
||||
Set<Entry<MultipurposeSocketAddress, List<NTPv4Packet>>> ens = recvmap.entrySet();
|
||||
for (Iterator<Entry<MultipurposeSocketAddress, List<NTPv4Packet>>> iterator = ens.iterator(); iterator
|
||||
Set<Entry<MultiProtocolSocketAddress, List<NTPv4Packet>>> ens = recvmap.entrySet();
|
||||
for (Iterator<Entry<MultiProtocolSocketAddress, List<NTPv4Packet>>> iterator = ens.iterator(); iterator
|
||||
.hasNext();) {
|
||||
Entry<MultipurposeSocketAddress, List<NTPv4Packet>> entry = (Entry<MultipurposeSocketAddress, List<NTPv4Packet>>) iterator
|
||||
Entry<MultiProtocolSocketAddress, List<NTPv4Packet>> entry = (Entry<MultiProtocolSocketAddress, List<NTPv4Packet>>) iterator
|
||||
.next();
|
||||
|
||||
List<NTPv4Packet> newv = entry.getValue();
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
package org.kne.cloud.network.ntp;
|
||||
|
||||
import org.kne.cloud.network.UDPSocketType;
|
||||
|
||||
public class NTPSocketType extends UDPSocketType {
|
||||
private static final NTPSocketType INSTANCE = new NTPSocketType();
|
||||
|
||||
private NTPSocketType() {
|
||||
super();
|
||||
}
|
||||
|
||||
public static NTPSocketType getInstance() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDefaultPort() {
|
||||
return 123;
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,8 @@ import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
import org.kne.cloud.clock.HighAccuracyClock;
|
||||
import org.kne.cloud.clock.NTPTimestamps;
|
||||
import org.kne.cloud.network.MultipurposeSocketAddress;
|
||||
import org.kne.cloud.network.MultiProtocolSocketAddress;
|
||||
import org.kne.cloud.network.ThreadTool;
|
||||
import org.kne.io.KNEChannels;
|
||||
import org.kne.math.Long128;
|
||||
|
||||
@@ -32,7 +33,7 @@ public class NTPv4Protocol implements Closeable, AutoCloseable {
|
||||
private static final long EPHEMERAL_TIMEOUT = 60000000000L;
|
||||
|
||||
public static class NTPPeer {
|
||||
private MultipurposeSocketAddress address;
|
||||
private MultiProtocolSocketAddress address;
|
||||
private boolean isEphemeral;// ephemeral
|
||||
private volatile long ephemeralUpdateTime = System.nanoTime();
|
||||
private volatile long pollInterval = DEFAULT_POLL_INTERVAL;
|
||||
@@ -70,11 +71,11 @@ public class NTPv4Protocol implements Closeable, AutoCloseable {
|
||||
this.pollInterval = pollInterval;
|
||||
}
|
||||
|
||||
public NTPPeer(MultipurposeSocketAddress address, int requestMode) {
|
||||
public NTPPeer(MultiProtocolSocketAddress address, int requestMode) {
|
||||
this(address, requestMode, false);
|
||||
}
|
||||
|
||||
protected NTPPeer(MultipurposeSocketAddress address, int requestMode, boolean isEphemeral) {
|
||||
protected NTPPeer(MultiProtocolSocketAddress address, int requestMode, boolean isEphemeral) {
|
||||
super();
|
||||
checkRequestMode(requestMode);
|
||||
this.address = address;
|
||||
@@ -90,10 +91,10 @@ public class NTPv4Protocol implements Closeable, AutoCloseable {
|
||||
}
|
||||
|
||||
public NTPPeer(String hostport, int requestMode) {
|
||||
this(new MultipurposeSocketAddress(hostport), requestMode);
|
||||
this(new MultiProtocolSocketAddress(hostport), requestMode);
|
||||
}
|
||||
|
||||
public MultipurposeSocketAddress getAddress() {
|
||||
public MultiProtocolSocketAddress getAddress() {
|
||||
return address;
|
||||
}
|
||||
|
||||
@@ -153,17 +154,28 @@ public class NTPv4Protocol implements Closeable, AutoCloseable {
|
||||
|
||||
private NTPContext context;
|
||||
private DatagramSocket dgs;
|
||||
private MultipurposeSocketAddress bind;
|
||||
private MultiProtocolSocketAddress bind;
|
||||
|
||||
public NTPv4Protocol(NTPContext context) throws UnknownHostException, IOException {
|
||||
this(context, new MultipurposeSocketAddress("UDP", "::0", NTP_DEFAULT_PORT));
|
||||
this(context, new MultiProtocolSocketAddress("UDP", "::0", NTP_DEFAULT_PORT));
|
||||
}
|
||||
|
||||
public NTPv4Protocol(NTPContext context, MultipurposeSocketAddress bind) throws UnknownHostException, IOException {
|
||||
public NTPv4Protocol(NTPContext context, MultiProtocolSocketAddress bind) throws UnknownHostException, IOException {
|
||||
this.bind = bind;
|
||||
this.context = context;
|
||||
dgs = bind.listenDatagramSocket();
|
||||
context.registerIO(this);
|
||||
Thread t4= ThreadTool.makeVThread("NTPv4 Packet Cleaner",()->{
|
||||
while(isClosed()){
|
||||
removeTimeoutTimestamp();
|
||||
try {
|
||||
Thread.sleep(1000);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
});
|
||||
t4.start();
|
||||
Thread tr = new Thread(recv);
|
||||
tr.setName("NTPv4 Receive Thread");
|
||||
tr.start();
|
||||
@@ -219,7 +231,6 @@ public class NTPv4Protocol implements Closeable, AutoCloseable {
|
||||
private void putOriginTimestamp(NTPv4Packet nv4, NTPPeer peer) {
|
||||
checkLock.lock();
|
||||
try {
|
||||
removeTimeoutTimestamp();
|
||||
checkList.add(new CheckListItem(nv4.getTransmitTimestamp128(), peer));
|
||||
} finally {
|
||||
checkLock.unlock();
|
||||
@@ -227,7 +238,9 @@ public class NTPv4Protocol implements Closeable, AutoCloseable {
|
||||
}
|
||||
|
||||
private void removeTimeoutTimestamp() {
|
||||
for (Iterator<CheckListItem> iterator = checkList.iterator(); iterator.hasNext();) {
|
||||
checkLock.lock();
|
||||
try {
|
||||
for (Iterator<CheckListItem> iterator = checkList.iterator(); iterator.hasNext(); ) {
|
||||
CheckListItem bigInteger = (CheckListItem) iterator.next();
|
||||
if (bigInteger.checkTimeout()) {
|
||||
if (showPacket)
|
||||
@@ -235,6 +248,9 @@ public class NTPv4Protocol implements Closeable, AutoCloseable {
|
||||
iterator.remove();
|
||||
}
|
||||
}
|
||||
}finally{
|
||||
checkLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
private void sendNTPPacket(NTPv4Packet nv4, SocketAddress target) throws IOException {
|
||||
@@ -266,7 +282,7 @@ public class NTPv4Protocol implements Closeable, AutoCloseable {
|
||||
return null;
|
||||
}
|
||||
|
||||
NTPPeer findPeer(MultipurposeSocketAddress addr) {
|
||||
NTPPeer findPeer(MultiProtocolSocketAddress addr) {
|
||||
Object[] objs = peers.toArray();
|
||||
for (int i = 0; i < objs.length; i++) {
|
||||
NTPPeer np = (NTPPeer) objs[i];
|
||||
@@ -291,7 +307,7 @@ public class NTPv4Protocol implements Closeable, AutoCloseable {
|
||||
while (!dgs.isClosed()) {
|
||||
AtomicReference<InetSocketAddress> isa = new AtomicReference<>();
|
||||
NTPv4Packet nv4 = receiveNTPPacket(isa);
|
||||
MultipurposeSocketAddress mpsafrom = new MultipurposeSocketAddress(bind.getType(), isa.get());
|
||||
MultiProtocolSocketAddress mpsafrom = new MultiProtocolSocketAddress(bind.getProtocol(), isa.get());
|
||||
switch (nv4.getMode()) {
|
||||
case NTPv4Packet.NTP_SYMMETRIC_ACTIVE:
|
||||
NTPv4Packet nv4r = new NTPv4Packet(context.getClock());
|
||||
@@ -393,7 +409,7 @@ public class NTPv4Protocol implements Closeable, AutoCloseable {
|
||||
HighAccuracyClock hac = new HighAccuracyClock();
|
||||
NTPContext context = new NTPContext(hac);
|
||||
System.out.println(context);
|
||||
NTPv4Protocol nvc = new NTPv4Protocol(context, new MultipurposeSocketAddress("{UDP}0.0.0.0:123"));// 106.55.184.199
|
||||
NTPv4Protocol nvc = new NTPv4Protocol(context, new MultiProtocolSocketAddress("{UDP}0.0.0.0:123"));// 106.55.184.199
|
||||
nvc.getPeers().add(new NTPPeer("{UDP}106.55.184.199:123", NTPv4Packet.NTP_CLIENT));
|
||||
nvc.getPeers().add(new NTPPeer("{UDP}time.windows.com:123", NTPv4Packet.NTP_CLIENT));
|
||||
nvc.getPeers().add(new NTPPeer("{UDP}127.0.0.1:123", NTPv4Packet.NTP_SYMMETRIC_ACTIVE));
|
||||
|
||||
@@ -4,8 +4,7 @@ import java.io.IOException;
|
||||
import java.net.UnknownHostException;
|
||||
|
||||
import org.kne.cloud.clock.HighAccuracyClock;
|
||||
import org.kne.cloud.network.MultipurposeSocketAddress;
|
||||
import org.kne.cloud.network.ntp.NTPv4Protocol.NTPPeer;
|
||||
import org.kne.cloud.network.MultiProtocolSocketAddress;
|
||||
|
||||
public class TestNTP1 {
|
||||
public static void main(String[] args) throws UnknownHostException, IOException {
|
||||
@@ -13,7 +12,7 @@ public class TestNTP1 {
|
||||
NTPContext context = new NTPContext(hac);
|
||||
context.syncToSystem();
|
||||
System.out.println(context);
|
||||
NTPv4Protocol nvc = new NTPv4Protocol(context, new MultipurposeSocketAddress("{UDP}0.0.0.0:123"));// 106.55.184.199
|
||||
NTPv4Protocol nvc = new NTPv4Protocol(context, new MultiProtocolSocketAddress("{UDP}0.0.0.0:123"));// 106.55.184.199
|
||||
//nvc.getPeers().add(new NTPPeer("{UDP}106.55.184.199:123", NTPv4Packet.NTP_CLIENT));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import java.io.IOException;
|
||||
import java.net.UnknownHostException;
|
||||
|
||||
import org.kne.cloud.clock.HighAccuracyClock;
|
||||
import org.kne.cloud.network.MultipurposeSocketAddress;
|
||||
import org.kne.cloud.network.MultiProtocolSocketAddress;
|
||||
import org.kne.cloud.network.ntp.NTPv4Protocol.NTPPeer;
|
||||
|
||||
public class TestNTP2 {
|
||||
@@ -13,7 +13,7 @@ public class TestNTP2 {
|
||||
NTPContext context = new NTPContext(hac);
|
||||
context.syncToSystem();
|
||||
System.out.println(context);
|
||||
NTPv4Protocol nvc = new NTPv4Protocol(context, new MultipurposeSocketAddress("{UDP}0.0.0.0:0"));// 106.55.184.199
|
||||
NTPv4Protocol nvc = new NTPv4Protocol(context, new MultiProtocolSocketAddress("{UDP}0.0.0.0:0"));// 106.55.184.199
|
||||
nvc.getPeers().add(new NTPPeer("{UDP}127.0.0.1:123", NTPv4Packet.NTP_SYMMETRIC_ACTIVE));
|
||||
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
|
||||
import org.kne.cloud.clock.AdjustedNanoClock;
|
||||
import org.kne.cloud.clock.HighAccuracyClock;
|
||||
import org.kne.cloud.network.MultipurposeSocketAddress;
|
||||
import org.kne.cloud.network.MultiProtocolSocketAddress;
|
||||
import org.kne.cloud.network.SpeedLimiter;
|
||||
import org.kne.cloud.network.ThreadTool;
|
||||
import org.kne.cloud.network.klalb.KLALBPacket;
|
||||
@@ -32,8 +32,8 @@ public class Kperf implements Runnable{
|
||||
|
||||
private static final boolean showpacket = false;
|
||||
private volatile AdjustedNanoClock adjnc = new AdjustedNanoClock();
|
||||
private MultipurposeSocketAddress targetAddress;
|
||||
private MultipurposeSocketAddress bindAddress;
|
||||
private MultiProtocolSocketAddress targetAddress;
|
||||
private MultiProtocolSocketAddress bindAddress;
|
||||
private volatile boolean connected=false;
|
||||
private volatile KLALBPacketLink link;
|
||||
|
||||
@@ -43,13 +43,13 @@ public class Kperf implements Runnable{
|
||||
private volatile boolean closed = false;
|
||||
|
||||
private volatile ThreadParker tlock=new ThreadParker();
|
||||
public Kperf(MultipurposeSocketAddress targetAddress,MultipurposeSocketAddress bindAddress) {
|
||||
public Kperf(MultiProtocolSocketAddress targetAddress, MultiProtocolSocketAddress bindAddress) {
|
||||
Objects.requireNonNull(targetAddress);
|
||||
this.targetAddress=targetAddress;
|
||||
this.bindAddress=bindAddress;
|
||||
}
|
||||
public Kperf(MultipurposeSocketAddress targetAddress) {
|
||||
this(targetAddress,new MultipurposeSocketAddress("[::0]:0"));
|
||||
public Kperf(MultiProtocolSocketAddress targetAddress) {
|
||||
this(targetAddress,new MultiProtocolSocketAddress("[::0]:0"));
|
||||
}
|
||||
public Kperf(KLALBPacketLink link) {
|
||||
Objects.requireNonNull(link);
|
||||
|
||||
@@ -7,7 +7,7 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
import org.kne.cloud.network.MultipurposeSocketAddress;
|
||||
import org.kne.cloud.network.MultiProtocolSocketAddress;
|
||||
import org.kne.cloud.network.SocketChannelListener;
|
||||
import org.kne.cloud.network.ThreadTool;
|
||||
import org.kne.cloud.network.klalb.KLALBController;
|
||||
@@ -58,7 +58,7 @@ for (int i = 0; i < nodes; i++) {
|
||||
for (int j = 0; j < 1; j++) {
|
||||
int ind=r.nextInt(size);
|
||||
NodeEntry ne=nodeList.get(ind);
|
||||
MultipurposeSocketAddress mpsa=new MultipurposeSocketAddress((InetSocketAddress) ne.getScl().getServerSocketChannel().getLocalAddress());
|
||||
MultiProtocolSocketAddress mpsa=new MultiProtocolSocketAddress((InetSocketAddress) ne.getScl().getServerSocketChannel().getLocalAddress());
|
||||
kct.addRemoteLines(mpsa);
|
||||
System.out.println("connect:" +ne.getKc().getSelf().getAddress());
|
||||
|
||||
@@ -75,7 +75,7 @@ for (int i = 0; i < nodes; i++) {
|
||||
}
|
||||
try {
|
||||
NodeEntry ne=nodeList.get(0);
|
||||
MultipurposeSocketAddress mpsa=new MultipurposeSocketAddress((InetSocketAddress) ne.getScl().getServerSocketChannel().getLocalAddress());
|
||||
MultiProtocolSocketAddress mpsa=new MultiProtocolSocketAddress((InetSocketAddress) ne.getScl().getServerSocketChannel().getLocalAddress());
|
||||
klalbController.addRemoteLines(mpsa);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
|
||||
@@ -4,7 +4,7 @@ import java.io.Serializable;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.kne.cloud.network.MultipurposeSocketAddress;
|
||||
import org.kne.cloud.network.MultiProtocolSocketAddress;
|
||||
|
||||
public class ScanRange implements Serializable {
|
||||
public ScanRange(String range) throws UnknownHostException {
|
||||
@@ -12,21 +12,21 @@ public class ScanRange implements Serializable {
|
||||
}
|
||||
protected void setMsaString(String range) throws UnknownHostException {
|
||||
String[]sp=range.split("~");
|
||||
MultipurposeSocketAddress begin=new MultipurposeSocketAddress(sp[0]);
|
||||
MultipurposeSocketAddress end;
|
||||
MultiProtocolSocketAddress begin=new MultiProtocolSocketAddress(sp[0]);
|
||||
MultiProtocolSocketAddress end;
|
||||
if(sp.length>1) {
|
||||
end=new MultipurposeSocketAddress(sp[1]);
|
||||
end=new MultiProtocolSocketAddress(sp[1]);
|
||||
}else {
|
||||
end=new MultipurposeSocketAddress(sp[0]);
|
||||
end=new MultiProtocolSocketAddress(sp[0]);
|
||||
}
|
||||
setMsa(begin, end);
|
||||
}
|
||||
public ScanRange(MultipurposeSocketAddress begin,MultipurposeSocketAddress end) throws UnknownHostException {
|
||||
public ScanRange(MultiProtocolSocketAddress begin, MultiProtocolSocketAddress end) throws UnknownHostException {
|
||||
setMsa(begin,end);
|
||||
|
||||
}
|
||||
|
||||
protected void setMsa(MultipurposeSocketAddress begin, MultipurposeSocketAddress end) throws UnknownHostException {
|
||||
protected void setMsa(MultiProtocolSocketAddress begin, MultiProtocolSocketAddress end) throws UnknownHostException {
|
||||
this.addressRange=new InetAddressRange(begin.getHost(), end.getHost());
|
||||
this.portRange=new PortRange(begin.getPort(), end.getPort());
|
||||
}
|
||||
@@ -47,7 +47,7 @@ public class ScanRange implements Serializable {
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return new MultipurposeSocketAddress(addressRange.getBeginHost(), portRange.getBegin())+"~"+new MultipurposeSocketAddress(addressRange.getEndHost(), portRange.getEnd());
|
||||
return new MultiProtocolSocketAddress(addressRange.getBeginHost(), portRange.getBegin())+"~"+new MultiProtocolSocketAddress(addressRange.getEndHost(), portRange.getEnd());
|
||||
}
|
||||
@Override
|
||||
public int hashCode() {
|
||||
|
||||
@@ -10,7 +10,6 @@ import java.net.UnknownHostException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
@@ -20,11 +19,11 @@ import java.util.concurrent.locks.ReentrantLock;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.kne.cloud.network.MultipurposeSocketAddress;
|
||||
import org.kne.cloud.network.MultiProtocolSocketAddress;
|
||||
import org.kne.cloud.network.ThreadTool;
|
||||
|
||||
public class TCPNetworkScanner implements Runnable {
|
||||
private BiConsumer<MultipurposeSocketAddress,Socket> consumer;
|
||||
private BiConsumer<MultiProtocolSocketAddress,Socket> consumer;
|
||||
private Consumer<Float>processConsumer;
|
||||
|
||||
public Consumer<Float> getProcessConsumer() {
|
||||
@@ -44,11 +43,11 @@ public class TCPNetworkScanner implements Runnable {
|
||||
return ranges;
|
||||
}
|
||||
|
||||
public BiConsumer<MultipurposeSocketAddress,Socket> getConsumer() {
|
||||
public BiConsumer<MultiProtocolSocketAddress,Socket> getConsumer() {
|
||||
return consumer;
|
||||
}
|
||||
|
||||
public void setConsumer(BiConsumer<MultipurposeSocketAddress,Socket> consumer) {
|
||||
public void setConsumer(BiConsumer<MultiProtocolSocketAddress,Socket> consumer) {
|
||||
this.consumer = consumer;
|
||||
}
|
||||
|
||||
@@ -128,13 +127,13 @@ ThreadPoolExecutor excd=(ThreadPoolExecutor) Executors.newFixedThreadPool(8);
|
||||
exc.execute(()->{
|
||||
|
||||
|
||||
MultipurposeSocketAddress mpsa;
|
||||
MultiProtocolSocketAddress mpsa;
|
||||
if (currAddress.equals(inetAddressRange.getBegin())) {
|
||||
mpsa = new MultipurposeSocketAddress(inetAddressRange.getBeginHost(), curport0);
|
||||
mpsa = new MultiProtocolSocketAddress(inetAddressRange.getBeginHost(), curport0);
|
||||
}else if (currAddress.equals(inetAddressRange.getEnd())) {
|
||||
mpsa = new MultipurposeSocketAddress(inetAddressRange.getEndHost(), curport0);
|
||||
mpsa = new MultiProtocolSocketAddress(inetAddressRange.getEndHost(), curport0);
|
||||
} else {
|
||||
mpsa = new MultipurposeSocketAddress(currAddress.getHostAddress(),
|
||||
mpsa = new MultiProtocolSocketAddress(currAddress.getHostAddress(),
|
||||
curport0);
|
||||
}
|
||||
Socket s = null;
|
||||
@@ -228,7 +227,7 @@ ThreadPoolExecutor excd=(ThreadPoolExecutor) Executors.newFixedThreadPool(8);
|
||||
System.arraycopy( new BigInteger(curr).add(BigInteger.valueOf(1)).toByteArray(), 0, curr, 0, curr.length); ;
|
||||
}
|
||||
|
||||
protected void acceptMultipurposeSocketAddress(MultipurposeSocketAddress scanned, Socket s) {
|
||||
protected void acceptMultipurposeSocketAddress(MultiProtocolSocketAddress scanned, Socket s) {
|
||||
|
||||
consumer.accept(scanned,s);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
package org.kne.cloud.network.srv6;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.kne.cloud.network.MultiProtocolSocketAddress;
|
||||
|
||||
public final class ExternalEndpointsResult {
|
||||
private final List<MultiProtocolSocketAddress> externalEndpoints;
|
||||
private final NodeInfoQueryStatus status;
|
||||
|
||||
public ExternalEndpointsResult(List<MultiProtocolSocketAddress> externalEndpoints, NodeInfoQueryStatus status) {
|
||||
this.externalEndpoints = Collections.unmodifiableList(new ArrayList<MultiProtocolSocketAddress>(
|
||||
externalEndpoints == null ? Collections.<MultiProtocolSocketAddress>emptyList() : externalEndpoints));
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public List<MultiProtocolSocketAddress> getExternalEndpoints() {
|
||||
return externalEndpoints;
|
||||
}
|
||||
|
||||
public NodeInfoQueryStatus getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(externalEndpoints, status);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (!(obj instanceof ExternalEndpointsResult)) {
|
||||
return false;
|
||||
}
|
||||
ExternalEndpointsResult other = (ExternalEndpointsResult) obj;
|
||||
return Objects.equals(externalEndpoints, other.externalEndpoints) && status == other.status;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ExternalEndpointsResult [externalEndpoints=" + externalEndpoints + ", status=" + status + "]";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package org.kne.cloud.network.srv6;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
public final class ExtraRoutesResult {
|
||||
private final List<String> extraRoutes;
|
||||
private final NodeInfoQueryStatus status;
|
||||
|
||||
public ExtraRoutesResult(List<String> extraRoutes, NodeInfoQueryStatus status) {
|
||||
this.extraRoutes = Collections.unmodifiableList(new ArrayList<String>(
|
||||
extraRoutes == null ? Collections.<String>emptyList() : extraRoutes));
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public List<String> getExtraRoutes() {
|
||||
return extraRoutes;
|
||||
}
|
||||
|
||||
public NodeInfoQueryStatus getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(extraRoutes, status);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (!(obj instanceof ExtraRoutesResult)) {
|
||||
return false;
|
||||
}
|
||||
ExtraRoutesResult other = (ExtraRoutesResult) obj;
|
||||
return Objects.equals(extraRoutes, other.extraRoutes) && status == other.status;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ExtraRoutesResult [extraRoutes=" + extraRoutes + ", status=" + status + "]";
|
||||
}
|
||||
}
|
||||
@@ -1,29 +1,14 @@
|
||||
package org.kne.cloud.network.srv6;
|
||||
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.EOFException;
|
||||
import java.io.Externalizable;
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInput;
|
||||
import java.io.ObjectOutput;
|
||||
import java.io.Serializable;
|
||||
import java.net.Inet6Address;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.Channels;
|
||||
import java.nio.channels.ReadableByteChannel;
|
||||
import java.nio.channels.WritableByteChannel;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
import org.kne.cloud.network.MultipurposeSocketAddress;
|
||||
import org.kne.cloud.network.NetworkPacket;
|
||||
import org.kne.cloud.network.ipv6.IPv6AddressGroup;
|
||||
import org.kne.cloud.network.MultiProtocolSocketAddress;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
@@ -49,11 +34,12 @@ public class JsonDataPacket extends KLALBRoutingProtocolPacket implements Serial
|
||||
private String data;
|
||||
|
||||
private static final int HEADER_LENGTH=3;
|
||||
private static final int MAX_JSON_PAYLOAD_LENGTH=65535-HEADER_LENGTH;
|
||||
|
||||
private static Gson gson;
|
||||
static{
|
||||
GsonBuilder gb=new GsonBuilder();
|
||||
MultipurposeSocketAddress.registerToGsonBuilder(gb);
|
||||
MultiProtocolSocketAddress.registerToGsonBuilder(gb);
|
||||
gson=gb.create();
|
||||
}
|
||||
|
||||
@@ -90,6 +76,9 @@ public class JsonDataPacket extends KLALBRoutingProtocolPacket implements Serial
|
||||
@Override
|
||||
public void writeToChannel(WritableByteChannel dto) throws IOException {
|
||||
byte[]bta=data.getBytes(Charset.forName("UTF-8"));
|
||||
if(bta.length>MAX_JSON_PAYLOAD_LENGTH) {
|
||||
throw new IOException("JSON payload exceeds " + MAX_JSON_PAYLOAD_LENGTH + " bytes");
|
||||
}
|
||||
getHeader().putChar(1,(char) bta.length);
|
||||
super.writeToChannel(dto);
|
||||
dto.write(ByteBuffer.wrap(bta));
|
||||
|
||||
@@ -14,6 +14,7 @@ import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
@@ -21,6 +22,8 @@ import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.CopyOnWriteArraySet;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
@@ -43,6 +46,14 @@ public class KLALBRoutingProtocol extends Thread{
|
||||
|
||||
private RouterInfo selfRouterInfo;
|
||||
private Map<IPv6Address, RouterInfo> netmap=new ConcurrentHashMap<>();
|
||||
private final AtomicLong selfCreateTime = new AtomicLong();
|
||||
private final AtomicInteger pendingNodeInfoUpdateFlags = new AtomicInteger();
|
||||
private static final int NODE_INFO_UPDATE_REPLAY_WINDOW_SIZE = 64;
|
||||
private final Map<IPv6Address, LinkedHashSet<Long>> seenNodeInfoUpdates = new HashMap<>();
|
||||
|
||||
public Map<IPv6Address, RouterInfo> getNetmap() {
|
||||
return netmap;
|
||||
}
|
||||
|
||||
|
||||
private volatile Map<IPv6Address,Long>addresses;
|
||||
@@ -81,6 +92,7 @@ public class KLALBRoutingProtocol extends Thread{
|
||||
private ReentrantLock sendLock=new ReentrantLock();
|
||||
|
||||
private Set<BiConsumer<SocketAddress,JsonDataPacket>>receivers=new CopyOnWriteArraySet<BiConsumer<SocketAddress,JsonDataPacket>>();
|
||||
private Set<BiConsumer<IPv6Address,Integer>> nodeInfoUpdateListeners = new CopyOnWriteArraySet<BiConsumer<IPv6Address,Integer>>();
|
||||
|
||||
public void addReceiver(BiConsumer<SocketAddress,JsonDataPacket> rec) {
|
||||
receivers.add(rec);
|
||||
@@ -90,6 +102,57 @@ public class KLALBRoutingProtocol extends Thread{
|
||||
receivers.remove(rec);
|
||||
}
|
||||
|
||||
public void addNodeInfoUpdateListener(BiConsumer<IPv6Address,Integer> listener) {
|
||||
nodeInfoUpdateListeners.add(listener);
|
||||
}
|
||||
|
||||
public void removeNodeInfoUpdateListener(BiConsumer<IPv6Address,Integer> listener) {
|
||||
nodeInfoUpdateListeners.remove(listener);
|
||||
}
|
||||
|
||||
public void announceNodeInfoUpdate(int flags) {
|
||||
int validFlags = flags & (RouterInfoPacket.NODE_INFO_TINY_UPDATE_REQUIRED
|
||||
| RouterInfoPacket.NODE_INFO_FULL_UPDATE_REQUIRED);
|
||||
if(validFlags != 0) {
|
||||
pendingNodeInfoUpdateFlags.getAndUpdate(old -> old | validFlags);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean markNodeInfoUpdateSeen(IPv6Address origin, long createTime) {
|
||||
synchronized(seenNodeInfoUpdates) {
|
||||
LinkedHashSet<Long> createTimes = seenNodeInfoUpdates.get(origin);
|
||||
if(createTimes == null) {
|
||||
createTimes = new LinkedHashSet<>();
|
||||
seenNodeInfoUpdates.put(origin, createTimes);
|
||||
}
|
||||
if(!createTimes.add(createTime)) {
|
||||
return false;
|
||||
}
|
||||
while(createTimes.size() > NODE_INFO_UPDATE_REPLAY_WINDOW_SIZE) {
|
||||
createTimes.remove(createTimes.iterator().next());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private void removeNodeInfoUpdateWindow(IPv6Address origin) {
|
||||
synchronized(seenNodeInfoUpdates) {
|
||||
seenNodeInfoUpdates.remove(origin);
|
||||
}
|
||||
}
|
||||
|
||||
private void notifyNodeInfoUpdate(IPv6Address origin, int flags) {
|
||||
for(BiConsumer<IPv6Address,Integer> listener : nodeInfoUpdateListeners) {
|
||||
try {
|
||||
listener.accept(origin, flags);
|
||||
} catch(Throwable e) {
|
||||
if(debug) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
Thread.currentThread().setName("KLALB路由协议接收线程");
|
||||
@@ -104,6 +167,17 @@ public class KLALBRoutingProtocol extends Thread{
|
||||
selfRouterInfo = getSelfRouterInfo();
|
||||
RouterInfo oslf=netmap.put(selfRouterInfo.getLocator().getAddress(), selfRouterInfo);
|
||||
noticeUpdate();
|
||||
int nodeInfoUpdateFlags = pendingNodeInfoUpdateFlags.getAndSet(0);
|
||||
if(nodeInfoUpdateFlags != 0) {
|
||||
RouterInfoPacket updatePacket = new RouterInfoPacket(getSelfRouterInfo(), true, -1);
|
||||
updatePacket.setNodeInfoUpdateFlags(nodeInfoUpdateFlags);
|
||||
try {
|
||||
floodPacket(null, updatePacket);
|
||||
} catch(IOException e) {
|
||||
pendingNodeInfoUpdateFlags.getAndUpdate(old -> old | nodeInfoUpdateFlags);
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
/*if(oslf==null||(!selfRouterInfo.equals(oslf))) {
|
||||
long cur=System.nanoTime();
|
||||
if(cur-floodTimer2>5000000000L) {
|
||||
@@ -144,9 +218,11 @@ public class KLALBRoutingProtocol extends Thread{
|
||||
timeout=2000000000L;
|
||||
}
|
||||
if(val.checkTimeOut(timeout)) {
|
||||
iterator.remove();
|
||||
if(netmap.remove(type.getKey(), val)) {
|
||||
removeNodeInfoUpdateWindow(type.getKey());
|
||||
noticeUpdate();
|
||||
}
|
||||
}
|
||||
/*List<NeighborInfo> ads=val.getNeighborAddresses();
|
||||
for (Iterator<NeighborInfo> iterator2 = ads.iterator(); iterator2.hasNext();) {
|
||||
Inet6Address address=iterator2.next().getLocator().getAddress();
|
||||
@@ -212,7 +288,7 @@ public class KLALBRoutingProtocol extends Thread{
|
||||
ds.receive(dgp);
|
||||
//System.out.println(Arrays.toString( Arrays.copyOf( dgp.getData(),dgp.getLength())));
|
||||
ByteArrayInputStream bi=new ByteArrayInputStream(dgp.getData(),0,dgp.getLength());
|
||||
KLALBRoutingProtocolPacket kp=KLALBRoutingProtocolPacket.readKLALBPacketFromChannel(Channels.newChannel(bi));
|
||||
KLALBRoutingProtocolPacket kp=KLALBRoutingProtocolPacket.readKLALBPacketFromChannel(Channels.newChannel(bi), dgp.getLength());
|
||||
|
||||
switch(kp.getType()) {
|
||||
case KLALBRoutingProtocolPacket.RINFO_REQ:
|
||||
@@ -233,24 +309,30 @@ public class KLALBRoutingProtocol extends Thread{
|
||||
RouterInfoPacket rifp=(RouterInfoPacket) kp;
|
||||
RouterInfo rif=rifp.getRinfo();
|
||||
RouterInfo oldrif=netmap.get(rif.getLocator().getAddress());
|
||||
IPv6Address origin = rif.getLocator().getAddress();
|
||||
int nodeInfoUpdateFlags = rifp.getNodeInfoUpdateFlags();
|
||||
boolean newNodeInfoUpdate = nodeInfoUpdateFlags != 0
|
||||
&& markNodeInfoUpdateSeen(origin, rif.getCreateTime());
|
||||
|
||||
if(debug)
|
||||
System.out.println(rif);
|
||||
if(oldrif==null||oldrif.getCreateTime()<rif.getCreateTime()) {
|
||||
boolean topologyUpdated = oldrif==null||oldrif.getCreateTime()<rif.getCreateTime();
|
||||
if(topologyUpdated) {
|
||||
if(debug)
|
||||
System.out.println("update RouterInfo");
|
||||
netmap.put(rif.getLocator().getAddress(), rif);
|
||||
noticeUpdate();
|
||||
if(rifp.isFlood()) {
|
||||
floodPacket( dgp.getSocketAddress(), rifp);
|
||||
|
||||
}
|
||||
|
||||
}else {
|
||||
|
||||
if(debug)
|
||||
System.out.println("dispose RouterInfo");
|
||||
}
|
||||
if(newNodeInfoUpdate) {
|
||||
notifyNodeInfoUpdate(origin, nodeInfoUpdateFlags);
|
||||
}
|
||||
if(rifp.isFlood() && (topologyUpdated || newNodeInfoUpdate)) {
|
||||
floodPacket(dgp.getSocketAddress(), rifp);
|
||||
}
|
||||
computeShortestPathIfUpdated();
|
||||
break;
|
||||
case KLALBRoutingProtocolPacket.JSON_DATA:
|
||||
@@ -313,6 +395,7 @@ public class KLALBRoutingProtocol extends Thread{
|
||||
}catch(BindException e) {
|
||||
e.printStackTrace();
|
||||
System.err.println("Cannot assign:"+dest);
|
||||
throw e;
|
||||
}finally {
|
||||
sendLock.unlock();
|
||||
}
|
||||
@@ -544,7 +627,9 @@ public class KLALBRoutingProtocol extends Thread{
|
||||
private RouterInfo getSelfRouterInfo() {
|
||||
List<IPv6NetworkLink>links=router.getLinkTabel();
|
||||
Object[] nls=links.toArray();
|
||||
RouterInfo ri=new RouterInfo(System.currentTimeMillis(),router.getLocator(),router.getASN());
|
||||
long currentTime = System.currentTimeMillis();
|
||||
long createTime = selfCreateTime.updateAndGet(previous -> Math.max(currentTime, previous + 1));
|
||||
RouterInfo ri=new RouterInfo(createTime,router.getLocator(),router.getASN());
|
||||
for(int i=0;i<nls.length;i++) {
|
||||
IPv6NetworkLink nl=(IPv6NetworkLink) nls[i];
|
||||
if((!nl.isLoopBack())&&nl.isUp()) {
|
||||
@@ -595,6 +680,13 @@ public class KLALBRoutingProtocol extends Thread{
|
||||
public Map<IPv6Address, Long> getAddresses() {
|
||||
return addresses;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询某地址广播的设备名称(未知返回null)
|
||||
*/
|
||||
public String getDeviceName(IPv6Address address) {
|
||||
return null;
|
||||
}
|
||||
public Map<IPv6Address, List<LinkDirection>> getPaths() {
|
||||
return paths;
|
||||
}
|
||||
|
||||
@@ -3,16 +3,19 @@ package org.kne.cloud.network.srv6;
|
||||
import java.io.IOException;
|
||||
import java.lang.ref.Cleaner;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.InetAddress;
|
||||
import java.net.SocketAddress;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.kne.cloud.network.MultipurposeSocketAddress;
|
||||
import org.kne.cloud.network.MultiProtocolSocketAddress;
|
||||
import org.kne.cloud.network.congestion.NOCongestionAlgorithm;
|
||||
import org.kne.cloud.network.congestion.SendPacketSlidingWindow;
|
||||
import org.kne.cloud.network.klalb.SendItem;
|
||||
import org.kne.opencl64.Releaser;
|
||||
|
||||
public class KLALBRoutingProtocolAPIClient {
|
||||
@@ -24,31 +27,106 @@ public class KLALBRoutingProtocolAPIClient {
|
||||
|
||||
private BiConsumer<SocketAddress, JsonDataPacket> rec = (addr, data) -> {
|
||||
KLALBRoutingProtocolJsonData dataobj = data.getDecodedData();
|
||||
InetSocketAddress addrs = (InetSocketAddress) addr;
|
||||
UUID ruid = dataobj.getUuid();
|
||||
JsonDataPacket relate = null;
|
||||
//System.out.println(ruid + " " + window.getSendmap());
|
||||
switch (dataobj.getType()) {
|
||||
case KLALBRoutingProtocolJsonData.OPEN_LINES_RESP:
|
||||
|
||||
if ((relate = window.ack(ruid)) != null) {
|
||||
List<?> connects = (List<?>) dataobj.getData();
|
||||
List<MultipurposeSocketAddress> connectsm = new ArrayList<MultipurposeSocketAddress>(connects.size());
|
||||
for (Object open : connects) {
|
||||
if (open instanceof MultipurposeSocketAddress) {
|
||||
connectsm.add((MultipurposeSocketAddress) open);
|
||||
} else {
|
||||
connectsm.add(new MultipurposeSocketAddress((String) open));
|
||||
|
||||
case KLALBRoutingProtocolJsonData.NODE_PROFILE_RESP:
|
||||
PendingRequest profileRequest = acknowledgeResponse(ruid,
|
||||
KLALBRoutingProtocolJsonData.NODE_PROFILE_REQ, addr);
|
||||
if (profileRequest != null) {
|
||||
((Consumer<NodeProfile>) profileRequest.callback)
|
||||
.accept(new NodeProfile(dataobj.getDeviceName(), dataobj.getDeviceDescription()));
|
||||
}
|
||||
break;
|
||||
case KLALBRoutingProtocolJsonData.NODE_EXTERNAL_ENDPOINTS_RESP:
|
||||
PendingRequest endpointsRequest = acknowledgeResponse(ruid,
|
||||
KLALBRoutingProtocolJsonData.NODE_EXTERNAL_ENDPOINTS_REQ, addr);
|
||||
if (endpointsRequest != null) {
|
||||
((Consumer<ExternalEndpointsResult>) endpointsRequest.callback).accept(
|
||||
new ExternalEndpointsResult(decodeExternalEndpoints(dataobj.getData()), dataobj.getStatus()));
|
||||
}
|
||||
((Consumer<List<MultipurposeSocketAddress>>) relate.getUserCallback()).accept(connectsm);
|
||||
break;
|
||||
case KLALBRoutingProtocolJsonData.NODE_EXTRA_ROUTES_RESP:
|
||||
PendingRequest routesRequest = acknowledgeResponse(ruid,
|
||||
KLALBRoutingProtocolJsonData.NODE_EXTRA_ROUTES_REQ, addr);
|
||||
if (routesRequest != null) {
|
||||
((Consumer<ExtraRoutesResult>) routesRequest.callback).accept(
|
||||
new ExtraRoutesResult(decodeExtraRoutes(dataobj.getData()), dataobj.getStatus()));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
private PendingRequest acknowledgeResponse(UUID uuid, String expectedRequestType, SocketAddress sourceAddress) {
|
||||
SendItem<JsonDataPacket> pending = window.getSendmap().get(uuid);
|
||||
if (pending == null) {
|
||||
return null;
|
||||
}
|
||||
KLALBRoutingProtocolJsonData request = pending.getPacket().getDecodedData();
|
||||
if (request == null || !expectedRequestType.equals(request.getType())) {
|
||||
return null;
|
||||
}
|
||||
Object callback = pending.getPacket().getUserCallback();
|
||||
if (!(callback instanceof PendingRequest)) {
|
||||
return null;
|
||||
}
|
||||
PendingRequest pendingRequest = (PendingRequest) callback;
|
||||
if (!expectedRequestType.equals(pendingRequest.expectedRequestType)
|
||||
|| !sameDestination(pendingRequest.expectedDestination, sourceAddress)) {
|
||||
return null;
|
||||
}
|
||||
return window.ack(uuid) == null ? null : pendingRequest;
|
||||
}
|
||||
|
||||
private static boolean sameDestination(SocketAddress expected, SocketAddress actual) {
|
||||
if (expected instanceof InetSocketAddress && actual instanceof InetSocketAddress) {
|
||||
InetSocketAddress expectedInet = (InetSocketAddress) expected;
|
||||
InetSocketAddress actualInet = (InetSocketAddress) actual;
|
||||
if (expectedInet.getPort() != actualInet.getPort()) return false;
|
||||
InetAddress expectedAddress = expectedInet.getAddress();
|
||||
InetAddress actualAddress = actualInet.getAddress();
|
||||
if (expectedAddress != null && actualAddress != null) return expectedAddress.equals(actualAddress);
|
||||
if (expectedAddress == null && actualAddress == null) {
|
||||
return expectedInet.getHostString().equalsIgnoreCase(actualInet.getHostString());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return expected != null && expected.equals(actual);
|
||||
}
|
||||
|
||||
private List<MultiProtocolSocketAddress> decodeExternalEndpoints(Object data) {
|
||||
if (!(data instanceof List<?>)) {
|
||||
return new ArrayList<MultiProtocolSocketAddress>();
|
||||
}
|
||||
List<?> encoded = (List<?>) data;
|
||||
List<MultiProtocolSocketAddress> endpoints = new ArrayList<MultiProtocolSocketAddress>(encoded.size());
|
||||
for (Object value : encoded) {
|
||||
if (value instanceof MultiProtocolSocketAddress) {
|
||||
endpoints.add((MultiProtocolSocketAddress) value);
|
||||
} else if (value instanceof String) {
|
||||
try {
|
||||
endpoints.add(new MultiProtocolSocketAddress((String) value));
|
||||
} catch (RuntimeException ignored) {
|
||||
// Ignore malformed entries while preserving the rest of the response.
|
||||
}
|
||||
}
|
||||
}
|
||||
return endpoints;
|
||||
}
|
||||
|
||||
private List<String> decodeExtraRoutes(Object data) {
|
||||
if (!(data instanceof List<?>)) {
|
||||
return new ArrayList<String>();
|
||||
}
|
||||
List<?> encoded = (List<?>) data;
|
||||
List<String> routes = new ArrayList<String>(encoded.size());
|
||||
for (Object value : encoded) {
|
||||
if (value instanceof String) {
|
||||
routes.add((String) value);
|
||||
}
|
||||
}
|
||||
return routes;
|
||||
}
|
||||
|
||||
public KLALBRoutingProtocolAPIClient(KLALBRoutingProtocol routingProtocol) {
|
||||
this.routingProtocol = routingProtocol;
|
||||
routingProtocol.addReceiver(rec);
|
||||
@@ -56,16 +134,29 @@ public class KLALBRoutingProtocolAPIClient {
|
||||
clr.register(this, releaser);
|
||||
}
|
||||
|
||||
public void requestOpenLines(SocketAddress addr, Consumer<List<MultipurposeSocketAddress>> callback)
|
||||
public void requestNodeProfile(SocketAddress addr, Consumer<NodeProfile> callback) throws IOException {
|
||||
sendNodeInfoRequest(KLALBRoutingProtocolJsonData.NODE_PROFILE_REQ, addr, callback);
|
||||
}
|
||||
|
||||
public void requestExternalEndpoints(SocketAddress addr, Consumer<ExternalEndpointsResult> callback)
|
||||
throws IOException {
|
||||
sendNodeInfoRequest(KLALBRoutingProtocolJsonData.NODE_EXTERNAL_ENDPOINTS_REQ, addr, callback);
|
||||
}
|
||||
|
||||
public void requestExtraRoutes(SocketAddress addr, Consumer<ExtraRoutesResult> callback) throws IOException {
|
||||
sendNodeInfoRequest(KLALBRoutingProtocolJsonData.NODE_EXTRA_ROUTES_REQ, addr, callback);
|
||||
}
|
||||
|
||||
private void sendNodeInfoRequest(String type, SocketAddress addr, Object callback) throws IOException {
|
||||
UUID suid = UUID.randomUUID();
|
||||
KLALBRoutingProtocolJsonData json = new KLALBRoutingProtocolJsonData(
|
||||
KLALBRoutingProtocolJsonData.OPEN_LINES_REQ, suid, null);
|
||||
KLALBRoutingProtocolJsonData json = new KLALBRoutingProtocolJsonData(type, suid, null);
|
||||
JsonDataPacket packet = new JsonDataPacket(json);
|
||||
packet.setUserCallback(callback);
|
||||
packet.setUserCallback(new PendingRequest(addr, type, callback));
|
||||
synchronized (this) {
|
||||
if (closed.get() || releaser.isReleased()) throw new IOException("Node info client is closed");
|
||||
window.put(suid, packet);
|
||||
routingProtocol.sendJsonPacketToAddress(packet, addr);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public KLALBRoutingProtocol getRoutingProtocol() {
|
||||
@@ -74,13 +165,27 @@ public class KLALBRoutingProtocolAPIClient {
|
||||
|
||||
private KLALBRoutingProtocolAPIClientReleaser releaser;
|
||||
|
||||
public void close() {
|
||||
releaser.run();
|
||||
public synchronized void close() {
|
||||
if (closed.compareAndSet(false, true)) releaser.run();
|
||||
}
|
||||
|
||||
public boolean isClosed() {
|
||||
return releaser.isReleased();
|
||||
return closed.get() || releaser.isReleased();
|
||||
}
|
||||
|
||||
private static final class PendingRequest {
|
||||
private final SocketAddress expectedDestination;
|
||||
private final String expectedRequestType;
|
||||
private final Object callback;
|
||||
|
||||
private PendingRequest(SocketAddress expectedDestination, String expectedRequestType, Object callback) {
|
||||
this.expectedDestination = expectedDestination;
|
||||
this.expectedRequestType = expectedRequestType;
|
||||
this.callback = callback;
|
||||
}
|
||||
}
|
||||
|
||||
private final AtomicBoolean closed = new AtomicBoolean();
|
||||
}
|
||||
|
||||
class KLALBRoutingProtocolAPIClientReleaser extends Releaser<BiConsumer<SocketAddress, JsonDataPacket>> {
|
||||
|
||||
@@ -2,15 +2,10 @@ package org.kne.cloud.network.srv6;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.ref.Cleaner;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.SocketAddress;
|
||||
import java.util.ArrayList;
|
||||
import java.util.UUID;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
import org.kne.cloud.network.MultipurposeSocketAddress;
|
||||
import org.kne.cloud.network.ThreadTool;
|
||||
import org.kne.cloud.network.congestion.SendPacketSlidingWindow;
|
||||
import org.kne.cloud.network.klalb.KLALBController;
|
||||
import org.kne.opencl64.Releaser;
|
||||
|
||||
@@ -18,19 +13,40 @@ public class KLALBRoutingProtocolAPIServer {
|
||||
private KLALBController controller;
|
||||
private KLALBRoutingProtocol routingProtocol;
|
||||
|
||||
private static final Cleaner clr = Cleaner.create();
|
||||
|
||||
private static final Cleaner clr=Cleaner.create();
|
||||
|
||||
private BiConsumer<SocketAddress, JsonDataPacket> rec=(addr,data)->{
|
||||
private BiConsumer<SocketAddress, JsonDataPacket> rec = (addr, data) -> {
|
||||
try {
|
||||
KLALBRoutingProtocolJsonData dataobj= data.getDecodedData();
|
||||
InetSocketAddress addrs=(InetSocketAddress) addr;
|
||||
switch(dataobj.getType()){
|
||||
case KLALBRoutingProtocolJsonData.OPEN_LINES_REQ:
|
||||
if(controller.getConfigItem()==null||(!controller.getConfigItem().isDenyLineTableQuery())) {
|
||||
KLALBRoutingProtocolJsonData json=new KLALBRoutingProtocolJsonData(KLALBRoutingProtocolJsonData.OPEN_LINES_RESP,dataobj.getUuid(),controller.getSelflineTable());
|
||||
routingProtocol.sendJsonPacketToAddress(new JsonDataPacket(json),addr);
|
||||
}
|
||||
KLALBRoutingProtocolJsonData dataobj = data.getDecodedData();
|
||||
switch (dataobj.getType()) {
|
||||
case KLALBRoutingProtocolJsonData.NODE_PROFILE_REQ:
|
||||
KLALBController.PublishedNodeInfo profilePublished = controller.getPublishedNodeInfo();
|
||||
KLALBRoutingProtocolJsonData profile = new KLALBRoutingProtocolJsonData(
|
||||
KLALBRoutingProtocolJsonData.NODE_PROFILE_RESP, dataobj.getUuid(), null);
|
||||
profile.setDeviceName(profilePublished.getDeviceName());
|
||||
profile.setDeviceDescription(profilePublished.getDeviceDescription());
|
||||
profile.setStatus(NodeInfoQueryStatus.OK);
|
||||
routingProtocol.sendJsonPacketToAddress(new JsonDataPacket(profile), addr);
|
||||
break;
|
||||
case KLALBRoutingProtocolJsonData.NODE_EXTERNAL_ENDPOINTS_REQ:
|
||||
boolean denyEndpoints = controller.getConfigItem() != null
|
||||
&& controller.getConfigItem().isDenyExternalEndpointQuery();
|
||||
KLALBController.PublishedNodeInfo endpointsPublished = controller.getPublishedNodeInfo();
|
||||
KLALBRoutingProtocolJsonData endpoints = new KLALBRoutingProtocolJsonData(
|
||||
KLALBRoutingProtocolJsonData.NODE_EXTERNAL_ENDPOINTS_RESP, dataobj.getUuid(),
|
||||
denyEndpoints ? new ArrayList<org.kne.cloud.network.MultiProtocolSocketAddress>()
|
||||
: new ArrayList<org.kne.cloud.network.MultiProtocolSocketAddress>(
|
||||
endpointsPublished.getExternalEndpoints()));
|
||||
endpoints.setStatus(denyEndpoints ? NodeInfoQueryStatus.DENIED : NodeInfoQueryStatus.OK);
|
||||
routingProtocol.sendJsonPacketToAddress(new JsonDataPacket(endpoints), addr);
|
||||
break;
|
||||
case KLALBRoutingProtocolJsonData.NODE_EXTRA_ROUTES_REQ:
|
||||
KLALBController.PublishedNodeInfo routesPublished = controller.getPublishedNodeInfo();
|
||||
KLALBRoutingProtocolJsonData routes = new KLALBRoutingProtocolJsonData(
|
||||
KLALBRoutingProtocolJsonData.NODE_EXTRA_ROUTES_RESP, dataobj.getUuid(),
|
||||
new ArrayList<String>(routesPublished.getExtraRoutes()));
|
||||
routes.setStatus(NodeInfoQueryStatus.OK);
|
||||
routingProtocol.sendJsonPacketToAddress(new JsonDataPacket(routes), addr);
|
||||
break;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
@@ -38,11 +54,11 @@ public class KLALBRoutingProtocolAPIServer {
|
||||
}
|
||||
};
|
||||
|
||||
public KLALBRoutingProtocolAPIServer(KLALBRoutingProtocol routingProtocol,KLALBController controller) {
|
||||
this.controller=controller;
|
||||
this.routingProtocol=routingProtocol;
|
||||
public KLALBRoutingProtocolAPIServer(KLALBRoutingProtocol routingProtocol, KLALBController controller) {
|
||||
this.controller = controller;
|
||||
this.routingProtocol = routingProtocol;
|
||||
routingProtocol.addReceiver(rec);
|
||||
this.releaser=new KLALBRoutingProtocolAPIServerReleaser(this.routingProtocol,rec);
|
||||
this.releaser = new KLALBRoutingProtocolAPIServerReleaser(this.routingProtocol, rec);
|
||||
clr.register(this, releaser);
|
||||
}
|
||||
|
||||
@@ -54,26 +70,25 @@ public class KLALBRoutingProtocolAPIServer {
|
||||
return routingProtocol;
|
||||
}
|
||||
|
||||
|
||||
private KLALBRoutingProtocolAPIServerReleaser releaser;
|
||||
private KLALBRoutingProtocolAPIServerReleaser releaser;
|
||||
|
||||
public void close() {
|
||||
releaser.run();
|
||||
}
|
||||
|
||||
|
||||
public boolean isClosed() {
|
||||
return releaser.isReleased();
|
||||
}
|
||||
|
||||
}
|
||||
class KLALBRoutingProtocolAPIServerReleaser extends Releaser<BiConsumer<SocketAddress, JsonDataPacket>>{
|
||||
|
||||
class KLALBRoutingProtocolAPIServerReleaser extends Releaser<BiConsumer<SocketAddress, JsonDataPacket>> {
|
||||
|
||||
private KLALBRoutingProtocol routingProtocol;
|
||||
|
||||
public KLALBRoutingProtocolAPIServerReleaser(KLALBRoutingProtocol routingProtocol,BiConsumer<SocketAddress, JsonDataPacket> resource) {
|
||||
public KLALBRoutingProtocolAPIServerReleaser(KLALBRoutingProtocol routingProtocol,
|
||||
BiConsumer<SocketAddress, JsonDataPacket> resource) {
|
||||
super(resource);
|
||||
this.routingProtocol=routingProtocol;
|
||||
this.routingProtocol = routingProtocol;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,66 +1,89 @@
|
||||
package org.kne.cloud.network.srv6;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
|
||||
public class KLALBRoutingProtocolJsonData {
|
||||
public static final String OPEN_LINES_REQ="openlinesreq";
|
||||
public static final String OPEN_LINES_RESP="openlinesresp";
|
||||
public static final String NODE_PROFILE_REQ = "nodeprofilereq";
|
||||
public static final String NODE_PROFILE_RESP = "nodeprofileresp";
|
||||
public static final String NODE_EXTERNAL_ENDPOINTS_REQ = "nodeexternalendpointsreq";
|
||||
public static final String NODE_EXTERNAL_ENDPOINTS_RESP = "nodeexternalendpointsresp";
|
||||
public static final String NODE_EXTRA_ROUTES_REQ = "nodeextraroutesreq";
|
||||
public static final String NODE_EXTRA_ROUTES_RESP = "nodeextraroutesresp";
|
||||
|
||||
private String type;
|
||||
private UUID uuid;
|
||||
private Object data;
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
public Object getData() {
|
||||
return data;
|
||||
}
|
||||
public UUID getUuid() {
|
||||
return uuid;
|
||||
}
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + ((data == null) ? 0 : data.hashCode());
|
||||
result = prime * result + ((type == null) ? 0 : type.hashCode());
|
||||
result = prime * result + ((uuid == null) ? 0 : uuid.hashCode());
|
||||
return result;
|
||||
}
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (obj == null)
|
||||
return false;
|
||||
if (getClass() != obj.getClass())
|
||||
return false;
|
||||
KLALBRoutingProtocolJsonData other = (KLALBRoutingProtocolJsonData) obj;
|
||||
if (data == null) {
|
||||
if (other.data != null)
|
||||
return false;
|
||||
} else if (!data.equals(other.data))
|
||||
return false;
|
||||
if (type == null) {
|
||||
if (other.type != null)
|
||||
return false;
|
||||
} else if (!type.equals(other.type))
|
||||
return false;
|
||||
if (uuid == null) {
|
||||
if (other.uuid != null)
|
||||
return false;
|
||||
} else if (!uuid.equals(other.uuid))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
private String deviceName;
|
||||
private String deviceDescription;
|
||||
private NodeInfoQueryStatus status;
|
||||
|
||||
public KLALBRoutingProtocolJsonData(String type, UUID uuid, Object data) {
|
||||
super();
|
||||
this.type = type;
|
||||
this.uuid = uuid;
|
||||
this.data = data;
|
||||
}
|
||||
@Override
|
||||
public String toString() {
|
||||
return "KLALBRoutingProtocolJsonData [type=" + type + ", uuid=" + uuid + ", data=" + data + "]";
|
||||
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public UUID getUuid() {
|
||||
return uuid;
|
||||
}
|
||||
|
||||
public Object getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
public String getDeviceName() {
|
||||
return deviceName;
|
||||
}
|
||||
|
||||
public void setDeviceName(String deviceName) {
|
||||
this.deviceName = deviceName;
|
||||
}
|
||||
|
||||
public String getDeviceDescription() {
|
||||
return deviceDescription;
|
||||
}
|
||||
|
||||
public void setDeviceDescription(String deviceDescription) {
|
||||
this.deviceDescription = deviceDescription;
|
||||
}
|
||||
|
||||
public NodeInfoQueryStatus getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public void setStatus(NodeInfoQueryStatus status) {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(type, uuid, data, deviceName, deviceDescription, status);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (!(obj instanceof KLALBRoutingProtocolJsonData)) {
|
||||
return false;
|
||||
}
|
||||
KLALBRoutingProtocolJsonData other = (KLALBRoutingProtocolJsonData) obj;
|
||||
return Objects.equals(type, other.type) && Objects.equals(uuid, other.uuid)
|
||||
&& Objects.equals(data, other.data) && Objects.equals(deviceName, other.deviceName)
|
||||
&& Objects.equals(deviceDescription, other.deviceDescription)
|
||||
&& Objects.equals(status, other.status);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "KLALBRoutingProtocolJsonData [type=" + type + ", uuid=" + uuid + ", data=" + data
|
||||
+ ", deviceName=" + deviceName + ", deviceDescription=" + deviceDescription + ", status=" + status
|
||||
+ "]";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,6 +96,10 @@ public abstract class KLALBRoutingProtocolPacket extends NetworkPacket {
|
||||
}
|
||||
|
||||
public static KLALBRoutingProtocolPacket readKLALBPacketFromChannel(ReadableByteChannel in) throws IOException {
|
||||
return readKLALBPacketFromChannel(in, -1);
|
||||
}
|
||||
|
||||
public static KLALBRoutingProtocolPacket readKLALBPacketFromChannel(ReadableByteChannel in, long packetLength) throws IOException {
|
||||
while(true) {
|
||||
ByteBuffer bb=NetworkPacket.bufferAllocator.allocate(40);
|
||||
bb.limit(1);
|
||||
@@ -111,11 +115,13 @@ public abstract class KLALBRoutingProtocolPacket extends NetworkPacket {
|
||||
switch(type) {
|
||||
case RINFO_REQ:
|
||||
klp=new RouterInfoRequestPacket(bb);
|
||||
klp.readFromChannel(in);
|
||||
if(packetLength < 0) klp.readFromChannel(in);
|
||||
else klp.readFromChannel(in, packetLength);
|
||||
return klp;
|
||||
case RINFO:
|
||||
klp=new RouterInfoPacket(bb);
|
||||
klp.readFromChannel(in);
|
||||
if(packetLength < 0) klp.readFromChannel(in);
|
||||
else klp.readFromChannel(in, packetLength);
|
||||
return klp;
|
||||
case JSON_DATA:
|
||||
klp=new JsonDataPacket(bb);
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
package org.kne.cloud.network.srv6;
|
||||
|
||||
public enum NodeInfoQueryStatus {
|
||||
OK,
|
||||
DENIED
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package org.kne.cloud.network.srv6;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public final class NodeProfile {
|
||||
private final String deviceName;
|
||||
private final String deviceDescription;
|
||||
|
||||
public NodeProfile(String deviceName, String deviceDescription) {
|
||||
this.deviceName = deviceName;
|
||||
this.deviceDescription = deviceDescription;
|
||||
}
|
||||
|
||||
public String getDeviceName() {
|
||||
return deviceName;
|
||||
}
|
||||
|
||||
public String getDeviceDescription() {
|
||||
return deviceDescription;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(deviceName, deviceDescription);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (!(obj instanceof NodeProfile)) {
|
||||
return false;
|
||||
}
|
||||
NodeProfile other = (NodeProfile) obj;
|
||||
return Objects.equals(deviceName, other.deviceName)
|
||||
&& Objects.equals(deviceDescription, other.deviceDescription);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "NodeProfile [deviceName=" + deviceName + ", deviceDescription=" + deviceDescription + "]";
|
||||
}
|
||||
}
|
||||
@@ -68,6 +68,7 @@ public class RouterInfo implements Serializable{
|
||||
}
|
||||
private static final long INFO_UPDATETIME=10000000000L;
|
||||
private static final long INFO_TIMEOUT=60000000000L;
|
||||
private static final int MAX_NEIGHBOR_COUNT = 512;
|
||||
private long putTime=System.nanoTime();
|
||||
public boolean checkUpdateTime() {
|
||||
return System.nanoTime()-putTime>INFO_UPDATETIME;
|
||||
@@ -99,13 +100,19 @@ public class RouterInfo implements Serializable{
|
||||
public RouterInfo() {
|
||||
}
|
||||
public void writeToStream(DataOutputStream out) throws IOException {
|
||||
int neighborCount = neighborAddresses == null ? -1 : neighborAddresses.size();
|
||||
if(neighborCount<0 || neighborCount>MAX_NEIGHBOR_COUNT) {
|
||||
throw new IOException("Invalid neighbor count: " + neighborCount);
|
||||
}
|
||||
locator.writeToStream(out);
|
||||
out.writeLong(asn);
|
||||
out.writeLong(createTime);
|
||||
out.writeInt(neighborAddresses.size());
|
||||
out.writeInt(neighborCount);
|
||||
for( NeighborInfo neighborInfo :neighborAddresses) {
|
||||
neighborInfo.writeToStream(out);
|
||||
}
|
||||
// Keep the legacy UTF slot so older RINFO decoders stay aligned.
|
||||
out.writeUTF("");
|
||||
}
|
||||
|
||||
protected void writeToChannel(WritableByteChannel dto) throws IOException {
|
||||
@@ -119,6 +126,9 @@ public class RouterInfo implements Serializable{
|
||||
asn=in.readLong();
|
||||
createTime=in.readLong();
|
||||
int size=in.readInt();
|
||||
if(size<0 || size>MAX_NEIGHBOR_COUNT) {
|
||||
throw new IOException("Invalid neighbor count: " + size);
|
||||
}
|
||||
neighborAddresses=new ArrayList<>(size);
|
||||
for (int i = 0; i < size; i++) {
|
||||
NeighborInfo nif=new NeighborInfo();
|
||||
@@ -126,6 +136,8 @@ public class RouterInfo implements Serializable{
|
||||
neighborAddresses.add(nif);
|
||||
|
||||
}
|
||||
// Consume the legacy device-name slot; names are no longer broadcast here.
|
||||
in.readUTF();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package org.kne.cloud.network.srv6;
|
||||
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.Externalizable;
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInput;
|
||||
@@ -23,6 +24,11 @@ import org.kne.cloud.network.NetworkPacket;
|
||||
import org.kne.cloud.network.ipv6.IPv6AddressGroup;
|
||||
|
||||
public class RouterInfoPacket extends KLALBRoutingProtocolPacket implements Serializable{
|
||||
public static final int NODE_INFO_TINY_UPDATE_REQUIRED = 1;
|
||||
public static final int NODE_INFO_FULL_UPDATE_REQUIRED = 2;
|
||||
private static final int NODE_INFO_UPDATE_FLAGS_MASK = NODE_INFO_TINY_UPDATE_REQUIRED
|
||||
| NODE_INFO_FULL_UPDATE_REQUIRED;
|
||||
private static final int NODE_INFO_UPDATE_MAGIC = 0xA5;
|
||||
|
||||
public RouterInfoPacket(RouterInfo routerInfo, boolean flood,long asn) {
|
||||
super(RINFO);
|
||||
@@ -35,6 +41,7 @@ public class RouterInfoPacket extends KLALBRoutingProtocolPacket implements Seri
|
||||
}
|
||||
|
||||
private RouterInfo rinfo;
|
||||
private transient int nodeInfoUpdateFlags;
|
||||
|
||||
public boolean isFlood() {
|
||||
return (getHeader().get(1)&1)==1;
|
||||
@@ -47,6 +54,14 @@ public class RouterInfoPacket extends KLALBRoutingProtocolPacket implements Seri
|
||||
public RouterInfo getRinfo() {
|
||||
return rinfo;
|
||||
}
|
||||
|
||||
public int getNodeInfoUpdateFlags() {
|
||||
return nodeInfoUpdateFlags;
|
||||
}
|
||||
|
||||
public void setNodeInfoUpdateFlags(int flags) {
|
||||
nodeInfoUpdateFlags = flags & NODE_INFO_UPDATE_FLAGS_MASK;
|
||||
}
|
||||
@Override
|
||||
protected long getHeaderSize() {
|
||||
return super.getHeaderSize()+9;
|
||||
@@ -68,12 +83,43 @@ public class RouterInfoPacket extends KLALBRoutingProtocolPacket implements Seri
|
||||
public void writeToChannel(WritableByteChannel dto) throws IOException {
|
||||
super.writeToChannel(dto);
|
||||
rinfo.writeToChannel(dto);
|
||||
int flags = nodeInfoUpdateFlags & NODE_INFO_UPDATE_FLAGS_MASK;
|
||||
if(flags != 0) {
|
||||
ByteBuffer extension = ByteBuffer.wrap(new byte[] {(byte) NODE_INFO_UPDATE_MAGIC, (byte) flags});
|
||||
while(extension.hasRemaining()) {
|
||||
dto.write(extension);
|
||||
}
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public void readFromChannel(ReadableByteChannel din, long length) throws IOException {
|
||||
super.readFromChannel(din, length);
|
||||
rinfo=new RouterInfo();
|
||||
if(length < 0) {
|
||||
rinfo.readFromChannel(din, length);
|
||||
nodeInfoUpdateFlags = 0;
|
||||
return;
|
||||
}
|
||||
long payloadLength = length - getHeaderSize();
|
||||
if(payloadLength < 0 || payloadLength > Integer.MAX_VALUE) {
|
||||
throw new IOException("Invalid RINFO packet length: " + length);
|
||||
}
|
||||
byte[] payload = new byte[(int) payloadLength];
|
||||
ByteBuffer payloadBuffer = ByteBuffer.wrap(payload);
|
||||
while(payloadBuffer.hasRemaining()) {
|
||||
if(din.read(payloadBuffer) == -1) {
|
||||
throw new java.io.EOFException();
|
||||
}
|
||||
}
|
||||
ByteArrayInputStream payloadInput = new ByteArrayInputStream(payload);
|
||||
rinfo.readFromStream(new DataInputStream(payloadInput));
|
||||
nodeInfoUpdateFlags = 0;
|
||||
int marker = payloadInput.read();
|
||||
int flags = payloadInput.read();
|
||||
int trailing = payloadInput.read();
|
||||
if(marker == NODE_INFO_UPDATE_MAGIC && flags >= 0 && trailing == -1) {
|
||||
nodeInfoUpdateFlags = flags & NODE_INFO_UPDATE_FLAGS_MASK;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -46,6 +46,8 @@ import org.kne.cloud.network.ipv6.PostcardEntry;
|
||||
import org.kne.cloud.network.ipv6.RouteItem;
|
||||
import org.kne.cloud.network.klalb.KLALBRemoteLink;
|
||||
import org.kne.cloud.network.klalb.KLALBUtils;
|
||||
import org.kne.cloud.network.klalb.PerformanceStrategy;
|
||||
import org.kne.cloud.network.klalb.ui.PerformanceStrategyItem;
|
||||
import org.kne.concurrent.HighPerformanceExecutor2;
|
||||
import org.kne.concurrent.TimeoutConcurrentHashMap;
|
||||
import org.pcap4j.packet.IllegalRawDataException;
|
||||
@@ -62,6 +64,8 @@ public class SRv6Router {
|
||||
|
||||
private static final int IPv6_BITS = 128; // IPv6地址位数
|
||||
|
||||
private PerformanceStrategy performanceStrategy=PerformanceStrategy.MULTI_FILL;
|
||||
|
||||
/*private TimeoutConcurrentHashMap<FlowSession, PacketIDGenerator> flowIDmap = new TimeoutConcurrentHashMap<FlowSession, PacketIDGenerator>(
|
||||
60000000000L);
|
||||
*/
|
||||
@@ -161,6 +165,14 @@ public class SRv6Router {
|
||||
|
||||
};
|
||||
|
||||
public void setPerformanceStrategy(PerformanceStrategy performanceStrategy) {
|
||||
this.performanceStrategy=performanceStrategy;
|
||||
}
|
||||
|
||||
public PerformanceStrategy getPerformanceStrategy(){
|
||||
return performanceStrategy;
|
||||
}
|
||||
|
||||
// 链路状态监听器实现
|
||||
private class LinkListener implements IPv6LinkStateListener {
|
||||
|
||||
@@ -282,6 +294,15 @@ public class SRv6Router {
|
||||
|
||||
private IPv6AddressGroup locator; // SRv6定位器
|
||||
private long asn = new SecureRandom().nextLong(1, Long.MAX_VALUE); // 自治系统号
|
||||
private volatile String deviceName; // 设备名称(随路由信息广播)
|
||||
|
||||
public String getDeviceName() {
|
||||
return deviceName;
|
||||
}
|
||||
|
||||
public void setDeviceName(String deviceName) {
|
||||
this.deviceName = deviceName;
|
||||
}
|
||||
|
||||
public IPv6AddressGroup getLocator() {
|
||||
return locator;
|
||||
@@ -765,43 +786,43 @@ public class SRv6Router {
|
||||
|
||||
//private static HighPerformanceExecutor2 defaultExecutor = new HighPerformanceExecutor2(4, Thread.ofVirtual().name("SRv6 Routing").factory());
|
||||
|
||||
public static HighPerformanceExecutor2 getDefaultExecutor() {
|
||||
/*public static HighPerformanceExecutor2 getDefaultExecutor() {
|
||||
return defaultExecutor;
|
||||
}
|
||||
}*/
|
||||
|
||||
// private ArrayBlockingQueue<Supplier<IPv6Packet>> packetQueue=new
|
||||
// ArrayBlockingQueue<>(10000);
|
||||
public void enqueuePacketSendTask(Supplier<IPv6Packet> sendTask) {
|
||||
// packetQueue.add(object);
|
||||
switch(performanceStrategy) {
|
||||
case SINGLE_CORE:
|
||||
runPacketSendTask0(sendTask, false);
|
||||
break;
|
||||
case MULTI_FILL:
|
||||
case MULTI_SCATTER:
|
||||
defaultExecutor.executeWithCongestionReport((state) -> {
|
||||
runPacketSendTask0(sendTask, state);
|
||||
}, 20,performanceStrategy);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
public void runPacketSendTask0(Supplier<IPv6Packet> sendTask,boolean ecn) {
|
||||
try {
|
||||
long start=System.nanoTime();
|
||||
IPv6Packet packet=sendTask.get();
|
||||
if(state) {
|
||||
if(ecn) {
|
||||
packet.markCE();
|
||||
}
|
||||
insertSRHandRoutePacket(null, packet); // 插入SRH并路由
|
||||
long time=System.nanoTime()-start;
|
||||
backplaneCount(time,state);
|
||||
backplaneCount(time,ecn);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
},20);
|
||||
}
|
||||
public void runPacketSendTask(Supplier<IPv6Packet> sendTask) {
|
||||
try {
|
||||
boolean state=false;
|
||||
long start=System.nanoTime();
|
||||
IPv6Packet packet=sendTask.get();
|
||||
if(state) {
|
||||
packet.markCE();
|
||||
}
|
||||
insertSRHandRoutePacket(null, packet); // 插入SRH并路由
|
||||
long time=System.nanoTime()-start;
|
||||
backplaneCount(time,state);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
runPacketSendTask0(sendTask,false);
|
||||
}
|
||||
|
||||
public void onReceive(IPv6NetworkLink link,IPv6Packet t){
|
||||
@@ -824,7 +845,7 @@ public class SRv6Router {
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
},20);
|
||||
},20,performanceStrategy);
|
||||
}
|
||||
|
||||
public void enqueuePacket(IPv6Packet packet) {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user